constraint: init constraint parameter

Add init() api to calculate the some of the constraints' parameter
(e.g. angle, distance, length, etc.) based on the existing elements.
This can reduce excessive movement when auto recompute is on.
This commit is contained in:
Zheng, Lei 2018-01-29 17:08:40 +08:00
parent 708668ed0b
commit a2d639200f
3 changed files with 87 additions and 31 deletions

View File

@ -910,6 +910,9 @@ class AsmConstraint(AsmGroup):
self.elements = elements self.elements = elements
return self.elements return self.elements
def getElementsInfo(self):
return [ e.Proxy.getInfo() for e in self.getElements() ]
Selection = namedtuple('AsmConstraintSelection', Selection = namedtuple('AsmConstraintSelection',
('SelObject','SelSubname','Assembly','Constraint','Elements')) ('SelObject','SelSubname','Assembly','Constraint','Elements'))
@ -1036,6 +1039,7 @@ class AsmConstraint(AsmGroup):
try: try:
for e in sel.Elements: for e in sel.Elements:
AsmElementLink.make(AsmElementLink.MakeInfo(cstr,*e)) AsmElementLink.make(AsmElementLink.MakeInfo(cstr,*e))
logger.catchDebug('init constraint', Constraint.init,cstr)
cstr.Proxy._initializing = False cstr.Proxy._initializing = False
if undo: if undo:
FreeCAD.closeActiveTransaction() FreeCAD.closeActiveTransaction()

View File

@ -411,8 +411,8 @@ class Constraint(ProxyType):
return getattr(obj,mcs._disabled,False) return getattr(obj,mcs._disabled,False)
@classmethod @classmethod
def check(mcs,tp,group,checkCount=False): def check(mcs,tp,elements,checkCount=False):
mcs.getType(tp).check(group,checkCount) mcs.getType(tp).check(elements,checkCount)
@classmethod @classmethod
def prepare(mcs,obj,solver): def prepare(mcs,obj,solver):
@ -476,6 +476,12 @@ class Constraint(ProxyType):
if cstr: if cstr:
return cstr.getIcon(obj) return cstr.getIcon(obj)
@classmethod
def init(mcs,obj):
cstr = mcs.getProxy(obj)
if cstr:
cstr.init(obj)
def _makeProp(name,tp,doc='',getter=propGet,internal=False,default=None): def _makeProp(name,tp,doc='',getter=propGet,internal=False,default=None):
PropertyInfo(Constraint,name,tp,doc,getter=getter, PropertyInfo(Constraint,name,tp,doc,getter=getter,
@ -515,6 +521,10 @@ class Base(object):
def __init__(self,_obj): def __init__(self,_obj):
pass pass
@classmethod
def init(cls,_obj):
pass
@classmethod @classmethod
def getPropertyInfoList(cls): def getPropertyInfoList(cls):
return cls._props return cls._props
@ -528,30 +538,30 @@ class Base(object):
cstrName(obj),solver.getName())) cstrName(obj),solver.getName()))
@classmethod @classmethod
def getEntityDef(cls,group,checkCount,obj=None): def getEntityDef(cls,elements,checkCount,obj=None):
entities = cls._entityDef entities = cls._entityDef
if len(group) == len(entities): if len(elements) == len(entities):
return entities return entities
if cls._workplane and len(group)==len(entities)+1: if cls._workplane and len(elements)==len(entities)+1:
return list(entities) + [_w] return list(entities) + [_w]
if not checkCount and len(group)<len(entities): if not checkCount and len(elements)<len(entities):
return entities[:len(group)] return entities[:len(elements)]
if not obj: if not obj:
name = cls.getName() name = cls.getName()
else: else:
name += cstrName(obj) name += cstrName(obj)
if len(group)<len(entities): if len(elements)<len(entities):
msg = entities[len(group)](None,None,None,None) msg = entities[len(elements)](None,None,None,None)
raise RuntimeError('Constraint "{}" requires the {} element to be' raise RuntimeError('Constraint "{}" requires the {} element to be'
' {}'.format(cls.getName(), _ordinal[len(group)], msg)) ' {}'.format(cls.getName(), _ordinal[len(elements)], msg))
raise RuntimeError('Constraint {} has too many elements, expecting ' raise RuntimeError('Constraint {} has too many elements, expecting '
'only {}'.format(name,len(entities))) 'only {}'.format(name,len(entities)))
@classmethod @classmethod
def check(cls,group,checkCount=False): def check(cls,elements,checkCount=False):
entities = cls.getEntityDef(group,checkCount) entities = cls.getEntityDef(elements,checkCount)
for i,e in enumerate(entities): for i,e in enumerate(entities):
info = group[i] info = elements[i]
msg = e(None,info.Part,info.Subname,info.Shape) msg = e(None,info.Part,info.Subname,info.Shape)
if not msg: if not msg:
continue continue
@ -733,8 +743,8 @@ class Locked(Base):
return ret return ret
@classmethod @classmethod
def check(cls,group,_checkCount=False): def check(cls,elements,_checkCount=False):
if not all([utils.isElement(info.Shape) for info in group]): if not all([utils.isElement(info.Shape) for info in elements]):
raise RuntimeError('Constraint "{}" requires all children to be ' raise RuntimeError('Constraint "{}" requires all children to be '
'of element (Vertex, Edge or Face)'.format(cls.getName())) 'of element (Vertex, Edge or Face)'.format(cls.getName()))
@ -744,11 +754,11 @@ class BaseMulti(Base):
_entityDef = (_wa,) _entityDef = (_wa,)
@classmethod @classmethod
def check(cls,group,checkCount=False): def check(cls,elements,checkCount=False):
if checkCount and len(group)<2: if checkCount and len(elements)<2:
raise RuntimeError('Constraint "{}" requires at least two ' raise RuntimeError('Constraint "{}" requires at least two '
'elements'.format(cls.getName())) 'elements'.format(cls.getName()))
for info in group: for info in elements:
msg = cls._entityDef[0](None,info.Part,info.Subname,info.Shape) msg = cls._entityDef[0](None,info.Part,info.Subname,info.Shape)
if msg: if msg:
raise RuntimeError('Constraint "{}" requires all the element ' raise RuntimeError('Constraint "{}" requires all the element '
@ -896,6 +906,11 @@ class Angle(Base2):
_tooltip = 'Add a "{}" constraint to set the angle of planes or linear\n'\ _tooltip = 'Add a "{}" constraint to set the angle of planes or linear\n'\
'edges of two parts.' 'edges of two parts.'
@classmethod
def init(cls,obj):
shapes = [ info.Shape for info in obj.Proxy.getElementsInfo() ]
obj.Angle = utils.getElementsAngle(shapes[0],shapes[1])
class Perpendicular(Base2): class Perpendicular(Base2):
_id = 28 _id = 28
@ -946,6 +961,12 @@ class PointsDistance(Base2):
_iconName = 'Assembly_ConstraintPointsDistance.svg' _iconName = 'Assembly_ConstraintPointsDistance.svg'
_tooltip = 'Add a "{}" to constrain the distance of two points.' _tooltip = 'Add a "{}" to constrain the distance of two points.'
@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 PointsProjectDistance(Base2): class PointsProjectDistance(Base2):
_id = 6 _id = 6
@ -1093,28 +1114,36 @@ class SketchPlane(BaseSketch):
'undefine the previous work plane' 'undefine the previous work plane'
@classmethod @classmethod
def getEntityDef(cls,group,checkCount,obj=None): def getEntityDef(cls,elements,checkCount,obj=None):
_ = checkCount _ = checkCount
_ = obj _ = obj
if not group: if not elements:
# If no element, then this constraint serves the prupose of clearing # If no element, then this constraint serves the prupose of clearing
# the current sketch plane # the current sketch plane
return [] return []
# if there is any child element in this constraint, we expect the first # if there is any child element in this constraint, we expect the first
# one to be a planar face or edge to define the work plane. The rest of # one to be a planar face or edge to define the work plane. The rest of
# entities must be from some draft wire or circle/arc. # entities must be from some draft wire or circle/arc
return [_wa] + [_d]*(len(group)-1) #
# Base.prepare() will call system.addSketchPlane() with all contained
# element below. However, the default implementation,
# SystemExtension.addSketchPlane(), only really uses the first one,
# i.e. the one obtained by _wa(), i.e. a tuple of entities
# (workplane,base,normal).
return [_wa] + [_d]*(len(elements)-1)
class BaseDraftWire(BaseSketch): class BaseDraftWire(BaseSketch):
_id = -1 _id = -1
@classmethod @classmethod
def check(cls,group,checkCount=False): def check(cls,elements,checkCount=False):
super(BaseDraftWire,cls).check(group,checkCount) super(BaseDraftWire,cls).check(elements,checkCount)
if not checkCount: if not checkCount:
return return
for info in group: for info in elements:
if utils.isDraftWire(info.Part): if utils.isDraftWire(info.Part):
return return
raise RuntimeError('Constraint "{}" requires at least one linear edge ' raise RuntimeError('Constraint "{}" requires at least one linear edge '
@ -1129,6 +1158,11 @@ class LineLength(BaseSketch):
_iconName = 'Assembly_ConstraintLineLength.svg' _iconName = 'Assembly_ConstraintLineLength.svg'
_tooltip='Add a "{}" constrain the length of a none-subdivided Draft.Wire' _tooltip='Add a "{}" constrain the length of a none-subdivided Draft.Wire'
@classmethod
def init(cls,obj):
info = obj.Proxy.getElementsInfo()[0]
obj.Length = info.Shape.Edge1.Length
@classmethod @classmethod
def prepare(cls,obj,solver): def prepare(cls,obj,solver):
func = PointsDistance.constraintFunc(obj,solver) func = PointsDistance.constraintFunc(obj,solver)
@ -1184,11 +1218,11 @@ class EqualLineArcLength(BaseSketch):
_tooltip='Add a "{}" constraint to make a line of the same length as an arc' _tooltip='Add a "{}" constraint to make a line of the same length as an arc'
@classmethod @classmethod
def check(cls,group,checkCount=False): def check(cls,elements,checkCount=False):
super(EqualLineArcLength,cls).check(group,checkCount) super(EqualLineArcLength,cls).check(elements,checkCount)
if not checkCount: if not checkCount:
return return
for i,info in enumerate(group): for i,info in enumerate(elements):
if i: if i:
if utils.isDraftCircle(info.Part): if utils.isDraftCircle(info.Part):
return return
@ -1222,11 +1256,11 @@ class EqualRadius(BaseSketch):
_tooltip='Add a "{}" constraint to make two circles/arcs of the same radius' _tooltip='Add a "{}" constraint to make two circles/arcs of the same radius'
@classmethod @classmethod
def check(cls,group,checkCount=False): def check(cls,elements,checkCount=False):
super(EqualRadius,cls).check(group,checkCount) super(EqualRadius,cls).check(elements,checkCount)
if not checkCount: if not checkCount:
return return
for info in group: for info in elements:
if utils.isDraftCircle(info.Part): if utils.isDraftCircle(info.Part):
return return
raise RuntimeError('Constraint "{}" requires at least one ' raise RuntimeError('Constraint "{}" requires at least one '

View File

@ -5,6 +5,7 @@ Most of the functions are borrowed directly from assembly2lib.py or lib3D.py in
assembly2 assembly2
''' '''
import math
from collections import namedtuple from collections import namedtuple
import FreeCAD, FreeCADGui, Part, Draft import FreeCAD, FreeCADGui, Part, Draft
import numpy as np import numpy as np
@ -367,6 +368,23 @@ def getNormal(obj):
q = rot.Q q = rot.Q
return q[3],q[0],q[1],q[2] return q[3],q[0],q[1],q[2]
def getElementDirection(obj,pla=None):
if isLinearEdge(obj):
shape = getElementShape(obj,Part.Edge)
vs = shape.Edge1.Vertexes
v = vs[0].Point - vs[1].Point
else:
rot = getElementRotation(obj)
v = rot.multVec(FreeCAD.Vector(0,0,1))
if pla:
v = pla.multVec(v)
return v
def getElementsAngle(o1,o2,pla1=None,pla2=None):
v1 = getElementDirection(o1,pla1)
v2 = getElementDirection(o2,pla2)
return math.degrees(v1.getAngle(v2))
def getElementCircular(obj): def getElementCircular(obj):
'return radius if it is closed, or a list of two endpoints' 'return radius if it is closed, or a list of two endpoints'
edge = getElementShape(obj,Part.Edge) edge = getElementShape(obj,Part.Edge)