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:
parent
05595e3f41
commit
b087200e50
21
assembly.py
21
assembly.py
|
@ -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
|
||||
|
||||
|
|
331
constraint.py
331
constraint.py
|
@ -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
|
||||
|
|
6
proxy.py
6
proxy.py
|
@ -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()
|
||||
|
|
83
solver.py
83
solver.py
|
@ -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
|
||||
|
|
23
utils.py
23
utils.py
|
@ -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'
|
||||
|
|
Loading…
Reference in New Issue
Block a user