FreeCAD_assembly3/gui.py

691 lines
21 KiB
Python

from collections import OrderedDict
import FreeCAD, FreeCADGui
from .deps import with_metaclass
from .utils import getElementPos,objName,addIconToFCAD,guilogger as logger
from .proxy import ProxyType
from .FCADLogger import FCADLogger
class SelectionObserver:
def __init__(self):
self._attached = False
self.cmds = []
self.elements = dict()
self.attach()
def setCommands(self,cmds):
self.cmds = cmds
def _setElementVisible(self,obj,subname,vis):
sobj = obj.getSubObject(subname,1)
from .assembly import isTypeOf,AsmConstraint,\
AsmElement,AsmElementLink
if isTypeOf(sobj,(AsmElement,AsmElementLink)):
res = sobj.Proxy.parent.Object.isElementVisible(sobj.Name)
if res and vis:
return False
sobj.Proxy.parent.Object.setElementVisible(sobj.Name,vis)
elif isTypeOf(sobj,AsmConstraint):
vis = [vis] * len(sobj.Group)
sobj.setPropertyStatus('VisibilityList','-Immutable')
sobj.VisibilityList = vis
sobj.setPropertyStatus('VisibilityList','Immutable')
else:
return
if vis:
FreeCADGui.Selection.updateSelection(vis,obj,subname)
def setElementVisible(self,docname,objname,subname,vis,presel=False):
if not AsmCmdManager.AutoElementVis:
self.elements.clear()
return
doc = FreeCAD.getDocument(docname)
if not doc:
return
obj = doc.getObject(objname)
if not obj:
return
key = (docname,objname,subname)
val = None
if not vis:
val = self.elements.get(key,None)
if val is None or (presel and val):
return
if logger.catchWarn('',self._setElementVisible,
obj,subname,vis) is False and presel:
return
if not vis:
self.elements.pop(key,None)
elif not presel:
self.elements[key] = True
else:
self.elements.setdefault(key,False)
def resetElementVisible(self):
elements = list(self.elements)
self.elements.clear()
for docname,objname,subname in elements:
doc = FreeCAD.getDocument(docname)
if not doc:
continue
obj = doc.getObject(objname)
if not obj:
continue
logger.catchWarn('',self._setElementVisible,obj,subname,False)
def onChange(self,hasSelection=True):
for cmd in self.cmds:
cmd.onSelectionChange(hasSelection)
def addSelection(self,docname,objname,subname,_pos):
self.onChange()
self.setElementVisible(docname,objname,subname,True)
def removeSelection(self,docname,objname,subname):
self.onChange(FreeCADGui.Selection.hasSelection())
self.setElementVisible(docname,objname,subname,False)
def setPreselection(self,docname,objname,subname):
self.setElementVisible(docname,objname,subname,True,True)
def removePreselection(self,docname,objname,subname):
self.setElementVisible(docname,objname,subname,False,True)
def setSelection(self,*_args):
self.onChange()
if AsmCmdManager.AutoElementVis:
self.resetElementVisible()
for sel in FreeCADGui.Selection.getSelectionEx('*',False):
for sub in sel.SubElementNames:
self.setElementVisible(sel.Object.Document.Name,
sel.Object.Name,sub,True)
def clearSelection(self,*_args):
self.onChange(False)
self.resetElementVisible()
def attach(self):
logger.trace('attach selection aboserver {}'.format(self._attached))
if not self._attached:
FreeCADGui.Selection.addObserver(self,False)
self._attached = True
def detach(self):
logger.trace('detach selection aboserver {}'.format(self._attached))
if self._attached:
FreeCADGui.Selection.removeObserver(self)
self._attached = False
self.clearSelection('')
class AsmCmdManager(ProxyType):
_HiddenToolbars = set()
Toolbars = OrderedDict()
Menus = OrderedDict()
_defaultMenuGroupName = '&Assembly3'
@staticmethod
def getToolbarParams():
return FreeCAD.ParamGet('User parameter:BaseApp/MainWindow/Toolbars')
@classmethod
def init(mcs):
if not mcs.getParam('Bool','GuiInited',False):
hgrp = mcs.getToolbarParams()
for toolbar in mcs._HiddenToolbars:
hgrp.SetBool(toolbar,False)
mcs.setParam('Bool','GuiInited',True)
@classmethod
def toggleToolbars(mcs):
hgrp = mcs.getToolbarParams()
show = False
for toolbar in mcs._HiddenToolbars:
if not hgrp.GetBool(toolbar,True):
show = True
break
from PySide import QtGui
mw = FreeCADGui.getMainWindow()
for toolbar in mcs._HiddenToolbars:
if show != hgrp.GetBool(toolbar,True):
hgrp.SetBool(toolbar,show)
tb = mw.findChild(QtGui.QToolBar,toolbar)
if not tb:
logger.error('cannot find toolbar "{}"'.format(toolbar))
tb.setVisible(show)
@classmethod
def register(mcs,cls):
if cls._id < 0:
return
super(AsmCmdManager,mcs).register(cls)
FreeCADGui.addCommand(cls.getName(),cls)
if cls._toolbarName:
tb = mcs.Toolbars.setdefault(cls._toolbarName,[])
if not tb and not getattr(cls,'_toolbarVisible',True):
mcs._HiddenToolbars.add(cls._toolbarName)
tb.append(cls)
if cls._menuGroupName is not None:
name = cls._menuGroupName
if not name:
name = mcs._defaultMenuGroupName
mcs.Menus.setdefault(name,[]).append(cls)
@classmethod
def getParamGroup(mcs):
return FreeCAD.ParamGet(
'User parameter:BaseApp/Preferences/Mod/Assembly3')
@classmethod
def getParam(mcs,tp,name,default=None):
return getattr(mcs.getParamGroup(),'Get'+tp)(name,default)
@classmethod
def setParam(mcs,tp,name,v):
getattr(mcs.getParamGroup(),'Set'+tp)(name,v)
def workbenchActivated(cls):
pass
def workbenchDeactivated(cls):
pass
def getContextMenuName(cls):
if cls.IsActive() and cls._contextMenuName:
return cls._contextMenuName
def getName(cls):
return 'asm3'+cls.__name__[3:]
def getMenuText(cls):
return cls._menuText
def getToolTip(cls):
return getattr(cls,'_tooltip',cls.getMenuText())
def IsActive(cls):
if cls._id<0 or not FreeCAD.ActiveDocument:
return False
if cls._active is None:
cls.checkActive()
return cls._active
def onSelectionChange(cls, hasSelection):
_ = hasSelection
class AsmCmdBase(with_metaclass(AsmCmdManager, object)):
_id = -1
_active = None
_toolbarName = 'Assembly3'
_menuGroupName = ''
_contextMenuName = 'Assembly'
_accel = None
@classmethod
def checkActive(cls):
cls._active = True
@classmethod
def GetResources(cls):
ret = {
'Pixmap':addIconToFCAD(cls._iconName),
'MenuText':cls.getMenuText(),
'ToolTip':cls.getToolTip()
}
if cls._accel:
ret['Accel'] = cls._accel
return ret
class AsmCmdNew(AsmCmdBase):
_id = 0
_menuText = 'Create assembly'
_iconName = 'Assembly_New_Assembly.svg'
_accel = 'A, N'
@classmethod
def Activated(cls):
from . import assembly
assembly.Assembly.make()
class AsmCmdSolve(AsmCmdBase):
_id = 1
_menuText = 'Solve constraints'
_iconName = 'AssemblyWorkbench.svg'
_accel = 'A, S'
@classmethod
def Activated(cls):
from . import solver
FreeCAD.setActiveTransaction('Assembly solve')
logger.report('command "{}" exception'.format(cls.getName()),
solver.solve,reportFailed=True)
FreeCAD.closeActiveTransaction()
class AsmCmdMove(AsmCmdBase):
_id = 2
_menuText = 'Move part'
_iconName = 'Assembly_Move.svg'
_accel = 'A, M'
_moveInfo = None
@classmethod
def Activated(cls):
from . import mover
mover.movePart(True,cls._moveInfo)
@classmethod
def canMove(cls):
from . import mover
cls._moveInfo = None
cls._moveInfo = mover.getMovingElementInfo()
mover.checkFixedPart(cls._moveInfo.ElementInfo)
return True
@classmethod
def checkActive(cls):
cls._active = True if logger.catchTrace('',cls.canMove) else False
@classmethod
def onSelectionChange(cls,hasSelection):
if not hasSelection:
cls._active = False
else:
cls._active = None
cls._moveInfo = None
class AsmCmdAxialMove(AsmCmdBase):
_id = 3
_menuText = 'Axial move part'
_iconName = 'Assembly_AxialMove.svg'
_useCenterballDragger = False
_accel = 'A, A'
@classmethod
def IsActive(cls):
return AsmCmdMove.IsActive()
@classmethod
def Activated(cls):
from . import mover
mover.movePart(False,AsmCmdMove._moveInfo)
class AsmCmdQuickMove(AsmCmdAxialMove):
_id = 13
_menuText = 'Quick move'
_tooltip = 'Bring an object contained in an assembly to where the mouse\n'\
'is located. This is designed to help bringing an object far\n'\
'away quickly into view.'
_iconName = 'Assembly_QuickMove.svg'
_accel = 'A, Q'
@classmethod
def Activated(cls):
from . import mover
mover.quickMove()
class AsmCmdCheckable(AsmCmdBase):
_id = -2
_saveParam = False
_defaultValue = False
@classmethod
def getAttributeName(cls):
return cls.__name__[6:]
@classmethod
def getChecked(cls):
return getattr(cls.__class__,cls.getAttributeName())
@classmethod
def setChecked(cls,v):
setattr(cls.__class__,cls.getAttributeName(),v)
if cls._saveParam:
cls.setParam('Bool',cls.getAttributeName(),v)
@classmethod
def onRegister(cls):
if cls._saveParam:
v = cls.getParam('Bool',cls.getAttributeName(),cls._defaultValue)
else:
v = False
cls.setChecked(v)
@classmethod
def GetResources(cls):
ret = super(AsmCmdCheckable,cls).GetResources()
ret['Checkable'] = cls.getChecked()
return ret
@classmethod
def Activated(cls,checked):
cls.setChecked(True if checked else False)
class AsmCmdLockMover(AsmCmdCheckable):
_id = 15
_menuText = 'Lock mover'
_tooltip = 'Lock mover for fixed part'
_iconName = 'Assembly_LockMover.svg'
_saveParam = True
@classmethod
def Activated(cls,checked):
super(AsmCmdLockMover,cls).Activated(checked)
AsmCmdMove._active = None
AsmCmdAxialMove._active = None
AsmCmdQuickMove._active = None
class AsmCmdToggleVisibility(AsmCmdBase):
_id = 17
_menuText = 'Toggle part visibility'
_tooltip = 'Toggle the visibility of the selected part'
_iconName = 'Assembly_TogglePartVisibility.svg'
_accel = 'A, Space'
@classmethod
def Activated(cls):
moveInfo = AsmCmdMove._moveInfo
if not moveInfo:
return
info = moveInfo.ElementInfo
if info.Subname:
subs = moveInfo.SelSubname[:-len(info.Subname)]
else:
subs = moveInfo.SelSubname
subs = subs.split('.')
if isinstance(info.Part,tuple):
part = info.Part[0]
vis = part.isElementVisible(str(info.Part[1]))
part.setElementVisible(str(info.Part[1]),not vis)
else:
from .assembly import resolveAssembly
partGroup = resolveAssembly(info.Parent).getPartGroup()
vis = partGroup.isElementVisible(info.Part.Name)
partGroup.setElementVisible(info.Part.Name,not vis)
FreeCADGui.Selection.clearSelection()
FreeCADGui.Selection.addSelection(moveInfo.SelObj,'.'.join(subs))
FreeCADGui.runCommand('Std_TreeSelection')
if vis:
FreeCADGui.runCommand('Std_TreeCollapse')
@classmethod
def IsActive(cls):
return AsmCmdMove._moveInfo is not None
class AsmCmdTrace(AsmCmdCheckable):
_id = 4
_menuText = 'Trace part move'
_iconName = 'Assembly_Trace.svg'
_object = None
_subname = None
@classmethod
def Activated(cls,checked):
super(AsmCmdTrace,cls).Activated(checked)
if not checked:
cls._object = None
return
sel = FreeCADGui.Selection.getSelectionEx('',False)
if len(sel)==1:
subs = sel[0].SubElementNames
if len(subs)==1:
cls._object = sel[0].Object
cls._subname = subs[0]
logger.info('trace {}.{}'.format(
cls._object.Name,cls._subname))
return
logger.info('trace moving element')
@classmethod
def getPosition(cls):
if not cls._object:
return
try:
if cls._object.Document != FreeCAD.ActiveDocument:
cls._object = None
return getElementPos((cls._object,cls._subname))
except Exception:
cls._object = None
class AsmCmdAutoRecompute(AsmCmdCheckable):
_id = 5
_menuText = 'Auto recompute'
_iconName = 'Assembly_AutoRecompute.svg'
_saveParam = True
class AsmCmdAutoElementVis(AsmCmdCheckable):
_id = 9
_menuText = 'Auto element visibility'
_iconName = 'Assembly_AutoElementVis.svg'
_saveParam = True
_defaultValue = True
@classmethod
def Activated(cls,checked):
super(AsmCmdAutoElementVis,cls).Activated(checked)
from .assembly import isTypeOf,AsmConstraint,\
AsmElement,AsmElementLink,AsmElementGroup
for doc in FreeCAD.listDocuments().values():
for obj in doc.Objects:
if isTypeOf(obj,(AsmConstraint,AsmElementGroup)):
obj.Visibility = False
if isTypeOf(obj,AsmConstraint):
obj.ViewObject.OnTopWhenSelected = 2
obj.setPropertyStatus('VisibilityList',
'NoModify' if checked else '-NoModify')
elif isTypeOf(obj,(AsmElementLink,AsmElement)):
if checked:
obj.Proxy.parent.Object.setElementVisible(
obj.Name,False)
obj.Visibility = False
obj.ViewObject.OnTopWhenSelected = 2
class AsmCmdAddWorkplane(AsmCmdBase):
_id = 8
_menuText = 'Add workplane'
_iconName = 'Assembly_Add_Workplane.svg'
_toolbarName = None
_menuGroupName = None
_accel = 'A, P'
_makeType = 0
@classmethod
def checkActive(cls):
from . import assembly
if logger.catchTrace('Add workplane selection',
assembly.AsmWorkPlane.getSelection):
cls._active = True
else:
cls._active = False
@classmethod
def onSelectionChange(cls,hasSelection):
cls._active = None if hasSelection else False
@classmethod
def Activated(cls,idx=0):
_ = idx
from . import assembly
assembly.AsmWorkPlane.make(tp=cls._makeType)
class AsmCmdAddWorkplaneXZ(AsmCmdAddWorkplane):
_id = 10
_menuText = 'Add XZ workplane'
_iconName = 'Assembly_Add_WorkplaneXZ.svg'
_makeType = 1
class AsmCmdAddWorkplaneZY(AsmCmdAddWorkplane):
_id = 11
_menuText = 'Add ZY workplane'
_iconName = 'Assembly_Add_WorkplaneZY.svg'
_makeType = 2
class AsmCmdAddOrigin(AsmCmdAddWorkplane):
_id = 14
_menuText = 'Add Origin'
_iconName = 'Assembly_Add_Origin.svg'
_makeType = 3
_accel = 'A, O'
class AsmCmdAddWorkplaneGroup(AsmCmdAddWorkplane):
_id = 12
_menuGroupName = ''
_toolbarName = AsmCmdBase._toolbarName
_cmds = (AsmCmdAddWorkplane.getName(),
AsmCmdAddWorkplaneXZ.getName(),
AsmCmdAddWorkplaneZY.getName(),
AsmCmdAddOrigin.getName())
@classmethod
def GetCommands(cls):
return cls._cmds
@classmethod
def Activated(cls,idx=0):
FreeCADGui.runCommand(cls._cmds[idx])
class AsmCmdGotoRelation(AsmCmdBase):
_id = 16
_menuText = 'Go to relation'
_tooltip = 'Select the corresponding part object in the relation group'
_iconName = 'Assembly_GotoRelation.svg'
_accel = 'A, R'
_toolbarName = ''
@classmethod
def Activated(cls):
from .assembly import AsmRelationGroup
if AsmCmdMove._moveInfo:
AsmRelationGroup.gotoRelation(AsmCmdMove._moveInfo)
return
sels = FreeCADGui.Selection.getSelectionEx('',0,True)
if sels and len(sels[0].SubElementNames)==1:
AsmRelationGroup.gotoRelationOfConstraint(
sels[0].Object,sels[0].SubElementNames[0])
@classmethod
def IsActive(cls):
if AsmCmdMove._moveInfo:
return True
if cls._active is None:
cls.checkActive()
return cls._active
@classmethod
def checkActive(cls):
from .assembly import isTypeOf, AsmConstraint, AsmElementLink
sels = FreeCADGui.Selection.getSelection('',1,True)
if sels and isTypeOf(sels[0],(AsmConstraint,AsmElementLink)):
cls._active = True
else:
cls._active = False
@classmethod
def onSelectionChange(cls,hasSelection):
cls._active = None if hasSelection else False
class AsmCmdUp(AsmCmdBase):
_id = 6
_menuText = 'Move item up'
_iconName = 'Assembly_TreeItemUp.svg'
@classmethod
def getSelection(cls):
from .assembly import isTypeOf, Assembly, AsmGroup
sels = FreeCADGui.Selection.getSelectionEx('',False)
if len(sels)!=1 or len(sels[0].SubElementNames)!=1:
return
ret= sels[0].Object.resolve(sels[0].SubElementNames[0])
obj,parent = ret[0],ret[1]
if isTypeOf(parent,Assembly) or not isTypeOf(parent,AsmGroup) or \
len(parent.Group) <= 1:
return
return (obj,parent,sels[0].Object,sels[0].SubElementNames[0])
@classmethod
def checkActive(cls):
cls._active = True if cls.getSelection() else False
@classmethod
def move(cls,step):
ret = cls.getSelection()
if not ret:
return
obj,parent,topParent,subname = ret
children = parent.Group
i = children.index(obj)
j = i+step
if j<0:
j = len(children)-1
elif j>=len(children):
j = 0
logger.debug('move {}:{} -> {}:{}'.format(
i,objName(obj),j,objName(children[j])))
FreeCAD.setActiveTransaction(cls._menuText)
readonly = 'Immutable' in parent.getPropertyStatus('Group')
if readonly:
parent.setPropertyStatus('Group','-Immutable')
parent.Group = {i:children[j],j:obj}
if readonly:
parent.setPropertyStatus('Group','Immutable')
FreeCAD.closeActiveTransaction();
# The tree view may deselect the item because of claimChildren changes,
# so we restore the selection here
FreeCADGui.Selection.addSelection(topParent,subname)
@classmethod
def onSelectionChange(cls,hasSelection):
cls._active = None if hasSelection else False
@classmethod
def Activated(cls):
cls.move(-1)
class AsmCmdDown(AsmCmdUp):
_id = 7
_menuText = 'Move item down'
_iconName = 'Assembly_TreeItemDown.svg'
@classmethod
def Activated(cls):
cls.move(1)
class ASmCmdMultiply(AsmCmdBase):
_id = 18
_menuText = 'Multiply constraint'
_tooltip = 'Mutiply the part owner of the first element to constrain\n'\
'against the rest of the elements.\n\n'\
'To activate this function, the FIRST part must be of the\n'\
'FIRST element of a link array. In will optionally expand\n'\
'colplanar circular edges with the same radius in the second\n'\
'element on wards. To disable auto expansion, use NoExpand\n'\
'property in the element link.'
_iconName = 'Assembly_ConstraintMultiply.svg'
@classmethod
def checkActive(cls):
from .assembly import AsmConstraint
if logger.catchTrace('',AsmConstraint.makeMultiply,True):
cls._active = True
else:
cls._active = False
@classmethod
def Activated(cls):
from .assembly import AsmConstraint
logger.report('',AsmConstraint.makeMultiply)
@classmethod
def onSelectionChange(cls,hasSelection):
cls._active = None if hasSelection else False