New tool: ProjectArray

This commit is contained in:
DeepSOIC 2015-11-13 03:03:06 +03:00
parent b53c6d0a8f
commit 11df51a085
4 changed files with 340 additions and 1 deletions

View File

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

View File

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

View File

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