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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + [jmaustpc] + + + Assembly_ConstraintOpposite + 2013-12-24 + http://www.freecadweb.org/wiki/index.php?title=Artwork + + + FreeCAD + + + FreeCAD/src/Mod/Assembly/Gui/Resources/icons/constraints/Assembly_ConstraintOpposite.svg + + + FreeCAD LGPL2+ + + + https://www.gnu.org/copyleft/lesser.html + + + [agryson] Alexander Gryson + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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('