Add support for draft wire

Special treatment for non-closed-or-subdivided draft wire object
directly added into part group.  Instead of constraining on the object
placement, we shall constraint on individual points. In other word,
instead of making the draft wire's placement as free parameters, we
make the cooridnates of indvidual points as free parameters.
This commit is contained in:
Zheng, Lei 2018-01-19 14:10:43 +08:00
parent cd9a60c29d
commit 05595e3f41
5 changed files with 354 additions and 103 deletions

View File

@ -488,17 +488,14 @@ class ViewProviderAsmElement(ViewProviderAsmOnTop):
Group=owner, Subname=subname),undo=True) Group=owner, Subname=subname),undo=True)
PartInfo = namedtuple('AsmPartInfo', ('Parent','SubnameRef','Part',
'PartName','Placement','Object','Subname','Shape'))
def getPartInfo(parent, subname): def getPartInfo(parent, subname):
'''Return a named tuple containing the part object element information '''Return a named tuple containing the part object element information
Parameters: Parameters:
parent: the parent document object, either an assembly, or a part group parent: the parent document object, either an assembly, or a part group
subname: subname reference to the part element (i.e. edge, face, vertex) subname: subname reference to the part element (i.e. edge, face, vertex)
Return a named tuple with the following fields: Return a named tuple with the following fields:
@ -513,8 +510,8 @@ def getPartInfo(parent, subname):
Placement: the placement of the part Placement: the placement of the part
Object: the object that owns the element. In case 'Part' is an assembly, we Object: the object that owns the element. In case 'Part' is an assembly, the
the element owner will always be some (grand)child of the 'Part' element owner will always be some (grand)child of the 'Part'
Subname: the subname reference to the element owner object. The reference is Subname: the subname reference to the element owner object. The reference is
realtive to the 'Part', i.e. Object = Part.getSubObject(subname), or if realtive to the 'Part', i.e. Object = Part.getSubObject(subname), or if
@ -633,7 +630,7 @@ def getPartInfo(parent, subname):
obj = part.getLinkedObject(False) obj = part.getLinkedObject(False)
partName = part.Name partName = part.Name
return PartInfo(Parent = parent, return utils.PartInfo(Parent = parent,
SubnameRef = subnameRef, SubnameRef = subnameRef,
Part = part, Part = part,
PartName = partName, PartName = partName,
@ -876,16 +873,16 @@ class AsmConstraint(AsmGroup):
ret = getattr(self,'elements',None) ret = getattr(self,'elements',None)
if ret or Constraint.isDisabled(obj): if ret or Constraint.isDisabled(obj):
return ret return ret
shapes = [] infos = []
elements = [] elements = []
for o in obj.Group: for o in obj.Group:
checkType(o,AsmElementLink) checkType(o,AsmElementLink)
info = o.Proxy.getInfo() info = o.Proxy.getInfo()
if not info: if not info:
return return
shapes.append(info.Shape) infos.append(info)
elements.append(o) elements.append(o)
Constraint.check(obj,shapes,True) Constraint.check(obj,infos,True)
self.elements = elements self.elements = elements
return self.elements return self.elements
@ -981,7 +978,7 @@ class AsmConstraint(AsmGroup):
if not Constraint.isDisabled(cstr): if not Constraint.isDisabled(cstr):
if cstr: if cstr:
typeid = Constraint.getTypeID(cstr) typeid = Constraint.getTypeID(cstr)
check = [o.Proxy.getInfo().Shape for o in cstr.Group] + elements check = [o.Proxy.getInfo() for o in cstr.Group] + elements
else: else:
check = elements check = elements
Constraint.check(typeid,check) Constraint.check(typeid,check)
@ -1137,7 +1134,8 @@ class AsmElementGroup(AsmGroup):
return return
for i,c in enumerate(reversed(label)): for i,c in enumerate(reversed(label)):
if not c.isdigit(): if not c.isdigit():
label = label[:i+1] if i:
label = label[:-i]
break; break;
i=0 i=0
while True: while True:

View File

@ -7,12 +7,18 @@ from .proxy import ProxyType, PropertyInfo, propGet, propGetValue
import os import os
_iconPath = os.path.join(utils.iconPath,'constraints') _iconPath = os.path.join(utils.iconPath,'constraints')
def _p(solver,partInfo,subname,shape): def _p(solver,partInfo,subname,shape,retAll=False):
'return a handle of a transformed point derived from "shape"' 'return a handle of a transformed point derived from "shape"'
if not solver: if not solver:
if utils.hasCenter(shape): if not utils.hasCenter(shape):
return 'a vertex or circular edge/face'
if not utils.isDraftWire(partInfo):
return return
return 'a vertex or circular edge/face' if utils.draftWireVertex2PointIndex(partInfo,subname) is None:
raise RuntimeError('Invalid draft wire vertex "{}" {}'.format(
subname,objName(partInfo)))
return
key = subname+'.p' key = subname+'.p'
h = partInfo.EntityMap.get(key,None) h = partInfo.EntityMap.get(key,None)
system = solver.system system = solver.system
@ -20,20 +26,37 @@ def _p(solver,partInfo,subname,shape):
system.log('cache {}: {}'.format(key,h)) system.log('cache {}: {}'.format(key,h))
else: else:
v = utils.getElementPos(shape) v = utils.getElementPos(shape)
system.NameTag = key nameTag = partInfo.PartName + '.' + key
e = system.addPoint3dV(*v) if utils.isDraftWire(partInfo.Part):
system.NameTag = partInfo.PartName + '.' + key v = partInfo.Placement.multVec(v)
h = system.addTransform(e,*partInfo.Params,group=partInfo.Group) params = []
system.log('{}: {},{}'.format(key,h,partInfo.Group)) for n,val in (('.x',v.x),('.y',v.y),('.z',v.z)):
system.NameTag = nameTag+n
params.append(system.addParamV(val,group=partInfo.Group))
system.NameTag = nameTag
e = system.addPoint3d(*params)
h = [e, params]
system.log('{}: add draft point {},{}'.format(key,h,v))
else:
system.NameTag = nameTag
e = system.addPoint3dV(*v)
system.NameTag = nameTag + 't'
h = system.addTransform(e[0],*partInfo.Params,group=partInfo.Group)
h = [h,e]
system.log('{}: {},{}'.format(key,h,partInfo.Group))
partInfo.EntityMap[key] = h partInfo.EntityMap[key] = h
return h return h if retAll else h[0]
def _n(solver,partInfo,subname,shape,retAll=False): def _n(solver,partInfo,subname,shape,retAll=False):
'return a handle of a transformed normal quaterion derived from shape' 'return a handle of a transformed normal quaterion derived from shape'
if not solver: if not solver:
if utils.isPlanar(shape): if not utils.isPlanar(shape):
return return 'an edge or face with a surface normal'
return 'an edge or face with a surface normal' if utils.isDraftWire(partInfo):
logger.warn('Use draft wire {} for normal. Draft wire placement'
' is not transformable'.format(objName(partInfo)))
return
key = subname+'.n' key = subname+'.n'
h = partInfo.EntityMap.get(key,None) h = partInfo.EntityMap.get(key,None)
system = solver.system system = solver.system
@ -43,17 +66,17 @@ def _n(solver,partInfo,subname,shape,retAll=False):
h = [] h = []
rot = utils.getElementRotation(shape) rot = utils.getElementRotation(shape)
system.NameTag = key
e = system.addNormal3dV(*utils.getNormal(rot))
nameTag = partInfo.PartName + '.' + key nameTag = partInfo.PartName + '.' + key
system.NameTag = nameTag system.NameTag = nameTag
e = system.addNormal3dV(*utils.getNormal(rot))
system.NameTag += 't'
h.append(system.addTransform(e,*partInfo.Params,group=partInfo.Group)) h.append(system.addTransform(e,*partInfo.Params,group=partInfo.Group))
# also add x axis pointing quaterion for convenience # also add x axis pointing quaterion for convenience
rot = FreeCAD.Rotation(FreeCAD.Vector(0,1,0),90).multiply(rot) rot = FreeCAD.Rotation(FreeCAD.Vector(0,1,0),90).multiply(rot)
system.NameTag = key + 'x'
e = system.addNormal3dV(*utils.getNormal(rot))
system.NameTag = nameTag + 'x' system.NameTag = nameTag + 'x'
e = system.addNormal3dV(*utils.getNormal(rot))
system.NameTag = nameTag + 'xt'
h.append(system.addTransform(e,*partInfo.Params,group=partInfo.Group)) h.append(system.addTransform(e,*partInfo.Params,group=partInfo.Group))
system.log('{}: {},{}'.format(key,h,partInfo.Group)) system.log('{}: {},{}'.format(key,h,partInfo.Group))
@ -63,29 +86,50 @@ def _n(solver,partInfo,subname,shape,retAll=False):
def _l(solver,partInfo,subname,shape,retAll=False): def _l(solver,partInfo,subname,shape,retAll=False):
'return a pair of handle of the end points of an edge in "shape"' 'return a pair of handle of the end points of an edge in "shape"'
if not solver: if not solver:
if utils.isLinearEdge(shape): if not utils.isLinearEdge(shape):
return 'a linear edge'
if not utils.isDraftWire(partInfo):
return return
return 'a linear edge' part = partInfo
vname1,vname2 = utils.edge2VertexIndex(subname)
if not vname1:
raise RuntimeError('Invalid draft subname {} or {}'.format(
subname,objName(part)))
v = shape.Edges[0].Vertexes
return _p(solver,partInfo,vname1,v[0]) or \
_p(solver,partInfo,vname2,v[1])
key = subname+'.l' key = subname+'.l'
h = partInfo.EntityMap.get(key,None) h = partInfo.EntityMap.get(key,None)
system = solver.system system = solver.system
if h: if h:
system.log('cache {}: {}'.format(key,h)) system.log('cache {}: {}'.format(key,h))
else: else:
system.NameTag = key
v = shape.Edges[0].Vertexes
p1 = system.addPoint3dV(*v[0].Point)
p2 = system.addPoint3dV(*v[-1].Point)
nameTag = partInfo.PartName + '.' + key nameTag = partInfo.PartName + '.' + key
system.NameTag = nameTag + '.p1' v = shape.Edges[0].Vertexes
tp1 = system.addTransform(p1,*partInfo.Params,group=partInfo.Group) if utils.isDraftWire(partInfo.Part):
system.NameTag = nameTag + '.p2' vname1,vname2 = utils.edge2VertexIndex(subname)
tp2 = system.addTransform(p2,*partInfo.Params,group=partInfo.Group) if not vname1:
raise RuntimeError('Invalid draft subname {} or {}'.format(
subname,objName(partInfo.Part)))
tp1 = _p(solver,partInfo,vname1,v[0])
tp2 = _p(solver,partInfo,vname2,v[1])
else:
system.NameTag = nameTag + 'p1'
p1 = system.addPoint3dV(*v[0].Point)
system.NameTag = nameTag + 'p1t'
tp1 = system.addTransform(p1,*partInfo.Params,group=partInfo.Group)
system.NameTag = nameTag + 'p2'
p2 = system.addPoint3dV(*v[-1].Point)
system.NameTag = nameTag + 'p2t'
tp2 = system.addTransform(p2,*partInfo.Params,group=partInfo.Group)
system.NameTag = nameTag system.NameTag = nameTag
h = system.addLineSegment(tp1,tp2,group=partInfo.Group) h = system.addLineSegment(tp1,tp2,group=partInfo.Group)
h = (h,tp1,tp2,p1,p2) h = (h,tp1,tp2)
system.log('{}: {},{}'.format(key,h,partInfo.Group)) system.log('{}: {},{}'.format(key,h,partInfo.Group))
partInfo.EntityMap[key] = h partInfo.EntityMap[key] = h
return h if retAll else h[0] return h if retAll else h[0]
def _ln(solver,partInfo,subname,shape,retAll=False): def _ln(solver,partInfo,subname,shape,retAll=False):
@ -120,10 +164,11 @@ def _w(solver,partInfo,subname,shape,retAll=False):
partInfo.EntityMap[key] = h partInfo.EntityMap[key] = h
return h if retAll else h[0] return h if retAll else h[0]
def _wa(solver,partInfo,subname,shape): def _wa(solver,partInfo,subname,shape,retAll=False):
_ = retAll
return _w(solver,partInfo,subname,shape,True) return _w(solver,partInfo,subname,shape,True)
def _c(solver,partInfo,subname,shape,requireArc=False): def _c(solver,partInfo,subname,shape,requireArc=False,retAll=False):
'return a handle of a transformed circle/arc derived from "shape"' 'return a handle of a transformed circle/arc derived from "shape"'
if not solver: if not solver:
r = utils.getElementCircular(shape) r = utils.getElementCircular(shape)
@ -143,23 +188,24 @@ def _c(solver,partInfo,subname,shape,requireArc=False):
r = utils.getElementCircular(shape) r = utils.getElementCircular(shape)
if not r: if not r:
raise RuntimeError('shape is not cicular') raise RuntimeError('shape is not cicular')
nameTag = partInfo.PartName + '.' + key
system.NameTag = nameTag + '.r'
hr = system.addDistanceV(r)
if requireArc or isinstance(r,(list,tuple)): if requireArc or isinstance(r,(list,tuple)):
l = _l(solver,partInfo,subname,shape,True) l = _l(solver,partInfo,subname,shape,True)
system.NameTag = partInfo.PartName + '.' + key system.NameTag = nameTag
h = system.addArcOfCircle(w,p,l[1],l[2],group=partInfo.Group) h = system.addArcOfCircle(w,p,l[1],l[2],group=partInfo.Group)
else: else:
nameTag = partInfo.PartName + '.' + key
system.NameTag = nameTag + '.r'
hr = system.addDistanceV(r)
system.NameTag = nameTag system.NameTag = nameTag
h = system.addCircle(p,n,hr,group=partInfo.Group) h = system.addCircle(p,n,hr,group=partInfo.Group)
h = (h,hr)
system.log('{}: {},{}'.format(key,h,partInfo.Group)) system.log('{}: {},{}'.format(key,h,partInfo.Group))
partInfo.EntityMap[key] = h partInfo.EntityMap[key] = h
return h return h if retAll else h[0]
def _a(solver,partInfo,subname,shape): def _a(solver,partInfo,subname,shape,retAll=False):
'return a handle of a transformed arc derived from "shape"' 'return a handle of a transformed arc derived from "shape"'
return _c(solver,partInfo,subname,shape,True) return _c(solver,partInfo,subname,shape,True,retAll)
class ConstraintCommand: class ConstraintCommand:
@ -312,6 +358,7 @@ def _makeProp(name,tp,doc='',getter=propGet,internal=False):
group='Constraint',internal=internal) group='Constraint',internal=internal)
_makeProp('Distance','App::PropertyDistance',getter=propGetValue) _makeProp('Distance','App::PropertyDistance',getter=propGetValue)
_makeProp('Length','App::PropertyDistance',getter=propGetValue)
_makeProp('Offset','App::PropertyDistance',getter=propGetValue) _makeProp('Offset','App::PropertyDistance',getter=propGetValue)
_makeProp('Cascade','App::PropertyBool',internal=True) _makeProp('Cascade','App::PropertyBool',internal=True)
_makeProp('Angle','App::PropertyAngle',getter=propGetValue) _makeProp('Angle','App::PropertyAngle',getter=propGetValue)
@ -381,7 +428,10 @@ class Base(object):
entities = cls.getEntityDef(group,checkCount) entities = cls.getEntityDef(group,checkCount)
for i,e in enumerate(entities): for i,e in enumerate(entities):
o = group[i] o = group[i]
msg = e(None,None,None,o) if isinstance(o,utils.PartInfo):
msg = e(None,o.Part,o.Subname,o.Shape)
else:
msg = e(None,None,None,o)
if not msg: if not msg:
continue continue
if i == len(cls._entityDef): if i == len(cls._entityDef):
@ -396,7 +446,7 @@ class Base(object):
return utils.getIcon(cls,Constraint.isDisabled(obj),_iconPath) return utils.getIcon(cls,Constraint.isDisabled(obj),_iconPath)
@classmethod @classmethod
def getEntities(cls,obj,solver): def getEntities(cls,obj,solver,retAll=False):
'''maps fcad element shape to entities''' '''maps fcad element shape to entities'''
elements = obj.Proxy.getElements() elements = obj.Proxy.getElements()
entities = cls.getEntityDef(elements,True,obj) entities = cls.getEntityDef(elements,True,obj)
@ -404,7 +454,7 @@ class Base(object):
for e,o in zip(entities,elements): for e,o in zip(entities,elements):
info = o.Proxy.getInfo() info = o.Proxy.getInfo()
partInfo = solver.getPartInfo(info) partInfo = solver.getPartInfo(info)
ret.append(e(solver,partInfo,info.Subname,info.Shape)) ret.append(e(solver,partInfo,info.Subname,info.Shape,retAll=retAll))
solver.system.log('{} entities: {}'.format(cstrName(obj),ret)) solver.system.log('{} entities: {}'.format(cstrName(obj),ret))
return ret return ret
@ -476,32 +526,64 @@ class Locked(Base):
@classmethod @classmethod
def prepare(cls,obj,solver): def prepare(cls,obj,solver):
ret = [] ret = []
system = solver.system
for element in obj.Proxy.getElements(): for element in obj.Proxy.getElements():
info = element.Proxy.getInfo() info = element.Proxy.getInfo()
if not utils.isVertex(info.Shape) and \ isVertex = utils.isVertex(info.Shape)
not utils.isLinearEdge(info.Shape): if not isVertex and not utils.isLinearEdge(info.Shape):
continue continue
if solver.isFixedPart(info): if solver.isFixedPart(info):
logger.warn('redundant locking element "{}" in constraint ' logger.warn('redundant locking element "{}" in constraint '
'{}'.format(info.Subname,objName(obj))) '{}'.format(info.Subname,objName(obj)))
continue continue
partInfo = solver.getPartInfo(info) partInfo = solver.getPartInfo(info)
system = solver.system
fixPoint = False
if isVertex:
names = [info.Subname]
elif utils.isDraftObject(info):
fixPoint = True
names = utils.edge2VertexIndex(info.Subname)
else:
names = [info.Subname+'.fp1', info.Subname+'.fp2']
nameTag = partInfo.PartName + '.' + info.Subname
for i,v in enumerate(info.Shape.Vertexes): for i,v in enumerate(info.Shape.Vertexes):
subname = info.Subname+'.'+str(i) surfix = '.fp{}'.format(i)
system.NameTag = subname + '.tp' system.NameTag = nameTag + surfix
# Create an entity for the transformed constant point
e1 = system.addPoint3dV(*info.Placement.multVec(v.Point)) e1 = system.addPoint3dV(*info.Placement.multVec(v.Point))
e2 = _p(solver,partInfo,subname,v)
if i==0: # Get the entity for the point expressed in variable parameters
e2 = _p(solver,partInfo,names[i],v)
if i==0 or fixPoint:
# We are fixing a vertex, or a linear edge. Either way, we
# shall add a point coincidence constraint here.
e0 = e1 e0 = e1
system.NameTag = partInfo.PartName + '.' + info.Subname system.NameTag = nameTag + surfix
ret.append(system.addPointsCoincident( e = system.addPointsCoincident(e1,e2,group=solver.group)
e1,e2,group=solver.group)) system.log('{}: fix point {},{},{}'.format(
cstrName(obj),e,e1,e2))
else: else:
system.NameTag = info.Subname + 'tl' # The second point, so we are fixing a linear edge. We can't
# add a second coincidence constraint, which will cause
# over-constraint. We constraint the second point to be on
# the line defined by the linear edge.
#
# First, get an entity of the transformed constant line
system.NameTag = nameTag + '.fl'
l = system.addLineSegment(e0,e1) l = system.addLineSegment(e0,e1)
system.NameTag = partInfo.PartName + '.' + info.Subname system.NameTag = nameTag
ret.append(system.addPointOnLine(e2,l,group=solver.group)) # Now, constraint the second variable point to the line
e = system.addPointOnLine(e2,l,group=solver.group)
system.log('{}: fix line {},{}'.format(cstrName(obj),e,l))
ret.append(e)
return ret return ret
@ -522,7 +604,10 @@ class BaseMulti(Base):
raise RuntimeError('Constraint "{}" requires at least two ' raise RuntimeError('Constraint "{}" requires at least two '
'elements'.format(cls.getName())) 'elements'.format(cls.getName()))
for o in group: for o in group:
msg = cls._entityDef[0](None,None,None,o) if isinstance(o,utils.PartInfo):
msg = cls._entityDef[0](None,o.Part,o.Subname,o.Shape)
else:
msg = cls._entityDef[0](None,None,None,o)
if msg: if msg:
raise RuntimeError('Constraint "{}" requires all the element ' raise RuntimeError('Constraint "{}" requires all the element '
'to be of {}'.format(cls.getName())) 'to be of {}'.format(cls.getName()))
@ -842,7 +927,43 @@ class BaseSketch(Base):
_toolbarName = 'Assembly3 Sketch Constraints' _toolbarName = 'Assembly3 Sketch Constraints'
class EqualLength(BaseSketch): class BaseDraftWire(BaseSketch):
_id = -1
@classmethod
def check(cls,group,checkCount=False):
super(BaseDraftWire,cls).check(group,checkCount)
if not checkCount:
return
for o in group:
if utils.isDraftWire(o):
return
raise RuntimeError('Constraint "{}" requires at least one linear edge '
'from a non-closed-or-subdivided Draft.Wire'.format(
cls.getName()))
class LineLength(BaseDraftWire):
_id = 34
_entityDef = (_l,)
_workplane = True
_props = ["Length"]
_iconName = 'Assembly_ConstraintLineLength.svg'
_tooltip='Add a "{}" constrain the length of a non-closed-or-subdivided '\
'Draft.Wire'
@classmethod
def prepare(cls,obj,solver):
func = PointsDistance.constraintFunc(obj,solver)
if func:
_,p1,p2 = cls.getEntities(obj,solver,retAll=True)[0]
params = cls.getPropertyValues(obj) + [p1,p2]
ret = func(*params,group=solver.group)
solver.system.log('{}: {}'.format(cstrName(obj),ret))
else:
logger.warn('{} no constraint func'.format(cstrName(obj)))
class EqualLength(BaseDraftWire):
_id = 9 _id = 9
_entityDef = (_l,_l) _entityDef = (_l,_l)
_workplane = True _workplane = True
@ -850,7 +971,7 @@ class EqualLength(BaseSketch):
_tooltip='Add a "{}" constraint to make two lines of the same length.' _tooltip='Add a "{}" constraint to make two lines of the same length.'
class LengthRatio(BaseSketch): class LengthRatio(BaseDraftWire):
_id = 10 _id = 10
_entityDef = (_l,_l) _entityDef = (_l,_l)
_workplane = True _workplane = True
@ -859,7 +980,7 @@ class LengthRatio(BaseSketch):
_tooltip='Add a "{}" to constrain the length ratio of two lines.' _tooltip='Add a "{}" to constrain the length ratio of two lines.'
class LengthDifference(BaseSketch): class LengthDifference(BaseDraftWire):
_id = 11 _id = 11
_entityDef = (_l,_l) _entityDef = (_l,_l)
_workplane = True _workplane = True
@ -877,13 +998,27 @@ class EqualLengthPointLineDistance(BaseSketch):
'line to be the same as the length of a another line.' 'line to be the same as the length of a another line.'
class EqualLineArcLength(BaseSketch): class EqualLineArcLength(BaseSketch):
_id = 15 _id = 15
_entityDef = (_l,_a) _entityDef = (_l,_a)
_workplane = True _workplane = True
_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
def check(cls,group,checkCount=False):
super(EqualLineArcLength,cls).check(group,checkCount)
if not checkCount:
return
for i,o in enumerate(group):
if i:
if utils.isDraftCircle(o):
return
elif utils.isDraftWire(o):
return
raise RuntimeError('Constraint "{}" requires at least one '
'non-closed-or-subdivided Draft.Wire or one Draft.Circle'.format(
cls.getName()))
class MidPoint(BaseSketch): class MidPoint(BaseSketch):
_id = 20 _id = 20
@ -893,13 +1028,25 @@ class MidPoint(BaseSketch):
_tooltip='Add a "{}" to constrain a point to the middle point of a line.' _tooltip='Add a "{}" to constrain a point to the middle point of a line.'
class Diameter(BaseSketch): class Diameter(BaseSketch):
_id = 25 _id = 25
_entityDef = (_c,) _entityDef = (_c,)
_prop = ("Diameter",) _prop = ("Diameter",)
_iconName = 'Assembly_ConstraintDiameter.svg'
_tooltip='Add a "{}" to constrain the diameter of a circle/arc' _tooltip='Add a "{}" to constrain the diameter of a circle/arc'
@classmethod
def check(cls,group,checkCount=False):
super(Diameter,cls).check(group,checkCount)
if not checkCount:
return
if len(group):
o = group[0]
if utils.isDraftCircle(o):
return
raise RuntimeError('Constraint "{}" requires a '
'Draft.Circle'.format(cls.getName()))
class EqualRadius(BaseSketch): class EqualRadius(BaseSketch):
_id = 33 _id = 33
@ -907,6 +1054,17 @@ class EqualRadius(BaseSketch):
_iconName = 'Assembly_ConstraintEqualRadius.svg' _iconName = 'Assembly_ConstraintEqualRadius.svg'
_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
def check(cls,group,checkCount=False):
super(EqualRadius,cls).check(group,checkCount)
if not checkCount:
return
for o in group:
if utils.isDraftCircle(o):
return
raise RuntimeError('Constraint "{}" requires at least one '
'Draft.Circle'.format(cls.getName()))
# class CubicLineTangent(BaseSketch): # class CubicLineTangent(BaseSketch):
# _id = 31 # _id = 31

View File

@ -2,10 +2,12 @@ import random
from collections import namedtuple from collections import namedtuple
import FreeCAD, FreeCADGui import FreeCAD, FreeCADGui
from .assembly import Assembly, isTypeOf, setPlacement from .assembly import Assembly, isTypeOf, setPlacement
from . import utils
from .utils import syslogger as logger, objName, isSamePlacement from .utils import syslogger as logger, objName, isSamePlacement
from .constraint import Constraint, cstrName from .constraint import Constraint, cstrName
from .system import System from .system import System
# Part: the part object
# PartName: text name of the part # PartName: text name of the part
# Placement: the original placement of the part # Placement: the original placement of the part
# Params: 7 parameters that defines the transformation of this part # Params: 7 parameters that defines the transformation of this part
@ -14,8 +16,8 @@ from .system import System
# norml, is essentially the XY reference plane of the part. # norml, is essentially the XY reference plane of the part.
# EntityMap: string -> entity handle map, for caching # EntityMap: string -> entity handle map, for caching
# Group: transforming entity group handle # Group: transforming entity group handle
PartInfo = namedtuple('SolverPartInfo', PartInfo = namedtuple('SolverPartInfo', ('Part','PartName','Placement',
('PartName','Placement','Params','Workplane','EntityMap','Group')) 'Params','Workplane','EntityMap','Group'))
class Solver(object): class Solver(object):
def __init__(self,assembly,reportFailed,dragPart,recompute,rollback): def __init__(self,assembly,reportFailed,dragPart,recompute,rollback):
@ -93,21 +95,48 @@ class Solver(object):
for part,partInfo in self._partMap.items(): for part,partInfo in self._partMap.items():
if part in self._fixedParts: if part in self._fixedParts:
continue continue
params = [ self.system.getParam(h).val for h in partInfo.Params ] if utils.isDraftWire(part):
p = params[:3] pointChanged = False
q = (params[4],params[5],params[6],params[3]) points = part.Points
pla = FreeCAD.Placement(FreeCAD.Vector(*p),FreeCAD.Rotation(*q)) for subname,h in partInfo.EntityMap.items():
if isSamePlacement(partInfo.Placement,pla): if not subname.endswith('.p') or\
self.system.log('not moving {}'.format(partInfo.PartName)) not subname.startswith('Vertex'):
continue
v = [ self.system.getParam(p).val for p in h[1] ]
v = FreeCAD.Vector(*v)
v = partInfo.Placement.inverse().multVec(v)
idx = utils.draftWireVertex2PointIndex(part,subname[:-2])
if utils.isSamePos(points[idx],v):
self.system.log('not moving {} point {}'.format(
partInfo.PartName,idx))
else:
pointChanged = True
self.system.log('moving {} point{} from {}->{}'.format(
partInfo.PartName,idx,points[idx],v))
if rollback is not None:
rollback.append((partInfo.PartName,
part,
(idx, points[idx])))
points[idx] = v
if pointChanged:
touched = True
part.Points = points
else: else:
touched = True params = [self.system.getParam(h).val for h in partInfo.Params]
self.system.log('moving {} {} {} {}'.format( p = params[:3]
partInfo.PartName,partInfo.Params,params,pla)) q = (params[4],params[5],params[6],params[3])
setPlacement(part,pla) pla = FreeCAD.Placement(FreeCAD.Vector(*p),FreeCAD.Rotation(*q))
if rollback is not None: if isSamePlacement(partInfo.Placement,pla):
rollback.append((partInfo.PartName, self.system.log('not moving {}'.format(partInfo.PartName))
part, else:
partInfo.Placement.copy())) touched = True
self.system.log('moving {} {} {} {}'.format(
partInfo.PartName,partInfo.Params,params,pla))
setPlacement(part,pla)
if rollback is not None:
rollback.append((partInfo.PartName,
part,
partInfo.Placement.copy()))
if recompute and touched: if recompute and touched:
assembly.recompute(True) assembly.recompute(True)
@ -125,18 +154,25 @@ class Solver(object):
else: else:
g = self.group g = self.group
self.system.NameTag = info.PartName if utils.isDraftWire(info):
params = self.system.addPlacement(info.Placement,group=g) # Special treatment for draft wire. We do not change its placement,
# but individual point position, instead.
params = None
h = None
else:
self.system.NameTag = info.PartName
params = self.system.addPlacement(info.Placement,group=g)
self.system.NameTag = info.PartName + '.p' self.system.NameTag = info.PartName + '.p'
p = self.system.addPoint3d(*params[:3],group=g) p = self.system.addPoint3d(*params[:3],group=g)
self.system.NameTag = info.PartName + '.n' self.system.NameTag = info.PartName + '.n'
n = self.system.addNormal3d(*params[3:],group=g) n = self.system.addNormal3d(*params[3:],group=g)
self.system.NameTag = info.PartName + '.w' self.system.NameTag = info.PartName + '.w'
w = self.system.addWorkplane(p,n,group=g) w = self.system.addWorkplane(p,n,group=g)
h = (w,p,n) h = (w,p,n)
partInfo = PartInfo(PartName = info.PartName, partInfo = PartInfo(Part = info.Part,
PartName = info.PartName,
Placement = info.Placement.copy(), Placement = info.Placement.copy(),
Params = params, Params = params,
Workplane = h, Workplane = h,
@ -211,7 +247,11 @@ def _solve(objs=None,recursive=None,reportFailed=True,
if rollback is not None: if rollback is not None:
for name,part,pla in reversed(rollback): for name,part,pla in reversed(rollback):
logger.debug('roll back {} to {}'.format(name,pla)) logger.debug('roll back {} to {}'.format(name,pla))
setPlacement(part,pla) if utils.isDraftWire(part):
idx,pt = pla
part.Points[idx] = pt
else:
setPlacement(part,pla)
raise raise
return True return True

View File

@ -455,7 +455,7 @@ def _directionConsine(wrkpln,l1,l2,supplement=False):
v1,v2 = _project(wrkpln,l1,l2) v1,v2 = _project(wrkpln,l1,l2)
if supplement: if supplement:
v1 = v1 * -1.0 v1 = v1 * -1.0
return v1.cross(v2)/(v1.magnitude()*v2.magnitude()) return v1.dot(v2)/(v1.magnitude()*v2.magnitude())
_x = 'i' _x = 'i'
_y = 'j' _y = 'j'
@ -1016,7 +1016,7 @@ class _Angle(_ProjectingConstraint):
return _directionConsine(self.wrkpln,self.l1,self.l2,self.supplement) return _directionConsine(self.wrkpln,self.l1,self.l2,self.supplement)
def getEq(self): def getEq(self):
return self.DirectionCosine - sp.cos(self.degree.SymObj*sp.pi/180.0) return self.DirectionCosine - sp.cos(sp.pi*self.degree/180.0)
class _Perpendicular(_Angle): class _Perpendicular(_Angle):
_args = ('l1', 'l2',) _args = ('l1', 'l2',)

View File

@ -5,7 +5,8 @@ Most of the functions are borrowed directly from assembly2lib.py or lib3D.py in
assembly2 assembly2
''' '''
import FreeCAD, FreeCADGui, Part from collections import namedtuple
import FreeCAD, FreeCADGui, Part, Draft
import numpy as np import numpy as np
from .FCADLogger import FCADLogger from .FCADLogger import FCADLogger
rootlogger = FCADLogger('asm3') rootlogger = FCADLogger('asm3')
@ -125,8 +126,30 @@ def getElementShape(obj,tp):
if len(f)==1: if len(f)==1:
return f[0] return f[0]
PartInfo = namedtuple('AsmPartInfo', ('Parent','SubnameRef','Part',
'PartName','Placement','Object','Subname','Shape'))
def isDraftWire(obj):
if isinstance(obj,PartInfo):
obj = obj.Part
proxy = getattr(obj,'Proxy',None)
return isinstance(proxy,Draft._Wire) and \
not obj.Closed and \
not obj.Subdivisions
def isDraftCircle(obj):
if isinstance(obj,PartInfo):
obj = obj.Part
proxy = getattr(obj,'Proxy',None)
return isinstance(proxy,Draft._Circle)
def isDraftObject(obj):
return isDraftWire(obj) or isDraftCircle(obj)
def isElement(obj): def isElement(obj):
if not isinstance(obj,(tuple,list)): if isinstance(obj,PartInfo):
shape = obj.Shape
elif not isinstance(obj,(tuple,list)):
shape = obj shape = obj
else: else:
sobj,_,shape = obj[0].getSubObject(obj[1],2) sobj,_,shape = obj[0].getSubObject(obj[1],2)
@ -446,6 +469,38 @@ def fit_rotation_axis_to_surface1( surface, n_u=3, n_v=3 ):
_tol = 10e-7 _tol = 10e-7
def isSamePos(p1,p2):
return p1.distanceToPoint(p2) < _tol
def isSamePlacement(pla1,pla2): def isSamePlacement(pla1,pla2):
return pla1.Base.distanceToPoint(pla2.Base) < _tol and \ return isSamePos(pla1.Base,pla2.Base) and \
np.linalg.norm(np.array(pla1.Rotation.Q)-np.array(pla2.Rotation.Q))<_tol np.linalg.norm(np.array(pla1.Rotation.Q)-np.array(pla2.Rotation.Q))<_tol
def getElementIndex(name,check=None):
'Return element index, 0 if invalid'
for i,c in enumerate(reversed(name)):
if not c.isdigit():
if not i:
break
idx = int(name[-i:])
if check and '{}{}'.format(check,idx)!=name:
break
return idx
return 0
def draftWireVertex2PointIndex(obj,name):
'Convert vertex index to draft wire point index, None if invalid'
idx = getElementIndex(name,'Vertex')
# We don't support subdivision yet (checked in isDraftWire())
if idx <= 0 or not isDraftWire(obj):
return
idx -= 1
if idx < len(obj.Points):
return idx
def edge2VertexIndex(name):
'deduct the vertex index from the edge index'
idx = getElementIndex(name,'Edge')
if not idx:
return None,None
return 'Vertex{}'.format(idx),'Vertex{}'.format(idx+1)