from collections import namedtuple import FreeCAD, FreeCADGui import asm3.utils as utils import asm3.slvs as slvs from asm3.utils import logger, objName Types = [] TypeMap = {} TypeNameMap = {} class ConstraintType(type): def __init__(cls, name, bases, attrs): super(ConstraintType,cls).__init__(name,bases,attrs) if cls._id >= 0: 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) # PartName: text name of the part # Placement: the original placement of the part # Params: 7 parameters that defines the transformation # 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 PartInfo = namedtuple('SolverPartInfo', ('PartName','Placement','Params','Workplane','EntityMap')) 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) logger.debug('{}: {},{}, {}'.format(key,h,e,v)) partInfo.EntityMap[key] = h return h def _p(system,partInfo,key,shape): 'return a slvs handle of a transformed point derived from "shape"' if not system: if utils.hasCenter(shape): return return 'a vertex or circular edge/face' return _addEntity('p',system,partInfo,key,shape) def _n(system,partInfo,key,shape): 'return a slvs handle of a transformed normal derived from "shape"' if not system: if utils.isAxisOfPlane(shape): return return 'an edge or face with a surface normal' return _addEntity('n',system,partInfo,key,shape) def _l(system,partInfo,key,shape,retAll=False): 'return a pair of slvs handle of the end points of an edge in "shape"' if not system: if utils.isLinearEdge(shape): return return 'a linear edge' key += '.l' h = partInfo.EntityMap.get(key,None) if h: logger.debug('cache {}: {}'.format(key,h)) else: v = shape.Edges[0].Vertexes p1 = system.addPoint3dV(*v[0].Point) p2 = system.addPoint3dV(*v[-1].Point) h = system.addLine(p1,p2) h = (h,p1,p2) logger.debug('{}: {}'.format(key,h)) 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): return return 'an edge or 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) h = (h,p,n) logger.debug('{}: {}'.format(key,h)) partInfo.EntityMap[key2] = h return h if retAll else h[0] def _c(system,partInfo,key,shape,requireArc=False): 'return a slvs handle of a transformed circle/arc derived from "shape"' if not system: r = utils.getElementCircular(shape) if not r or (requireArc and not isinstance(r,list,tuple)): return return 'an cicular arc edge' if requireArc else 'a circular edge' key2 = key+'.c' h = partInfo.EntityMap.get(key2,None) if h: logger.debug('cache {}: {}'.format(key,h)) else: h = _w(system,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) h += l[1:] h = system.addArcOfCircleV(*h) elif requireArc: raise RuntimeError('shape is not an arc') else: h = h[1:] h.append(system.addDistanceV(r)) h = system.addCircle(*h) logger.debug('{}: {}, {}'.format(key,h,r)) partInfo.EntityMap[key2] = h return h def _a(system,partInfo,key,shape): return _c(system,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 = [] _workplane = False _props = [] _func = None @classmethod def getName(cls): return cls.__name__ @classmethod def slvsFunc(cls): try: if not cls._func: cls._func = getattr(slvs.System,'add'+cls.getName()) return cls._func except AttributeError: logger.error('Invalid slvs constraint "{}"'.format(cls.getName())) @classmethod def getEntityDef(cls,group,checkCount,name=None): entities = cls._entities if len(group) != len(entities): if not checkCount and len(group)