constraint: reorganise constraints

Extended some constraint to support BaseMulti.
This commit is contained in:
Zheng, Lei 2018-07-26 10:56:25 +08:00
parent 731bdb071c
commit 819dee3381
3 changed files with 255 additions and 136 deletions

View File

@ -383,6 +383,10 @@ class ConstraintCommand:
def _toolbarName(self):
return self.tp._toolbarName
@property
def _toolbarVisible(self):
return self.tp._toolbarVisible
def workbenchActivated(self):
self._active = None
@ -399,25 +403,15 @@ class ConstraintCommand:
return self.tp.GetResources()
def Activated(self):
from .assembly import AsmConstraint
guilogger.report('constraint "{}" command exception'.format(
self.tp.getName()), AsmConstraint.make,self.tp._id)
self.tp.activate()
def IsActive(self):
if not FreeCAD.ActiveDocument:
return False
if self._active is None:
self.checkActive()
self._active = self.tp.checkActive()
return self._active
def checkActive(self):
from .assembly import AsmConstraint
if guilogger.catchTrace('selection "{}" exception'.format(
self.tp.getName()), AsmConstraint.getSelection, self.tp._id):
self._active = True
else:
self._active = False
def onSelectionChange(self,hasSelection):
self._active = None if hasSelection else False
@ -482,12 +476,12 @@ class Constraint(ProxyType):
return mcs.getProxy(obj).prepare(obj,solver)
@classmethod
def getFixedParts(mcs,solver,cstrs,parts):
def getFixedParts(mcs,solver,cstrs,partGroup):
firstInfo = None
ret = set()
ret = partGroup.Proxy.derivedParts
from .assembly import isTypeOf, AsmWorkPlane
for obj in parts:
for obj in partGroup.Group:
if not hasattr(obj,'Placement'):
ret.add(obj)
elif isTypeOf(obj,AsmWorkPlane) and getattr(obj,'Fixed',False):
@ -603,6 +597,7 @@ class Base(with_metaclass(Constraint, object)):
_workplane = False
_props = []
_toolbarName = 'Assembly3 Constraints'
_toolbarVisible = True
_iconName = 'Assembly_ConstraintGeneral.svg'
_menuText = 'Create "{}" constraint'
@ -613,14 +608,31 @@ class Base(with_metaclass(Constraint, object)):
def init(cls,_obj):
pass
@classmethod
def activate(cls):
from .assembly import AsmConstraint
guilogger.report('constraint "{}" command exception'.format(
cls.getName()), AsmConstraint.make, cls._id)
@classmethod
def checkActive(cls):
from .assembly import AsmConstraint
if guilogger.catchTrace('selection "{}" exception'.format(
cls.getName()), AsmConstraint.getSelection, cls._id):
return True
else:
return False
@classmethod
def getPropertyInfoList(cls):
return cls._props
@classmethod
def constraintFunc(cls,obj,solver):
def constraintFunc(cls,obj,solver,name=None):
try:
return getattr(solver.system,'add'+cls.getName())
if not name:
name = getattr(cls,'_cstrFuncName','add'+cls.getName())
return getattr(solver.system,name)
except AttributeError:
logger.warn('{} not supported in solver "{}"'.format(
cstrName(obj),solver.getName()))
@ -689,13 +701,12 @@ class Base(with_metaclass(Constraint, object)):
@classmethod
def prepare(cls,obj,solver):
func = cls.constraintFunc(obj,solver)
if func:
params = cls.getPropertyValues(obj) + cls.getEntities(obj,solver)
ret = func(*params,group=solver.group)
solver.system.log('{}: {}'.format(cstrName(obj),ret))
return ret
else:
logger.warn('{} no constraint func'.format(cstrName(obj)))
if not func:
return
params = cls.getPropertyValues(obj) + cls.getEntities(obj,solver)
ret = func(*params,group=solver.group)
solver.system.log('{}: {}'.format(cstrName(obj),ret))
return ret
@classmethod
def hasFixedPart(cls,_obj):
@ -850,23 +861,37 @@ class BaseMulti(Base):
_id = -1
_entityDef = (_wa,)
@classmethod
def onRegister(cls):
assert(not cls._workplane)
assert(len(cls._entityDef)<=2)
@classmethod
def check(cls,elements,checkCount=False):
if checkCount and len(elements)<2:
raise RuntimeError('Constraint "{}" requires at least two '
'elements'.format(cls.getName()))
for info in elements:
count = min(len(elements),len(cls._entityDef))
for i,entityDef in enumerate(cls._entityDef[:count]):
info = elements[i]
msg = entityDef(None,info.Part,info.Subname,info.Shape)
if msg:
raise RuntimeError('Constraint "{}" requires the {} element '
'to be of {}'.format(cls.getName(),_ordinal[i],msg))
if len(elements)<=count:
return
i = len(cls._entityDef)
for info in elements[i:]:
msg = cls._entityDef[0](None,info.Part,info.Subname,info.Shape)
if msg:
raise RuntimeError('Constraint "{}" requires all the element '
'to be of {}'.format(cls.getName(),msg))
return
raise RuntimeError('Constraint "{}" requires the {} element '
'onwards to all be of {}'.format(
cls.getName(),_ordinal[i],msg))
@classmethod
def prepare(cls,obj,solver):
func = cls.constraintFunc(obj,solver);
if not func:
logger.warn('{} no constraint func'.format(cstrName(obj)))
return
props = cls.getPropertyValues(obj)
ret = []
@ -928,17 +953,25 @@ class BaseMulti(Base):
logger.warn('{} has no effective constraint'.format(cstrName(obj)))
return
e0 = None
firstInfo = None
for e in elements:
info = e.Proxy.getInfo()
e = None
info0 = None
idx0 = 1 if len(cls._entityDef)>1 else 0
for i,element in enumerate(elements):
info = element.Proxy.getInfo()
partInfo = solver.getPartInfo(info)
if not e0:
e0 = cls._entityDef[0](solver,partInfo,info.Subname,info.Shape)
firstInfo = partInfo
if i==idx0:
e0 = cls._entityDef[idx0](
solver,partInfo,info.Subname,info.Shape)
info0 = partInfo
else:
e = cls._entityDef[0](solver,partInfo,info.Subname,info.Shape)
params = props + [e0,e]
solver.system.checkRedundancy(obj,firstInfo,partInfo)
if e0 and e:
if idx0:
params = props + [e,e0]
solver.system.checkRedundancy(obj,partInfo,info0)
else:
params = props + [e0,e]
solver.system.checkRedundancy(obj,info0,partInfo)
h = func(*params,group=solver.group)
if isinstance(h,(list,tuple)):
ret += list(h)
@ -953,7 +986,6 @@ class BaseCascade(BaseMulti):
return super(BaseCascade,cls).prepare(obj,solver)
func = cls.constraintFunc(obj,solver);
if not func:
logger.warn('{} no constraint func'.format(cstrName(obj)))
return
props = cls.getPropertyValues(obj)
prev = None
@ -984,20 +1016,22 @@ class BaseCascade(BaseMulti):
return ret
class PlaneAlignment(BaseCascade):
_id = 37
_iconName = 'Assembly_ConstraintAlignment.svg'
_props = ['Cascade','Offset'] + _AngleProps
_tooltip = \
'Add a "{}" constraint to align planar faces of two or more parts.\n'\
'The faces become coplanar or parallel with an optional distance'
class PlaneCoincident(BaseCascade):
_id = 35
_iconName = 'Assembly_ConstraintCoincidence.svg'
_props = ['Multiply','Cascade','Offset','OffsetX','OffsetY'] + _AngleProps
_tooltip = \
'Add a "{}" constraint to conincide planes of two or more parts.\n'\
'The planes are coincided at their centers with an optional distance.'
class PlaneAlignment(BaseCascade):
_id = 37
_iconName = 'Assembly_ConstraintAlignment.svg'
_props = ['Cascade','Offset'] + _AngleProps
_tooltip = 'Add a "{}" constraint to rotate planes of two or more parts\n'\
'into the same orientation'
'Add a "{}" constraint to conincide planar faces of two or more parts.\n'\
'The faces are coincided at their centers with an optional distance.'
class AxialAlignment(BaseMulti):
@ -1005,15 +1039,18 @@ class AxialAlignment(BaseMulti):
_entityDef = (_lna,)
_iconName = 'Assembly_ConstraintAxial.svg'
_props = ['Multiply'] + _AngleProps
_tooltip = 'Add a "{}" constraint to align planes of two or more parts.\n'\
'The planes are aligned at the direction of their surface normal axis.'
_tooltip = 'Add a "{}" constraint to align edges/faces of two or\n'\
'more parts. The constraint acceps linear edges, which become\n'\
'colinear, and planar faces, which are aligned uses their surface\n'\
'normal axis, and cylindrical face, which are aligned using the\n'\
'axial direction. Different types of geometry elements can be mixed.'
class SameOrientation(BaseMulti):
_id = 2
_entityDef = (_n,)
_iconName = 'Assembly_ConstraintOrientation.svg'
_tooltip = 'Add a "{}" constraint to align planes of two or more parts.\n'\
_tooltip = 'Add a "{}" constraint to align faces of two or more parts.\n'\
'The planes are aligned to have the same orientation (i.e. rotation)'
@ -1022,23 +1059,19 @@ class MultiParallel(BaseMulti):
_entityDef = (_lw,)
_iconName = 'Assembly_ConstraintMultiParallel.svg'
_props = _AngleProps
_tooltip = 'Add a "{}" constraint to make planes ormal or linear edges\n'\
_tooltip = 'Add a "{}" constraint to make planar faces or linear edges\n'\
'of two or more parts parallel.'
class Base2(Base):
_id = -1
_toolbarName = 'Assembly3 Constraints2'
class Angle(Base2):
class Angle(Base):
_id = 27
_entityDef = (_ln,_ln)
_workplane = True
_props = ["Angle","Supplement"]
_iconName = 'Assembly_ConstraintAngle.svg'
_tooltip = 'Add a "{}" constraint to set the angle of planes or linear\n'\
'edges of two parts.'
_tooltip = \
'Add a "{}" constraint to set the angle of planar faces or linear\n'\
'edges of two parts.'
@classmethod
def init(cls,obj):
@ -1046,19 +1079,19 @@ class Angle(Base2):
obj.Angle = utils.getElementsAngle(shapes[0],shapes[1])
class Perpendicular(Base2):
class Perpendicular(Base):
_id = 28
_entityDef = (_lw,_lw)
_workplane = True
_iconName = 'Assembly_ConstraintPerpendicular.svg'
_tooltip = 'Add a "{}" constraint to make planes or linear edges of two\n'\
'parts perpendicular.'
_tooltip = \
'Add a "{}" constraint to make planar faces or linear edges of two\n'\
'parts perpendicular.'
@classmethod
def prepare(cls,obj,solver):
system = solver.system
params = cls.getEntities(obj,solver)
e1,e2 = params[0],params[1]
e1,e2 = cls.getEntities(obj,solver)[:2]
isPlane = isinstance(e1,list),isinstance(e2,list)
if all(isPlane):
ret = system.addPerpendicular(e1[2],e2[2],group=solver.group)
@ -1071,45 +1104,58 @@ class Perpendicular(Base2):
return ret
class Parallel(Base2):
_id = -1
_entityDef = (_ln,_ln)
_workplane = True
_iconName = 'Assembly_ConstraintParallel.svg'
_tooltip = 'Add a "{}" constraint to make planes or linear edges of two\n'\
'parts parallel.'
class PointsCoincident(Base2):
class PointsCoincident(Base):
_id = 1
_entityDef = (_p,_p)
_workplane = True
_iconName = 'Assembly_ConstraintPointsCoincident.svg'
_tooltip = 'Add a "{}" constraint to conincide two points.'
_iconName = 'Assembly_ConstraintPointCoincident.svg'
_tooltips = 'Add a "{}" constraint to conincide two points in 2D or 3D'
class PointInPlane(Base2):
class PointInPlane(BaseMulti):
_id = 3
_entityDef = (_p,_w)
_iconName = 'Assembly_ConstraintPointInPlane.svg'
_tooltip = 'Add a "{}" to constrain a point inside a plane.'
_tooltip = 'Add a "{}" to constrain one or more point inside a plane.'
class PointOnLine(Base2):
class PointOnLine(Base):
_id = 4
_entityDef = (_p,_l)
_entityDef = (_p,_lna)
_workplane = True
_iconName = 'Assembly_ConstraintPointOnLine.svg'
_tooltip = 'Add a "{}" to constrain a point on to a line.'
_tooltip = 'Add a "{}" to constrain a point on to a line in 2D or 3D.'
@classmethod
def prepare(cls,obj,solver):
func = cls.constraintFunc(obj,solver)
if not func:
return
params = cls.getEntities(obj,solver)
if isinstance(params[1], NormalInfo):
params[1] = params[1].ln
else:
params[1] = params[1].entity
ret = func(*params,group=solver.group)
solver.system.log('{}: {}'.format(cstrName(obj),ret))
return ret
class PointsDistance(Base2):
_id = 5
_entityDef = (_p,_p)
_workplane = True
_props = ["Distance"]
class PointsOnCircle(BaseMulti):
_id = 26
_entityDef = (_p,_c)
_iconName = 'Assembly_ConstraintPointOnCircle.svg'
_tooltip='Add a "{}" to constrain one or more points on to a clyndrical\n'\
'plane defined by a cricle.'
_cstrFuncName = 'addPointOnCircle'
class PointsDistance(BaseCascade):
_id = 44
_entityDef = (_p,)
_props = ["Cascade","Distance"]
_iconName = 'Assembly_ConstraintPointsDistance.svg'
_tooltip = 'Add a "{}" to constrain the distance of two points.'
_tooltip = 'Add a "{}" to constrain the distance of two or more points.'
@classmethod
def init(cls,obj):
@ -1118,40 +1164,60 @@ class PointsDistance(Base2):
obj.Distance = points[0].distanceToPoint(points[1])
class PointsProjectDistance(Base2):
_id = 6
_entityDef = (_p,_p,_l)
_props = ["Distance"]
_iconName = 'Assembly_ConstraintPointsProjectDistance.svg'
_tooltip = 'Add a "{}" to constrain the distance of two points\n' \
'projected on a line.'
class PointPlaneDistance(Base2):
class PointsPlaneDistance(BaseMulti):
_id = 7
_entityDef = (_p,_w)
_props = ["Distance"]
_iconName = 'Assembly_ConstraintPointPlaneDistance.svg'
_tooltip='Add a "{}" to constrain the distance between a point and a plane'
_tooltip='Add a "{}" to constrain the distance between one or more points '\
'and a plane'
_cstrFuncName = 'addPointPlaneDistance'
class PointLineDistance(Base2):
class PointLineDistance(Base):
_id = 8
_entityDef = (_p,_l)
_workplane = True
_props = ["Distance"]
_iconName = 'Assembly_ConstraintPointLineDistance.svg'
_tooltip='Add a "{}" to constrain the distance between a point and a line'
_tooltip='Add a "{}" to constrain the distance between a point '\
'and a linear edge in 2D or 3D'
class EqualPointLineDistance(Base2):
_id = 13
_entityDef = (_p,_l,_p,_l)
class More(Base):
_id = 47
_iconName = 'Assembly_ConstraintMore.svg'
_tooltip='Toggle toolbars for more constraints'
@classmethod
def activate(cls):
gui.AsmCmdManager.toggleToolbars()
@classmethod
def checkActive(cls):
return True
class Base2(Base):
_id = -1
_toolbarName = 'Assembly3 Constraints2'
_toolbarVisible = False
class PointDistance(Base2):
_id = 5
_entityDef = (_p,_p)
_workplane = True
_iconName = 'Assembly_ConstraintEqualPointLineDistance.svg'
_tooltip='Add a "{}" to constrain the distance between a point and a\n'\
'line to be the same as the distance between another point\n'\
'and line.'
_props = ["Distance"]
_iconName = 'Assembly_ConstraintPointDistance.svg'
_tooltip = 'Add a "{}" to constrain the distance of two points in 2D or 3D.'
_cstrFuncName = 'addPointsDistance'
@classmethod
def init(cls,obj):
points = [ info.Placement.multVec(info.Shape.Vertex1.Point)
for info in obj.Proxy.getElementsInfo() ]
obj.Distance = points[0].distanceToPoint(points[1])
class EqualAngle(Base2):
@ -1225,13 +1291,6 @@ class LineVertical(Base2):
_tooltip='Add a "{}" constraint to make a line segment vertical when\n'\
'projected onto a plane.'
class PointOnCircle(Base2):
_id = 26
_entityDef = (_p,_c)
_iconName = 'Assembly_ConstraintPointOnCircle.svg'
_tooltip='Add a "{}" to constrain a point on to a clyndrical plane\n' \
'defined by a cricle.'
class ArcLineTangent(Base2):
_id = 30
@ -1241,17 +1300,11 @@ class ArcLineTangent(Base2):
_tooltip='Add a "{}" constraint to make a line tangent to an arc\n'\
'at the start or end point of the arc.'
class Colinear(Base2):
_id = 39
_entityDef = (_lna, _lna)
_workplane = True
_iconName = 'Assembly_ConstraintColinear.svg'
_tooltip='Add a "{}" constraint to make to line colinear'
class BaseSketch(Base):
_id = -1
_toolbarName = 'Assembly3 Sketch Constraints'
_toolbarVisible = False
class SketchPlane(BaseSketch):
@ -1313,15 +1366,14 @@ class LineLength(BaseSketch):
@classmethod
def prepare(cls,obj,solver):
func = PointsDistance.constraintFunc(obj,solver)
if func:
_,p0,p1 = cls.getEntities(obj,solver,retAll=True)[0]
params = cls.getPropertyValues(obj) + [p0,p1]
ret = func(*params,group=solver.group)
solver.system.log('{}: {}'.format(cstrName(obj),ret))
return ret
else:
logger.warn('{} no constraint func'.format(cstrName(obj)))
func = cls.constraintFunc(obj,solver,'addPointsDistance')
if not func:
return
_,p0,p1 = cls.getEntities(obj,solver,retAll=True)[0]
params = cls.getPropertyValues(obj) + [p0,p1]
ret = func(*params,group=solver.group)
solver.system.log('{}: {}'.format(cstrName(obj),ret))
return ret
class EqualLength(BaseDraftWire):
@ -1414,6 +1466,34 @@ class EqualRadius(BaseSketch):
raise RuntimeError('Constraint "{}" requires at least one '
'Draft.Circle'.format(cls.getName()))
class PointsProjectDistance(BaseSketch):
_id = 6
_entityDef = (_p,_p,_l)
_props = ["Distance"]
_iconName = 'Assembly_ConstraintPointsProjectDistance.svg'
_tooltip = 'Add a "{}" to constrain the distance of two points\n' \
'projected on a line.'
class EqualPointLineDistance(BaseSketch):
_id = 13
_entityDef = (_p,_l,_p,_l)
_workplane = True
_iconName = 'Assembly_ConstraintEqualPointLineDistance.svg'
_tooltip='Add a "{}" to constrain the distance between a point and a\n'\
'line to be the same as the distance between another point\n'\
'and line.'
class Colinear(BaseSketch):
_id = 39
_entityDef = (_lna, _lna)
_workplane = True
_iconName = 'Assembly_ConstraintColinear.svg'
_tooltip='Add a "{}" constraint to make to line colinear'
# class CubicLineTangent(BaseSketch):
# _id = 31
#

50
gui.py
View File

@ -118,10 +118,41 @@ class SelectionObserver:
class AsmCmdManager(ProxyType):
_HiddenToolbars = set()
Toolbars = OrderedDict()
Menus = OrderedDict()
_defaultMenuGroupName = '&Assembly3'
@staticmethod
def getToolbarParams():
return FreeCAD.ParamGet('User parameter:BaseApp/MainWindow/Toolbars')
@classmethod
def init(mcs):
if not mcs.getParam('Bool','GuiInited',False):
hgrp = mcs.getToolbarParams()
for toolbar in mcs._HiddenToolbars:
hgrp.SetBool(toolbar,False)
mcs.setParam('Bool','GuiInited',True)
@classmethod
def toggleToolbars(mcs):
hgrp = mcs.getToolbarParams()
show = False
for toolbar in mcs._HiddenToolbars:
if not hgrp.GetBool(toolbar,True):
show = True
break
from PySide import QtGui
mw = FreeCADGui.getMainWindow()
for toolbar in mcs._HiddenToolbars:
if show != hgrp.GetBool(toolbar,True):
hgrp.SetBool(toolbar,show)
tb = mw.findChild(QtGui.QToolBar,toolbar)
if not tb:
logger.error('cannot find toolbar "{}"'.format(toolbar))
tb.setVisible(show)
@classmethod
def register(mcs,cls):
if cls._id < 0:
@ -129,22 +160,29 @@ class AsmCmdManager(ProxyType):
super(AsmCmdManager,mcs).register(cls)
FreeCADGui.addCommand(cls.getName(),cls)
if cls._toolbarName:
mcs.Toolbars.setdefault(cls._toolbarName,[]).append(cls)
tb = mcs.Toolbars.setdefault(cls._toolbarName,[])
if not tb and not getattr(cls,'_toolbarVisible',True):
mcs._HiddenToolbars.add(cls._toolbarName)
tb.append(cls)
if cls._menuGroupName is not None:
name = cls._menuGroupName
if not name:
name = mcs._defaultMenuGroupName
mcs.Menus.setdefault(name,[]).append(cls)
def getParamGroup(cls):
@classmethod
def getParamGroup(mcs):
return FreeCAD.ParamGet(
'User parameter:BaseApp/Preferences/Mod/Assembly3')
def getParam(cls,tp,name,default=None):
return getattr(cls.getParamGroup(),'Get'+tp)(name,default)
@classmethod
def getParam(mcs,tp,name,default=None):
return getattr(mcs.getParamGroup(),'Get'+tp)(name,default)
def setParam(cls,tp,name,v):
getattr(cls.getParamGroup(),'Set'+tp)(name,v)
@classmethod
def setParam(mcs,tp,name,v):
getattr(mcs.getParamGroup(),'Set'+tp)(name,v)
def workbenchActivated(cls):
pass

View File

@ -39,6 +39,7 @@ class Assembly3Workbench(FreeCADGui.Workbench):
def Initialize(self):
from .mover import AsmDocumentObserver
from .gui import AsmCmdManager
AsmCmdManager.init()
cmdSet = set()
for name,cmds in AsmCmdManager.Toolbars.items():
cmdSet.update(cmds)