from future.utils import with_metaclass from collections import namedtuple import FreeCAD, FreeCADGui import asm3.utils as utils from asm3.gui import AsmCmdManager from asm3.utils import logger, objName from asm3.proxy import ProxyType, PropertyInfo, propGet, propGetValue import os _iconPath = os.path.join(utils.iconPath,'constraints') def _p(solver,partInfo,subname,shape): 'return a handle of a transformed point derived from "shape"' if not solver: if utils.hasCenter(shape): return return 'a vertex or circular edge/face' key = subname+'.p' h = partInfo.EntityMap.get(key,None) system = solver.system if h: system.log('cache {}: {}'.format(key,h)) else: v = utils.getElementPos(shape) system.NameTag = subname e = system.addPoint3dV(*v) system.NameTag = partInfo.PartName h = system.addTransform(e,*partInfo.Params,group=partInfo.Group) system.log('{}: {},{}'.format(key,h,partInfo.Group)) partInfo.EntityMap[key] = h return h def _n(solver,partInfo,subname,shape): 'return a 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' key = subname+'.n' h = partInfo.EntityMap.get(key,None) system = solver.system if h: system.log('cache {}: {}'.format(key,h)) else: system.NameTag = subname e = system.addNormal3dV(*utils.getElementNormal(shape)) system.NameTag = partInfo.PartName h = system.addTransform(e,*partInfo.Params,group=partInfo.Group) system.log('{}: {},{}'.format(key,h,partInfo.Group)) partInfo.EntityMap[key] = h return h def _l(solver,partInfo,subname,shape,retAll=False): 'return a pair of handle of the end points of an edge in "shape"' if not solver: if utils.isLinearEdge(shape): return return 'a linear edge' key = subname+'.l' h = partInfo.EntityMap.get(key,None) system = solver.system if h: system.log('cache {}: {}'.format(key,h)) else: system.NameTag = subname v = shape.Edges[0].Vertexes p1 = system.addPoint3dV(*v[0].Point) p2 = system.addPoint3dV(*v[-1].Point) system.NameTag = partInfo.PartName tp1 = system.addTransform(p1,*partInfo.Params,group=partInfo.Group) tp2 = system.addTransform(p2,*partInfo.Params,group=partInfo.Group) h = system.addLineSegment(tp1,tp2,group=partInfo.Group) h = (h,tp1,tp2,p1,p2) system.log('{}: {},{}'.format(key,h,partInfo.Group)) partInfo.EntityMap[key] = h return h if retAll else h[0] 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: if utils.isLinearEdge(shape) or utils.isPlanar(shape): return return 'a linear edge or edge/face with planar surface' if utils.isLinearEdge(shape): return _l(solver,partInfo,subname,shape,retAll) return _n(solver,partInfo,subname,shape) def _w(solver,partInfo,subname,shape,retAll=False): 'return a handle of a transformed plane/workplane from "shape"' if not solver: if utils.isPlanar(shape): return return 'an edge/face with a planar surface' key = subname+'.w' h = partInfo.EntityMap.get(key,None) system = solver.system if h: system.log('cache {}: {}'.format(key,h)) else: p = _p(solver,partInfo,subname,shape) n = _n(solver,partInfo,subname,shape) system.NameTag = partInfo.PartName h = system.addWorkplane(p,n,group=partInfo.Group) h = (h,p,n) system.log('{}: {},{}'.format(key,h,partInfo.Group)) partInfo.EntityMap[key] = h return h if retAll else h[0] def _wa(solver,partInfo,subname,shape): return _w(solver,partInfo,subname,shape,True) def _c(solver,partInfo,subname,shape,requireArc=False): 'return a handle of a transformed circle/arc derived from "shape"' if not solver: 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' key = subname+'.c' h = partInfo.EntityMap.get(key,None) system = solver.system if h: system.log('cache {}: {}'.format(key,h)) else: h = [_w(solver,partInfo,subname,shape,False)] r = utils.getElementCircular(shape) if not r: raise RuntimeError('shape is not cicular') if isinstance(r,(list,tuple)): l = _l(solver,partInfo,subname,shape,True) h += l[1:] system.NameTag = partInfo.PartName h = system.addArcOfCircleV(*h,group=partInfo.Group) elif requireArc: raise RuntimeError('shape is not an arc') else: system.NameTag = partInfo.PartName h.append(solver.addDistanceV(r)) h = system.addCircle(*h,group=partInfo.Group) system.log('{}: {},{}'.format(key,h,partInfo.Group)) partInfo.EntityMap[key] = h return h def _a(solver,partInfo,subname,shape): return _c(solver,partInfo,subname,shape,True) class ConstraintCommand: _toolbarName = 'Assembly3 Constraints' _menuGroupName = '' def __init__(self,tp): self.tp = tp self._id = 100 + tp._id def workbenchActivated(self): pass def workbenchDeactivated(self): pass def getContextMenuName(self): pass def getName(self): return 'asm3Add'+self.tp.getName() def GetResources(self): return self.tp.GetResources() def Activated(self): from asm3.assembly import AsmConstraint AsmConstraint.make(self.tp._id) def IsActive(self): return FreeCADGui.ActiveDocument and self.tp._active def checkActive(self): from asm3.assembly import AsmConstraint try: AsmConstraint.getSelection(self.tp._id) except Exception as e: logger.trace('selection "{}" exception: {}'.format( self.tp.getName(),e.message),frame=1) self.tp._active = False else: self.tp._active = True def onClearSelection(self): self.tp._active = False class Constraint(ProxyType): 'constraint meta class' _typeID = '_ConstraintType' _typeEnum = 'ConstraintType' _disabled = 'Disabled' @classmethod def register(mcs,cls): super(Constraint,mcs).register(cls) if cls._menuItem: AsmCmdManager.register(ConstraintCommand(cls)) @classmethod def attach(mcs,obj,checkType=True): if checkType: if not mcs._disabled in obj.PropertiesList: obj.addProperty("App::PropertyBool",mcs._disabled,"Base",'') return super(Constraint,mcs).attach(obj,checkType) @classmethod def onChanged(mcs,obj,prop): if prop == mcs._disabled: obj.ViewObject.signalChangeIcon() return return super(Constraint,mcs).onChanged(obj,prop) @classmethod def isDisabled(mcs,obj): return getattr(obj,mcs._disabled,False) @classmethod def check(mcs,tp,group): mcs.getType(tp).check(group) @classmethod def prepare(mcs,obj,solver): return mcs.getProxy(obj).prepare(obj,solver) @classmethod def isLocked(mcs,obj): return isinstance(mcs.getProxy(obj),Locked) @classmethod def getIcon(mcs,obj): cstr = mcs.getProxy(obj) if cstr: return cstr.getIcon(obj) def _makeProp(name,tp,doc='',getter=propGet,internal=False): PropertyInfo(Constraint,name,tp,doc,getter=getter, group='Constraint',internal=internal) _makeProp('Distance','App::PropertyDistance',getter=propGetValue) _makeProp('Offset','App::PropertyDistance',getter=propGetValue) _makeProp('Cascade','App::PropertyBool',internal=True) _makeProp('Angle','App::PropertyAngle',getter=propGetValue) _makeProp('Ratio','App::PropertyFloat') _makeProp('Difference','App::PropertyFloat') _makeProp('Diameter','App::PropertyFloat') _makeProp('Radius','App::PropertyFloat') _makeProp('Supplement','App::PropertyBool', 'If True, then the second angle is calculated as 180-angle') _makeProp('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') def cstrName(obj): return '{}<{}>'.format(objName(obj),Constraint.getTypeName(obj)) class Base(with_metaclass(Constraint,object)): _id = -1 _entityDef = () _workplane = False _props = [] _iconName = 'Assembly_ConstraintGeneral.svg' _menuText = 'Create "{}" constraint' _active = False _menuItem = False def __init__(self,_obj): pass @classmethod def getPropertyInfoList(cls): return cls._props @classmethod def constraintFunc(cls,obj,solver): try: return getattr(solver.system,'add'+cls.getName()) except AttributeError: logger.warn('{} not supported in solver "{}"'.format( cstrName(obj),solver.getName())) @classmethod def getEntityDef(cls,group,checkCount,obj=None): entities = cls._entityDef if len(group) != len(entities): if not checkCount and len(group)