279 lines
11 KiB
Python
279 lines
11 KiB
Python
#/***************************************************************************
|
|
# * Copyright (c) Victor Titov (DeepSOIC) *
|
|
# * (vv.titov@gmail.com) 2018 *
|
|
# * *
|
|
# * This file is part of the FreeCAD CAx development system. *
|
|
# * *
|
|
# * This library is free software; you can redistribute it and/or *
|
|
# * modify it under the terms of the GNU Library General Public *
|
|
# * License as published by the Free Software Foundation; either *
|
|
# * version 2 of the License, or (at your option) any later version. *
|
|
# * *
|
|
# * This library is distributed in the hope that it will be useful, *
|
|
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
|
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
|
# * GNU Library General Public License for more details. *
|
|
# * *
|
|
# * You should have received a copy of the GNU Library General Public *
|
|
# * License along with this library; see the file COPYING.LIB. If not, *
|
|
# * write to the Free Software Foundation, Inc., 59 Temple Place, *
|
|
# * Suite 330, Boston, MA 02111-1307, USA *
|
|
# * *
|
|
# ***************************************************************************/
|
|
|
|
#This is a temporary replacement for C++-powered Container class that should be eventually introduced into FreeCAD
|
|
|
|
import FreeCAD as App
|
|
|
|
class Container(object):
|
|
"""Container class: a unified interface for container objects, such as Group, Part, Body, or Document.
|
|
This is a temporary implementation."""
|
|
Object = None #DocumentObject or Document, the actual container
|
|
|
|
def __init__(self, obj):
|
|
self.Object = obj
|
|
|
|
def self_check(self):
|
|
if self.Object is None:
|
|
raise ValueError("Null!")
|
|
if not isAContainer(self.Object):
|
|
raise NotAContainerError(self.Object)
|
|
|
|
def getAllChildren(self):
|
|
"""Returns all objects directly contained by the container. all = static + dynamic."""
|
|
return self.getStaticChildren() + self.getDynamicChildren()
|
|
|
|
def getStaticChildren(self):
|
|
"""Returns children tightly bound to the container, such as Origin. The key thing
|
|
about them is that they are not supposed to be removed or added from/to the container."""
|
|
|
|
self.self_check()
|
|
container = self.Object
|
|
if container.isDerivedFrom('App::Document'):
|
|
return []
|
|
elif container.hasExtension('App::OriginGroupExtension'):
|
|
if container.Origin is not None:
|
|
return [container.Origin]
|
|
else:
|
|
return []
|
|
elif container.isDerivedFrom('App::Origin'):
|
|
return container.OriginFeatures
|
|
elif container.hasExtension('App::GroupExtension'):
|
|
return []
|
|
raise RuntimeError("getStaticChildren: unexpected container type!")
|
|
|
|
def getDynamicChildren(self):
|
|
"""Returns dynamic children, i.e. the stuff that can be removed from the container."""
|
|
self.self_check()
|
|
container = self.Object
|
|
|
|
if container.isDerivedFrom('App::Document'):
|
|
# find all objects not contained by any Part or Body
|
|
result = set(container.Objects)
|
|
for obj in container.Objects:
|
|
if isAContainer(obj):
|
|
children = set(Container(obj).getAllChildren())
|
|
result = result - children
|
|
return list(result)
|
|
elif container.hasExtension('App::GroupExtension'):
|
|
result = container.Group
|
|
if container.hasExtension('App::GeoFeatureGroupExtension'):
|
|
#geofeaturegroup's group contains all objects within the CS, we don't want that
|
|
result = [obj for obj in result if obj.getParentGroup() is not container]
|
|
return result
|
|
elif container.isDerivedFrom('App::Origin'):
|
|
return []
|
|
raise RuntimeError("getDynamicChildren: unexpected container type!")
|
|
|
|
def isACS(self):
|
|
"""isACS(): returns true if the container forms internal coordinate system."""
|
|
self.self_check()
|
|
container = self.Object
|
|
|
|
if container.isDerivedFrom('App::Document'):
|
|
return True #Document is a special thing... is it a CS or not is a matter of coding convenience.
|
|
elif container.hasExtension('App::GeoFeatureGroupExtension'):
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
def isAVisGroup(self):
|
|
"""isAVisGroup(): returns True if the container consumes viewproviders of children, and thus affects their visibility."""
|
|
self.self_check()
|
|
container = self.Object
|
|
|
|
if container.isDerivedFrom('App::Document'):
|
|
return True #Document is a special thing... Return value is a matter of coding convenience.
|
|
elif container.hasExtension('App::GeoFeatureGroupExtension'):
|
|
return True
|
|
elif container.isDerivedFrom('App::Origin'):
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
def getCSChildren(self):
|
|
if not self.isACS():
|
|
raise TypeError("Container is not a coordinate system")
|
|
container = self.Object
|
|
return _getMetacontainerChildren(self, Container.isACS)
|
|
|
|
def getVisGroupChildren(self):
|
|
if not self.isAVisGroup():
|
|
raise TypeError("Container is not a visibility group")
|
|
container = self.Object
|
|
return _getMetacontainerChildren(self, Container.isAVisGroup)
|
|
|
|
def hasObject(self, obj):
|
|
"""Returns True if the container contains specified object directly."""
|
|
return obj in self.getAllChildren()
|
|
|
|
def hasObjectRecursive(self, obj):
|
|
return self.Object in ContainerChain(obj)
|
|
|
|
def Placement(self):
|
|
if self.isACS():
|
|
if hasattr(self.Object, 'Placement'):
|
|
return self.Object.Placement
|
|
return App.Placement()
|
|
|
|
def getFullTransform(self):
|
|
"""getFullTransform(): returns Placement that converts coordinates of objects in this container to global CS."""
|
|
chain = ContainerChain(self.Object) + [self.Object]
|
|
plm = App.Placement()
|
|
for cnt in chain:
|
|
plm = plm.multiply(Container(cnt).Placement())
|
|
return plm
|
|
|
|
def _getMetacontainerChildren(container, isrightcontainer_func):
|
|
"""Gathers up children of metacontainer - a container structure formed by containers of specific type.
|
|
For example, coordinate systems form a kind of container structure.
|
|
|
|
container: instance of Container class
|
|
isrightcontainer_func: a function f(cnt)->bool, where cnt is a Container object."""
|
|
|
|
result = []
|
|
list_traversing_now = [container] #list of Container instances
|
|
list_to_be_traversed_next = [] #list of Container instances
|
|
visited_containers = set([container.Object]) #set of DocumentObjects
|
|
|
|
while len(list_traversing_now) > 0:
|
|
list_to_be_traversed_next = []
|
|
for itcnt in list_traversing_now:
|
|
children = itcnt.getAllChildren()
|
|
result.extend(children)
|
|
for child in children:
|
|
if isAContainer(child):
|
|
newcnt = Container(child)
|
|
if not isrightcontainer_func(newcnt):
|
|
list_to_be_traversed_next.append(newcnt)
|
|
list_traversing_now = list_to_be_traversed_next
|
|
|
|
return result
|
|
|
|
|
|
|
|
def isAContainer(obj):
|
|
'''isAContainer(obj): returns True if obj is an object container, such as
|
|
Group, Part, Body. The important characteristic of an object being a
|
|
container is that it can be activated to receive new objects. Documents
|
|
are considered containers, too.'''
|
|
|
|
if obj.isDerivedFrom('App::Document'):
|
|
return True
|
|
if obj.hasExtension('App::GroupExtension'):
|
|
return True
|
|
if obj.isDerivedFrom('App::Origin'):
|
|
return True
|
|
return False
|
|
|
|
#from Part-o-magic...
|
|
def ContainerOf(obj):
|
|
"""ContainerOf(obj): returns the container that immediately has obj."""
|
|
cnt = None
|
|
for dep in obj.InList:
|
|
if isAContainer(dep):
|
|
if Container(dep).hasObject(obj):
|
|
if cnt is not None and dep is not cnt:
|
|
raise ContainerTreeError("Container tree is not a tree")
|
|
cnt = dep
|
|
if cnt is None:
|
|
return obj.Document
|
|
return cnt
|
|
|
|
def getVisGroupOf(obj):
|
|
chain = VisGroupChain(obj)
|
|
return chain[-1]
|
|
|
|
#from Part-o-magic... over-engineered, but proven to work
|
|
def ContainerChain(feat):
|
|
'''ContainerChain(feat): container path to feat (not including feat itself).
|
|
Last container directly contains the feature.
|
|
Example of output: [<document>,<SuperPart>,<Part>,<Body>]'''
|
|
|
|
if feat.isDerivedFrom('App::Document'):
|
|
return []
|
|
|
|
list_traversing_now = [feat]
|
|
set_of_deps = set()
|
|
list_of_deps = []
|
|
|
|
while len(list_traversing_now) > 0:
|
|
list_to_be_traversed_next = []
|
|
for feat in list_traversing_now:
|
|
for dep in feat.InList:
|
|
if isAContainer(dep) and Container(dep).hasObject(feat):
|
|
if not (dep in set_of_deps):
|
|
set_of_deps.add(dep)
|
|
list_of_deps.append(dep)
|
|
list_to_be_traversed_next.append(dep)
|
|
if len(list_to_be_traversed_next) > 1:
|
|
raise ContainerTreeError("Container tree is not a tree")
|
|
list_traversing_now = list_to_be_traversed_next
|
|
|
|
return [feat.Document] + list_of_deps[::-1]
|
|
|
|
def CSChain(feat):
|
|
cnt_chain = ContainerChain(feat)
|
|
return [cnt for cnt in cnt_chain if Container(cnt).isACS()]
|
|
|
|
def VisGroupChain(feat):
|
|
cnt_chain = ContainerChain(feat)
|
|
return [cnt for cnt in cnt_chain if Container(cnt).isAVisGroup()]
|
|
|
|
def activeContainer():
|
|
'''activeContainer(): returns active container.
|
|
If there is an active body, it is returned as active container. ActivePart is ignored.
|
|
If there is no active body, active Part is returned.
|
|
If there is no active Part either, active Document is returned.
|
|
If no active document, None is returned.'''
|
|
import FreeCAD as App
|
|
import FreeCADGui as Gui
|
|
|
|
if hasattr(App, "ActiveContainer"):
|
|
return App.ActiveContainer.Object
|
|
|
|
if Gui.ActiveDocument is None:
|
|
return None
|
|
vw = Gui.ActiveDocument.ActiveView
|
|
if vw is None:
|
|
return None
|
|
if not hasattr(vw, 'getActiveObject'): #v0.16
|
|
return App.ActiveDocument
|
|
activeBody = vw.getActiveObject("pdbody")
|
|
activePart = vw.getActiveObject("part")
|
|
if activeBody:
|
|
return activeBody
|
|
elif activePart:
|
|
return activePart
|
|
else:
|
|
return App.ActiveDocument
|
|
|
|
|
|
class ContainerError(RuntimeError):
|
|
pass
|
|
class NotAContainerError(ContainerError):
|
|
def __init__(self):
|
|
ContainerError.__init__(self, u"{obj} is not recognized as container".format(obj.Name))
|
|
class ContainerTreeError(ContainerError):
|
|
pass
|