New tool: ProjectArray
This commit is contained in:
parent
b53c6d0a8f
commit
11df51a085
|
@ -62,6 +62,8 @@ class LatticeWorkbench (Workbench):
|
|||
cmdsArrayTools = cmdsArrayTools + mod.exportedCommands
|
||||
import latticeArrayFilter as mod
|
||||
cmdsArrayTools = cmdsArrayTools + mod.exportedCommands
|
||||
import latticeProjectArray as mod
|
||||
cmdsArrayTools = cmdsArrayTools + mod.exportedCommands
|
||||
|
||||
import latticeApply as mod
|
||||
cmdsArrayTools = cmdsArrayTools + mod.exportedCommands
|
||||
|
|
|
@ -74,7 +74,7 @@ class LatticeArrayFilter(latticeBaseFeature.LatticeFeature):
|
|||
def derivedExecute(self,obj):
|
||||
#validity check
|
||||
if not latticeBaseFeature.isObjectLattice(obj.Base):
|
||||
latticeExecuter.warning(obj,"A generic shape is expected, but a lattice object was supplied. It will be treated as a generic shape.")
|
||||
latticeExecuter.warning(obj,"A lattice object is expected as Base, but a generic shape was provided. It will be treated as a lattice object; results may be unexpected.")
|
||||
|
||||
output = [] #variable to receive the final list of placements
|
||||
leaves = LCE.AllLeaves(obj.Base.Shape)
|
||||
|
|
|
@ -39,6 +39,9 @@ def makeOrientationFromLocalAxes(ZAx, XAx = None):
|
|||
corrected and modified
|
||||
|
||||
'''
|
||||
return makeOrientationFromLocalAxesUni("ZX",XAx= XAx, ZAx= ZAx)
|
||||
|
||||
#dead old code that worked
|
||||
if XAx is None:
|
||||
XAx = App.Vector(0,0,1) #Why Z? Because I prefer local X axis to be aligned so that local XZ plane is parallel to global Z axis.
|
||||
#First, compute all three axes.
|
||||
|
@ -67,3 +70,92 @@ def makeOrientationFromLocalAxes(ZAx, XAx = None):
|
|||
# and extract rotation from placement.
|
||||
ori = tmpplm.Rotation
|
||||
return ori
|
||||
|
||||
def makeOrientationFromLocalAxesUni(priorityString, XAx = None, YAx = None, ZAx = None):
|
||||
'''
|
||||
makeOrientationFromLocalAxesUni(priorityString, XAx = None, YAx = None, ZAx = None):
|
||||
constructs App.Rotation to get into alignment with given local axes.
|
||||
Priority string is a string like "ZXY", which defines how axes are made
|
||||
perpendicular. For example, "ZXY" means that Z is followed strictly, X is
|
||||
made to be perpendicular to Z, and Y is completely ignored (a new one will
|
||||
be computed from X and Z). The strict axis must be specified, all other are
|
||||
optional.
|
||||
'''
|
||||
|
||||
if XAx is None:
|
||||
XAx = App.Vector()
|
||||
if YAx is None:
|
||||
YAx = App.Vector()
|
||||
if ZAx is None:
|
||||
ZAx = App.Vector()
|
||||
|
||||
axDic = {"X": XAx, "Y": YAx, "Z": ZAx}
|
||||
|
||||
#expand priority string to list all axes
|
||||
if len(priorityString) == 0:
|
||||
priorityString = "ZXY"
|
||||
if len(priorityString) == 1:
|
||||
if priorityString == "X":
|
||||
priorityString = priorityString + "Z"
|
||||
elif priorityString == "Y":
|
||||
priorityString = priorityString + "Z"
|
||||
elif priorityString == "Z":
|
||||
priorityString = priorityString + "X"
|
||||
if len(priorityString) == 2:
|
||||
for ch in "XYZ":
|
||||
if not (ch in priorityString):
|
||||
priorityString = priorityString + ch
|
||||
break
|
||||
|
||||
mainAx = axDic[priorityString[0]] #Driving axis
|
||||
secAx = axDic[priorityString[1]] #Hint axis
|
||||
thirdAx = axDic[priorityString[2]] #Ignored axis
|
||||
#Note: since we need to change the actual XAx,YAx,ZAx while assigning to
|
||||
# mainAx, secAx, thirdAx, we can't use '=' operator, because '=' reassigns
|
||||
# the reference, and the variables lose linkage. For that purpose,
|
||||
# _assignVector routine was introuced. It assigns the coordinates of the
|
||||
# vector, without replacing referenes
|
||||
|
||||
#force the axes be perpendicular
|
||||
mainAx.normalize()
|
||||
tmpAx = mainAx.cross(secAx)
|
||||
if tmpAx.Length < ParaConfusion*10.0:
|
||||
#failed, try some other secondary axis
|
||||
#FIXME: consider thirdAx, maybe??
|
||||
_assignVector( secAx, { "X":App.Vector(0,0,1),
|
||||
"Y":App.Vector(0,0,1), #FIXME: revise
|
||||
"Z":App.Vector(0,0,1)
|
||||
}[priorityString[1]])
|
||||
tmpAx = mainAx.cross(secAx)
|
||||
if tmpAx.Length < ParaConfusion*10.0:
|
||||
#failed again. (mainAx is Z). try some other secondary axis.
|
||||
# Z axis
|
||||
_assignVector(secAx, {"X":App.Vector(1,0,0),
|
||||
"Y":App.Vector(0,1,0), #FIXME: revise
|
||||
"Z":App.Vector(1,0,0)
|
||||
}[priorityString[1]])
|
||||
tmpAx = mainAx.cross(secAx)
|
||||
assert(tmpAx.Length > ParaConfusion*10.0)
|
||||
tmpAx.normalize()
|
||||
_assignVector(secAx, tmpAx.cross(mainAx))
|
||||
|
||||
#secAx was made perpendicular and valid, so we can compute the last axis.
|
||||
# Here we need to take care to produce right handedness.
|
||||
_assignVector(thirdAx, tmpAx)
|
||||
if XAx.cross(YAx).dot(ZAx) < 0.0:
|
||||
_assignVector(thirdAx, tmpAx * (-1.0))
|
||||
|
||||
#hacky way of constucting rotation to a local coordinate system:
|
||||
# make matrix,
|
||||
m = App.Matrix()
|
||||
m.A = list(XAx)+[0.0]+list(YAx)+[0.0]+list(ZAx)+[0.0]+[0.0]*3+[1.0]
|
||||
m.transpose() # local axes vectors are columns of matrix, but we put them in as rwos, because it is convenient, and then transpose it.
|
||||
# make placement out of matrix,
|
||||
tmpplm = App.Placement(m)
|
||||
# and extract rotation from placement.
|
||||
ori = tmpplm.Rotation
|
||||
return ori
|
||||
|
||||
def _assignVector(lhs, rhs):
|
||||
'''A helper function for assigning vectors without creating new ones. Used as a hack to make aliases in OrientationFromLocalAxesUni'''
|
||||
(lhs.x,lhs.y,lhs.z) = tuple(rhs)
|
245
latticeProjectArray.py
Normal file
245
latticeProjectArray.py
Normal file
|
@ -0,0 +1,245 @@
|
|||
#***************************************************************************
|
||||
#* *
|
||||
#* Copyright (c) 2015 - Victor Titov (DeepSOIC) *
|
||||
#* <vv.titov@gmail.com> *
|
||||
#* *
|
||||
#* 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 *
|
||||
#* *
|
||||
#***************************************************************************
|
||||
|
||||
import FreeCAD as App
|
||||
import Part
|
||||
|
||||
from latticeCommon import *
|
||||
import latticeBaseFeature
|
||||
import latticeCompoundExplorer as LCE
|
||||
import latticeExecuter
|
||||
import latticeGeomUtils as Utils
|
||||
|
||||
__title__="Lattice ProjectArray module for FreeCAD"
|
||||
__author__ = "DeepSOIC"
|
||||
__url__ = ""
|
||||
|
||||
|
||||
# -------------------------- common stuff --------------------------------------------------
|
||||
|
||||
def makeProjectArray(name):
|
||||
'''makeProjectArray(name): makes a Lattice ProjectArray object.'''
|
||||
return latticeBaseFeature.makeLatticeFeature(name, LatticeProjectArray, ViewProviderProjectArray)
|
||||
|
||||
class LatticeProjectArray(latticeBaseFeature.LatticeFeature):
|
||||
"The Lattice ProjectArray object"
|
||||
|
||||
def derivedInit(self,obj):
|
||||
self.Type = "LatticeProjectArray"
|
||||
|
||||
obj.addProperty("App::PropertyLink","Base","Lattice ProjectArray","Array to be altered")
|
||||
obj.addProperty("App::PropertyLink","Tool","Lattice ProjectArray","Shape to project onto")
|
||||
|
||||
obj.addProperty("App::PropertyEnumeration","PositionMode","Lattice ProjectArray","")
|
||||
obj.PositionMode = ['keep','projected','mixed']
|
||||
obj.PositionMode = 'projected'
|
||||
|
||||
obj.addProperty("App::PropertyFloat","PosMixFraction","Lattice ProjectArray","Value controls mixing of positioning between the shape and the placement. 0 is on shape, 1 is at placement.")
|
||||
|
||||
obj.addProperty("App::PropertyDistance","PosElevation","Lattice ProjectArray","Extra displacement along normal to face or along gap, away from the shape.")
|
||||
|
||||
obj.addProperty("App::PropertyEnumeration","OrientMode","Lattice ProjectArray","")
|
||||
obj.OrientMode = ['keep','along gap','tangent plane','along u', 'along v']
|
||||
obj.OrientMode = 'along u'
|
||||
|
||||
obj.addProperty("App::PropertyEnumeration","Multisolution","Lattice ProjectArray","Specify the way of dealing with multiple solutions of projection")
|
||||
obj.Multisolution = ['use first','use all']
|
||||
|
||||
|
||||
def derivedExecute(self,obj):
|
||||
#validity check
|
||||
if not latticeBaseFeature.isObjectLattice(obj.Base):
|
||||
latticeExecuter.warning(obj,"A lattice object is expected as Base, but a generic shape was provided. It will be treated as a lattice object; results may be unexpected.")
|
||||
|
||||
toolShape = obj.Tool.Shape
|
||||
if latticeBaseFeature.isObjectLattice(obj.Tool):
|
||||
latticeExecuter.warning(obj,"A lattice object was provided as Tool. It will be converted into points; orientations will be ignored.")
|
||||
leaves = LCE.AllLeaves(toolShape)
|
||||
points = [Part.Vertex(leaf.Placement.Base) for leaf in leaves]
|
||||
toolShape = Part.makeCompound(points)
|
||||
|
||||
leaves = LCE.AllLeaves(obj.Base.Shape)
|
||||
input = [leaf.Placement for leaf in leaves]
|
||||
|
||||
output = [] #variable to receive the final list of placements
|
||||
|
||||
#cache settings
|
||||
elev = float(obj.PosElevation)
|
||||
posIsKeep = obj.PositionMode == 'keep'
|
||||
posIsProjected = obj.PositionMode == 'projected'
|
||||
posIsMixed = obj.PositionMode == 'mixed'
|
||||
mixF = float(obj.PosMixFraction)
|
||||
oriIsKeep = obj.OrientMode == 'keep'
|
||||
oriIsAlongGap = obj.OrientMode == 'along gap'
|
||||
oriIsTangentPlane = obj.OrientMode == 'tangent plane'
|
||||
oriIsAlongU = obj.OrientMode == 'along u'
|
||||
oriIsAlongV = obj.OrientMode == 'along v'
|
||||
|
||||
isMultiSol = obj.Multisolution == 'use all'
|
||||
|
||||
for plm in input:
|
||||
v = Part.Vertex(plm.Base)
|
||||
projection = v.distToShape(toolShape)
|
||||
(dist, gaps, infos) = projection
|
||||
for iSol in range(0,len(gaps)):
|
||||
(posKeep, posPrj) = gaps[iSol]
|
||||
(dummy, dummy, dummy, el_topo, el_index, el_params) = infos[iSol]
|
||||
|
||||
# Fetch all possible parameters (some may not be required, depending on modes)
|
||||
normal = posKeep - posPrj
|
||||
if normal.Length < DistConfusion:
|
||||
normal = None
|
||||
|
||||
tangU = None
|
||||
tangV = None
|
||||
if el_topo == 'Face':
|
||||
face = toolShape.Faces[el_index]
|
||||
if normal is None:
|
||||
normal = face.normalAt(*el_params)
|
||||
(tangU, tangV) = face.tangentAt(*el_params)
|
||||
elif el_topo == "Edge":
|
||||
edge = toolShape.Edges[el_index]
|
||||
tangU = edge.tangentAt(el_params)
|
||||
|
||||
if normal is not None:
|
||||
normal.normalize()
|
||||
|
||||
#mode logic - compute new placement
|
||||
if posIsKeep:
|
||||
pos = plm.Base
|
||||
elif posIsProjected:
|
||||
pos = posPrj
|
||||
elif posIsMixed:
|
||||
pos = posKeep*mixF + posPrj*(1-mixF)
|
||||
else:
|
||||
raise ValueError("Positioning mode not implemented: " + obj.PositionMode )
|
||||
|
||||
if abs(elev) > DistConfusion:
|
||||
if normal is None:
|
||||
raise ValueError("Normal vector not available for a placement resting on " + el_topo +". Normal vector is required for nonzero position elevation.")
|
||||
pos += normal * elev
|
||||
|
||||
|
||||
if oriIsKeep:
|
||||
ori = plm.Rotation
|
||||
elif oriIsAlongGap:
|
||||
if normal is None:
|
||||
raise ValueError("Normal vector not available for a placement resting on " + el_topo +". Normal vector is required for orientation mode '"+obj.OrientMode+"'")
|
||||
ori = Utils.makeOrientationFromLocalAxesUni("X",XAx= normal*(-1.0))
|
||||
elif oriIsTangentPlane:
|
||||
if normal is None:
|
||||
raise ValueError("Normal vector not available for a placement resting on " + el_topo +". Normal vector is required for orientation mode '"+obj.OrientMode+"'")
|
||||
ori = Utils.makeOrientationFromLocalAxesUni("Z",ZAx= normal)
|
||||
elif oriIsAlongU:
|
||||
if normal is None:
|
||||
raise ValueError("Normal vector not available for a placement resting on " + el_topo +". Normal vector is required for orientation mode '"+obj.OrientMode+"'")
|
||||
if tangU is None:
|
||||
raise ValueError("TangentU vector not available for point on " + el_topo +". TangentU vector is required for orientation mode '"+obj.OrientMode+"'")
|
||||
ori = Utils.makeOrientationFromLocalAxesUni("ZX",ZAx= normal, XAx= tangU)
|
||||
elif oriIsAlongV:
|
||||
if normal is None:
|
||||
raise ValueError("Normal vector not available for a placement resting on " + el_topo +". Normal vector is required for orientation mode '"+obj.OrientMode+"'")
|
||||
if tangV is None:
|
||||
raise ValueError("TangentV vector not available for point on " + el_topo +". TangentV vector is required for orientation mode '"+obj.OrientMode+"'")
|
||||
ori = Utils.makeOrientationFromLocalAxesUni("ZX",ZAx= normal, XAx= tangV)
|
||||
else:
|
||||
raise ValueError("Orientation mode not implemented: " + obj.OrientMode )
|
||||
|
||||
output.append(App.Placement(pos,ori))
|
||||
|
||||
if not isMultiSol:
|
||||
break
|
||||
|
||||
return output
|
||||
|
||||
|
||||
class ViewProviderProjectArray(latticeBaseFeature.ViewProviderLatticeFeature):
|
||||
"A View Provider for the Lattice ProjectArray object"
|
||||
|
||||
def getIcon(self):
|
||||
return getIconPath("Lattice_ProjectArray.svg")
|
||||
|
||||
def claimChildren(self):
|
||||
return [self.Object.Base]
|
||||
|
||||
def CreateLatticeProjectArray(name):
|
||||
sel = FreeCADGui.Selection.getSelectionEx()
|
||||
|
||||
# selection order independece logic (lattice object and generic shape stencil can be told apart)
|
||||
iLtc = 0 #index of lattice object in selection
|
||||
iStc = 1 #index of stencil object in selection
|
||||
for i in range(0,len(sel)):
|
||||
if latticeBaseFeature.isObjectLattice(sel[i]):
|
||||
iLtc = i
|
||||
iStc = i-1 #this may give negative index, but python accepts negative indexes
|
||||
break
|
||||
FreeCAD.ActiveDocument.openTransaction("Create ProjectArray")
|
||||
FreeCADGui.addModule("latticeProjectArray")
|
||||
FreeCADGui.addModule("latticeExecuter")
|
||||
FreeCADGui.doCommand("sel = Gui.Selection.getSelectionEx()")
|
||||
FreeCADGui.doCommand("f = latticeProjectArray.makeProjectArray(name = '"+name+"')")
|
||||
FreeCADGui.doCommand("f.Base = App.ActiveDocument."+sel[iLtc].ObjectName)
|
||||
FreeCADGui.doCommand("f.Tool = App.ActiveDocument."+sel[iStc].ObjectName)
|
||||
|
||||
FreeCADGui.doCommand("for child in f.ViewObject.Proxy.claimChildren():\n"+
|
||||
" child.ViewObject.hide()")
|
||||
FreeCADGui.doCommand("latticeExecuter.executeFeature(f)")
|
||||
FreeCADGui.doCommand("f = None")
|
||||
FreeCAD.ActiveDocument.commitTransaction()
|
||||
|
||||
|
||||
# -------------------------- /common stuff --------------------------------------------------
|
||||
|
||||
# -------------------------- Gui command --------------------------------------------------
|
||||
|
||||
class _CommandProjectArray:
|
||||
"Command to create Lattice ProjectArray feature"
|
||||
|
||||
def GetResources(self):
|
||||
return {'Pixmap' : getIconPath("Lattice_ProjectArray.svg"),
|
||||
'MenuText': QtCore.QT_TRANSLATE_NOOP("Lattice_ProjectArray","Project Array"),
|
||||
'Accel': "",
|
||||
'ToolTip': QtCore.QT_TRANSLATE_NOOP("Lattice_ProjectArray","Project Array: alter placements based on their proximity to a shape.")}
|
||||
|
||||
def Activated(self):
|
||||
sel = FreeCADGui.Selection.getSelectionEx()
|
||||
if len(sel) == 2:
|
||||
CreateLatticeProjectArray(name= "Project")
|
||||
else:
|
||||
mb = QtGui.QMessageBox()
|
||||
mb.setIcon(mb.Icon.Warning)
|
||||
mb.setText(translate("Lattice_ProjectArray", "Select one lattice object to be projected, and one shape to project onto, first!", None))
|
||||
mb.setWindowTitle(translate("Lattice_ProjectArray","Bad selection", None))
|
||||
mb.exec_()
|
||||
|
||||
def IsActive(self):
|
||||
if FreeCAD.ActiveDocument:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
FreeCADGui.addCommand('Lattice_ProjectArray', _CommandProjectArray())
|
||||
|
||||
exportedCommands = ['Lattice_ProjectArray']
|
||||
|
||||
# -------------------------- /Gui command --------------------------------------------------
|
Loading…
Reference in New Issue
Block a user