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)
def getPartInfo(parent, subname):
def getElementInfo(parent, subname):
'''Return a named tuple containing the part object element information
Parameters:
@ -630,7 +630,7 @@ def getPartInfo(parent, subname):
obj = part.getLinkedObject(False)
partName = part.Name
return utils.PartInfo(Parent = parent,
return utils.ElementInfo(Parent = parent,
SubnameRef = subnameRef,
Part = part,
PartName = partName,
@ -749,7 +749,7 @@ class AsmElementLink(AsmBase):
self.info = None
if not getattr(self,'Object',None):
return
self.info = getPartInfo(self.getAssembly().getPartGroup(),
self.info = getElementInfo(self.getAssembly().getPartGroup(),
self.getElementSubname())
return self.info
@ -1619,7 +1619,7 @@ class AsmMovingPart(object):
movingPart.tracePoint = movingPart.draggerPlacement.Base
def update(self):
info = getPartInfo(self.parent,self.subname)
info = getElementInfo(self.parent,self.subname)
self.oldPlacement = info.Placement.copy()
self.part = info.Part
self.partName = info.PartName
@ -1691,11 +1691,12 @@ class AsmMovingPart(object):
# AsmMovingPart.update()
return self.draggerPlacement
def getMovingPartInfo():
def getMovingElementInfo():
'''Extract information from current selection for part moving
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
the highest level assembly in selected hierarchy.
@ -1722,7 +1723,7 @@ def getMovingPartInfo():
objName(sels[0].Object),sels[0].SubElementNames[0]))
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:
return
return (ret, info)
@ -1741,14 +1742,14 @@ def getMovingPartInfo():
assembly = ret[-1].Assembly
for r in ret2:
if assembly == r.Assembly:
return (ret2, getPartInfo(r.Assembly,r.Subname))
return (ret2, getElementInfo(r.Assembly,r.Subname))
raise RuntimeError('not child parent selection')
def canMovePart():
return logger.catchTrace('',getMovingPartInfo) is not None
return logger.catchTrace('',getMovingElementInfo) is not None
def movePart(useCenterballDragger=None):
ret = logger.catch('exception when moving part', getMovingPartInfo)
ret = logger.catch('exception when moving part', getMovingElementInfo)
if not ret:
return False

View File

@ -1,5 +1,5 @@
from collections import namedtuple
import FreeCAD, FreeCADGui
import FreeCAD, FreeCADGui, Part
from . import utils, gui
from .utils import objName,cstrlogger as logger, guilogger
from .proxy import ProxyType, PropertyInfo, propGet, propGetValue
@ -12,11 +12,10 @@ def _p(solver,partInfo,subname,shape,retAll=False):
if not solver:
if not utils.hasCenter(shape):
return 'a vertex or circular edge/face'
if not utils.isDraftWire(partInfo):
return
if utils.draftWireVertex2PointIndex(partInfo,subname) is None:
raise RuntimeError('Invalid draft wire vertex "{}" {}'.format(
subname,objName(partInfo)))
if utils.isDraftWire(partInfo):
if utils.draftWireVertex2PointIndex(partInfo,subname) is None:
raise RuntimeError('Invalid draft wire vertex "{}" {}'.format(
subname,objName(partInfo)))
return
key = subname+'.p'
@ -24,27 +23,46 @@ def _p(solver,partInfo,subname,shape,retAll=False):
system = solver.system
if 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:
v = utils.getElementPos(shape)
nameTag = partInfo.PartName + '.' + key
if utils.isDraftWire(partInfo.Part):
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))
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
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]
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 utils.isPlanar(shape):
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'
' is not transformable'.format(objName(partInfo)))
' is not transformable'.format(partInfo.PartName))
return
key = subname+'.n'
@ -88,7 +106,7 @@ def _l(solver,partInfo,subname,shape,retAll=False):
if not solver:
if not utils.isLinearEdge(shape):
return 'a linear edge'
if not utils.isDraftWire(partInfo):
if not utils.isDraftWire(partInfo.Part):
return
part = partInfo
vname1,vname2 = utils.edge2VertexIndex(subname)
@ -132,6 +150,14 @@ def _l(solver,partInfo,subname,shape,retAll=False):
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):
'return a handle for either a line or a normal depends on the shape'
if not solver:
@ -173,8 +199,10 @@ def _c(solver,partInfo,subname,shape,requireArc=False,retAll=False):
if not solver:
r = utils.getElementCircular(shape)
if r:
if requireArc and not isinstance(r,tuple):
return 'an arc edge'
return
return 'an cicular edge'
return 'a cicular edge'
if requireArc:
key = subname+'.a'
else:
@ -183,26 +211,76 @@ def _c(solver,partInfo,subname,shape,requireArc=False,retAll=False):
system = solver.system
if 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:
w,p,n,_ = _w(solver,partInfo,subname,shape,True)
r = utils.getElementCircular(shape)
if not r:
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)):
l = _l(solver,partInfo,subname,shape,True)
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:
system.NameTag = nameTag
h = system.addCircle(p,n,hr,group=partInfo.Group)
h = system.addCircle(p,n,hr,group=g)
h = (h,hr)
system.log('{}: {},{}'.format(key,h,partInfo.Group))
partInfo.EntityMap[key] = h
system.log('{}: {},{}'.format(key,h,g))
partInfo.EntityMap[key] = h
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):
'return a handle of a transformed arc derived from "shape"'
return _c(solver,partInfo,subname,shape,True,retAll)
@ -298,31 +376,34 @@ class Constraint(ProxyType):
return mcs.getProxy(obj).prepare(obj,solver)
@classmethod
def getFixedParts(mcs,cstrs):
firstPart = None
firstPartName = None
def getFixedParts(mcs,solver,cstrs):
firstInfo = None
found = False
ret = set()
for obj in cstrs:
cstr = mcs.getProxy(obj)
if cstr.hasFixedPart(obj):
found = True
for info in cstr.getFixedParts(obj):
for info in cstr.getFixedParts(solver,obj):
logger.debug('fixed part ' + info.PartName)
ret.add(info.Part)
if not found and not firstPart:
if not found and not firstInfo:
elements = obj.Proxy.getElements()
if elements:
info = elements[0].Proxy.getInfo()
firstPart = info.Part
firstPartName = info.PartName
firstInfo = elements[0].Proxy.getInfo()
if not found:
if not firstPart:
if not firstInfo:
return None
logger.debug('lock first part {}'.format(firstPartName))
ret.add(firstPart)
if utils.isDraftObject(firstInfo.Part):
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
@classmethod
@ -353,20 +434,20 @@ class Constraint(ProxyType):
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,
group='Constraint',internal=internal)
group='Constraint',internal=internal,default=default)
_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('Cascade','App::PropertyBool',internal=True)
_makeProp('Angle','App::PropertyAngle',getter=propGetValue)
_makeProp('LockAngle','App::PropertyBool')
_makeProp('Ratio','App::PropertyFloat')
_makeProp('Ratio','App::PropertyFloat',default=1.0)
_makeProp('Difference','App::PropertyFloat')
_makeProp('Diameter','App::PropertyFloat')
_makeProp('Radius','App::PropertyFloat')
_makeProp('Diameter','App::PropertyDistance',getter=propGetValue,default=10.0)
_makeProp('Radius','App::PropertyDistance',getter=propGetValue,default=5.0)
_makeProp('Supplement','App::PropertyBool',
'If True, then the second angle is calculated as 180-angle')
_makeProp('AtEnd','App::PropertyBool',
@ -428,7 +509,7 @@ class Base(object):
entities = cls.getEntityDef(group,checkCount)
for i,e in enumerate(entities):
o = group[i]
if isinstance(o,utils.PartInfo):
if isinstance(o,utils.ElementInfo):
msg = e(None,o.Part,o.Subname,o.Shape)
else:
msg = e(None,None,None,o)
@ -496,12 +577,13 @@ class Locked(Base):
_tooltip = 'Add a "{}" constraint to fix part(s)'
@classmethod
def getFixedParts(cls,obj):
def getFixedParts(cls,_solver,obj):
ret = []
for e in obj.Proxy.getElements():
info = e.Proxy.getInfo()
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)
return ret
@ -512,11 +594,12 @@ class Locked(Base):
ret = []
for e in obj.Proxy.getElements():
info = e.Proxy.getInfo()
if not utils.isVertex(info.Shape) and \
not utils.isLinearEdge(info.Shape):
ret.append(cls.Info(Part=info.Part,Shape=None))
continue
ret.append(cls.Info(Part=info.Part,Shape=info.Shape))
shape = None
if utils.isVertex(info.Shape) or \
utils.isDraftCircle(info) or \
utils.isLinearEdge(info.Shape):
shape = info.Shape
ret.append(cls.Info(Part=info.Part,Shape=shape))
return ret
@classmethod
@ -524,67 +607,77 @@ class Locked(Base):
return len(obj.Proxy.getElements())>0
@classmethod
def prepare(cls,obj,solver):
def lockElement(cls,info,solver):
ret = []
system = solver.system
for element in obj.Proxy.getElements():
info = element.Proxy.getInfo()
isVertex = utils.isVertex(info.Shape)
if not isVertex and not utils.isLinearEdge(info.Shape):
continue
if solver.isFixedPart(info):
logger.warn('redundant locking element "{}" in constraint '
'{}'.format(info.Subname,objName(obj)))
continue
isVertex = utils.isVertex(info.Shape)
if not isVertex and utils.isDraftCircle(info):
solver.getPartInfo(info,True,solver.group)
return ret
partInfo = solver.getPartInfo(info)
if not isVertex and not utils.isLinearEdge(info.Shape):
return ret
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']
if solver.isFixedPart(info):
logger.warn('redundant locking element "{}" in constraint '
'{}'.format(info.Subname,info.PartName))
return ret
nameTag = partInfo.PartName + '.' + info.Subname
partInfo = solver.getPartInfo(info)
for i,v in enumerate(info.Shape.Vertexes):
surfix = '.fp{}'.format(i)
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):
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
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
e1 = system.addPoint3dV(*info.Placement.multVec(v.Point))
ret.append(e)
# 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
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)
return ret
@classmethod
def prepare(cls,obj,solver):
ret = []
for element in obj.Proxy.getElements():
ret += cls.lockElement(element.Proxy.getInfo(),solver)
return ret
@classmethod
@ -604,7 +697,7 @@ class BaseMulti(Base):
raise RuntimeError('Constraint "{}" requires at least two '
'elements'.format(cls.getName()))
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)
else:
msg = cls._entityDef[0](None,None,None,o)
@ -942,9 +1035,9 @@ class BaseDraftWire(BaseSketch):
'from a non-closed-or-subdivided Draft.Wire'.format(
cls.getName()))
class LineLength(BaseDraftWire):
class LineLength(BaseSketch):
_id = 34
_entityDef = (_l,)
_entityDef = (_dl,)
_workplane = True
_props = ["Length"]
_iconName = 'Assembly_ConstraintLineLength.svg'
@ -1030,23 +1123,11 @@ class MidPoint(BaseSketch):
class Diameter(BaseSketch):
_id = 25
_entityDef = (_c,)
_prop = ("Diameter",)
_entityDef = (_dc,)
_props = ("Diameter",)
_iconName = 'Assembly_ConstraintDiameter.svg'
_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):
_id = 33

View File

@ -11,7 +11,8 @@ class PropertyInfo(object):
'For holding information to create dynamic properties'
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.Type = tp
self.Group = group
@ -19,6 +20,7 @@ class PropertyInfo(object):
self.Enum = enum
self.get = getter.__get__(self,self.__class__)
self.Internal = internal
self.Default = default
self.Key = host.addPropertyInfo(self,duplicate)
class ProxyType(type):
@ -106,6 +108,8 @@ class ProxyType(type):
obj.addProperty(prop.Type,prop.Name,prop.Group,prop.Doc)
if 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))
obj.ViewObject.signalChangeIcon()

View File

@ -1,4 +1,4 @@
import random
import random, math
from collections import namedtuple
import FreeCAD, FreeCADGui
from .assembly import Assembly, isTypeOf, setPlacement
@ -35,9 +35,12 @@ class Solver(object):
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:
logger.warn('no fixed part found')
self.system.log('no fixed part found')
return
for cstr in cstrs:
@ -96,21 +99,21 @@ class Solver(object):
if part in self._fixedParts:
continue
if utils.isDraftWire(part):
pointChanged = False
changed = False
points = part.Points
for subname,h in partInfo.EntityMap.items():
if not subname.endswith('.p') or\
not subname.startswith('Vertex'):
for key,h in partInfo.EntityMap.items():
if not key.endswith('.p') or\
not key.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])
idx = utils.draftWireVertex2PointIndex(part,key[:-2])
if utils.isSamePos(points[idx],v):
self.system.log('not moving {} point {}'.format(
partInfo.PartName,idx))
else:
pointChanged = True
changed = True
self.system.log('moving {} point{} from {}->{}'.format(
partInfo.PartName,idx,points[idx],v))
if rollback is not None:
@ -118,7 +121,7 @@ class Solver(object):
part,
(idx, points[idx])))
points[idx] = v
if pointChanged:
if changed:
touched = True
part.Points = points
else:
@ -132,11 +135,43 @@ class Solver(object):
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()))
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:
assembly.recompute(True)
@ -144,12 +179,12 @@ class Solver(object):
def isFixedPart(self,info):
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)
if partInfo:
return partInfo
if info.Part in self._fixedParts:
if fixed or info.Part in self._fixedParts:
g = self._fixedGroup
else:
g = self.group
@ -177,9 +212,9 @@ class Solver(object):
Params = params,
Workplane = h,
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
return partInfo
@ -245,13 +280,19 @@ def _solve(objs=None,recursive=None,reportFailed=True,
System.touch(assembly,False)
except Exception:
if rollback is not None:
for name,part,pla in reversed(rollback):
logger.debug('roll back {} to {}'.format(name,pla))
if utils.isDraftWire(part):
idx,pt = pla
for name,part,v in reversed(rollback):
logger.debug('roll back {} to {}'.format(name,v))
if isinstance(v,FreeCAD.Placement):
setPlacement(part,v)
elif utils.isDraftWire(part):
idx,pt = v
part.Points[idx] = pt
else:
setPlacement(part,pla)
elif utils.isDraftWire(part):
r,a1,a2 = v
part.Radius = r
part.FirstAngle = a1
part.LastAngle = a2
raise
return True

View File

@ -126,11 +126,11 @@ def getElementShape(obj,tp):
if len(f)==1:
return f[0]
PartInfo = namedtuple('AsmPartInfo', ('Parent','SubnameRef','Part',
ElementInfo = namedtuple('AsmElementInfo', ('Parent','SubnameRef','Part',
'PartName','Placement','Object','Subname','Shape'))
def isDraftWire(obj):
if isinstance(obj,PartInfo):
if isinstance(obj,ElementInfo):
obj = obj.Part
proxy = getattr(obj,'Proxy',None)
return isinstance(proxy,Draft._Wire) and \
@ -138,7 +138,7 @@ def isDraftWire(obj):
not obj.Subdivisions
def isDraftCircle(obj):
if isinstance(obj,PartInfo):
if isinstance(obj,ElementInfo):
obj = obj.Part
proxy = getattr(obj,'Proxy',None)
return isinstance(proxy,Draft._Circle)
@ -147,7 +147,7 @@ def isDraftObject(obj):
return isDraftWire(obj) or isDraftCircle(obj)
def isElement(obj):
if isinstance(obj,PartInfo):
if isinstance(obj,ElementInfo):
shape = obj.Shape
elif not isinstance(obj,(tuple,list)):
shape = obj
@ -469,12 +469,25 @@ def fit_rotation_axis_to_surface1( surface, n_u=3, n_v=3 ):
_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):
return p1.distanceToPoint(p2) < _tol
def isSamePlacement(pla1,pla2):
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):
'Return element index, 0 if invalid'