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

View File

@ -411,8 +411,8 @@ class Constraint(ProxyType):
return getattr(obj,mcs._disabled,False)
@classmethod
def check(mcs,tp,group,checkCount=False):
mcs.getType(tp).check(group,checkCount)
def check(mcs,tp,elements,checkCount=False):
mcs.getType(tp).check(elements,checkCount)
@classmethod
def prepare(mcs,obj,solver):
@ -476,6 +476,12 @@ class Constraint(ProxyType):
if cstr:
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):
PropertyInfo(Constraint,name,tp,doc,getter=getter,
@ -515,6 +521,10 @@ class Base(object):
def __init__(self,_obj):
pass
@classmethod
def init(cls,_obj):
pass
@classmethod
def getPropertyInfoList(cls):
return cls._props
@ -528,30 +538,30 @@ class Base(object):
cstrName(obj),solver.getName()))
@classmethod
def getEntityDef(cls,group,checkCount,obj=None):
def getEntityDef(cls,elements,checkCount,obj=None):
entities = cls._entityDef
if len(group) == len(entities):
if len(elements) == len(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]
if not checkCount and len(group)<len(entities):
return entities[:len(group)]
if not checkCount and len(elements)<len(entities):
return entities[:len(elements)]
if not obj:
name = cls.getName()
else:
name += cstrName(obj)
if len(group)<len(entities):
msg = entities[len(group)](None,None,None,None)
if len(elements)<len(entities):
msg = entities[len(elements)](None,None,None,None)
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 '
'only {}'.format(name,len(entities)))
@classmethod
def check(cls,group,checkCount=False):
entities = cls.getEntityDef(group,checkCount)
def check(cls,elements,checkCount=False):
entities = cls.getEntityDef(elements,checkCount)
for i,e in enumerate(entities):
info = group[i]
info = elements[i]
msg = e(None,info.Part,info.Subname,info.Shape)
if not msg:
continue
@ -733,8 +743,8 @@ class Locked(Base):
return ret
@classmethod
def check(cls,group,_checkCount=False):
if not all([utils.isElement(info.Shape) for info in group]):
def check(cls,elements,_checkCount=False):
if not all([utils.isElement(info.Shape) for info in elements]):
raise RuntimeError('Constraint "{}" requires all children to be '
'of element (Vertex, Edge or Face)'.format(cls.getName()))
@ -744,11 +754,11 @@ class BaseMulti(Base):
_entityDef = (_wa,)
@classmethod
def check(cls,group,checkCount=False):
if checkCount and len(group)<2:
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 group:
for info in elements:
msg = cls._entityDef[0](None,info.Part,info.Subname,info.Shape)
if msg:
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'\
'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):
_id = 28
@ -946,6 +961,12 @@ class PointsDistance(Base2):
_iconName = 'Assembly_ConstraintPointsDistance.svg'
_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):
_id = 6
@ -1093,28 +1114,36 @@ class SketchPlane(BaseSketch):
'undefine the previous work plane'
@classmethod
def getEntityDef(cls,group,checkCount,obj=None):
def getEntityDef(cls,elements,checkCount,obj=None):
_ = checkCount
_ = obj
if not group:
if not elements:
# If no element, then this constraint serves the prupose of clearing
# the current sketch plane
return []
# 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
# entities must be from some draft wire or circle/arc.
return [_wa] + [_d]*(len(group)-1)
# entities must be from some draft wire or circle/arc
#
# 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):
_id = -1
@classmethod
def check(cls,group,checkCount=False):
super(BaseDraftWire,cls).check(group,checkCount)
def check(cls,elements,checkCount=False):
super(BaseDraftWire,cls).check(elements,checkCount)
if not checkCount:
return
for info in group:
for info in elements:
if utils.isDraftWire(info.Part):
return
raise RuntimeError('Constraint "{}" requires at least one linear edge '
@ -1129,6 +1158,11 @@ class LineLength(BaseSketch):
_iconName = 'Assembly_ConstraintLineLength.svg'
_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
def prepare(cls,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'
@classmethod
def check(cls,group,checkCount=False):
super(EqualLineArcLength,cls).check(group,checkCount)
def check(cls,elements,checkCount=False):
super(EqualLineArcLength,cls).check(elements,checkCount)
if not checkCount:
return
for i,info in enumerate(group):
for i,info in enumerate(elements):
if i:
if utils.isDraftCircle(info.Part):
return
@ -1222,11 +1256,11 @@ class EqualRadius(BaseSketch):
_tooltip='Add a "{}" constraint to make two circles/arcs of the same radius'
@classmethod
def check(cls,group,checkCount=False):
super(EqualRadius,cls).check(group,checkCount)
def check(cls,elements,checkCount=False):
super(EqualRadius,cls).check(elements,checkCount)
if not checkCount:
return
for info in group:
for info in elements:
if utils.isDraftCircle(info.Part):
return
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
'''
import math
from collections import namedtuple
import FreeCAD, FreeCADGui, Part, Draft
import numpy as np
@ -367,6 +368,23 @@ def getNormal(obj):
q = rot.Q
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):
'return radius if it is closed, or a list of two endpoints'
edge = getElementShape(obj,Part.Edge)