From 98eaca97f905c37dfb10a3cc7d820071bd3034b1 Mon Sep 17 00:00:00 2001 From: Russell Johnson <47639332+Russ4262@users.noreply.github.com> Date: Tue, 25 Feb 2020 19:12:43 -0600 Subject: [PATCH 1/4] ProfileEdges: New feature - profile open edges Open edges, exterior or interior, can now be used to generate paths. The new feature behaves as though the parent face is vertically oriented (standing). It is preferred that the user select upper (top) edges. Selecting bottom edges in some cases may not produce a path. Path ends calculations might need slight adjustment in the code. Path ends are *near* perpendicular to ends of edge. Cut direction seems sporadic, but the `Direction` property will switch the direction of cut. The `CW` and `CCW` direction labels might not align with the actual direction. Additional algorithm modification is necessary to enforce fidelity to the labels. Existing properties, `UseComp` and `OffsetExtra` are observed with open faces. Negative values for `OffsetExtra` have not been tested - only zero and positive. --- src/Mod/Path/PathScripts/PathAreaOp.py | 78 +- src/Mod/Path/PathScripts/PathProfileEdges.py | 941 ++++++++++++++++++- 2 files changed, 995 insertions(+), 24 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathAreaOp.py b/src/Mod/Path/PathScripts/PathAreaOp.py index 1a7a4f6f53..965e5d89e5 100644 --- a/src/Mod/Path/PathScripts/PathAreaOp.py +++ b/src/Mod/Path/PathScripts/PathAreaOp.py @@ -30,21 +30,17 @@ import PathScripts.PathUtils as PathUtils import PathScripts.PathGeom as PathGeom import Draft import math +import Part # from PathScripts.PathUtils import waiting_effects from PySide import QtCore if FreeCAD.GuiUp: import FreeCADGui - __title__ = "Base class for PathArea based operations." __author__ = "sliptonic (Brad Collette)" __url__ = "http://www.freecadweb.org" __doc__ = "Base class and properties for Path.Area based operations." -__contributors__ = "russ4262 (Russell Johnson)" -__createdDate__ = "2017" -__scriptVersion__ = "2p" -__lastModified__ = "2020-02-13 17:11 CST" LOGLEVEL = PathLog.Level.INFO PathLog.setLevel(LOGLEVEL, PathLog.thisModule()) @@ -53,8 +49,6 @@ if LOGLEVEL is PathLog.Level.DEBUG: PathLog.trackModule() # Qt translation handling - - def translate(context, text, disambig=None): return QtCore.QCoreApplication.translate(context, text, disambig) @@ -286,6 +280,59 @@ class ObjectOp(PathOp.ObjectOp): return pp, simobj + def _buildProfileOpenEdges(self, obj, baseShape, isHole, start, getsim): + '''_buildPathArea(obj, baseShape, isHole, start, getsim) ... internal function.''' + # pylint: disable=unused-argument + PathLog.track() + + paths = [] + heights = [i for i in self.depthparams] + PathLog.debug('depths: {}'.format(heights)) + lstIdx = len(heights) - 1 + for i in range(0, len(heights)): + hWire = Part.Wire(Part.__sortEdges__(baseShape.Edges)) + hWire.translate(FreeCAD.Vector(0, 0, heights[i] - hWire.BoundBox.ZMin)) + + pathParams = {} # pylint: disable=assignment-from-no-return + pathParams['shapes'] = [hWire] + pathParams['feedrate'] = self.horizFeed + pathParams['feedrate_v'] = self.vertFeed + pathParams['verbose'] = True + pathParams['resume_height'] = obj.SafeHeight.Value + pathParams['retraction'] = obj.ClearanceHeight.Value + pathParams['return_end'] = True + # Note that emitting preambles between moves breaks some dressups and prevents path optimization on some controllers + pathParams['preamble'] = False + #if not self.areaOpRetractTool(obj): + # pathParams['threshold'] = 2.001 * self.radius + + if self.endVector is None: + V = hWire.Wires[0].Vertexes + lv = len(V) - 1 + pathParams['start'] = FreeCAD.Vector(V[0].X, V[0].Y, V[0].Z) + if obj.Direction == 'CCW': + pathParams['start'] = FreeCAD.Vector(V[lv].X, V[lv].Y, V[lv].Z) + else: + pathParams['start'] = self.endVector + + obj.PathParams = str({key: value for key, value in pathParams.items() if key != 'shapes'}) + PathLog.debug("Path with params: {}".format(obj.PathParams)) + + (pp, end_vector) = Path.fromShapes(**pathParams) + paths.extend(pp.Commands) + PathLog.debug('pp: {}, end vector: {}'.format(pp, end_vector)) + + self.endVector = end_vector # pylint: disable=attribute-defined-outside-init + + simobj = None + if getsim and False: + areaParams['ToolRadius'] = self.radius - self.radius * .005 + area.setParams(**areaParams) + sec = area.makeSections(mode=0, project=False, heights=heights)[-1].getShape() + simobj = sec.extrude(FreeCAD.Vector(0, 0, baseobject.BoundBox.ZMax)) + + return paths, simobj + def opExecute(self, obj, getsim=False): # pylint: disable=arguments-differ '''opExecute(obj, getsim=False) ... implementation of Path.Area ops. determines the parameters for _buildPathArea(). @@ -306,6 +353,7 @@ class ObjectOp(PathOp.ObjectOp): self.tempObjectNames = [] # pylint: disable=attribute-defined-outside-init self.stockBB = PathUtils.findParentJob(obj).Stock.Shape.BoundBox # pylint: disable=attribute-defined-outside-init self.useTempJobClones('Delete') # Clear temporary group and recreate for temp job clones + self.profileEdgesIsOpen = False if obj.EnableRotation != 'Off': # Calculate operation heights based upon rotation radii @@ -384,9 +432,13 @@ class ObjectOp(PathOp.ObjectOp): shapes = [j['shape'] for j in jobs] + if self.profileEdgesIsOpen is True: + if PathOp.FeatureStartPoint & self.opFeatures(obj) and obj.UseStartPoint: + osp = obj.StartPoint + self.commandlist.append(Path.Command('G0', {'X': osp.x, 'Y': osp.y, 'F': self.horizRapid})) + sims = [] numShapes = len(shapes) - for ns in range(0, numShapes): (shape, isHole, sub, angle, axis, strDep, finDep) = shapes[ns] # pylint: disable=unused-variable if ns < numShapes - 1: @@ -405,12 +457,18 @@ class ObjectOp(PathOp.ObjectOp): user_depths=None) try: - (pp, sim) = self._buildPathArea(obj, shape, isHole, start, getsim) + if self.profileEdgesIsOpen is True: + (pp, sim) = self._buildProfileOpenEdges(obj, shape, isHole, start, getsim) + else: + (pp, sim) = self._buildPathArea(obj, shape, isHole, start, getsim) except Exception as e: # pylint: disable=broad-except FreeCAD.Console.PrintError(e) FreeCAD.Console.PrintError("Something unexpected happened. Check project and tool config.") else: - ppCmds = pp.Commands + if self.profileEdgesIsOpen is True: + ppCmds = pp + else: + ppCmds = pp.Commands if obj.EnableRotation != 'Off' and self.rotateFlag is True: # Rotate model to index for cut if axis == 'X': diff --git a/src/Mod/Path/PathScripts/PathProfileEdges.py b/src/Mod/Path/PathScripts/PathProfileEdges.py index b39f6c7447..5fce9b0774 100644 --- a/src/Mod/Path/PathScripts/PathProfileEdges.py +++ b/src/Mod/Path/PathScripts/PathProfileEdges.py @@ -30,16 +30,19 @@ import PathScripts.PathOp as PathOp import PathScripts.PathProfileBase as PathProfileBase import PathScripts.PathUtils as PathUtils -from DraftGeomUtils import findWires -from PySide import QtCore +import DraftGeomUtils +import Draft +import math +import PySide PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) -#PathLog.trackModule(PathLog.thisModule()) +# PathLog.trackModule(PathLog.thisModule()) # Qt translation handling def translate(context, text, disambig=None): - return QtCore.QCoreApplication.translate(context, text, disambig) + return PySide.QtCore.QCoreApplication.translate(context, text, disambig) + __title__ = "Path Profile Edges Operation" __author__ = "sliptonic (Brad Collette)" @@ -63,9 +66,19 @@ class ObjectProfile(PathProfileBase.ObjectProfile): '''areaOpShapes(obj) ... returns envelope for all wires formed by the base edges.''' PathLog.track() + self.tmpGrp = FreeCAD.ActiveDocument.addObject('App::DocumentObjectGroup', 'tmpDebugGrp') + tmpGrpNm = self.tmpGrp.Name + self.JOB = PathUtils.findParentJob(obj) + + self.offsetExtra = abs(obj.OffsetExtra.Value) + if obj.UseComp: + self.useComp = True + self.ofstRadius = self.radius + self.offsetExtra self.commandlist.append(Path.Command("(Compensated Tool Path. Diameter: " + str(self.radius * 2) + ")")) else: + self.useComp = False + self.ofstRadius = self.offsetExtra self.commandlist.append(Path.Command("(Uncompensated Tool Path)")) shapes = [] @@ -77,26 +90,926 @@ class ObjectProfile(PathProfileBase.ObjectProfile): edgelist = [] for sub in b[1]: edgelist.append(getattr(b[0].Shape, sub)) - basewires.append((b[0], findWires(edgelist))) + basewires.append((b[0], DraftGeomUtils.findWires(edgelist))) if zMin is None or b[0].Shape.BoundBox.ZMin < zMin: zMin = b[0].Shape.BoundBox.ZMin - for base,wires in basewires: + PathLog.debug('PathProfileEdges areaOpShapes():: len(basewires) is {}'.format(len(basewires))) + for base, wires in basewires: for wire in wires: - f = Part.makeFace(wire, 'Part::FaceMakerSimple') + if wire.isClosed() is True: + # f = Part.makeFace(wire, 'Part::FaceMakerSimple') + # if planar error, Comment out previous line, uncomment the next two + (origWire, flatWire) = self._flattenWire(obj, wire, obj.FinalDepth.Value) + f = origWire.Shape.Wires[0] + if f is not False: + # shift the compound to the bottom of the base object for proper sectioning + zShift = zMin - f.BoundBox.ZMin + newPlace = FreeCAD.Placement(FreeCAD.Vector(0, 0, zShift), f.Placement.Rotation) + f.Placement = newPlace + env = PathUtils.getEnvelope(base.Shape, subshape=f, depthparams=self.depthparams) + shapes.append((env, False)) + else: + PathLog.error(translate('PathProfileEdges', 'The selected edge(s) are inaccessible.')) + else: + cutWireObjs = False + (origWire, flatWire) = self._flattenWire(obj, wire, obj.FinalDepth.Value) + cutShp = self._getCutAreaCrossSection(obj, base, origWire, flatWire) + if cutShp is not False: + cutWireObjs = self._extractPathWire(obj, base, flatWire, cutShp) + + if cutWireObjs is not False: + for cW in cutWireObjs: + shapes.append((cW, False)) + self.profileEdgesIsOpen = True + else: + PathLog.error(translate('PathProfileEdges', 'The selected edge(s) are inaccessible.')) + + # Delete the temporary objects + if PathLog.getLevel(PathLog.thisModule()) != 4: + for to in self.tmpGrp.Group: + FreeCAD.ActiveDocument.removeObject(to.Name) + FreeCAD.ActiveDocument.removeObject(tmpGrpNm) + else: + if FreeCAD.GuiUp: + import FreeCADGui + FreeCADGui.ActiveDocument.getObject(tmpGrpNm).Visibility = False - # shift the compound to the bottom of the base object for - # proper sectioning - zShift = zMin - f.BoundBox.ZMin - newPlace = FreeCAD.Placement(FreeCAD.Vector(0, 0, zShift), f.Placement.Rotation) - f.Placement = newPlace - env = PathUtils.getEnvelope(base.Shape, subshape=f, depthparams=self.depthparams) - shapes.append((env, False)) return shapes + def _flattenWire(self, obj, wire, trgtDep): + '''_flattenWire(obj, wire)... Return a flattened version of the wire''' + PathLog.debug('_flattenWire()') + wBB = wire.BoundBox + tmpGrp = self.tmpGrp + + OW = FreeCAD.ActiveDocument.addObject('Part::Feature', 'tmpOriginalWire') + OW.Shape = wire + OW.purgeTouched() + tmpGrp.addObject(OW) + + if wBB.ZLength > 0.0: + PathLog.debug('Wire is not horizontally co-planar. Flattening it.') + + # Extrude non-horizontal wire + extFwdLen = wBB.ZLength * 2.2 + mbbEXT = self._extrudeObject(OW, extFwdLen, False) + + # Create cross-section of shape and translate + sliceZ = wire.BoundBox.ZMin + (extFwdLen / 2) + crsectFaceShp = self._makeCrossSection(mbbEXT.Shape, sliceZ, trgtDep) + if crsectFaceShp is not False: + # srtWire = Part.Wire(Part.__sortEdges__(crsectFaceShp.Edges)) + FW = FreeCAD.ActiveDocument.addObject('Part::Feature', 'tmpFlattenedWire') + FW.Shape = crsectFaceShp # srtWire + FW.recompute() + FW.purgeTouched() + tmpGrp.addObject(FW) + + return (OW, FW) + else: + return False + else: + srtWire = Part.Wire(Part.__sortEdges__(wire.Edges)) + srtWire.translate(FreeCAD.Vector(0, 0, trgtDep - srtWire.BoundBox.ZMin)) + FW = FreeCAD.ActiveDocument.addObject('Part::Feature', 'tmpOriginalWireSorted') + FW.Shape = srtWire + FW.purgeTouched() + tmpGrp.addObject(FW) + + return (OW, FW) + + def _getCutAreaCrossSection(self, obj, base, origWire, flatWireObj): + PathLog.debug('_getCutAreaCrossSection()') + tmpGrp = self.tmpGrp + FCAD = FreeCAD.ActiveDocument + tolerance = self.JOB.GeometryTolerance.Value + # toolDiam = float(obj.ToolController.Tool.Diameter) + toolDiam = 2 * self.radius # self.radius defined in PathAreaOp or PathprofileBase modules + minBfr = toolDiam * 1.25 + bbBfr = (self.ofstRadius * 2) * 1.25 + if bbBfr < minBfr: + bbBfr = minBfr + fwBB = flatWireObj.Shape.BoundBox + wBB = origWire.Shape.BoundBox + minArea = (self.ofstRadius - tolerance)**2 * math.pi + + useWire = origWire.Shape.Wires[0] + numOrigEdges = len(useWire.Edges) + sdv = wBB.ZMax + fdv = obj.FinalDepth.Value + extLenFwd = sdv - fdv + WIRE = flatWireObj.Shape.Wires[0] + numEdges = len(WIRE.Edges) + + # Identify first/last edges and first/last vertex on wire + begE = WIRE.Edges[0] # beginning edge + endE = WIRE.Edges[numEdges - 1] # ending edge + blen = begE.Length + elen = endE.Length + Vb = begE.Vertexes[0] # first vertex of wire + Ve = endE.Vertexes[1] # last vertex of wire + pb = FreeCAD.Vector(Vb.X, Vb.Y, fdv) + pe = FreeCAD.Vector(Ve.X, Ve.Y, fdv) + + # Identify endpoints connecting circle center and diameter + vectDist = pe.sub(pb) + diam = vectDist.Length + cntr = vectDist.multiply(0.5).add(pb) + R = diam / 2 + + pl = FreeCAD.Placement() + pl.Rotation = FreeCAD.Rotation(FreeCAD.Vector(0, 0, 1), 0) + pl.Base = FreeCAD.Vector(0, 0, 0) + + # Obtain beginning point perpendicular points + if blen > 0.1: + bcp = begE.valueAt(begE.getParameterByLength(0.1)) # point returned 0.1 mm along edge + else: + bcp = FreeCAD.Vector(begE.Vertexes[1].X, begE.Vertexes[1].Y, fdv) + if elen > 0.1: + ecp = endE.valueAt(endE.getParameterByLength(elen - 0.1)) # point returned 0.1 mm along edge + else: + ecp = FreeCAD.Vector(endE.Vertexes[1].X, endE.Vertexes[1].Y, fdv) + + # Create intersection tags for determining which side of wire to cut + (begInt, begExt, iTAG, eTAG) = self._makeIntersectionTags(useWire, numOrigEdges, fdv) + self.iTAG = iTAG + self.eTAG = eTAG + + # Create extended wire boundbox, and extrude + extBndbox = self._makeExtendedBoundBox(wBB, bbBfr, fdv) + extBndboxEXT = self._extrudeObject(extBndbox, extLenFwd) # (objToExt, extFwdLen) + + # Cut model(selected edges) from extended edges boundbox + cutArea = extBndboxEXT.Shape.cut(base.Shape) + CA = FCAD.addObject('Part::Feature', 'tmpBndboxCutByBase') + CA.Shape = cutArea + CA.purgeTouched() + tmpGrp.addObject(CA) + + # Get top and bottom faces of cut area (CA), and combine faces when necessary + topFc = list() + botFc = list() + bbZMax = CA.Shape.BoundBox.ZMax + bbZMin = CA.Shape.BoundBox.ZMin + for f in range(0, len(CA.Shape.Faces)): + Fc = CA.Shape.Faces[f] + if abs(Fc.BoundBox.ZMax - bbZMax) < tolerance and abs(Fc.BoundBox.ZMin - bbZMax) < tolerance: + topFc.append(f) + if abs(Fc.BoundBox.ZMax - bbZMin) < tolerance and abs(Fc.BoundBox.ZMin - bbZMin) < tolerance: + botFc.append(f) + topComp = Part.makeCompound([CA.Shape.Faces[f] for f in topFc]) + topComp.translate(FreeCAD.Vector(0, 0, fdv - topComp.BoundBox.ZMin)) # Translate face to final depth + if len(botFc) > 1: + PathLog.debug('len(botFc) > 1') + bndboxFace = Part.Face(extBndbox.Shape.Wires[0]) + tmpFace = Part.Face(extBndbox.Shape.Wires[0]) + for f in botFc: + Q = tmpFace.cut(CA.Shape.Faces[f]) + tmpFace = Q + botComp = bndboxFace.cut(tmpFace) + else: + botComp = Part.makeCompound([CA.Shape.Faces[f] for f in botFc]) + botComp.translate(FreeCAD.Vector(0, 0, fdv - botComp.BoundBox.ZMin)) # Translate face to final depth + + # Convert compound shapes to FC objects for use in multicommon operation + TP = FCAD.addObject('Part::Feature', 'tmpTopCompound') + TP.Shape = topComp + TP.recompute() + TP.purgeTouched() + tmpGrp.addObject(TP) + BT = FCAD.addObject('Part::Feature', 'tmpBotCompound') + BT.Shape = botComp + BT.recompute() + BT.purgeTouched() + tmpGrp.addObject(BT) + + # Make common of the two + comFC = FCAD.addObject('Part::MultiCommon', 'tmpCommonTopBotFaces') + comFC.Shapes = [TP, BT] + comFC.recompute() + TP.purgeTouched() + BT.purgeTouched() + comFC.purgeTouched() + tmpGrp.addObject(comFC) + + # Determine with which set of intersection tags the model intersects + (cmnIntArea, cmnExtArea) = self._checkTagIntersection(iTAG, eTAG, 'QRY', comFC) + if cmnExtArea > cmnIntArea: + PathLog.debug('Cutting on Ext side.') + self.cutSide = 'E' + self.cutSideTags = eTAG.Shape + tagCOM = begExt.CenterOfMass + else: + PathLog.debug('Cutting on Int side.') + self.cutSide = 'I' + self.cutSideTags = iTAG.Shape + tagCOM = begInt.CenterOfMass + + # Make two beginning style(oriented) 'L' shape stops + begStop = self._makeStop('BEG', bcp, pb, 'BegStop') + altBegStop = self._makeStop('END', bcp, pb, 'BegStop') + + # Identify to which style 'L' stop the beginning intersection tag is closest, + # and create partner end 'L' stop geometry, and save for application later + lenBS_extETag = begStop.CenterOfMass.sub(tagCOM).Length + lenABS_extETag = altBegStop.CenterOfMass.sub(tagCOM).Length + if lenBS_extETag < lenABS_extETag: + endStop = self._makeStop('END', ecp, pe, 'EndStop') + pathStops = Part.makeCompound([begStop, endStop]) + else: + altEndStop = self._makeStop('BEG', ecp, pe, 'EndStop') + pathStops = Part.makeCompound([altBegStop, altEndStop]) + pathStops.translate(FreeCAD.Vector(0, 0, fdv - pathStops.BoundBox.ZMin)) + + # Identify closed wire in cross-section that corresponds to user-selected edge(s) + workShp = comFC.Shape + fcShp = workShp + wire = origWire.Shape # flatWireObj.Shape + WS = workShp.Wires + lenWS = len(WS) + if lenWS < 3: + wi = 0 + else: + wi = None + for wvt in wire.Vertexes: + for w in range(0, lenWS): + twr = WS[w] + for v in range(0, len(twr.Vertexes)): + V = twr.Vertexes[v] + if abs(V.X - wvt.X) < tolerance: + if abs(V.Y - wvt.Y) < tolerance: + # Same vertex found. This wire to be used for offset + wi = w + break + # Efor + + if wi is None: + PathLog.error('The cut area cross-section wire does not coincide with selected edge. Wires[] index is None.') + tmpGrp.purgeTouched() + return False + else: + PathLog.debug('Cross-section Wires[] index is {}.'.format(wi)) + + nWire = Part.Wire(Part.__sortEdges__(workShp.Wires[wi].Edges)) + fcShp = Part.Face(nWire) + fcShp.translate(FreeCAD.Vector(0, 0, fdv - workShp.BoundBox.ZMin)) + # Eif + + # verify that wire chosen is not inside the physical model + if wi > 0: # and isInterior is False: + PathLog.debug('Multiple wires in cut area. First choice is not 0. Testing.') + testArea = fcShp.cut(base.Shape) + # testArea = fcShp + TA = FreeCAD.ActiveDocument.addObject('Part::Feature','tmpCutFaceTest') + TA.Shape = testArea + TA.purgeTouched() + tmpGrp.addObject(TA) + + isReady = self._checkTagIntersection(iTAG, eTAG, self.cutSide, TA) + PathLog.debug('isReady {}.'.format(isReady)) + + if isReady is False: + PathLog.debug('Using wire index {}.'.format(wi - 1)) + pWire = Part.Wire(Part.__sortEdges__(workShp.Wires[wi - 1].Edges)) + pfcShp = Part.Face(pWire) + pfcShp.translate(FreeCAD.Vector(0, 0, fdv - workShp.BoundBox.ZMin)) + workShp = pfcShp.cut(fcShp) + + if testArea.Area < minArea: + PathLog.debug('offset area is less than minArea of {}.'.format(minArea)) + PathLog.debug('Using wire index {}.'.format(wi - 1)) + pWire = Part.Wire(Part.__sortEdges__(workShp.Wires[wi - 1].Edges)) + pfcShp = Part.Face(pWire) + pfcShp.translate(FreeCAD.Vector(0, 0, fdv - workShp.BoundBox.ZMin)) + workShp = pfcShp.cut(fcShp) + # Eif + + # Add path stops at ends of wire + cutShp = workShp.cut(pathStops) + + CF = FreeCAD.ActiveDocument.addObject('Part::Feature','tmpCutFace') + CF.Shape = cutShp + CF.recompute() + CF.purgeTouched() + tmpGrp.addObject(CF) + + tmpGrp.purgeTouched() + return cutShp # CF.Shape + + def _checkTagIntersection(self, iTAG, eTAG, cutSide, tstObj): + # Identify intersection of Common area and Interior Tags + intCmn = FreeCAD.ActiveDocument.addObject('Part::MultiCommon', 'tmpCmnIntTags') + intCmn.Shapes = [tstObj, iTAG] + intCmn.recompute() + tstObj.purgeTouched() + iTAG.purgeTouched() + intCmn.purgeTouched() + self.tmpGrp.addObject(intCmn) + + # Identify intersection of Common area and Exterior Tags + extCmn = FreeCAD.ActiveDocument.addObject('Part::MultiCommon', 'tmpCmnExtTags') + extCmn.Shapes = [tstObj, eTAG] + extCmn.recompute() + tstObj.purgeTouched() + eTAG.purgeTouched() + extCmn.purgeTouched() + self.tmpGrp.addObject(extCmn) + + # Calculate common intersection (solid model side, or the non-cut side) area with tags, to determine physical cut side + cmnIntArea = intCmn.Shape.Area + cmnExtArea = extCmn.Shape.Area + if cutSide == 'QRY': + return (cmnIntArea, cmnExtArea) + + if cmnExtArea > cmnIntArea: + PathLog.debug('Cutting on Ext side.') + if cutSide == 'E': + return True + else: + PathLog.debug('Cutting on Int side.') + if cutSide == 'I': + return True + return False + + def _extractPathWire(self, obj, base, fWire, cutShp): + PathLog.debug('_extractPathWire()') + + subLoops = list() + rtnWIRES = list() + osWrIdxs = list() + subDistFactor = 1.0 # Raise to include sub wires at greater distance from original + tmpGrp = self.tmpGrp + fdv = obj.FinalDepth.Value + wire = fWire.Shape + lstVrtIdx = len(wire.Vertexes) - 1 + lstVrt = wire.Vertexes[lstVrtIdx] + frstVrt = wire.Vertexes[0] + cent0 = FreeCAD.Vector(frstVrt.X, frstVrt.Y, fdv) + cent1 = FreeCAD.Vector(lstVrt.X, lstVrt.Y, fdv) + + pl = FreeCAD.Placement() + pl.Rotation = FreeCAD.Rotation(FreeCAD.Vector(0, 0, 1), 0) + pl.Base = FreeCAD.Vector(0, 0, 0) + + # Calculate offset shape, containing cut region + ofstShp = self._extractFaceOffset(obj, cutShp, False) + + # CHECK for ZERO area of offset shape + try: + osArea = ofstShp.Area + except Exception as ee: + PathLog.error('No area to offset shape returned.') + tmpGrp.purgeTouched() + return False + + os = FreeCAD.ActiveDocument.addObject('Part::Feature', 'tmpOffsetShape') + os.Shape = ofstShp + os.recompute() + os.purgeTouched() + tmpGrp.addObject(os) + + numOSWires = len(ofstShp.Wires) + for w in range(0, numOSWires): + osWrIdxs.append(w) + + # Identify two vertexes for dividing offset loop + NEAR0 = self._findNearestVertex(ofstShp, cent0) + min0i = 0 + min0 = NEAR0[0][4] + for n in range(0, len(NEAR0)): + N = NEAR0[n] + if N[4] < min0: + min0 = N[4] + min0i = n + (w0, vi0, pnt0, vrt0, d0) = NEAR0[0] # min0i + near0 = Draft.makeWire([cent0, pnt0], placement=pl, closed=False, face=False, support=None) + near0.recompute() + near0.purgeTouched() + tmpGrp.addObject(near0) + + NEAR1 = self._findNearestVertex(ofstShp, cent1) + min1i = 0 + min1 = NEAR1[0][4] + for n in range(0, len(NEAR1)): + N = NEAR1[n] + if N[4] < min1: + min1 = N[4] + min1i = n + (w1, vi1, pnt1, vrt1, d1) = NEAR1[0] # min1i + near1 = Draft.makeWire([cent1, pnt1], placement=pl, closed=False, face=False, support=None) + near1.recompute() + near1.purgeTouched() + tmpGrp.addObject(near1) + + if w0 != w1: + PathLog.debug('w0 is {}.'.format(w0)) + PathLog.debug('w1 is {}.'.format(w1)) + PathLog.warning('Offset wire endpoint indexes are not equal: {}, {}'.format(w0, w1)) + + ''' + PathLog.debug('min0i is {}.'.format(min0i)) + PathLog.debug('min1i is {}.'.format(min1i)) + PathLog.debug('NEAR0[{}] is {}.'.format(w0, NEAR0[w0])) + PathLog.debug('NEAR1[{}] is {}.'.format(w1, NEAR1[w1])) + PathLog.debug('NEAR0 is {}.'.format(NEAR0)) + PathLog.debug('NEAR1 is {}.'.format(NEAR1)) + ''' + + mainWire = ofstShp.Wires[w0] + + # Check for additional closed loops in offset wire by checking distance to iTAG or eTAG elements + if numOSWires > 1: + # check all wires for proximity(children) to intersection tags + tagsComList = list() + for T in self.cutSideTags.Faces: + tcom = T.CenterOfMass + tv = FreeCAD.Vector(tcom.x, tcom.y, 0.0) + tagsComList.append(tv) + subDist = self.ofstRadius * subDistFactor + for w in osWrIdxs: + if w != w0: + cutSub = False + VTXS = ofstShp.Wires[w].Vertexes + for V in VTXS: + v = FreeCAD.Vector(V.X, V.Y, 0.0) + for t in tagsComList: + if t.sub(v).Length < subDist: + cutSub = True + break + if cutSub is True: + break + if cutSub is True: + sub = Part.Wire(Part.__sortEdges__(ofstShp.Wires[w].Edges)) + subLoops.append(sub) + # Eif + + # Break offset loop into two wires - one of which is the desired profile path wire. + # (edgeIdxs0, edgeIdxs1) = self._separateWireAtVertexes(mainWire, ofstShp.Vertexes[vi0], ofstShp.Vertexes[vi1]) + (edgeIdxs0, edgeIdxs1) = self._separateWireAtVertexes(mainWire, mainWire.Vertexes[vi0], mainWire.Vertexes[vi1]) + edgs0 = list() + edgs1 = list() + for e in edgeIdxs0: + edgs0.append(mainWire.Edges[e]) + for e in edgeIdxs1: + edgs1.append(mainWire.Edges[e]) + part0 = Part.Wire(Part.__sortEdges__(edgs0)) + part1 = Part.Wire(Part.__sortEdges__(edgs1)) + + # Determine which part is nearest original edge(s) + distToPart0 = self._distMidToMid(wire.Wires[0], part0.Wires[0]) + distToPart1 = self._distMidToMid(wire.Wires[0], part1.Wires[0]) + if distToPart0 < distToPart1: + rtnWIRES.append(part0) + else: + rtnWIRES.append(part1) + rtnWIRES.extend(subLoops) + + tmpGrp.purgeTouched() + return rtnWIRES + + def _extractFaceOffset(self, obj, fcShape, isHole): + '''_extractFaceOffset(obj, fcShape, isHole) ... internal function. + Original _buildPathArea() version copied from PathAreaOp.py module. This version is modified. + Adjustments made based on notes by @sliptonic - https://github.com/sliptonic/FreeCAD/wiki/PathArea-notes.''' + PathLog.debug('_extractFaceOffset()') + + areaParams = {} + JOB = PathUtils.findParentJob(obj) + tolrnc = JOB.GeometryTolerance.Value + if self.useComp is True: + offset = self.ofstRadius # + tolrnc + else: + offset = self.offsetExtra # + tolrnc + + if isHole is False: + offset = 0 - offset + + areaParams['Offset'] = offset + areaParams['Fill'] = 1 + areaParams['Coplanar'] = 0 + areaParams['SectionCount'] = 1 # -1 = full(all per depthparams??) sections + areaParams['Reorient'] = True + areaParams['OpenMode'] = 0 + areaParams['MaxArcPoints'] = 400 # 400 + areaParams['Project'] = True + # areaParams['JoinType'] = 1 + + area = Path.Area() # Create instance of Area() class object + area.setPlane(PathUtils.makeWorkplane(fcShape)) # Set working plane + area.add(fcShape) # obj.Shape to use for extracting offset + area.setParams(**areaParams) # set parameters + + # Save parameters for debugging + # obj.AreaParams = str(area.getParams()) + # PathLog.debug("Area with params: {}".format(area.getParams())) + + offsetShape = area.getShape() + + return offsetShape + + def _findNearestVertex(self, shape, point): + PathLog.debug('_findNearestVertex()') + PT = FreeCAD.Vector(point.x, point.y, 0.0) + + def sortDist(tup): + return tup[4] + + PNTS = list() + for w in range(0, len(shape.Wires)): + WR = shape.Wires[w] + V = WR.Vertexes[0] + P = FreeCAD.Vector(V.X, V.Y, 0.0) + dist = P.sub(PT).Length + vi = 0 + pnt = P + vrt = V + for v in range(0, len(WR.Vertexes)): + V = WR.Vertexes[v] + P = FreeCAD.Vector(V.X, V.Y, 0.0) + d = P.sub(PT).Length + if d < dist: + dist = d + vi = v + pnt = P + vrt = V + PNTS.append((w, vi, pnt, vrt, dist)) + PNTS.sort(key=sortDist) + return PNTS + + def _separateWireAtVertexes(self, wire, VV1, VV2): + PathLog.debug('_separateWireAtVertexes()') + tolerance = self.JOB.GeometryTolerance.Value + grps = [[], []] + wireIdxs = [[], []] + V1 = FreeCAD.Vector(VV1.X, VV1.Y, VV1.Z) + V2 = FreeCAD.Vector(VV2.X, VV2.Y, VV2.Z) + + lenE = len(wire.Edges) + FLGS = list() + for e in range(0, lenE): + FLGS.append(0) + + chk4 = False + for e in range(0, lenE): + v = 0 + E = wire.Edges[e] + fv0 = FreeCAD.Vector(E.Vertexes[0].X, E.Vertexes[0].Y, E.Vertexes[0].Z) + fv1 = FreeCAD.Vector(E.Vertexes[1].X, E.Vertexes[1].Y, E.Vertexes[1].Z) + + if fv0.sub(V1).Length < tolerance: + v = 1 + if fv1.sub(V2).Length < tolerance: + v += 3 + chk4 = True + elif fv1.sub(V1).Length < tolerance: + v = 1 + if fv0.sub(V2).Length < tolerance: + v += 3 + chk4 = True + + if fv0.sub(V2).Length < tolerance: + v = 3 + if fv1.sub(V1).Length < tolerance: + v += 1 + chk4 = True + elif fv1.sub(V2).Length < tolerance: + v = 3 + if fv0.sub(V1).Length < tolerance: + v += 1 + chk4 = True + FLGS[e] += v + # Efor + PathLog.debug('_separateWireAtVertexes() FLGS: \n{}'.format(FLGS)) + + PRE = list() + POST = list() + IDXS = list() + IDX1 = list() + IDX2 = list() + for e in range(0, lenE): + f = FLGS[e] + PRE.append(f) + POST.append(f) + IDXS.append(e) + IDX1.append(e) + IDX2.append(e) + + PRE.extend(FLGS) + PRE.extend(POST) + lenFULL = len(PRE) + IDXS.extend(IDX1) + IDXS.extend(IDX2) + + if chk4 is True: + # find beginning 1 edge + begIdx = None + begFlg = False + for e in range(0, lenFULL): + f = PRE[e] + i = IDXS[e] + if f == 4: + begIdx = e + grps[0].append(f) + wireIdxs[0].append(i) + break + # find first 3 edge + endIdx = None + for e in range(begIdx + 1, lenE + begIdx): + f = PRE[e] + i = IDXS[e] + grps[1].append(f) + wireIdxs[1].append(i) + else: + # find beginning 1 edge + begIdx = None + begFlg = False + for e in range(0, lenFULL): + f = PRE[e] + if f == 1: + if begFlg is False: + begFlg = True + else: + begIdx = e + break + # find first 3 edge and group all first wire edges + endIdx = None + for e in range(begIdx, lenE + begIdx): + f = PRE[e] + i = IDXS[e] + if f == 3: + grps[0].append(f) + wireIdxs[0].append(i) + endIdx = e + break + else: + grps[0].append(f) + wireIdxs[0].append(i) + # Collect remaining edges + for e in range(endIdx + 1, lenFULL): + f = PRE[e] + i = IDXS[e] + if f == 1: + grps[1].append(f) + wireIdxs[1].append(i) + break + else: + wireIdxs[1].append(i) + grps[1].append(f) + # Efor + # Eif + + if PathLog.getLevel(PathLog.thisModule()) != 4: + PathLog.debug('grps[0]: {}'.format(grps[0])) + PathLog.debug('grps[1]: {}'.format(grps[1])) + PathLog.debug('wireIdxs[0]: {}'.format(wireIdxs[0])) + PathLog.debug('wireIdxs[1]: {}'.format(wireIdxs[1])) + PathLog.debug('PRE: {}'.format(PRE)) + PathLog.debug('IDXS: {}'.format(IDXS)) + + return (wireIdxs[0], wireIdxs[1]) + + def _makeCrossSection(self, shape, sliceZ, zHghtTrgt=False): + '''_makeCrossSection(shape, sliceZ, zHghtTrgt=None)... + Creates cross-section objectc from shape. Translates cross-section to zHghtTrgt if available. + Makes face shape from cross-section object. Returns face shape at zHghtTrgt.''' + # Create cross-section of shape and translate + wires = list() + slcs = shape.slice(FreeCAD.Vector(0, 0, 1), sliceZ) + if len(slcs) > 0: + for i in slcs: + wires.append(i) + comp = Part.Compound(wires) + if zHghtTrgt is not False: + comp.translate(FreeCAD.Vector(0, 0, zHghtTrgt - comp.BoundBox.ZMin)) + return comp + + return False + + def _makeExtendedBoundBox(self, wBB, bbBfr, zDep): + pl = FreeCAD.Placement() + pl.Rotation = FreeCAD.Rotation(FreeCAD.Vector(0, 0, 1), 0) + pl.Base = FreeCAD.Vector(0, 0, 0) + + p1 = FreeCAD.Vector(wBB.XMin - bbBfr, wBB.YMin - bbBfr, zDep) + p2 = FreeCAD.Vector(wBB.XMax + bbBfr, wBB.YMin - bbBfr, zDep) + p3 = FreeCAD.Vector(wBB.XMax + bbBfr, wBB.YMax + bbBfr, zDep) + p4 = FreeCAD.Vector(wBB.XMin - bbBfr, wBB.YMax + bbBfr, zDep) + bb = Draft.makeWire([p1, p2, p3, p4], placement=pl, closed=True, face=False, support=None) + bb.Label = 'ProfileEdges_BoundBox' + bb.recompute() + bb.purgeTouched() + self.tmpGrp.addObject(bb) + + return bb + + def _makeSimpleCircle(self, rad, plcmnt, isFace=False, label='SimpleCircle'): + C = Draft.makeCircle(rad, placement=plcmnt, face=isFace) + C.Label = 'tmp' + label + C.recompute() + C.purgeTouched() + self.tmpGrp.addObject(C) + return C + + def _makeIntersectionTags(self, useWire, numOrigEdges, fdv): + # Create circular probe tags around perimiter of wire + extTags = list() + intTags = list() + tagRad = (self.radius / 2) + tagCnt = 0 + begInt = None + begExt = None + for e in range(0, numOrigEdges): + E = useWire.Edges[e] + LE = E.Length + if LE > (self.radius * 2): + nt = math.ceil(LE / (tagRad * math.pi)) # (tagRad * 2 * math.pi) is circumference + else: + nt = 4 # desired + 1 + mid = LE / nt + spc = self.radius / 10 + for i in range(0, nt): + if i == 0: + if e == 0: + if LE > 0.2: + aspc = 0.1 + else: + aspc = LE * 0.75 + cp1 = E.valueAt(E.getParameterByLength(0)) + cp2 = E.valueAt(E.getParameterByLength(aspc)) + (intTObj, extTObj) = self._makeOffsetCircleTag(cp1, cp2, tagRad, fdv, 'BeginEdge[{}]_'.format(e)) + if intTObj is not False: + begInt = intTObj.Shape + begExt = extTObj.Shape + else: + d = i * mid + cp1 = E.valueAt(E.getParameterByLength(d - spc)) + cp2 = E.valueAt(E.getParameterByLength(d + spc)) + (intTObj, extTObj) = self._makeOffsetCircleTag(cp1, cp2, tagRad, fdv, 'Edge[{}]_'.format(e)) + if intTObj is not False: + tagCnt += nt + intTags.append(intTObj) + extTags.append(extTObj) + tagArea = math.pi * tagRad**2 * tagCnt + # FreeCAD object required for Part::MultiCommon usage + intTagsComp = Part.makeCompound([T.Shape for T in intTags]) + iTAG = FreeCAD.ActiveDocument.addObject('Part::Feature', 'tmpInteriorTags') + iTAG.Shape = intTagsComp + iTAG.purgeTouched() + self.tmpGrp.addObject(iTAG) + # FreeCAD object required for Part::MultiCommon usage + extTagsComp = Part.makeCompound([T.Shape for T in extTags]) + eTAG = FreeCAD.ActiveDocument.addObject('Part::Feature', 'tmpExteriorTags') + eTAG.Shape = extTagsComp + eTAG.purgeTouched() + self.tmpGrp.addObject(eTAG) + return (begInt, begExt, iTAG, eTAG) + + def _makeOffsetCircleTag(self, p1, p2, cutterRad, depth, lbl, reverse=False): + pb = FreeCAD.Vector(p1.x, p1.y, 0.0) + pe = FreeCAD.Vector(p2.x, p2.y, 0.0) + + toMid = pe.sub(pb).multiply(0.5) + lenToMid = toMid.Length + if lenToMid == 0.0: + # Probably a vertical line segment + return (False, False) + + cutFactor = (cutterRad / 2.1) / lenToMid # = 2 is tangent to wire; > 2 allows tag to overlap wire; < 2 pulls tag away from wire + perpE = FreeCAD.Vector(-1 * toMid.y, toMid.x, 0.0).multiply(-1 * cutFactor) # exterior tag + extPnt = pb.add(toMid.add(perpE)) + + pl = FreeCAD.Placement() + pl.Rotation = FreeCAD.Rotation(FreeCAD.Vector(0, 0, 1), 0) + # make exterior tag + adjlbl = lbl + 'Ext' + pl.Base = extPnt.add(FreeCAD.Vector(0, 0, depth)) + extTag = self._makeSimpleCircle((cutterRad / 2), pl, True, adjlbl) + + # make interior tag + adjlbl = lbl + 'Int' + perpI = FreeCAD.Vector(-1 * toMid.y, toMid.x, 0.0).multiply(cutFactor) # interior tag + intPnt = pb.add(toMid.add(perpI)) + pl.Base = intPnt.add(FreeCAD.Vector(0, 0, depth)) + intTag = self._makeSimpleCircle((cutterRad / 2), pl, True, adjlbl) + + return (intTag, extTag) + + def _extrudeObject(self, objToExt, extFwdLen, solid=True): + # Extrude non-horizontal wire + E = FreeCAD.ActiveDocument.addObject('Part::Extrusion', 'tmpExtrusion') + E.Base = objToExt + E.DirMode = 'Custom' + E.Dir = FreeCAD.Vector(0, 0, 1) + E.LengthFwd = extFwdLen + E.Solid = solid + E.recompute() + E.purgeTouched() + self.tmpGrp.addObject(E) + return E + + def _makeStop(self, sType, pA, pB, lbl): + rad = self.radius + ofstRad = self.ofstRadius + extra = self.radius / 10 + + pl = FreeCAD.Placement() + pl.Rotation = FreeCAD.Rotation(FreeCAD.Vector(0, 0, 1), 0) + pl.Base = FreeCAD.Vector(0, 0, 0) + + E = FreeCAD.Vector(pB.x, pB.y, 0) # endpoint + C = FreeCAD.Vector(pA.x, pA.y, 0) # checkpoint + lenEC = E.sub(C).Length + + if self.useComp is True or (self.useComp is False and self.offsetExtra != 0): + # 'L' stop shape and edge legend + # --1-- + # | | + # 2 6 + # | | + # | ----5----| + # | 4 + # -----3-------| + # positive dist in _makePerp2DVector() is CCW rotation + p1 = E + if sType == 'BEG': + p2 = self._makePerp2DVector(C, E, -0.25) # E1 + p3 = self._makePerp2DVector(p1, p2, ofstRad + 1 + extra) # E2 + p4 = self._makePerp2DVector(p2, p3, 0.25 + ofstRad + extra) # E3 + p5 = self._makePerp2DVector(p3, p4, 1 + extra) # E4 + p6 = self._makePerp2DVector(p4, p5, ofstRad + extra) # E5 + elif sType == 'END': + p2 = self._makePerp2DVector(C, E, 0.25) # E1 + p3 = self._makePerp2DVector(p1, p2, -1 * (ofstRad + 1 + extra)) # E2 + p4 = self._makePerp2DVector(p2, p3, -1 * (0.25 + ofstRad + extra)) # E3 + p5 = self._makePerp2DVector(p3, p4, -1 * (1 + extra)) # E4 + p6 = self._makePerp2DVector(p4, p5, -1 * (ofstRad + extra)) # E5 + p7 = E # E6 + S = Draft.makeWire([p1, p2, p3, p4, p5, p6, p7], placement=pl, closed=True, face=True) + else: + # 'L' stop shape and edge legend + # : + # |----2-------| + # 3 1 + # |-----4------| + # positive dist in _makePerp2DVector() is CCW rotation + p1 = E + if sType == 'BEG': + p2 = self._makePerp2DVector(C, E, -1 * (0.25 + abs(self.offsetExtra))) # left, 0.25 + p3 = self._makePerp2DVector(p1, p2, 0.25 + abs(self.offsetExtra)) + p4 = self._makePerp2DVector(p2, p3, (0.5 + abs(self.offsetExtra))) # FIRST POINT + p5 = self._makePerp2DVector(p3, p4, 0.25 + abs(self.offsetExtra)) # E1 SECOND + elif sType == 'END': + p2 = self._makePerp2DVector(C, E, (0.25 + abs(self.offsetExtra))) # left, 0.25 + p3 = self._makePerp2DVector(p1, p2, -1 * (0.25 + abs(self.offsetExtra))) + p4 = self._makePerp2DVector(p2, p3, -1 * (0.5 + abs(self.offsetExtra))) # FIRST POINT + p5 = self._makePerp2DVector(p3, p4, -1 * (0.25 + abs(self.offsetExtra))) # E1 SECOND + p6 = p1 # E4 + S = Draft.makeWire([p1, p2, p3, p4, p5, p6], placement=pl, closed=True, face=True) + # Eif + S.Label = 'tmp' + lbl + S.recompute() + S.purgeTouched() + self.tmpGrp.addObject(S) + return S.Shape + + def _makePerp2DVector(self, v1, v2, dist): + p1 = FreeCAD.Vector(v1.x, v1.y, 0.0) + p2 = FreeCAD.Vector(v2.x, v2.y, 0.0) + toEnd = p2.sub(p1) + factor = dist / toEnd.Length + perp = FreeCAD.Vector(-1 * toEnd.y, toEnd.x, 0.0).multiply(factor) + return p1.add(toEnd.add(perp)) + + def _distMidToMid(self, wireA, wireB): + mpA = self._findWireMidpoint(wireA) + mpB = self._findWireMidpoint(wireB) + return mpA.sub(mpB).Length + + def _findWireMidpoint(self, wire): + midPnt = None + dist = 0.0 + wL = wire.Length + midW = wL / 2 + + for e in range(0, len(wire.Edges)): + E = wire.Edges[e] + elen = E.Length + d_ = dist + elen + if dist < midW and midW <= d_: + dtm = midW - dist + midPnt = E.valueAt(E.getParameterByLength(dtm)) + break + else: + dist += elen + return midPnt + + def SetupProperties(): return PathProfileBase.SetupProperties() + def Create(name, obj = None): '''Create(name) ... Creates and returns a Profile based on edges operation.''' if obj is None: From ab0ace8f614ece31eec672a315432cc124f1c0a9 Mon Sep 17 00:00:00 2001 From: Bernd Hahnebach Date: Wed, 26 Feb 2020 20:55:47 +0100 Subject: [PATCH 2/4] FEM: improve imports --- src/Mod/Fem/femcommands/manager.py | 2 +- .../_ViewProviderFemConstraintElectrostaticPotential.py | 4 ++-- .../femguiobjects/_ViewProviderFemConstraintFlowVelocity.py | 4 ++-- .../_ViewProviderFemConstraintInitialFlowVelocity.py | 4 ++-- src/Mod/Fem/femguiobjects/_ViewProviderFemMeshGmsh.py | 2 +- src/Mod/Fem/feminout/importCcxFrdResults.py | 2 +- src/Mod/Fem/femmesh/gmshtools.py | 2 +- src/Mod/Fem/femresult/resulttools.py | 2 +- src/Mod/Fem/femsolver/calculix/solver.py | 2 +- src/Mod/Fem/femsolver/calculix/tasks.py | 4 ++-- src/Mod/Fem/femsolver/elmer/equations/elasticity.py | 2 +- src/Mod/Fem/femsolver/elmer/equations/electrostatic.py | 2 +- src/Mod/Fem/femsolver/elmer/equations/equation.py | 2 +- src/Mod/Fem/femsolver/elmer/equations/flow.py | 2 +- src/Mod/Fem/femsolver/elmer/equations/fluxsolver.py | 2 +- src/Mod/Fem/femsolver/elmer/equations/heat.py | 2 +- src/Mod/Fem/femsolver/elmer/solver.py | 2 +- src/Mod/Fem/femsolver/elmer/tasks.py | 4 ++-- src/Mod/Fem/femsolver/elmer/writer.py | 6 +++--- src/Mod/Fem/femsolver/run.py | 4 ++-- src/Mod/Fem/femsolver/z88/solver.py | 2 +- src/Mod/Fem/femsolver/z88/tasks.py | 4 ++-- src/Mod/Fem/femtools/ccxtools.py | 4 ++-- 23 files changed, 33 insertions(+), 33 deletions(-) diff --git a/src/Mod/Fem/femcommands/manager.py b/src/Mod/Fem/femcommands/manager.py index e6490737dc..69774a2107 100644 --- a/src/Mod/Fem/femcommands/manager.py +++ b/src/Mod/Fem/femcommands/manager.py @@ -28,7 +28,7 @@ __url__ = "http://www.freecadweb.org" # @{ import FreeCAD -import femtools.femutils as femutils +from femtools import femutils if FreeCAD.GuiUp: import FreeCADGui diff --git a/src/Mod/Fem/femguiobjects/_ViewProviderFemConstraintElectrostaticPotential.py b/src/Mod/Fem/femguiobjects/_ViewProviderFemConstraintElectrostaticPotential.py index cca9537471..cc1f3bac35 100644 --- a/src/Mod/Fem/femguiobjects/_ViewProviderFemConstraintElectrostaticPotential.py +++ b/src/Mod/Fem/femguiobjects/_ViewProviderFemConstraintElectrostaticPotential.py @@ -33,8 +33,8 @@ import FreeCADGui from . import ViewProviderFemConstraint # for the panel -import femtools.femutils as femutils -import femtools.membertools as membertools +from femtools import femutils +from femtools import membertools from FreeCAD import Units from . import FemSelectionWidgets diff --git a/src/Mod/Fem/femguiobjects/_ViewProviderFemConstraintFlowVelocity.py b/src/Mod/Fem/femguiobjects/_ViewProviderFemConstraintFlowVelocity.py index 3e7c648db4..f8f012f7d1 100644 --- a/src/Mod/Fem/femguiobjects/_ViewProviderFemConstraintFlowVelocity.py +++ b/src/Mod/Fem/femguiobjects/_ViewProviderFemConstraintFlowVelocity.py @@ -33,8 +33,8 @@ import FreeCADGui from . import ViewProviderFemConstraint # for the panel -import femtools.femutils as femutils -import femtools.membertools as membertools +from femtools import femutils +from femtools import membertools from FreeCAD import Units from . import FemSelectionWidgets diff --git a/src/Mod/Fem/femguiobjects/_ViewProviderFemConstraintInitialFlowVelocity.py b/src/Mod/Fem/femguiobjects/_ViewProviderFemConstraintInitialFlowVelocity.py index 5cfdb43387..5262150101 100644 --- a/src/Mod/Fem/femguiobjects/_ViewProviderFemConstraintInitialFlowVelocity.py +++ b/src/Mod/Fem/femguiobjects/_ViewProviderFemConstraintInitialFlowVelocity.py @@ -33,8 +33,8 @@ import FreeCADGui from . import ViewProviderFemConstraint # for the panel -import femtools.femutils as femutils -import femtools.membertools as membertools +from femtools import femutils +from femtools import membertools from FreeCAD import Units diff --git a/src/Mod/Fem/femguiobjects/_ViewProviderFemMeshGmsh.py b/src/Mod/Fem/femguiobjects/_ViewProviderFemMeshGmsh.py index 9de35883d1..cb9b470a1b 100644 --- a/src/Mod/Fem/femguiobjects/_ViewProviderFemMeshGmsh.py +++ b/src/Mod/Fem/femguiobjects/_ViewProviderFemMeshGmsh.py @@ -390,7 +390,7 @@ class _TaskPanelFemMeshGmsh: self.gmsh_runs = True self.console_log("We are going to start ...") self.get_active_analysis() - import femmesh.gmshtools as gmshtools + from femmesh import gmshtools gmsh_mesh = gmshtools.GmshTools(self.obj, self.analysis) self.console_log("Start Gmsh ...") error = "" diff --git a/src/Mod/Fem/feminout/importCcxFrdResults.py b/src/Mod/Fem/feminout/importCcxFrdResults.py index e1311824d1..697809e664 100644 --- a/src/Mod/Fem/feminout/importCcxFrdResults.py +++ b/src/Mod/Fem/feminout/importCcxFrdResults.py @@ -127,7 +127,7 @@ def importFrd( # complementary result object calculations import femresult.resulttools as restools - import femtools.femutils as femutils + from femtools import femutils if not res_obj.MassFlowRate: # information 1: # only compact result if not Flow 1D results diff --git a/src/Mod/Fem/femmesh/gmshtools.py b/src/Mod/Fem/femmesh/gmshtools.py index 594f34cfa9..73c863ef92 100644 --- a/src/Mod/Fem/femmesh/gmshtools.py +++ b/src/Mod/Fem/femmesh/gmshtools.py @@ -35,7 +35,7 @@ from FreeCAD import Console import Fem from FreeCAD import Units from . import meshtools -import femtools.femutils as femutils +from femtools import femutils class GmshTools(): diff --git a/src/Mod/Fem/femresult/resulttools.py b/src/Mod/Fem/femresult/resulttools.py index be35b03775..e3e0b276e6 100644 --- a/src/Mod/Fem/femresult/resulttools.py +++ b/src/Mod/Fem/femresult/resulttools.py @@ -27,7 +27,7 @@ __url__ = "http://www.freecadweb.org" # @{ import FreeCAD -import femtools.femutils as femutils +from femtools import femutils import numpy as np from math import isnan diff --git a/src/Mod/Fem/femsolver/calculix/solver.py b/src/Mod/Fem/femsolver/calculix/solver.py index cdee5bd634..35aa09a91b 100644 --- a/src/Mod/Fem/femsolver/calculix/solver.py +++ b/src/Mod/Fem/femsolver/calculix/solver.py @@ -30,7 +30,7 @@ import os import glob import FreeCAD -import femtools.femutils as femutils +from femtools import femutils from .. import run from .. import solverbase diff --git a/src/Mod/Fem/femsolver/calculix/tasks.py b/src/Mod/Fem/femsolver/calculix/tasks.py index c1be39c03b..348c4b021f 100644 --- a/src/Mod/Fem/femsolver/calculix/tasks.py +++ b/src/Mod/Fem/femsolver/calculix/tasks.py @@ -32,8 +32,8 @@ import subprocess import os.path import FreeCAD -import femtools.femutils as femutils -import femtools.membertools as membertools +from femtools import femutils +from femtools import membertools import feminout.importCcxFrdResults as importCcxFrdResults import feminout.importCcxDatResults as importCcxDatResults diff --git a/src/Mod/Fem/femsolver/elmer/equations/elasticity.py b/src/Mod/Fem/femsolver/elmer/equations/elasticity.py index 23cdae5ff6..33a755c3e2 100644 --- a/src/Mod/Fem/femsolver/elmer/equations/elasticity.py +++ b/src/Mod/Fem/femsolver/elmer/equations/elasticity.py @@ -26,7 +26,7 @@ __url__ = "http://www.freecadweb.org" ## \addtogroup FEM # @{ -import femtools.femutils as femutils +from femtools import femutils from ... import equationbase from . import linear diff --git a/src/Mod/Fem/femsolver/elmer/equations/electrostatic.py b/src/Mod/Fem/femsolver/elmer/equations/electrostatic.py index c16edc60c1..96e4de9a2c 100644 --- a/src/Mod/Fem/femsolver/elmer/equations/electrostatic.py +++ b/src/Mod/Fem/femsolver/elmer/equations/electrostatic.py @@ -26,7 +26,7 @@ __url__ = "http://www.freecadweb.org" ## \addtogroup FEM # @{ -import femtools.femutils as femutils +from femtools import femutils from ... import equationbase from . import linear diff --git a/src/Mod/Fem/femsolver/elmer/equations/equation.py b/src/Mod/Fem/femsolver/elmer/equations/equation.py index 4078677195..8fbf7bbbe3 100644 --- a/src/Mod/Fem/femsolver/elmer/equations/equation.py +++ b/src/Mod/Fem/femsolver/elmer/equations/equation.py @@ -28,7 +28,7 @@ __url__ = "http://www.freecadweb.org" import FreeCAD as App from ... import equationbase -import femtools.membertools as membertools +from femtools import membertools if App.GuiUp: import FreeCADGui as Gui diff --git a/src/Mod/Fem/femsolver/elmer/equations/flow.py b/src/Mod/Fem/femsolver/elmer/equations/flow.py index f84b5b1d20..d3c3e26872 100644 --- a/src/Mod/Fem/femsolver/elmer/equations/flow.py +++ b/src/Mod/Fem/femsolver/elmer/equations/flow.py @@ -26,7 +26,7 @@ __url__ = "http://www.freecadweb.org" ## \addtogroup FEM # @{ -import femtools.femutils as femutils +from femtools import femutils from . import nonlinear from ... import equationbase diff --git a/src/Mod/Fem/femsolver/elmer/equations/fluxsolver.py b/src/Mod/Fem/femsolver/elmer/equations/fluxsolver.py index ed6c59440d..64a85ab070 100644 --- a/src/Mod/Fem/femsolver/elmer/equations/fluxsolver.py +++ b/src/Mod/Fem/femsolver/elmer/equations/fluxsolver.py @@ -26,7 +26,7 @@ __url__ = "http://www.freecadweb.org" ## \addtogroup FEM # @{ -import femtools.femutils as femutils +from femtools import femutils from ... import equationbase from . import linear diff --git a/src/Mod/Fem/femsolver/elmer/equations/heat.py b/src/Mod/Fem/femsolver/elmer/equations/heat.py index 9c05eafea0..fb74164827 100644 --- a/src/Mod/Fem/femsolver/elmer/equations/heat.py +++ b/src/Mod/Fem/femsolver/elmer/equations/heat.py @@ -26,7 +26,7 @@ __url__ = "http://www.freecadweb.org" ## \addtogroup FEM # @{ -import femtools.femutils as femutils +from femtools import femutils from . import nonlinear from ... import equationbase diff --git a/src/Mod/Fem/femsolver/elmer/solver.py b/src/Mod/Fem/femsolver/elmer/solver.py index 5553863114..b270fceae9 100644 --- a/src/Mod/Fem/femsolver/elmer/solver.py +++ b/src/Mod/Fem/femsolver/elmer/solver.py @@ -26,7 +26,7 @@ __url__ = "http://www.freecadweb.org" ## \addtogroup FEM # @{ -import femtools.femutils as femutils +from femtools import femutils from .. import run from .. import solverbase diff --git a/src/Mod/Fem/femsolver/elmer/tasks.py b/src/Mod/Fem/femsolver/elmer/tasks.py index 86b3cf4d52..334272d14e 100644 --- a/src/Mod/Fem/femsolver/elmer/tasks.py +++ b/src/Mod/Fem/femsolver/elmer/tasks.py @@ -31,8 +31,8 @@ import os.path import sys import FreeCAD -import femtools.femutils as femutils -import femtools.membertools as membertools +from femtools import femutils +from femtools import membertools from .. import run from .. import settings diff --git a/src/Mod/Fem/femsolver/elmer/writer.py b/src/Mod/Fem/femsolver/elmer/writer.py index dd69d6e604..dd4abdd25a 100644 --- a/src/Mod/Fem/femsolver/elmer/writer.py +++ b/src/Mod/Fem/femsolver/elmer/writer.py @@ -34,9 +34,9 @@ import tempfile from FreeCAD import Units from FreeCAD import Console import Fem -import femtools.femutils as femutils -import femtools.membertools as membertools -import femmesh.gmshtools as gmshtools +from femmesh import gmshtools +from femtools import femutils +from femtools import membertools from .. import settings from . import sifio diff --git a/src/Mod/Fem/femsolver/run.py b/src/Mod/Fem/femsolver/run.py index 7b22ba2936..51afcfa299 100644 --- a/src/Mod/Fem/femsolver/run.py +++ b/src/Mod/Fem/femsolver/run.py @@ -40,8 +40,8 @@ import shutil import tempfile import FreeCAD as App -import femtools.femutils as femutils -import femtools.membertools as membertools +from femtools import femutils +from femtools import membertools from . import settings from . import signal from . import task diff --git a/src/Mod/Fem/femsolver/z88/solver.py b/src/Mod/Fem/femsolver/z88/solver.py index 8ff21e9ced..f0c7e9872a 100644 --- a/src/Mod/Fem/femsolver/z88/solver.py +++ b/src/Mod/Fem/femsolver/z88/solver.py @@ -30,7 +30,7 @@ import os import glob import FreeCAD -import femtools.femutils as femutils +from femtools import femutils from .. import run from .. import solverbase diff --git a/src/Mod/Fem/femsolver/z88/tasks.py b/src/Mod/Fem/femsolver/z88/tasks.py index 2192930caf..413bda7283 100644 --- a/src/Mod/Fem/femsolver/z88/tasks.py +++ b/src/Mod/Fem/femsolver/z88/tasks.py @@ -31,8 +31,8 @@ import subprocess import os.path import FreeCAD -import femtools.femutils as femutils -import femtools.membertools as membertools +from femtools import femutils +from femtools import membertools import feminout.importZ88O2Results as importZ88O2Results from .. import run diff --git a/src/Mod/Fem/femtools/ccxtools.py b/src/Mod/Fem/femtools/ccxtools.py index 6d750f1c0d..871f0824bd 100644 --- a/src/Mod/Fem/femtools/ccxtools.py +++ b/src/Mod/Fem/femtools/ccxtools.py @@ -32,8 +32,8 @@ import os import sys import subprocess import FreeCAD -import femtools.femutils as femutils -import femtools.membertools as membertools +from femtools import femutils +from femtools import membertools from PySide import QtCore if FreeCAD.GuiUp: from PySide import QtGui From e0a2e3313223eecd30e3c7cf765c525f4e53b6bf Mon Sep 17 00:00:00 2001 From: Bernd Hahnebach Date: Wed, 26 Feb 2020 20:55:49 +0100 Subject: [PATCH 3/4] FEM: constants, add new module --- src/Mod/Fem/CMakeLists.txt | 1 + src/Mod/Fem/femtools/constants.py | 56 +++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 src/Mod/Fem/femtools/constants.py diff --git a/src/Mod/Fem/CMakeLists.txt b/src/Mod/Fem/CMakeLists.txt index 77a341b59a..76003ccfbe 100755 --- a/src/Mod/Fem/CMakeLists.txt +++ b/src/Mod/Fem/CMakeLists.txt @@ -221,6 +221,7 @@ SET(FemTools_SRCS femtools/__init__.py femtools/membertools.py femtools/ccxtools.py + femtools/constants.py femtools/errors.py femtools/femutils.py femtools/tokrules.py diff --git a/src/Mod/Fem/femtools/constants.py b/src/Mod/Fem/femtools/constants.py new file mode 100644 index 0000000000..f1a215b278 --- /dev/null +++ b/src/Mod/Fem/femtools/constants.py @@ -0,0 +1,56 @@ +# *************************************************************************** +# * Copyright (c) 2020 Bernd Hahnebach * +# * * +# * This program is free software; you can redistribute it and/or modify * +# * it under the terms of the GNU Lesser General Public License (LGPL) * +# * as published by the Free Software Foundation; either version 2 of * +# * the License, or (at your option) any later version. * +# * for detail see the LICENCE text file. * +# * * +# * This program is distributed in the hope that it will be useful, * +# * but WITHOUT ANY WARRANTY; without even the implied warranty of * +# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +# * GNU Library General Public License for more details. * +# * * +# * You should have received a copy of the GNU Library General Public * +# * License along with this program; if not, write to the Free Software * +# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * +# * USA * +# * * +# *************************************************************************** +""" Collection of natural constants for the Fem module. + +This module contains natural constants for the Fem module. +All constants are in SI units. +""" + + +__title__ = "Constants" +__author__ = "Bernd Hahnebach" +__url__ = "http://www.freecadweb.org" + + +def gravity(): + return "9.82 m/s^2" + + +def stefan_boltzmann(): + return "5.67e-8 W/(m^2*K^4)" + + +def permittivity_of_vakuum(): + return "8.8542e-12 s^4*A^2/(m*kg)" + + +def boltzmann_constant(): + return "1.3807e-23 J/K" + + +""" +from FreeCAD import Units +from femtools import constants +Units.Quantity(constants.gravity()).getValueAs("mm/s^2") + +""" + +# TODO: a unit test to be sure these values are returned! From b70cae82d27926054164fa675d26f5f33fd32e3e Mon Sep 17 00:00:00 2001 From: Bernd Hahnebach Date: Wed, 26 Feb 2020 20:55:49 +0100 Subject: [PATCH 4/4] FEM: constants, use new module in elmer and ccx --- src/Mod/Fem/femsolver/calculix/writer.py | 6 +++++- src/Mod/Fem/femsolver/elmer/writer.py | 9 +++++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/Mod/Fem/femsolver/calculix/writer.py b/src/Mod/Fem/femsolver/calculix/writer.py index 8087f56d78..e93b06e016 100644 --- a/src/Mod/Fem/femsolver/calculix/writer.py +++ b/src/Mod/Fem/femsolver/calculix/writer.py @@ -63,6 +63,9 @@ class FemInputWriterCcx(writerbase.FemInputWriter): self.dir_name, "{}_inout_nodes.txt".format(self.mesh_object.Name) ) + from femtools import constants + from FreeCAD import Units + self.gravity = Units.Quantity(constants.gravity()).getValueAs("mm/s^2") def write_calculix_input_file(self): timestart = time.process_time() @@ -1095,9 +1098,10 @@ class FemInputWriterCcx(writerbase.FemInputWriter): f.write("** " + selwei_obj.Label + "\n") f.write("*DLOAD\n") f.write( - "{},GRAV,9810,{},{},{}\n" + "{},GRAV,{},{},{},{}\n" .format( self.ccx_eall, + self.gravity, selwei_obj.Gravity_x, selwei_obj.Gravity_y, selwei_obj.Gravity_z diff --git a/src/Mod/Fem/femsolver/elmer/writer.py b/src/Mod/Fem/femsolver/elmer/writer.py index dd4abdd25a..48c366d966 100644 --- a/src/Mod/Fem/femsolver/elmer/writer.py +++ b/src/Mod/Fem/femsolver/elmer/writer.py @@ -35,6 +35,7 @@ from FreeCAD import Units from FreeCAD import Console import Fem from femmesh import gmshtools +from femtools import constants from femtools import femutils from femtools import membertools from .. import settings @@ -60,10 +61,10 @@ UNITS = { CONSTS_DEF = { - "Gravity": "9.82 m/s^2", - "StefanBoltzmann": "5.67e-8 W/(m^2*K^4)", - "PermittivityOfVacuum": "8.8542e-12 s^4*A^2/(m*kg)", - "BoltzmannConstant": "1.3807e-23 J/K", + "Gravity": constants.gravity(), + "StefanBoltzmann": constants.stefan_boltzmann(), + "PermittivityOfVacuum": constants.permittivity_of_vakuum(), + "BoltzmannConstant": constants.boltzmann_constant(), }