diff --git a/Gui/Resources/icons/constraints/Assembly_ConstraintAxial.svg b/Gui/Resources/icons/constraints/Assembly_ConstraintAxial.svg
new file mode 100644
index 0000000..fa07274
--- /dev/null
+++ b/Gui/Resources/icons/constraints/Assembly_ConstraintAxial.svg
@@ -0,0 +1,610 @@
+
+
+
+
diff --git a/Gui/Resources/icons/constraints/Assembly_ConstraintOpposite.svg b/Gui/Resources/icons/constraints/Assembly_ConstraintMultiParallel.svg
similarity index 74%
rename from Gui/Resources/icons/constraints/Assembly_ConstraintOpposite.svg
rename to Gui/Resources/icons/constraints/Assembly_ConstraintMultiParallel.svg
index 90c6e4b..5130ec1 100644
--- a/Gui/Resources/icons/constraints/Assembly_ConstraintOpposite.svg
+++ b/Gui/Resources/icons/constraints/Assembly_ConstraintMultiParallel.svg
@@ -93,9 +93,14 @@
-
+
+
+
+
+
+
-
+
@@ -109,7 +114,7 @@
[jmaustpc]
- Assembly_ConstraintOpposite
+ Assembly_ConstraintParallel
2013-12-24
http://www.freecadweb.org/wiki/index.php?title=Artwork
@@ -117,7 +122,7 @@
FreeCAD
- FreeCAD/src/Mod/Assembly/Gui/Resources/icons/constraints/Assembly_ConstraintOpposite.svg
+ FreeCAD/src/Mod/Assembly/Gui/Resources/icons/constraints/Assembly_ConstraintParallel.svg
FreeCAD LGPL2+
@@ -137,26 +142,37 @@
-
+
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
-
+
diff --git a/Gui/Resources/icons/constraints/Assembly_ConstraintParallel.svg b/Gui/Resources/icons/constraints/Assembly_ConstraintParallel.svg
index 5130ec1..90c6e4b 100644
--- a/Gui/Resources/icons/constraints/Assembly_ConstraintParallel.svg
+++ b/Gui/Resources/icons/constraints/Assembly_ConstraintParallel.svg
@@ -93,14 +93,9 @@
-
-
-
-
-
-
+
-
+
@@ -114,7 +109,7 @@
[jmaustpc]
- Assembly_ConstraintParallel
+ Assembly_ConstraintOpposite
2013-12-24
http://www.freecadweb.org/wiki/index.php?title=Artwork
@@ -122,7 +117,7 @@
FreeCAD
- FreeCAD/src/Mod/Assembly/Gui/Resources/icons/constraints/Assembly_ConstraintParallel.svg
+ FreeCAD/src/Mod/Assembly/Gui/Resources/icons/constraints/Assembly_ConstraintOpposite.svg
FreeCAD LGPL2+
@@ -142,37 +137,26 @@
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
-
+
-
+
diff --git a/assembly.py b/assembly.py
index a0bbf66..2d8d538 100644
--- a/assembly.py
+++ b/assembly.py
@@ -835,9 +835,7 @@ class Assembly(AsmGroup):
'{}'.format(objName(ret)))
return ret
except IndexError:
- if not create:
- return # constraint group is optional, so, no exception
- if obj.Group:
+ if not create or obj.Group:
raise RuntimeError('Invalid assembly')
ret = AsmConstraintGroup.make(obj)
obj.setLink({0:ret})
@@ -865,6 +863,9 @@ class Assembly(AsmGroup):
def getElementGroup(self,create=False):
obj = self.obj
+ if create:
+ # make sure previous group exists
+ self.getConstraintGroup(True)
try:
ret = obj.Group[1]
checkType(ret,AsmElementGroup)
@@ -878,13 +879,15 @@ class Assembly(AsmGroup):
except IndexError:
if not create:
raise RuntimeError('Missing element group')
- self.getConstraintGroup(True)
ret = AsmElementGroup.make(obj)
obj.setLink({1:ret})
return ret
def getPartGroup(self,create=False):
obj = self.obj
+ if create:
+ # make sure previous group exists
+ self.getElementGroup(True)
try:
ret = obj.Group[2]
checkType(ret,AsmPartGroup)
@@ -898,8 +901,6 @@ class Assembly(AsmGroup):
except IndexError:
if not create:
raise RuntimeError('Missing part group')
- self.getConstraintGroup(True)
- self.getElementGroup(True)
ret = AsmPartGroup.make(obj)
obj.setLink({2:ret})
return ret
diff --git a/constraint.py b/constraint.py
index 10a2877..6fd86c6 100644
--- a/constraint.py
+++ b/constraint.py
@@ -12,6 +12,38 @@ pixmapDisabled = QPixmap(os.path.join(
iconPath,'Assembly_ConstraintDisabled.svg'))
iconSize = (16,16)
+def cstrName(obj):
+ return '{}<{}>'.format(objName(obj),obj.Type)
+
+PropertyInfo = namedtuple('AsmPropertyInfo',
+ ('Name','Type','Group','Doc','Enum','Getter'))
+
+_propInfo = {}
+
+def _propGet(obj,prop):
+ return getattr(obj,prop)
+
+def _propGetValue(obj,prop):
+ return getattr(getattr(obj,prop),'Value')
+
+def _makePropInfo(name,tp,doc='',enum=None,getter=_propGet,group='Constraint'):
+ _propInfo[name] = PropertyInfo(name,tp,group,doc,enum,getter)
+
+_makePropInfo('Distance','App::PropertyDistance',getter=_propGetValue)
+_makePropInfo('Offset','App::PropertyDistance',getter=_propGetValue)
+_makePropInfo('Cascade','App::PropertyBool')
+_makePropInfo('Angle','App::PropertyAngle',getter=_propGetValue)
+_makePropInfo('Ratio','App::PropertyFloat')
+_makePropInfo('Difference','App::PropertyFloat')
+_makePropInfo('Diameter','App::PropertyFloat')
+_makePropInfo('Radius','App::PropertyFloat')
+_makePropInfo('Supplement','App::PropertyBool',
+ 'If True, then the second angle is calculated as 180-angle')
+_makePropInfo('AtEnd','App::PropertyBool',
+ 'If True, then tangent at the end point, or else at the start point')
+
+_ordinal = ['1st', '2nd', '3rd', '4th', '5th', '6th', '7th' ]
+
Types = []
TypeMap = {}
TypeNameMap = {}
@@ -23,64 +55,82 @@ class ConstraintType(type):
if cls._id in TypeMap:
raise RuntimeError(
'Duplicate constriant type id {}'.format(cls._id))
- if cls.slvsFunc():
- TypeMap[cls._id] = cls
- TypeNameMap[cls.getName()] = cls
- cls._idx = len(Types)
- logger.debug('register constraint "{}":{},{}'.format(
- cls.getName(),cls._id,cls._idx))
- Types.append(cls)
+ if not cls.slvsFunc():
+ return
+
+ if cls._props:
+ for i,prop in enumerate(cls._props):
+ try:
+ cls._props[i] = _propInfo[prop]
+ except AttributeError:
+ raise RuntimeError('Unknonw property "{}" in '
+ 'constraint type "{}"'.format(prop,cls.getName()))
+ TypeMap[cls._id] = cls
+ TypeNameMap[cls.getName()] = cls
+ cls._idx = len(Types)
+ logger.debug('register constraint "{}":{},{}'.format(
+ cls.getName(),cls._id,cls._idx))
+ Types.append(cls)
# PartName: text name of the part
# Placement: the original placement of the part
-# Params: 7 parameters that defines the transformation
+# Params: 7 parameters that defines the transformation of this part
+# RParams: 7 parameters that defines the rotation transformation of this part
# Workplane: a tuple of three entity handles, that is the workplane, the origin
# point, and the normal. The workplane, defined by the origin and
# norml, is essentially the XY reference plane of the part.
# EntityMap: string -> entity handle map, for caching
# Group: transforming entity group handle
+# X: a point entity (1,0,0) rotated by this part's placement
+# Y: a point entity (0,1,0) rotated by this part's placement
+# Z: a point entity (0,0,1) rotated by this part's placement
PartInfo = namedtuple('SolverPartInfo',
- ('PartName','Placement','Params','Workplane','EntityMap','Group'))
+ ('PartName','Placement','Params','RParams','Workplane','EntityMap',
+ 'Group', 'X','Y','Z'))
-def _addEntity(etype,system,partInfo,key,shape):
- key += '.{}'.format(etype)
- h = partInfo.EntityMap.get(key,None)
- if h:
- logger.debug('cache {}: {}'.format(key,h))
- return h
- if etype == 'p': # point
- v = utils.getElementPos(shape)
- e = system.addPoint3dV(*v)
- elif etype == 'n': # normal
- v = utils.getElementNormal(shape)
- e = system.addNormal3dV(*v)
- else:
- raise RuntimeError('unknown entity type {}'.format(etype))
- h = system.addTransform(e,*partInfo.Params,group=partInfo.Group)
- logger.debug('{}: {},{}, {}, {}'.format(key,h,partInfo.Group,e,v))
- partInfo.EntityMap[key] = h
- return h
-
-def _p(system,partInfo,key,shape):
+def _p(solver,partInfo,key,shape):
'return a slvs handle of a transformed point derived from "shape"'
- if not system:
+ if not solver:
if utils.hasCenter(shape):
return
return 'a vertex or circular edge/face'
- return _addEntity('p',system,partInfo,key,shape)
+ key += '.p'
+ h = partInfo.EntityMap.get(key,None)
+ if h:
+ logger.debug('cache {}: {}'.format(key,h))
+ else:
+ v = utils.getElementPos(shape)
+ system = solver.system
+ e = system.addPoint3dV(*v)
+ h = system.addTransform(e,*partInfo.Params,group=partInfo.Group)
+ logger.debug('{}: {},{}, {}, {}'.format(key,h,partInfo.Group,e,v))
+ partInfo.EntityMap[key] = h
+ return h
-def _n(system,partInfo,key,shape):
- 'return a slvs handle of a transformed normal derived from "shape"'
- if not system:
- if utils.isAxisOfPlane(shape):
+def _n(solver,partInfo,key,shape,retAll=False):
+ 'return a slvs handle of a transformed normal quaterion derived from shape'
+ if not solver:
+ if utils.isPlanar(shape):
return
return 'an edge or face with a surface normal'
- return _addEntity('n',system,partInfo,key,shape)
+ key += '.n'
+ h = partInfo.EntityMap.get(key,None)
+ if h:
+ logger.debug('cache {}: {}'.format(key,h))
+ else:
+ system = solver.system
+ params = [ system.addParamV(n) for n in utils.getElementNormal(shape) ]
+ e = system.addNormal3d(*params)
+ h = system.addTransform(e,*partInfo.Params,group=partInfo.Group)
+ h = [h,e,params]
+ logger.debug('{}: {},{}'.format(key,h,partInfo.Group))
+ partInfo.EntityMap[key] = h
+ return h if retAll else h[0]
-def _l(system,partInfo,key,shape,retAll=False):
+def _l(solver,partInfo,key,shape,retAll=False):
'return a pair of slvs handle of the end points of an edge in "shape"'
- if not system:
+ if not solver:
if utils.isLinearEdge(shape):
return
return 'a linear edge'
@@ -89,38 +139,52 @@ def _l(system,partInfo,key,shape,retAll=False):
if h:
logger.debug('cache {}: {}'.format(key,h))
else:
+ system = solver.system
v = shape.Edges[0].Vertexes
p1 = system.addPoint3dV(*v[0].Point)
p2 = system.addPoint3dV(*v[-1].Point)
- h = system.addLine(p1,p2,group=partInfo.Group)
+ h = system.addLineSegment(p1,p2,group=partInfo.Group)
h = (h,p1,p2)
logger.debug('{}: {},{}'.format(key,h,partInfo.Group))
partInfo.EntityMap[key] = h
return h if retAll else h[0]
-def _w(system,partInfo,key,shape,retAll=False):
- 'return a slvs handle of a transformed plane/workplane from "shape"'
- if not system:
- if utils.isAxisOfPlane(shape):
+def _ln(solver,partInfo,key,shape,retAll=False):
+ 'return a slvs handle for either a line or a normal depends on the shape'
+ if not solver:
+ if utils.isLinearEdge(shape) or utils.isPlanar(shape):
return
- return 'an edge or face with a planar surface'
+ return 'a linear edge or edge/face with planar surface'
+ if utils.isLinearEdge(shape):
+ return _l(solver,partInfo,key,shape,retAll)
+ return _n(solver,partInfo,key,shape)
+
+def _w(solver,partInfo,key,shape,retAll=False):
+ 'return a slvs handle of a transformed plane/workplane from "shape"'
+ if not solver:
+ if utils.isPlanar(shape):
+ return
+ return 'an edge/face with a planar surface'
key2 = key+'.w'
h = partInfo.EntityMap.get(key2,None)
if h:
logger.debug('cache {}: {}'.format(key,h))
else:
- p = _p(system,partInfo,key,shape)
- n = _n(system,partInfo,key,shape)
- h = system.addWorkplane(p,n,group=partInfo.Group)
+ p = _p(solver,partInfo,key,shape)
+ n = _n(solver,partInfo,key,shape)
+ h = solver.system.addWorkplane(p,n,group=partInfo.Group)
h = (h,p,n)
logger.debug('{}: {},{}'.format(key,h,partInfo.Group))
partInfo.EntityMap[key2] = h
return h if retAll else h[0]
-def _c(system,partInfo,key,shape,requireArc=False):
+def _wa(solver,partInfo,key,shape):
+ return _w(solver,partInfo,key,shape,True)
+
+def _c(solver,partInfo,key,shape,requireArc=False):
'return a slvs handle of a transformed circle/arc derived from "shape"'
- if not system:
+ if not solver:
r = utils.getElementCircular(shape)
if not r or (requireArc and not isinstance(r,list,tuple)):
return
@@ -130,46 +194,33 @@ def _c(system,partInfo,key,shape,requireArc=False):
if h:
logger.debug('cache {}: {}'.format(key,h))
else:
- h = _w(system,partInfo,key,shape,True)
+ h = _w(solver,partInfo,key,shape,True)
r = utils.getElementCircular(shape)
if not r:
raise RuntimeError('shape is not cicular')
if isinstance(r,(list,tuple)):
- l = _l(system,partInfo,key,shape,True)
+ l = _l(solver,partInfo,key,shape,True)
h += l[1:]
- h = system.addArcOfCircleV(*h,group=partInfo.Group)
+ h = solver.system.addArcOfCircleV(*h,group=partInfo.Group)
elif requireArc:
raise RuntimeError('shape is not an arc')
else:
h = h[1:]
- h.append(system.addDistanceV(r))
- h = system.addCircle(*h,group=partInfo.Group)
+ h.append(solver.addDistanceV(r))
+ h = solver.system.addCircle(*h,group=partInfo.Group)
logger.debug('{}: {},{} {}'.format(key,h,partInfo.Group,r))
partInfo.EntityMap[key2] = h
return h
-def _a(system,partInfo,key,shape):
- return _c(system,partInfo,key,shape,True)
+def _a(solver,partInfo,key,shape):
+ return _c(solver,partInfo,key,shape,True)
-_PropertyDistance = ('Value','Distance','PropertyDistance','Constraint')
-_PropertyAngle = ('Value','Angle','PropertyAngle','Constraint')
-_PropertyRatio = (None,'Ratio','PropertyFloat','Constraint')
-_PropertyDifference = (None,'Difference','PropertyFloat','Constraint')
-_PropertyDiameter = (None,'Diameter','PropertyFloat','Constraint')
-_PropertyRadius = (None,'Radius','PropertyFloat','Constraint')
-_PropertySupplement = (None,'Supplement','PropertyBool','Constraint',
- 'If True, then the second angle is calculated as 180-angle')
-_PropertyAtEnd = (None,'AtEnd','PropertyBool','Constraint',
- 'If True, then tangent at the end point, or else at the start point')
-
-_ordinal = ['1st', '2nd', '3rd', '4th', '5th', '6th', '7th' ]
-
class Base:
__metaclass__ = ConstraintType
_id = -1
- _entities = []
+ _entityDef = []
_workplane = False
_props = []
_func = None
@@ -177,17 +228,20 @@ class Base:
_iconDisabled = None
_iconName = 'Assembly_ConstraintGeneral.svg'
- def __init__(self,obj,props):
+ def __init__(self,obj):
if obj._Type != self._id:
if self._id < 0:
raise RuntimeError('invalid constraint type {} id: '
'{}'.format(self.__class__,self._id))
obj._Type = self._id
+ props = obj.PropertiesList
for prop in self.__class__._props:
- if prop[1] not in props:
- obj.addProperty(*prop[1:])
+ if prop.Name not in props:
+ obj.addProperty(prop.Type,prop.Name,prop.Group,prop.Doc)
+ if prop.Enum:
+ setattr(obj,prop.Name,prop.Enum)
else:
- obj.setPropertyStatus(prop[1],'-Hidden')
+ obj.setPropertyStatus(prop.Name,'-Hidden')
@classmethod
def getName(cls):
@@ -203,8 +257,8 @@ class Base:
logger.error('Invalid slvs constraint "{}"'.format(cls.getName()))
@classmethod
- def getEntityDef(cls,group,checkCount,name=None):
- entities = cls._entities
+ def getEntityDef(cls,group,checkCount,obj=None):
+ entities = cls._entityDef
if len(group) != len(entities):
if not checkCount and len(group)0.0:
+ h = system.addPointPlaneDistance(d,p1,w2,group=solver.group)
+ logger.debug('{}: point plane distance {},{},{}'.format(
+ cstrName(obj),h,p1,w2,d))
+ h = system.addPointsCoincident(p1,p2,w2,group=solver.group)
+ logger.debug('{}: points conincident {},{},{}'.format(
+ cstrName(obj),h,p1,p2,w2))
+ else:
+ h = system.addPointsCoincident(p1,p2,group=solver.group)
+ logger.debug('{}: points conincident {},{},{}'.format(
+ cstrName(obj),h,p1,p2))
+ h = system.addParallel(n1,n2,group=solver.group)
+ logger.debug('{}: parallel {},{},{}'.format(cstrName(obj),h,n1,n2))
+
+
+class PlaneAlignment(BaseCascade):
+ _id = 37
+ _iconName = 'Assembly_ConstraintAlignment.svg'
+ _props = ["Offset", 'Cascade']
+
+ @classmethod
+ def prepareElements(cls,obj,solver,e1,e2):
+ system = solver.system
+ d = abs(obj.Offset.Value)
+ _,p1,n1 = e1
+ w2,_,n2 = e2
+ if d>0.0:
+ h = system.addPointPlaneDistance(d,p1,w2,group=solver.group)
+ logger.debug('{}: point plane distance {},{},{}'.format(
+ cstrName(obj),h,p1,w2,d))
+ else:
+ h = system.addPointInPlane(p1,w2,group=solver.group)
+ logger.debug('{}: point in plane {},{}'.format(
+ cstrName(obj),h,p1,w2))
+ h = system.addParallel(n1,n2,group=solver.group)
+ logger.debug('{}: parallel {},{},{}'.format(cstrName(obj),h,n1,n2))
+
+
+class AxialAlignment(BaseMulti):
+ _id = 36
+ _iconName = 'Assembly_ConstraintAxial.svg'
+
+ @classmethod
+ def prepareElements(cls,obj,solver,e1,e2):
+ system = solver.system
+ _,p1,n1 = e1
+ w2,p2,n2 = e2
+ h = system.addPointsCoincident(p1,p2,w2,group=solver.group)
+ logger.debug('{}: points coincident {},{},{},{}'.format(
+ cstrName(obj),h,p1,p2,w2))
+ h = system.addParallel(n1,n2,group=solver.group)
+ logger.debug('{}: parallel {},{},{}'.format(cstrName(obj),h,n1,n2))
+
+
+class SameOrientation(BaseMulti):
+ _id = 2
+ _entityDef = [_n]
+ _iconName = 'Assembly_ConstraintOrientation.svg'
+
+ @classmethod
+ def prepareElements(cls,obj,solver,n1,n2):
+ h = solver.system.addSameOrientation(n1,n2,group=solver.group)
+ logger.debug('{}: {} {},{},{}'.format(
+ cstrName(obj),cls.getName(),h,n1,n2))
+
+
+class Angle(Base):
+ _id = 27
+ _entityDef = (_ln,_ln)
+ _workplane = True
+ _props = ["Angle","Supplement"]
+ _iconName = 'Assembly_ConstraintAngle.svg'
+
+
+class Perpendicular(Base):
+ _id = 28
+ _entityDef = (_ln,_ln)
+ _workplane = True
+ _iconName = 'Assembly_ConstraintPerpendicular.svg'
+
+
+class Parallel(Base):
+ _id = 29
+ _entityDef = (_ln,_ln)
+ _workplane = True
+ _iconName = 'Assembly_ConstraintParallel.svg'
+
+
+class MultiParallel(BaseMulti):
+ _id = 291
+ _entityDef = [_ln]
+ _iconName = 'Assembly_ConstraintMultiParallel.svg'
+
+ @classmethod
+ def prepareElements(cls,obj,solver,e1,e2):
+ h = solver.system.addParallel(e1,e2,group=solver.group)
+ logger.debug('{}: {} {},{},{}'.format(
+ cstrName(obj),cls.getName(),h,e1,e2))
+
+
class PointsCoincident(Base):
_id = 1
- _entities = (_p,_p)
+ _entityDef = (_p,_p)
_workplane = True
- _iconName = 'Assembly_ConstraintCoincidence.svg'
-
-
-class SameOrientation(Base):
- _id = 2
- _entities = (_n,_n)
- _iconName = 'Assembly_ConstraintOrientation.svg'
class PointInPlane(Base):
_id = 3
- _entities = (_p,_w)
+ _entityDef = (_p,_w)
class PointOnLine(Base):
_id = 4
- _entities = (_p,_l)
+ _entityDef = (_p,_l)
_workplane = True
class PointsDistance(Base):
_id = 5
- _entities = (_p,_p)
+ _entityDef = (_p,_p)
_workplane = True
- _props = [_PropertyDistance]
+ _props = ["Distance"]
class PointsProjectDistance(Base):
_id = 6
- _entities = (_p,_p,_l)
- _props = [_PropertyDistance]
+ _entityDef = (_p,_p,_l)
+ _props = ["Distance"]
class PointPlaneDistance(Base):
_id = 7
- _entities = (_p,_w)
- _props = [_PropertyDistance]
+ _entityDef = (_p,_w)
+ _props = ["Distance"]
class PointLineDistance(Base):
_id = 8
- _entities = (_p,_l)
+ _entityDef = (_p,_l)
_workplane = True
- _props = [_PropertyDistance]
+ _props = ["Distance"]
class EqualLength(Base):
_id = 9
- _entities = (_l,_l)
+ _entityDef = (_l,_l)
_workplane = True
class LengthRatio(Base):
_id = 10
- _entities = (_l,_l)
+ _entityDef = (_l,_l)
_workplane = True
- _props = [_PropertyRatio]
+ _props = ["Ratio"]
class LengthDifference(Base):
_id = 11
- _entities = (_l,_l)
+ _entityDef = (_l,_l)
_workplane = True
- _props = [_PropertyDifference]
+ _props = ["Difference"]
class EqualLengthPointLineDistance(Base):
_id = 12
- _entities = (_p,_l,_l)
+ _entityDef = (_p,_l,_l)
_workplane = True
class EqualPointLineDistance(Base):
_id = 13
- _entities = (_p,_l,_p,_l)
+ _entityDef = (_p,_l,_p,_l)
_workplane = True
class EqualAngle(Base):
_id = 14
- _entities = (_l,_l,_l,_l)
+ _entityDef = (_l,_l,_l,_l)
_workplane = True
- _props = [_PropertySupplement]
+ _props = ["Supplement"]
class EqualLineArcLength(Base):
_id = 15
- _entities = (_l,_a)
+ _entityDef = (_l,_a)
_workplane = True
class Symmetric(Base):
_id = 16
- _entities = (_p,_p,_w)
+ _entityDef = (_p,_p,_w)
_workplane = True
class SymmetricHorizontal(Base):
_id = 17
- _entities = (_p,_p,_w)
+ _entityDef = (_p,_p,_w)
class SymmetricVertical(Base):
_id = 18
- _entities = (_p,_p,_w)
+ _entityDef = (_p,_p,_w)
class SymmetricLine(Base):
_id = 19
- _entities = (_p,_p,_l,_w)
+ _entityDef = (_p,_p,_l,_w)
class MidPoint(Base):
_id = 20
- _entities = (_p,_p,_l)
+ _entityDef = (_p,_p,_l)
_workplane = True
class PointsHorizontal(Base):
_id = 21
- _entities = (_p,_p)
+ _entityDef = (_p,_p)
_workplane = True
class PointsVertical(Base):
_id = 22
- _entities = (_p,_p)
+ _entityDef = (_p,_p)
_workplane = True
class LineHorizontal(Base):
_id = 23
- _entities = [_l]
+ _entityDef = [_l]
_workplane = True
class LineVertical(Base):
_id = 24
- _entities = [_l]
+ _entityDef = [_l]
_workplane = True
class Diameter(Base):
_id = 25
- _entities = [_c]
- _prop = [_PropertyDiameter]
+ _entityDef = [_c]
+ _prop = ["Diameter"]
class PointOnCircle(Base):
_id = 26
- _entities = [_p,_c]
-
-
-class Angle(Base):
- _id = 27
- _entities = (_l,_l)
- _workplane = True
- _props = [_PropertyAngle,_PropertySupplement]
-
-
-class Perpendicular(Base):
- _id = 28
- _entities = (_l,_l)
- _workplane = True
-
-
-class Parallel(Base):
- _id = 29
- _entities = (_l,_l)
- _workplane = True
+ _entityDef = [_p,_c]
class ArcLineTangent(Base):
_id = 30
- _entities = (_c,_l)
- _props = [_PropertyAtEnd]
+ _entityDef = (_c,_l)
+ _props = ["AtEnd"]
# class CubicLineTangent(Base):
@@ -491,13 +707,13 @@ class ArcLineTangent(Base):
class EqualRadius(Base):
_id = 33
- _entities = (_c,_c)
- _props = [_PropertyRadius]
+ _entityDef = (_c,_c)
+ _props = ["Radius"]
class WhereDragged(Base):
_id = 34
- _entities = [_p]
+ _entityDef = [_p]
_workplane = True
@@ -505,20 +721,12 @@ TypeEnum = namedtuple('AsmConstraintEnum',
(c.getName() for c in Types))(*range(len(Types)))
def attach(obj,checkType=True):
- props = None
if checkType:
- props = obj.PropertiesList
- # if not '_Type' in props:
- # raise RuntimeError('Object "{}" has no _Type property'.format(
- # objName(obj)))
- if 'Type' in props:
- raise RuntimeError('Object {} already as property "Type"'.format(
- objName(obj)))
-
- # The 'Type' property here is to let user select the type in property
- # editor. It is marked as 'transient' to avoid having to save the
- # enumeration value for each object.
- obj.addProperty("App::PropertyEnumeration","Type","Constraint",'',2)
+ if 'Type' not in obj.PropertiesList:
+ # The 'Type' property here is to let user select the type in
+ # property editor. It is marked as 'transient' to avoid having to
+ # save the enumeration value for each object.
+ obj.addProperty("App::PropertyEnumeration","Type","Base",'',2)
obj.Type = TypeEnum._fields
idx = 0
try:
@@ -531,20 +739,22 @@ def attach(obj,checkType=True):
constraintType = TypeNameMap[obj.Type]
cstr = getattr(obj.Proxy,'_cstr',None)
if type(cstr) is not constraintType:
+ logger.debug('attaching {}, {} -> {}'.format(
+ objName(obj),type(cstr).__name__,constraintType.__name__),frame=1)
if cstr:
cstr.detach(obj)
- if not props:
- props = obj.PropertiesList
- obj.Proxy._cstr = constraintType(obj,props)
+ obj.Proxy._cstr = constraintType(obj)
obj.ViewObject.signalChangeIcon()
def onChanged(obj,prop):
if prop == 'Type':
- attach(obj,False)
+ if hasattr(obj.Proxy,'_cstr'):
+ attach(obj,False)
return
elif prop == '_Type':
- obj.Type = TypeMap[obj._Type]._idx
+ if hasattr(obj,'Type'):
+ obj.Type = TypeMap[obj._Type]._idx
return
elif prop == 'Disabled':
obj.ViewObject.signalChangeIcon()
@@ -564,4 +774,6 @@ def isLocked(obj):
return not obj.Disabled and isinstance(obj.Proxy._cstr,Locked)
def getIcon(obj):
- return obj.Proxy._cstr.getIcon(obj)
+ cstr = getattr(obj.Proxy,'_cstr',None)
+ if cstr:
+ return cstr.getIcon(obj)
diff --git a/solver.py b/solver.py
index 188a5dc..c60e418 100644
--- a/solver.py
+++ b/solver.py
@@ -3,6 +3,7 @@ import asm3.slvs as slvs
import asm3.assembly as asm
from asm3.utils import logger, objName, isSamePlacement
import asm3.constraint as constraint
+import random
class AsmSolver(object):
def __init__(self,assembly,reportFailed):
@@ -26,6 +27,16 @@ class AsmSolver(object):
self._entityMap = {}
self._fixedParts = set()
+ self.system.GroupHandle = self._fixedGroup
+
+ # convenience constants
+ self.zero = self.system.addParamV(0)
+ self.one = self.system.addParamV(1)
+ self.o = self.system.addPoint3d(self.zero,self.zero,self.zero)
+ self.x = self.system.addPoint3d(self.one,self.zero,self.zero)
+ self.y = self.system.addPoint3d(self.zero,self.one,self.zero)
+ self.z = self.system.addPoint3d(self.zero,self.zero,self.one)
+
for cstr in cstrs:
if constraint.isLocked(cstr):
constraint.prepare(cstr,self)
@@ -35,15 +46,16 @@ class AsmSolver(object):
logger.debug('lock first part {}'.format(objName(parts[0])))
self._fixedParts.add(parts[0])
- self.system.GroupHandle = self._fixedGroup
for cstr in self._cstrs:
logger.debug('preparing {}, type {}'.format(
objName(cstr),cstr.Type))
self.system.GroupHandle += 1
handle = self.system.ConstraintHandle
constraint.prepare(cstr,self)
- for h in range(handle,self.system.ConstraintHandle):
- self._cstrMap[h+1] = cstr
+ handles = range(handle+1,self.system.ConstraintHandle+1)
+ for h in handles:
+ self._cstrMap[h] = cstr
+ logger.debug('{} handles: {}'.format(objName(cstr),handles))
logger.debug('solving {}'.format(objName(assembly)))
ret = self.system.solve(group=self.group,reportFailed=reportFailed)
@@ -64,13 +76,15 @@ class AsmSolver(object):
objName(cstr),cstr.Type,h)
logger.error(msg)
if ret==1:
- reason = 'redundent constraints'
+ reason = 'inconsistent constraints'
elif ret==2:
reason = 'not converging'
elif ret==3:
reason = 'too many unknowns'
elif ret==4:
reason = 'init failed'
+ elif ret==5:
+ reason = 'redundent constraints'
else:
reason = 'unknown failure'
raise RuntimeError('Failed to solve {}: {}'.format(
@@ -96,6 +110,9 @@ class AsmSolver(object):
for doc in undoDocs:
doc.commitTransaction()
+ def isFixedPart(self,info):
+ return info.Part in self._fixedParts
+
def addFixedPart(self,info):
logger.debug('lock part ' + info.PartName)
self._fixedParts.add(info.Part)
@@ -127,12 +144,13 @@ class AsmSolver(object):
entityMap = {}
self._entityMap[info.Object] = entityMap
+ pla = info.Placement
if info.Part in self._fixedParts:
g = self._fixedGroup
else:
g = self.group
- q = info.Placement.Rotation.Q
- vals = list(info.Placement.Base) + [q[3],q[0],q[1],q[2]]
+ q = pla.Rotation.Q
+ vals = list(pla.Base) + [q[3],q[0],q[1],q[2]]
params = [self.system.addParamV(v,g) for v in vals]
p = self.system.addPoint3d(*params[:3],group=g)
@@ -140,11 +158,16 @@ class AsmSolver(object):
w = self.system.addWorkplane(p,n,group=g)
h = (w,p,n)
- logger.debug('{} {}, {}, {}, {}'.format(
- info.PartName,info.Placement,h,params,vals))
+ rparams = [self.zero]*3 + params[3:]
+ x = self.system.addTransform(self.x,*rparams,group=g)
+ y = self.system.addTransform(self.y,*rparams,group=g)
+ z = self.system.addTransform(self.z,*rparams,group=g)
+
+ partInfo = constraint.PartInfo(info.PartName, info.Placement.copy(),
+ params,rparams,h,entityMap,g,x,y,z)
+
+ logger.debug('{}'.format(partInfo))
- partInfo = constraint.PartInfo(
- info.PartName, info.Placement.copy(),params,h,entityMap,g)
self._partMap[info.Part] = partInfo
return partInfo
diff --git a/utils.py b/utils.py
index 8b8de1a..a5602ac 100644
--- a/utils.py
+++ b/utils.py
@@ -33,19 +33,19 @@ def getElement(obj,tp):
if isinstance(obj,tp):
return obj
-def isPlane(obj):
- face = getElement(obj,Part.Face)
- if not face:
+def isPlanar(obj):
+ shape = getElement(obj,(Part.Face,Part.Edge))
+ if not shape:
return False
- elif str(face.Surface) == '':
+ elif str(shape.Surface) == '':
return True
- elif hasattr(face.Surface,'Radius'):
+ elif hasattr(shape.Surface,'Radius'):
return False
- elif str(face.Surface).startswith('