Add support for draft circle/arc

When added to an assembly, a normal part object has six degrees of
freedom, three positional and three rotational. A draft circle has one
additional dof, its radius. And a draft arc has three additional dof,
radius, first angle and last angle.
This commit is contained in:
Zheng, Lei 2018-01-20 16:32:27 +08:00
parent 05595e3f41
commit b087200e50
5 changed files with 302 additions and 162 deletions

View File

@ -488,7 +488,7 @@ class ViewProviderAsmElement(ViewProviderAsmOnTop):
Group=owner, Subname=subname),undo=True) Group=owner, Subname=subname),undo=True)
def getPartInfo(parent, subname): def getElementInfo(parent, subname):
'''Return a named tuple containing the part object element information '''Return a named tuple containing the part object element information
Parameters: Parameters:
@ -630,7 +630,7 @@ def getPartInfo(parent, subname):
obj = part.getLinkedObject(False) obj = part.getLinkedObject(False)
partName = part.Name partName = part.Name
return utils.PartInfo(Parent = parent, return utils.ElementInfo(Parent = parent,
SubnameRef = subnameRef, SubnameRef = subnameRef,
Part = part, Part = part,
PartName = partName, PartName = partName,
@ -749,7 +749,7 @@ class AsmElementLink(AsmBase):
self.info = None self.info = None
if not getattr(self,'Object',None): if not getattr(self,'Object',None):
return return
self.info = getPartInfo(self.getAssembly().getPartGroup(), self.info = getElementInfo(self.getAssembly().getPartGroup(),
self.getElementSubname()) self.getElementSubname())
return self.info return self.info
@ -1619,7 +1619,7 @@ class AsmMovingPart(object):
movingPart.tracePoint = movingPart.draggerPlacement.Base movingPart.tracePoint = movingPart.draggerPlacement.Base
def update(self): def update(self):
info = getPartInfo(self.parent,self.subname) info = getElementInfo(self.parent,self.subname)
self.oldPlacement = info.Placement.copy() self.oldPlacement = info.Placement.copy()
self.part = info.Part self.part = info.Part
self.partName = info.PartName self.partName = info.PartName
@ -1691,11 +1691,12 @@ class AsmMovingPart(object):
# AsmMovingPart.update() # AsmMovingPart.update()
return self.draggerPlacement return self.draggerPlacement
def getMovingPartInfo(): def getMovingElementInfo():
'''Extract information from current selection for part moving '''Extract information from current selection for part moving
It returns a tuple containing the selected assembly hierarchy (obtained from It returns a tuple containing the selected assembly hierarchy (obtained from
Assembly.findChildren()), and AsmPartInfo of the selected child part object. Assembly.findChildren()), and AsmElementInfo of the selected child part
object.
If there is only one selection, then the moving part will be one belong to If there is only one selection, then the moving part will be one belong to
the highest level assembly in selected hierarchy. the highest level assembly in selected hierarchy.
@ -1722,7 +1723,7 @@ def getMovingPartInfo():
objName(sels[0].Object),sels[0].SubElementNames[0])) objName(sels[0].Object),sels[0].SubElementNames[0]))
if len(sels[0].SubElementNames)==1: if len(sels[0].SubElementNames)==1:
info = getPartInfo(ret[0].Assembly,ret[0].Subname) info = getElementInfo(ret[0].Assembly,ret[0].Subname)
if not info: if not info:
return return
return (ret, info) return (ret, info)
@ -1741,14 +1742,14 @@ def getMovingPartInfo():
assembly = ret[-1].Assembly assembly = ret[-1].Assembly
for r in ret2: for r in ret2:
if assembly == r.Assembly: if assembly == r.Assembly:
return (ret2, getPartInfo(r.Assembly,r.Subname)) return (ret2, getElementInfo(r.Assembly,r.Subname))
raise RuntimeError('not child parent selection') raise RuntimeError('not child parent selection')
def canMovePart(): def canMovePart():
return logger.catchTrace('',getMovingPartInfo) is not None return logger.catchTrace('',getMovingElementInfo) is not None
def movePart(useCenterballDragger=None): def movePart(useCenterballDragger=None):
ret = logger.catch('exception when moving part', getMovingPartInfo) ret = logger.catch('exception when moving part', getMovingElementInfo)
if not ret: if not ret:
return False return False

View File

@ -1,5 +1,5 @@
from collections import namedtuple from collections import namedtuple
import FreeCAD, FreeCADGui import FreeCAD, FreeCADGui, Part
from . import utils, gui from . import utils, gui
from .utils import objName,cstrlogger as logger, guilogger from .utils import objName,cstrlogger as logger, guilogger
from .proxy import ProxyType, PropertyInfo, propGet, propGetValue from .proxy import ProxyType, PropertyInfo, propGet, propGetValue
@ -12,11 +12,10 @@ def _p(solver,partInfo,subname,shape,retAll=False):
if not solver: if not solver:
if not utils.hasCenter(shape): if not utils.hasCenter(shape):
return 'a vertex or circular edge/face' return 'a vertex or circular edge/face'
if not utils.isDraftWire(partInfo): if utils.isDraftWire(partInfo):
return if utils.draftWireVertex2PointIndex(partInfo,subname) is None:
if utils.draftWireVertex2PointIndex(partInfo,subname) is None: raise RuntimeError('Invalid draft wire vertex "{}" {}'.format(
raise RuntimeError('Invalid draft wire vertex "{}" {}'.format( subname,objName(partInfo)))
subname,objName(partInfo)))
return return
key = subname+'.p' key = subname+'.p'
@ -24,27 +23,46 @@ def _p(solver,partInfo,subname,shape,retAll=False):
system = solver.system system = solver.system
if h: if h:
system.log('cache {}: {}'.format(key,h)) system.log('cache {}: {}'.format(key,h))
return h if retAll else h[0]
if utils.isDraftWire(partInfo.Part):
v = utils.getElementPos(shape)
nameTag = partInfo.PartName + '.' + key
v = partInfo.Placement.multVec(v)
params = []
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))
elif utils.isDraftCircle(partInfo.Part):
shape = utils.getElementShape((partInfo.Part,'Edge1'),Part.Edge)
if subname == 'Vertex1':
e = _c(solver,partInfo,'Edge1',shape,retAll=True)
h = [e[2]]
elif subname == 'Vertex2':
e = _a(solver,partInfo,'Edge1',shape,retAll=True)
h = [e[1]]
else:
raise RuntimeError('Invalid draft circle vertex {} of '
'{}'.format(subname,objName(partInfo.Part)))
system.log('{}: add circle point {},{}'.format(key,h,e))
else: else:
v = utils.getElementPos(shape) v = utils.getElementPos(shape)
nameTag = partInfo.PartName + '.' + key nameTag = partInfo.PartName + '.' + key
if utils.isDraftWire(partInfo.Part): system.NameTag = nameTag
v = partInfo.Placement.multVec(v) e = system.addPoint3dV(*v)
params = [] system.NameTag = nameTag + 't'
for n,val in (('.x',v.x),('.y',v.y),('.z',v.z)): h = system.addTransform(e[0],*partInfo.Params,group=partInfo.Group)
system.NameTag = nameTag+n h = [h,e]
params.append(system.addParamV(val,group=partInfo.Group)) system.log('{}: {},{}'.format(key,h,partInfo.Group))
system.NameTag = nameTag
e = system.addPoint3d(*params) partInfo.EntityMap[key] = h
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
return h if retAll else h[0] return h if retAll else h[0]
def _n(solver,partInfo,subname,shape,retAll=False): def _n(solver,partInfo,subname,shape,retAll=False):
@ -52,9 +70,9 @@ def _n(solver,partInfo,subname,shape,retAll=False):
if not solver: if not solver:
if not utils.isPlanar(shape): if not utils.isPlanar(shape):
return 'an edge or face with a surface normal' return 'an edge or face with a surface normal'
if utils.isDraftWire(partInfo): if utils.isDraftWire(partInfo.Part):
logger.warn('Use draft wire {} for normal. Draft wire placement' logger.warn('Use draft wire {} for normal. Draft wire placement'
' is not transformable'.format(objName(partInfo))) ' is not transformable'.format(partInfo.PartName))
return return
key = subname+'.n' key = subname+'.n'
@ -88,7 +106,7 @@ def _l(solver,partInfo,subname,shape,retAll=False):
if not solver: if not solver:
if not utils.isLinearEdge(shape): if not utils.isLinearEdge(shape):
return 'a linear edge' return 'a linear edge'
if not utils.isDraftWire(partInfo): if not utils.isDraftWire(partInfo.Part):
return return
part = partInfo part = partInfo
vname1,vname2 = utils.edge2VertexIndex(subname) vname1,vname2 = utils.edge2VertexIndex(subname)
@ -132,6 +150,14 @@ def _l(solver,partInfo,subname,shape,retAll=False):
return h if retAll else h[0] return h if retAll else h[0]
def _dl(solver,partInfo,subname,shape,retAll=False):
'return a handle of a draft wire'
if not solver:
if utils.isDraftWire(partInfo):
return
raise RuntimeError('Requires a non-closed-or-subdivided draft wire')
return _l(solver,partInfo,subname,shape,retAll)
def _ln(solver,partInfo,subname,shape,retAll=False): def _ln(solver,partInfo,subname,shape,retAll=False):
'return a handle for either a line or a normal depends on the shape' 'return a handle for either a line or a normal depends on the shape'
if not solver: if not solver:
@ -173,8 +199,10 @@ def _c(solver,partInfo,subname,shape,requireArc=False,retAll=False):
if not solver: if not solver:
r = utils.getElementCircular(shape) r = utils.getElementCircular(shape)
if r: if r:
if requireArc and not isinstance(r,tuple):
return 'an arc edge'
return return
return 'an cicular edge' return 'a cicular edge'
if requireArc: if requireArc:
key = subname+'.a' key = subname+'.a'
else: else:
@ -183,26 +211,76 @@ def _c(solver,partInfo,subname,shape,requireArc=False,retAll=False):
system = solver.system system = solver.system
if h: if h:
system.log('cache {}: {}'.format(key,h)) system.log('cache {}: {}'.format(key,h))
return h if retAll else h[0]
g = partInfo.Group
nameTag = partInfo.PartName + '.' + key
if utils.isDraftCircle(partInfo.Part):
part = partInfo.Part
w,p,n = partInfo.Workplane
if part.FirstAngle == part.LastAngle:
if requireArc:
raise RuntimeError('expecting an arc from {}'.format(
partInfo.PartName))
system.NameTag = nameTag + '.r'
r = system.addParamV(part.Radius.Value,group=g)
system.NameTag = nameTag + '.p0'
p0 = system.addPoint2d(w,r,solver.v0,group=g)
system.NameTag = nameTag
e = system.addCircle(p,n,system.addDistance(r),group=g)
h = [e,r,p0]
system.log('{}: add draft circle {}, {}'.format(key,h,g))
else:
system.NameTag = nameTag + '.c'
center = system.addPoint2d(w,solver.v0,solver.v0,group=g)
params = []
points = []
v = shape.Vertexes
for i in 0,1:
for n,val in ('.x{}',v[i].Point.x),('.y{}',v[i].Point.y):
system.NameTag = nameTag+n.format(i)
params.append(system.addParamV(val,group=g))
system.NameTag = nameTag + '.p{}'.format(i)
points.append(system.addPoint2d(w,*params[-2:],group=g))
system.NameTag = nameTag
e = system.addArcOfCircle(w,center,*points,group=g)
h = [e,points[1],points[0],params]
system.log('{}: add draft arc {}, {}'.format(key,h,g))
# exhaust all possible keys from a draft circle to save
# recomputation
sub = subname + '.c' if requireArc else '.a'
partInfo.EntityMap[sub] = h
else: else:
w,p,n,_ = _w(solver,partInfo,subname,shape,True) w,p,n,_ = _w(solver,partInfo,subname,shape,True)
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' system.NameTag = nameTag + '.r'
hr = system.addDistanceV(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 = nameTag 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=g)
else: else:
system.NameTag = nameTag system.NameTag = nameTag
h = system.addCircle(p,n,hr,group=partInfo.Group) h = system.addCircle(p,n,hr,group=g)
h = (h,hr) h = (h,hr)
system.log('{}: {},{}'.format(key,h,partInfo.Group)) system.log('{}: {},{}'.format(key,h,g))
partInfo.EntityMap[key] = h
partInfo.EntityMap[key] = h
return h if retAll else h[0] return h if retAll else h[0]
def _dc(solver,partInfo,subname,shape,requireArc=False,retAll=False):
'return a handle of a draft circle'
if not solver:
if utils.isDraftCircle(partInfo):
return
raise RuntimeError('Requires a draft circle')
return _c(solver,partInfo,subname,shape,requireArc,retAll)
def _a(solver,partInfo,subname,shape,retAll=False): 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,retAll) return _c(solver,partInfo,subname,shape,True,retAll)
@ -298,31 +376,34 @@ class Constraint(ProxyType):
return mcs.getProxy(obj).prepare(obj,solver) return mcs.getProxy(obj).prepare(obj,solver)
@classmethod @classmethod
def getFixedParts(mcs,cstrs): def getFixedParts(mcs,solver,cstrs):
firstPart = None firstInfo = None
firstPartName = None
found = False found = False
ret = set() ret = set()
for obj in cstrs: for obj in cstrs:
cstr = mcs.getProxy(obj) cstr = mcs.getProxy(obj)
if cstr.hasFixedPart(obj): if cstr.hasFixedPart(obj):
found = True found = True
for info in cstr.getFixedParts(obj): for info in cstr.getFixedParts(solver,obj):
logger.debug('fixed part ' + info.PartName) logger.debug('fixed part ' + info.PartName)
ret.add(info.Part) ret.add(info.Part)
if not found and not firstPart: if not found and not firstInfo:
elements = obj.Proxy.getElements() elements = obj.Proxy.getElements()
if elements: if elements:
info = elements[0].Proxy.getInfo() firstInfo = elements[0].Proxy.getInfo()
firstPart = info.Part
firstPartName = info.PartName
if not found: if not found:
if not firstPart: if not firstInfo:
return None return None
logger.debug('lock first part {}'.format(firstPartName)) if utils.isDraftObject(firstInfo.Part):
ret.add(firstPart) Locked.lockElement(firstInfo,solver)
logger.debug('lock first draft object {}'.format(
firstInfo.PartName))
solver.getPartInfo(firstInfo,True,solver.group)
else:
logger.debug('lock first part {}'.format(firstInfo.PartName))
ret.add(firstInfo.Part)
return ret return ret
@classmethod @classmethod
@ -353,20 +434,20 @@ class Constraint(ProxyType):
return cstr.getIcon(obj) return cstr.getIcon(obj)
def _makeProp(name,tp,doc='',getter=propGet,internal=False): 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,
group='Constraint',internal=internal) group='Constraint',internal=internal,default=default)
_makeProp('Distance','App::PropertyDistance',getter=propGetValue) _makeProp('Distance','App::PropertyDistance',getter=propGetValue)
_makeProp('Length','App::PropertyDistance',getter=propGetValue) _makeProp('Length','App::PropertyDistance',getter=propGetValue,default=5.0)
_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)
_makeProp('LockAngle','App::PropertyBool') _makeProp('LockAngle','App::PropertyBool')
_makeProp('Ratio','App::PropertyFloat') _makeProp('Ratio','App::PropertyFloat',default=1.0)
_makeProp('Difference','App::PropertyFloat') _makeProp('Difference','App::PropertyFloat')
_makeProp('Diameter','App::PropertyFloat') _makeProp('Diameter','App::PropertyDistance',getter=propGetValue,default=10.0)
_makeProp('Radius','App::PropertyFloat') _makeProp('Radius','App::PropertyDistance',getter=propGetValue,default=5.0)
_makeProp('Supplement','App::PropertyBool', _makeProp('Supplement','App::PropertyBool',
'If True, then the second angle is calculated as 180-angle') 'If True, then the second angle is calculated as 180-angle')
_makeProp('AtEnd','App::PropertyBool', _makeProp('AtEnd','App::PropertyBool',
@ -428,7 +509,7 @@ 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]
if isinstance(o,utils.PartInfo): if isinstance(o,utils.ElementInfo):
msg = e(None,o.Part,o.Subname,o.Shape) msg = e(None,o.Part,o.Subname,o.Shape)
else: else:
msg = e(None,None,None,o) msg = e(None,None,None,o)
@ -496,12 +577,13 @@ class Locked(Base):
_tooltip = 'Add a "{}" constraint to fix part(s)' _tooltip = 'Add a "{}" constraint to fix part(s)'
@classmethod @classmethod
def getFixedParts(cls,obj): def getFixedParts(cls,_solver,obj):
ret = [] ret = []
for e in obj.Proxy.getElements(): for e in obj.Proxy.getElements():
info = e.Proxy.getInfo() info = e.Proxy.getInfo()
if not utils.isVertex(info.Shape) and \ if not utils.isVertex(info.Shape) and \
not utils.isLinearEdge(info.Shape): not utils.isLinearEdge(info.Shape) and \
not utils.isDraftCircle(info):
ret.append(info) ret.append(info)
return ret return ret
@ -512,11 +594,12 @@ class Locked(Base):
ret = [] ret = []
for e in obj.Proxy.getElements(): for e in obj.Proxy.getElements():
info = e.Proxy.getInfo() info = e.Proxy.getInfo()
if not utils.isVertex(info.Shape) and \ shape = None
not utils.isLinearEdge(info.Shape): if utils.isVertex(info.Shape) or \
ret.append(cls.Info(Part=info.Part,Shape=None)) utils.isDraftCircle(info) or \
continue utils.isLinearEdge(info.Shape):
ret.append(cls.Info(Part=info.Part,Shape=info.Shape)) shape = info.Shape
ret.append(cls.Info(Part=info.Part,Shape=shape))
return ret return ret
@classmethod @classmethod
@ -524,67 +607,77 @@ class Locked(Base):
return len(obj.Proxy.getElements())>0 return len(obj.Proxy.getElements())>0
@classmethod @classmethod
def prepare(cls,obj,solver): def lockElement(cls,info,solver):
ret = [] ret = []
system = solver.system system = solver.system
for element in obj.Proxy.getElements(): isVertex = utils.isVertex(info.Shape)
info = element.Proxy.getInfo() if not isVertex and utils.isDraftCircle(info):
isVertex = utils.isVertex(info.Shape) solver.getPartInfo(info,True,solver.group)
if not isVertex and not utils.isLinearEdge(info.Shape): return ret
continue
if solver.isFixedPart(info):
logger.warn('redundant locking element "{}" in constraint '
'{}'.format(info.Subname,objName(obj)))
continue
partInfo = solver.getPartInfo(info) if not isVertex and not utils.isLinearEdge(info.Shape):
return ret
fixPoint = False if solver.isFixedPart(info):
if isVertex: logger.warn('redundant locking element "{}" in constraint '
names = [info.Subname] '{}'.format(info.Subname,info.PartName))
elif utils.isDraftObject(info): return ret
fixPoint = True
names = utils.edge2VertexIndex(info.Subname)
else:
names = [info.Subname+'.fp1', info.Subname+'.fp2']
nameTag = partInfo.PartName + '.' + info.Subname partInfo = solver.getPartInfo(info)
for i,v in enumerate(info.Shape.Vertexes): fixPoint = False
surfix = '.fp{}'.format(i) 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):
surfix = '.fp{}'.format(i)
system.NameTag = nameTag + surfix
# Create an entity for the transformed constant point
e1 = system.addPoint3dV(*info.Placement.multVec(v.Point))
# 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
system.NameTag = nameTag + surfix system.NameTag = nameTag + surfix
e = system.addPointsCoincident(e1,e2,group=solver.group)
system.log('{}: fix point {},{},{}'.format(
info.PartName,e,e1,e2))
else:
# 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)
system.NameTag = nameTag
# Now, constraint the second variable point to the line
e = system.addPointOnLine(e2,l,group=solver.group)
system.log('{}: fix line {},{}'.format(info.PartName,e,l))
# Create an entity for the transformed constant point ret.append(e)
e1 = system.addPoint3dV(*info.Placement.multVec(v.Point))
# Get the entity for the point expressed in variable parameters return ret
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
system.NameTag = nameTag + surfix
e = system.addPointsCoincident(e1,e2,group=solver.group)
system.log('{}: fix point {},{},{}'.format(
cstrName(obj),e,e1,e2))
else:
# 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)
system.NameTag = nameTag
# 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)
@classmethod
def prepare(cls,obj,solver):
ret = []
for element in obj.Proxy.getElements():
ret += cls.lockElement(element.Proxy.getInfo(),solver)
return ret return ret
@classmethod @classmethod
@ -604,7 +697,7 @@ 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:
if isinstance(o,utils.PartInfo): if isinstance(o,utils.ElementInfo):
msg = cls._entityDef[0](None,o.Part,o.Subname,o.Shape) msg = cls._entityDef[0](None,o.Part,o.Subname,o.Shape)
else: else:
msg = cls._entityDef[0](None,None,None,o) msg = cls._entityDef[0](None,None,None,o)
@ -942,9 +1035,9 @@ class BaseDraftWire(BaseSketch):
'from a non-closed-or-subdivided Draft.Wire'.format( 'from a non-closed-or-subdivided Draft.Wire'.format(
cls.getName())) cls.getName()))
class LineLength(BaseDraftWire): class LineLength(BaseSketch):
_id = 34 _id = 34
_entityDef = (_l,) _entityDef = (_dl,)
_workplane = True _workplane = True
_props = ["Length"] _props = ["Length"]
_iconName = 'Assembly_ConstraintLineLength.svg' _iconName = 'Assembly_ConstraintLineLength.svg'
@ -1030,23 +1123,11 @@ class MidPoint(BaseSketch):
class Diameter(BaseSketch): class Diameter(BaseSketch):
_id = 25 _id = 25
_entityDef = (_c,) _entityDef = (_dc,)
_prop = ("Diameter",) _props = ("Diameter",)
_iconName = 'Assembly_ConstraintDiameter.svg' _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

View File

@ -11,7 +11,8 @@ class PropertyInfo(object):
'For holding information to create dynamic properties' 'For holding information to create dynamic properties'
def __init__(self,host,name,tp,doc='', enum=None, def __init__(self,host,name,tp,doc='', enum=None,
getter=propGet,group='Base',internal=False,duplicate=False): getter=propGet,group='Base',internal=False,
duplicate=False,default=None):
self.Name = name self.Name = name
self.Type = tp self.Type = tp
self.Group = group self.Group = group
@ -19,6 +20,7 @@ class PropertyInfo(object):
self.Enum = enum self.Enum = enum
self.get = getter.__get__(self,self.__class__) self.get = getter.__get__(self,self.__class__)
self.Internal = internal self.Internal = internal
self.Default = default
self.Key = host.addPropertyInfo(self,duplicate) self.Key = host.addPropertyInfo(self,duplicate)
class ProxyType(type): class ProxyType(type):
@ -106,6 +108,8 @@ class ProxyType(type):
obj.addProperty(prop.Type,prop.Name,prop.Group,prop.Doc) obj.addProperty(prop.Type,prop.Name,prop.Group,prop.Doc)
if prop.Enum: if prop.Enum:
setattr(obj,prop.Name,prop.Enum) setattr(obj,prop.Name,prop.Enum)
if prop.Default is not None:
setattr(obj,prop.Name,prop.Default)
setattr(obj.Proxy,mcs._proxyName,cls(obj)) setattr(obj.Proxy,mcs._proxyName,cls(obj))
obj.ViewObject.signalChangeIcon() obj.ViewObject.signalChangeIcon()

View File

@ -1,4 +1,4 @@
import random import random, math
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
@ -35,9 +35,12 @@ class Solver(object):
self.system.GroupHandle = self._fixedGroup self.system.GroupHandle = self._fixedGroup
self._fixedParts = Constraint.getFixedParts(cstrs) # convenience constant of zero
self.v0 = self.system.addParamV(0,group=self._fixedGroup)
self._fixedParts = Constraint.getFixedParts(self,cstrs)
if self._fixedParts is None: if self._fixedParts is None:
logger.warn('no fixed part found') self.system.log('no fixed part found')
return return
for cstr in cstrs: for cstr in cstrs:
@ -96,21 +99,21 @@ class Solver(object):
if part in self._fixedParts: if part in self._fixedParts:
continue continue
if utils.isDraftWire(part): if utils.isDraftWire(part):
pointChanged = False changed = False
points = part.Points points = part.Points
for subname,h in partInfo.EntityMap.items(): for key,h in partInfo.EntityMap.items():
if not subname.endswith('.p') or\ if not key.endswith('.p') or\
not subname.startswith('Vertex'): not key.startswith('Vertex'):
continue continue
v = [ self.system.getParam(p).val for p in h[1] ] v = [ self.system.getParam(p).val for p in h[1] ]
v = FreeCAD.Vector(*v) v = FreeCAD.Vector(*v)
v = partInfo.Placement.inverse().multVec(v) v = partInfo.Placement.inverse().multVec(v)
idx = utils.draftWireVertex2PointIndex(part,subname[:-2]) idx = utils.draftWireVertex2PointIndex(part,key[:-2])
if utils.isSamePos(points[idx],v): if utils.isSamePos(points[idx],v):
self.system.log('not moving {} point {}'.format( self.system.log('not moving {} point {}'.format(
partInfo.PartName,idx)) partInfo.PartName,idx))
else: else:
pointChanged = True changed = True
self.system.log('moving {} point{} from {}->{}'.format( self.system.log('moving {} point{} from {}->{}'.format(
partInfo.PartName,idx,points[idx],v)) partInfo.PartName,idx,points[idx],v))
if rollback is not None: if rollback is not None:
@ -118,7 +121,7 @@ class Solver(object):
part, part,
(idx, points[idx]))) (idx, points[idx])))
points[idx] = v points[idx] = v
if pointChanged: if changed:
touched = True touched = True
part.Points = points part.Points = points
else: else:
@ -132,11 +135,43 @@ class Solver(object):
touched = True touched = True
self.system.log('moving {} {} {} {}'.format( self.system.log('moving {} {} {} {}'.format(
partInfo.PartName,partInfo.Params,params,pla)) partInfo.PartName,partInfo.Params,params,pla))
setPlacement(part,pla)
if rollback is not None: if rollback is not None:
rollback.append((partInfo.PartName, rollback.append((partInfo.PartName,
part, part,
partInfo.Placement.copy())) partInfo.Placement.copy()))
setPlacement(part,pla)
if utils.isDraftCircle(part):
changed = False
h = partInfo.EntityMap.get('Edge1.c',None)
if not h:
continue
v0 = (part.Radius.Value,
part.FirstAngle.Value,
part.LastAngle.Value)
if part.FirstAngle == part.LastAngle:
v = (self.system.getParam(h[1]).val,v0[1],v0[2])
else:
params = [self.system.getParam(p).val for p in h[3]]
p0 = FreeCAD.Vector(1,0,0)
p1 = FreeCAD.Vector(params[0],params[1],0)
p2 = FreeCAD.Vector(params[2],params[3],0)
v = (p1.Length,
math.degrees(p0.getAngle(p1)),
math.degrees(p0.getAngle(p2)))
if utils.isSameValue(v0,v):
self.system.log('not change draft circle {}'.format(
partInfo.PartName))
else:
touched = True
self.system.log('change draft circle {} {}->{}'.format(
partInfo.PartName,v0,v))
if rollback is not None:
rollback.append((partInfo.PartName, part, v0))
part.Radius = v[0]
part.FirstAngle = v[1]
part.LastAngle = v[2]
if recompute and touched: if recompute and touched:
assembly.recompute(True) assembly.recompute(True)
@ -144,12 +179,12 @@ class Solver(object):
def isFixedPart(self,info): def isFixedPart(self,info):
return info.Part in self._fixedParts return info.Part in self._fixedParts
def getPartInfo(self,info): def getPartInfo(self,info,fixed=False,group=0):
partInfo = self._partMap.get(info.Part,None) partInfo = self._partMap.get(info.Part,None)
if partInfo: if partInfo:
return partInfo return partInfo
if info.Part in self._fixedParts: if fixed or info.Part in self._fixedParts:
g = self._fixedGroup g = self._fixedGroup
else: else:
g = self.group g = self.group
@ -177,9 +212,9 @@ class Solver(object):
Params = params, Params = params,
Workplane = h, Workplane = h,
EntityMap = {}, EntityMap = {},
Group = g) Group = group if group else g)
self.system.log('{}'.format(partInfo)) self.system.log('{}, {}'.format(partInfo,g))
self._partMap[info.Part] = partInfo self._partMap[info.Part] = partInfo
return partInfo return partInfo
@ -245,13 +280,19 @@ def _solve(objs=None,recursive=None,reportFailed=True,
System.touch(assembly,False) System.touch(assembly,False)
except Exception: except Exception:
if rollback is not None: if rollback is not None:
for name,part,pla in reversed(rollback): for name,part,v in reversed(rollback):
logger.debug('roll back {} to {}'.format(name,pla)) logger.debug('roll back {} to {}'.format(name,v))
if utils.isDraftWire(part): if isinstance(v,FreeCAD.Placement):
idx,pt = pla setPlacement(part,v)
elif utils.isDraftWire(part):
idx,pt = v
part.Points[idx] = pt part.Points[idx] = pt
else: elif utils.isDraftWire(part):
setPlacement(part,pla) r,a1,a2 = v
part.Radius = r
part.FirstAngle = a1
part.LastAngle = a2
raise raise
return True return True

View File

@ -126,11 +126,11 @@ def getElementShape(obj,tp):
if len(f)==1: if len(f)==1:
return f[0] return f[0]
PartInfo = namedtuple('AsmPartInfo', ('Parent','SubnameRef','Part', ElementInfo = namedtuple('AsmElementInfo', ('Parent','SubnameRef','Part',
'PartName','Placement','Object','Subname','Shape')) 'PartName','Placement','Object','Subname','Shape'))
def isDraftWire(obj): def isDraftWire(obj):
if isinstance(obj,PartInfo): if isinstance(obj,ElementInfo):
obj = obj.Part obj = obj.Part
proxy = getattr(obj,'Proxy',None) proxy = getattr(obj,'Proxy',None)
return isinstance(proxy,Draft._Wire) and \ return isinstance(proxy,Draft._Wire) and \
@ -138,7 +138,7 @@ def isDraftWire(obj):
not obj.Subdivisions not obj.Subdivisions
def isDraftCircle(obj): def isDraftCircle(obj):
if isinstance(obj,PartInfo): if isinstance(obj,ElementInfo):
obj = obj.Part obj = obj.Part
proxy = getattr(obj,'Proxy',None) proxy = getattr(obj,'Proxy',None)
return isinstance(proxy,Draft._Circle) return isinstance(proxy,Draft._Circle)
@ -147,7 +147,7 @@ def isDraftObject(obj):
return isDraftWire(obj) or isDraftCircle(obj) return isDraftWire(obj) or isDraftCircle(obj)
def isElement(obj): def isElement(obj):
if isinstance(obj,PartInfo): if isinstance(obj,ElementInfo):
shape = obj.Shape shape = obj.Shape
elif not isinstance(obj,(tuple,list)): elif not isinstance(obj,(tuple,list)):
shape = obj shape = obj
@ -469,12 +469,25 @@ def fit_rotation_axis_to_surface1( surface, n_u=3, n_v=3 ):
_tol = 10e-7 _tol = 10e-7
def isSameValue(v1,v2):
if isinstance(v1,(tuple,list)):
assert(len(v1)==len(v2))
vs = zip(v1,v2)
else:
vs = (v1,v2),
for v1,v2 in vs:
v = v1-v2
if v>=_tol or v<=-_tol:
return False
return True
def isSamePos(p1,p2): def isSamePos(p1,p2):
return p1.distanceToPoint(p2) < _tol return p1.distanceToPoint(p2) < _tol
def isSamePlacement(pla1,pla2): def isSamePlacement(pla1,pla2):
return isSamePos(pla1.Base,pla2.Base) and \ return isSamePos(pla1.Base,pla2.Base) and \
np.linalg.norm(np.array(pla1.Rotation.Q)-np.array(pla2.Rotation.Q))<_tol isSameValue(pla1.Rotation.Q,pla2.Rotation.Q)
def getElementIndex(name,check=None): def getElementIndex(name,check=None):
'Return element index, 0 if invalid' 'Return element index, 0 if invalid'