Merge branch 'master' into master

This commit is contained in:
sliptonic
2020-02-26 17:11:00 -06:00
committed by GitHub
28 changed files with 1095 additions and 62 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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 = ""

View File

@@ -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

View File

@@ -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():

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -34,9 +34,10 @@ 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 constants
from femtools import femutils
from femtools import membertools
from .. import settings
from . import sifio
@@ -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(),
}

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -0,0 +1,56 @@
# ***************************************************************************
# * Copyright (c) 2020 Bernd Hahnebach <bernd@bimstatik.org> *
# * *
# * 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!

View File

@@ -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':

View File

@@ -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: