AnimateDocumentObserver.py
1 # -*- coding: utf-8 -*-
2 
3 # ***************************************************************************
4 # * *
5 # * Animate workbench - FreeCAD Workbench for lightweight animation *
6 # * Copyright (c) 2019 Jiří Valášek jirka362@gmail.com *
7 # * *
8 # * This file is part of the Animate workbench. *
9 # * *
10 # * This program is free software; you can redistribute it and/or modify *
11 # * it under the terms of the GNU Lesser General Public License (LGPL) *
12 # * as published by the Free Software Foundation; either version 2 of *
13 # * the License, or (at your option) any later version. *
14 # * for detail see the LICENCE text file. *
15 # * *
16 # * Animate workbench is distributed in the hope that it will be useful, *
17 # * but WITHOUT ANY WARRANTY; without even the implied warranty of *
18 # * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
19 # * GNU Lesser General Public License for more details. *
20 # * *
21 # * You should have received a copy of the GNU Library General Public *
22 # * License along with Animate workbench; if not, write to the Free *
23 # * Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, *
24 # * MA 02111-1307 USA *
25 # * *
26 # ***************************************************************************/
27 
28 
33 
34 import FreeCAD
35 
36 from PySide2.QtWidgets import QMessageBox
37 
38 
39 ANIMATE_OBJECT_GROUP_CLASSES = ["TrajectoryProxy", "ControlProxy",
40  "CollisionDetectorProxy", "RobWorldProxy",
41  "RobRotationProxy", "RobTranslationProxy"]
42 
43 
45 ANIMATE_CLASSES = ["TrajectoryProxy", "ControlProxy", "ServerProxy",
46  "CollisionDetectorProxy", "CollisionProxy", "RobWorldProxy",
47  "RobRotationProxy", "RobTranslationProxy"]
48 
49 
50 ALLOWED_IN_CONTROL = ["TrajectoryProxy", "ServerProxy",
51  "CollisionDetectorProxy", "RobWorldProxy",
52  "RobRotationProxy", "RobTranslationProxy"]
53 
54 
55 
62 
64 
65 
67 
68 
70 
71 
73 
74 
77  __instance = None
78  server_proxies = {}
79 
80 
85 
86  def __new__(cls, *args, **kwargs):
87  # Make AnimateDocumentObserver a singleton
88  if cls.__instance is None:
89  cls.__instance = super(AnimateDocumentObserver,
90  cls).__new__(cls, *args, **kwargs)
91  return cls.__instance
92 
93 
102 
103  def slotBeforeChangeObject(self, obj, prop):
104  # If any group is about to be changed, start a transaction
105  if prop == "Group":
106  FreeCAD.ActiveDocument.openTransaction()
107  self.group_before = obj.Group
108 
109 
121 
122  def slotChangedObject(self, obj, prop):
123  # If objects are added to a group
124  if prop == "Group" and len(obj.Group) > len(self.group_before):
125  # If a new object is added to a group object from Animate workbench
126  new_obj = obj.Group[-1]
127  if self.isAnimateGroup(obj):
128  # An object not from Animate workbench was added to it
129  if self.foreignObjectInAnimateGroup(new_obj, obj):
130  QMessageBox.warning(
131  None, 'Forbidden action detected',
132  "Group objects from Animate workbench can group\n"
133  + "only selected objects from Animate workbench.\n"
134  + "Check the user guide for more info.")
135  FreeCAD.ActiveDocument.undo()
136  else:
137  FreeCAD.ActiveDocument.commitTransaction()
138 
139  # An object is added to a group not from Animate workbench
140  else:
141  # The added object was from Animate workbench
142  if self.animateObjectInForeignGroup(new_obj, obj):
143  QMessageBox.warning(
144  None, 'Forbidden action detected',
145  "Objects from Animate workbench can be grouped\n"
146  + "only by selected objects from Animate "
147  + "workbench.\nCheck the user guide "
148  + "for more info.")
149  FreeCAD.ActiveDocument.undo()
150  else:
151  FreeCAD.ActiveDocument.commitTransaction()
152 
153  # If objects are removed from a group
154  elif prop == "Group" and len(obj.Group) < len(self.group_before):
155  # If a Collision is removed from
156  removed = set(self.group_before).difference(set(obj.Group)).pop()
157  if removed.Proxy.__class__.__name__ == "CollisionProxy" and \
158  hasattr(obj, "Proxy") and not obj.Proxy.resetting and \
159  not obj.Proxy.checking:
160  QMessageBox.warning(
161  None, 'Forbidden action detected',
162  "Collision objects cannot be removed from\n"
163  + "a CollisionDetector group.")
164  FreeCAD.ActiveDocument.undo()
165  else:
166  FreeCAD.ActiveDocument.commitTransaction()
167 
168 
177 
178  def isAnimateGroup(self, obj):
179  # If a proxy is NoneType extrapolate it from object name
180  if not hasattr(obj, "Proxy"):
181  return False
182  else:
183  if obj.Proxy.__class__.__name__ == "NoneType":
184  obj_type = obj.Name.rstrip('0123456789') + "Proxy"
185  else:
186  obj_type = obj.Proxy.__class__.__name__
187 
188  # Check if proxy is an Animate group class
189  if obj_type not in ANIMATE_OBJECT_GROUP_CLASSES:
190  return False
191  else:
192  return True
193 
194 
203 
204  def isAnimateObject(self, obj):
205  # If a proxy is NoneType extrapolate it from object name
206  if not hasattr(obj, "Proxy"):
207  return False
208  else:
209  if obj.Proxy.__class__.__name__ == "NoneType":
210  obj_type = obj.Name.rstrip('0123456789') + "Proxy"
211  else:
212  obj_type = obj.Proxy.__class__.__name__
213 
214  # Check if proxy is an Animate object class
215  if obj_type not in ANIMATE_CLASSES:
216  return False
217  else:
218  return True
219 
220 
233 
234  def foreignObjectInAnimateGroup(self, obj, group):
235  # If an obj proxy is NoneType extrapolate it from object name
236  if not hasattr(obj, "Proxy"):
237  return False
238  else:
239  if obj.Proxy.__class__.__name__ == "NoneType":
240  obj_type = obj.Name.rstrip('0123456789') + "Proxy"
241  else:
242  obj_type = obj.Proxy.__class__.__name__
243 
244  # If an obj proxy is NoneType extrapolate it from object name
245  if group.Proxy.__class__.__name__ == "NoneType":
246  group_type = group.Name.rstrip('0123456789') + "Proxy"
247  else:
248  group_type = group.Proxy.__class__.__name__
249 
250  # Only a Trajectory can be in a Trajectory group
251  if group_type == "TrajectoryProxy" and obj_type == "TrajectoryProxy":
252  return False
253 
254  # Only some objects can be in a Control group
255  elif group_type == "ControlProxy" and obj_type in ALLOWED_IN_CONTROL:
256  return False
257 
258  elif group_type == "CollisionDetectorProxy" and \
259  obj.Name.find("Collision") != -1:
260  return False
261  # Only RobRotation and RobTranslation can be in RobWorld, RobRotation
262  # and RobTRanslation groups
263  elif (group_type in ["RobWorldProxy", "RobRotationProxy",
264  "RobTranslationProxy"]) and \
265  (obj_type in ["RobRotationProxy", "RobTranslationProxy"]):
266  return False
267  return True
268 
269 
278 
279  def animateObjectInForeignGroup(self, obj, group):
280  return not self.isAnimateGroup(group) and self.isAnimateObject(obj)
281 
282 
290 
291  def slotDeletedDocument(self, doc):
292  # Check at least one server is in the document about to be closed
293  if doc.Name in self.server_proxies:
294  # Notify all servers in the document
295  for server_proxy in self.server_proxies[doc.Name]:
296  server_proxy.onDocumentClosed()
297 
298 
307 
308  def addServerToNotify(self, server_proxy, document_name):
309  # Add a server proxy to the dictionary under a document name it's on
310  if document_name in self.server_proxies:
311  if server_proxy not in self.server_proxies[document_name]:
312  self.server_proxies[document_name].append(server_proxy)
313 
314  # Add a new document name to the dictionary and assign it a list
315  # with a server proxy
316  else:
317  self.server_proxies[document_name] = [server_proxy]
318 
319 
320 
327 
329  # Check FreeCAD doesn't have an AnimateDocumentObserver already assigned
330  # and assign one
331  if not hasattr(FreeCAD, "animate_observer"):
332  FreeCAD.animate_observer = AnimateDocumentObserver()
333  FreeCAD.addDocumentObserver(FreeCAD.animate_observer)
dictionary server_proxies
A dict of document names and ServerProxies in them.
def slotBeforeChangeObject(self, obj, prop)
Qt slot method called when an object in a surveyed document is about to change.
def addObserver()
Adds an AnimateDocumentObserver between FreeCAD's document observers safely.
def slotChangedObject(self, obj, prop)
Qt slot method called when an object in an observed document was changed.
def addServerToNotify(self, server_proxy, document_name)
Method to add a server which needs to be notified when its document is closing.
def isAnimateObject(self, obj)
Method to check whether an object comes from the Animate workbench.
Class that keeps Animate workbench objects in recommended structures.
def animateObjectInForeignGroup(self, obj, group)
Method testing whether an Animate object is in a foreign group object.
def isAnimateGroup(self, obj)
Method to check whether a group object comes from the Animate workbench.
def foreignObjectInAnimateGroup(self, obj, group)
Method testing whether a forbidden object is in an Animate group object.
def __new__(cls, *args, **kwargs)
Method creating an AnimateDocumentObserver singleton instance.
group_before
A list of objects inside a group object about to change.
def slotDeletedDocument(self, doc)
Qt slot method called if a document is about to be closed.