RobRotation.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 
35 
36 import FreeCAD
37 import FreeCADGui
38 
39 from RobotPanel import RobotPanel
40 
41 from PySide2.QtWidgets import QMessageBox
42 from bisect import bisect
43 from pivy import coin
44 from os import path
45 
46 
47 PATH_TO_ICONS = path.join(FreeCAD.getHomePath(), "Mod", "Animate", "Resources",
48  "Icons")
49 
50 
51 PATH_TO_UI = path.join(FreeCAD.getHomePath(), "Mod", "Animate", "Resources",
52  "UIs")
53 
54 
55 
70 
72 
73 
75 
76 
78 
79  updated = False
80 
81  def printt(self):
82  print("This is RobRotation")
83 
84 
93 
94  def __init__(self, fp):
95  # Add (and preset) properties
96  self.setProperties(fp)
97  fp.Proxy = self
98 
99 
110 
111  def onChanged(self, fp, prop):
112  # Ignore updates to ranges
113  if self.updated:
114  self.updated = False
115  return
116 
117  # Control allowed theta range limits
118  elif prop == "thetaMinimum" and hasattr(fp, "thetaMaximum"):
119  self.updated = True
120  fp.thetaMaximum = (fp.thetaMaximum, fp.thetaMinimum,
121  float("inf"), 1)
122  elif prop == "thetaMaximum" and hasattr(fp, "thetaMinimum"):
123  self.updated = True
124  fp.thetaMinimum = (fp.thetaMinimum, -float("inf"),
125  fp.thetaMaximum, 1)
126 
127  # Check that the rotation has valid format
128  elif self.is_rotation_property(prop) and \
129  hasattr(fp, "thetaMaximum") and \
130  hasattr(fp, "thetaMinimum") and \
131  hasattr(self, "fp"):
132  traj_valid = self.is_ValidRotation(
133  fp.Timestamps, fp.thetaSequence)
134  if traj_valid != fp.ValidRotation:
135  fp.ValidRotation = traj_valid
136 
137  elif prop == "Placement":
138  # Propagate the Placement updates down the chain
139  if hasattr(fp, "Group") and len(fp.Group) != 0:
140  for child in fp.Group:
141  child.ParentFramePlacement = fp.Placement
142  child.purgeTouched()
143 
144  # Display animated objects in a pose specified by the rotation
145  # and current time
146  if hasattr(fp, "AnimatedObjects") and len(fp.AnimatedObjects) != 0:
147  for o in fp.AnimatedObjects:
148  o.Placement = fp.Placement
149  o.purgeTouched()
150 
151  elif prop == "ParentFramePlacement":
152  # If parent frame changed, recompute placement
153  fp.Placement = fp.ParentFramePlacement.multiply(
154  fp.ObjectPlacement)
155 
156 
166 
167  def execute(self, fp):
168  # Check that joint is not controlled by a RobotPanel
169  if not fp.RobotPanelActive:
170  # Check that current rotation has valid format
171  if not fp.ValidRotation:
172  FreeCAD.Console.PrintWarning(fp.Name +
173  ".execute(): Rotation " +
174  "is not in a valid format.\n")
175  return
176 
177  # Update theta according to current time and rotation
178  indices, weights = self.find_timestamp_indices_and_weights(fp)
179 
180  fp.theta = fp.thetaOffset \
181  + (weights[0]*fp.thetaSequence[indices[0]]
182  + weights[1]*fp.thetaSequence[indices[1]])
183 
184  # DH transformation
185  T_theta = FreeCAD.Placement(FreeCAD.Vector(0, 0, 0),
186  FreeCAD.Rotation(FreeCAD.Vector(0, 0, 1),
187  fp.theta))
188  T_d = FreeCAD.Placement(FreeCAD.Vector(0, 0, fp.d),
189  FreeCAD.Rotation(FreeCAD.Vector(0, 0, 1), 0))
190  T_a = FreeCAD.Placement(FreeCAD.Vector(fp.a, 0, 0),
191  FreeCAD.Rotation(FreeCAD.Vector(1, 0, 0), 0))
192  T_alpha = FreeCAD.Placement(FreeCAD.Vector(0, 0, 0),
193  FreeCAD.Rotation(FreeCAD.Vector(1, 0, 0),
194  fp.alpha))
195 
196  fp.ObjectPlacement = T_theta.multiply(T_d.multiply(
197  T_a.multiply(T_alpha)))
198 
199  # Placement update
200  fp.Placement = fp.ParentFramePlacement.multiply(fp.ObjectPlacement)
201 
202 
211 
212  def onDocumentRestored(self, fp):
213  fp.ViewObject.Proxy.setProperties(fp.ViewObject)
214  self.setProperties(fp)
215 
216  # supporting methods-------------------------------------------------------
217 
225 
226  def setProperties(self, fp):
227  # Add (and preset) properties
228  # Animation properties
229  if not hasattr(fp, "ValidRotation"):
230  fp.addProperty("App::PropertyBool", "ValidRotation", "General",
231  "This property records if rotation was changed."
232  ).ValidRotation = False
233  if not hasattr(fp, "RobotPanelActive"):
234  fp.addProperty("App::PropertyBool", "RobotPanelActive", "General",
235  "This property records if robot panel is active."
236  ).RobotPanelActive = False
237  if not hasattr(fp, "AnimatedObjects"):
238  fp.addProperty("App::PropertyLinkListGlobal", "AnimatedObjects",
239  "General", "Objects that will be animated.")
240  if not hasattr(fp, "Interpolate"):
241  fp.addProperty("App::PropertyBool", "Interpolate", "General",
242  "Interpolate RobRotation between timestamps."
243  ).Interpolate = True
244  if not hasattr(fp, "AllowServer"):
245  fp.addProperty("App::PropertyBool", "AllowServer", "General",
246  "Should this object allow a Server object to "
247  + "change it.").AllowServer = True
248  if not hasattr(fp, "AllowControl"):
249  fp.addProperty("App::PropertyBool", "AllowControl", "General",
250  "Should this object allow a Control object "
251  + " to change it."
252  ).AllowControl = True
253  if not hasattr(fp, "Time"):
254  fp.addProperty("App::PropertyFloat", "Time", "General",
255  "Animation time in seconds.").Time = 0
256  if not hasattr(fp, "ParentFramePlacement"):
257  fp.addProperty("App::PropertyPlacement", "ParentFramePlacement",
258  "General", "Current placement of a Parent Frame.")
259  if not hasattr(fp, "ObjectPlacement"):
260  fp.addProperty("App::PropertyPlacement", "ObjectPlacement",
261  "General",
262  "Current Object placement in a Parent Frame.")
263 
264  # DH parameters
265  if not hasattr(fp, "d"):
266  fp.addProperty("App::PropertyFloat", "d", "d-hParameters",
267  "Displacement along Z axis.").d = 0
268  if not hasattr(fp, "a"):
269  fp.addProperty("App::PropertyFloat", "a", "d-hParameters",
270  "Displacement along X axis.").a = 0
271  if not hasattr(fp, "alpha"):
272  fp.addProperty("App::PropertyFloat", "alpha", "d-hParameters",
273  "Rotation angle about X axis in degrees.").alpha = 0
274  if not hasattr(fp, "theta"):
275  fp.addProperty("App::PropertyFloat", "theta", "d-hParameters",
276  "Rotation angle about X axis in degrees.").theta = 0
277  if not hasattr(fp, "thetaMaximum"):
278  fp.addProperty("App::PropertyFloatConstraint", "thetaMaximum",
279  "d-hParameters", "Upper limit of rotation angle"
280  + " about Z axis in degrees."
281  ).thetaMaximum = (360, 0, float("inf"), 1)
282  elif hasattr(fp, "thetaMinimum"):
283  fp.thetaMaximum = (fp.thetaMaximum, fp.thetaMinimum,
284  float("inf"), 1)
285  if not hasattr(fp, "thetaMinimum"):
286  fp.addProperty("App::PropertyFloatConstraint", "thetaMinimum",
287  "d-hParameters", "Lower limit of rotation angle"
288  + " about Z axis in degrees."
289  ).thetaMinimum = (0, -float("inf"), 360, 1)
290  elif hasattr(fp, "thetaMaximum"):
291  fp.thetaMinimum = (fp.thetaMinimum, -float("inf"),
292  fp.thetaMaximum, 1)
293  if not hasattr(fp, "thetaOffset"):
294  fp.addProperty("App::PropertyFloat", "thetaOffset",
295  "d-hParameters", "Offset of rotation angle"
296  + " about Z axis in degrees.").thetaOffset = 0
297 
298  # Frame properties
299  if not hasattr(fp, "ShowFrame"):
300  fp.addProperty("App::PropertyBool", "ShowFrame", "Frame",
301  "Show a frame for current pose."
302  ).ShowFrame = True
303  if not hasattr(fp, "FrameTransparency"):
304  fp.addProperty("App::PropertyPercent", "FrameTransparency",
305  "Frame", "Transparency of the frame in percents."
306  ).FrameTransparency = 0
307  if not hasattr(fp, "ShowFrameArrowheads"):
308  fp.addProperty("App::PropertyBool", "ShowFrameArrowheads", "Frame",
309  "Show arrowheads for frame axis arrow's."
310  ).ShowFrameArrowheads = True
311  if not hasattr(fp, "FrameArrowheadLength"):
312  fp.addProperty("App::PropertyFloatConstraint",
313  "FrameArrowheadLength", "Frame",
314  "Frame axis arrow's arrowhead length.\n"
315  + "Range is < 1.0 | 1e6 >."
316  ).FrameArrowheadLength = (10, 1.0, 1e6, 1)
317  else:
318  fp.FrameArrowheadLength = (fp.FrameArrowheadLength, 1.0, 1e6, 1)
319  if not hasattr(fp, "FrameArrowheadRadius"):
320  fp.addProperty("App::PropertyFloatConstraint",
321  "FrameArrowheadRadius", "Frame",
322  "Frame axis arrow's arrowhead bottom radius.\n"
323  + "Range is < 0.5 | 1e6 >."
324  ).FrameArrowheadRadius = (5, 0.5, 1e6, 0.5)
325  else:
326  fp.FrameArrowheadRadius = (fp.FrameArrowheadRadius, 0.5, 1e6, 0.5)
327  if not hasattr(fp, "ShaftLength"):
328  fp.addProperty("App::PropertyFloatConstraint", "ShaftLength",
329  "Frame", "Frame axis arrow's shaft length.\n"
330  + "Range is < 1.0 | 1e6 >."
331  ).ShaftLength = (20, 1.0, 1e6, 1)
332  else:
333  fp.ShaftLength = (fp.ShaftLength, 1.0, 1e6, 1)
334  if not hasattr(fp, "ShaftWidth"):
335  fp.addProperty("App::PropertyFloatConstraint", "ShaftWidth",
336  "Frame", "Frame axis arrow's shaft width.\n"
337  + "Range is < 1.0 | 64 >."
338  ).ShaftWidth = (4, 1.0, 64, 1)
339  else:
340  fp.ShaftWidth = (fp.ShaftWidth, 1.0, 64, 1)
341  if not hasattr(fp, "ShowFrameLabels"):
342  fp.addProperty("App::PropertyBool", "ShowFrameLabels",
343  "Frame", "Show label for frame axes."
344  ).ShowFrameLabels = True
345 
346  # Label properties
347  if not hasattr(fp, "FontSize"):
348  fp.addProperty("App::PropertyIntegerConstraint", "FontSize",
349  "Labels", "Label font size.\n"
350  + "Range is < 1 | 100 >."
351  ).FontSize = (10, 1, 100, 1)
352  else:
353  fp.FontSize = (fp.FontSize, 1, 100, 1)
354  if not hasattr(fp, "DistanceToAxis"):
355  fp.addProperty("App::PropertyFloatConstraint", "DistanceToAxis",
356  "Labels", "Distance from label to its axis.\n"
357  + "Range is < 0.5 | 1e6 >."
358  ).DistanceToAxis = (5, 0.5, 1e6, 0.5)
359  else:
360  fp.DistanceToAxis = (fp.DistanceToAxis, 0.5, 1e6, 0.5)
361  if not hasattr(fp, "Subscription"):
362  fp.addProperty("App::PropertyString", "Subscription", "Labels",
363  "Subscription added to an axis name."
364  ).Subscription = ""
365  if not hasattr(fp, "Superscription"):
366  fp.addProperty("App::PropertyString", "Superscription", "Labels",
367  "Superscription added to an axis name."
368  ).Superscription = ""
369  if not hasattr(fp, "FontFamily"):
370  fp.addProperty("App::PropertyEnumeration", "FontFamily",
371  "Labels", "Label font family."
372  ).FontFamily = ["SERIF", "SANS", "TYPEWRITER"]
373  if not hasattr(fp, "FontStyle"):
374  fp.addProperty("App::PropertyEnumeration", "FontStyle",
375  "Labels", "Label font style."
376  ).FontStyle = ["NONE", "BOLD", "ITALIC",
377  "BOLD ITALIC"]
378 
379  # Placement properties
380  if not hasattr(fp, "Placement"):
381  fp.addProperty("App::PropertyPlacement", "Placement", "Base",
382  "Current placement for animated objects in "
383  + "world frame.")
384 
385  # Rotation properties
386  if not hasattr(fp, "Timestamps"):
387  fp.addProperty("App::PropertyFloatList", "Timestamps",
388  "Rotation", "Timestamps at which we define\n"
389  + "translation and rotation.")
390  if not hasattr(fp, "thetaSequence"):
391  fp.addProperty("App::PropertyFloatList", "thetaSequence",
392  "Rotation", "Rotation angles about Z axis in"
393  + " degrees.")
394 
395  # Add feature python
396  self.fp = fp
397 
398  # Make some properties read-only
399  fp.setEditorMode("ObjectPlacement", 1)
400  fp.setEditorMode("ParentFramePlacement", 1)
401  fp.setEditorMode("theta", 1)
402 
403  # Hide some properties
404  fp.setEditorMode("Placement", 2)
405  fp.setEditorMode("ValidRotation", 2)
406  fp.setEditorMode("RobotPanelActive", 2)
407 
408  import AnimateDocumentObserver
410 
411 
419 
420  def change_joint_sequence(self, joint_sequence):
421  # Check that RobRotation has a correct format and load it
422  if self.is_ValidRotation(rotation=joint_sequence):
423  self.fp.Timestamps = joint_sequence["Timestamps"]
424  self.fp.thetaSequence = joint_sequence["thetaSequence"]
425  else:
426  FreeCAD.Console.PrintError("Invalid joint sequence!")
427 
428 
438 
439  def is_rotation_property(self, prop):
440  return prop in ["Timestamps", "thetaSequence"]
441 
442 
458 
459  def is_ValidRotation(self, timestamps=[], thetas=[], rotation=None):
460  # Check all keys are included and record lengths of their lists
461  if rotation is not None and isinstance(rotation, dict):
462  for key in ["Timestamps", "thetaSequence"]:
463  if key not in rotation.keys():
464  FreeCAD.Console.PrintWarning("Rotation misses key " +
465  key + ".\n")
466  return False
467  timestamps = rotation["Timestamps"]
468  thetas = rotation["thetaSequence"]
469 
470  # Check that all lists have the same length
471  if len(timestamps) == 0 or \
472  (len(timestamps) != 0 and len(timestamps) != len(thetas)):
473  FreeCAD.Console.PrintWarning("Rotation has lists with "
474  + "inconsistent or zero "
475  + "lengths.\n")
476  return False
477 
478  # Check timestamps correspond to list of increasing values
479  if any([timestamps[i] >= timestamps[i+1]
480  for i in range(len(timestamps)-1)]):
481  FreeCAD.Console.PrintWarning("Rotation 'Timestamps' is not "
482  + "list of increasing values.\n")
483  return False
484 
485  if not all([self.fp.thetaMinimum <= th <= self.fp.thetaMaximum
486  for th in thetas]):
487  FreeCAD.Console.PrintWarning("Rotation 'thetaSequence' elements"
488  + " are out of theta range.\n")
489  return False
490 
491  return True
492 
493 
512 
514  # Retrieve indices corresponding to current time
515  # If the time is before the first Timestamp use the first Timestamp
516  if fp.Time <= fp.Timestamps[0]:
517  indices = [0, 0]
518  weights = [1, 0]
519 
520  # If the time is after the last Timpestamp use the last Timestamp
521  elif fp.Time >= fp.Timestamps[-1]:
522  indices = [-1, -1]
523  weights = [1, 0]
524 
525  # If time is in the range of Timesteps
526  else:
527  # Find the index of the closest higher value
528  indices = [bisect(fp.Timestamps, fp.Time)]
529  # Add the previous index
530  indices.insert(0, indices[0]-1)
531  weights = [fp.Timestamps[indices[1]] - fp.Time,
532  fp.Time - fp.Timestamps[indices[0]]]
533  if not fp.Interpolate:
534  if weights[0] > weights[1]:
535  weights = [1, 0]
536  else:
537  weights = [0, 1]
538  else:
539  weights = [weights[0]/sum(weights), weights[1]/sum(weights)]
540 
541  return indices, weights
542 
543 
555 
556  def __getstate__(self):
557  return None
558 
559 
568 
569  def __setstate__(self, state):
570  return None
571 
572 
573 
588 
590 
591 
593 
594 
596 
597 
599 
600 
602 
603 
605 
606 
608 
609 
611 
612 
614 
615 
617 
618 
620 
621 
623 
624 
626 
627 
629 
630 
632 
633 
635 
636 
638 
639 
641 
642  panel = None
643  fp = None
644 
645  # standard methods---------------------------------------------------------
646 
655 
656  def __init__(self, vp):
657  self.setProperties(vp)
658  vp.Proxy = self
659 
660 
669 
670  def attach(self, vp):
671  # prepare transformation to keep pose corresponding to placement
672  self.tf_object2world = coin.SoTransform()
673 
674  labels = self.makeLabels()
675  self.font = coin.SoFontStyle()
676 
677  frame = self.makeFrame(labels[:3])
678  frame.insertChild(self.tf_object2world, 0)
679  frame.insertChild(self.font, 1)
680  self.frame = coin.SoSwitch()
681  self.frame.addChild(frame)
682 
683  self.visualisations = coin.SoSwitch()
684  self.visualisations.addChild(self.frame)
685  self.visualisations.whichChild.setValue(coin.SO_SWITCH_ALL)
686  vp.RootNode.addChild(self.visualisations)
687 
688  vp.Object.Proxy.setProperties(vp.Object)
689  self.setProperties(vp)
690  self.fp = vp.Object
691 
692 
702 
703  def updateData(self, fp, prop):
704  # Placement changes
705  if prop == "Placement" and hasattr(fp, "Placement"):
706  trans = fp.Placement.Base
707  rot = fp.Placement.Rotation
708  self.tf_object2world.translation.setValue((trans.x, trans.y,
709  trans.z))
710  self.tf_object2world.rotation.setValue(rot.Q)
711 
712  # Frame changes
713  elif prop == "ShowFrame" and hasattr(fp, "ShowFrame"):
714  if fp.ShowFrame:
715  self.frame.whichChild.setValue(coin.SO_SWITCH_ALL)
716  else:
717  self.frame.whichChild.setValue(coin.SO_SWITCH_NONE)
718 
719  elif prop == "FrameTransparency" and hasattr(fp, "FrameTransparency"):
720  self.frame_color_x.orderedRGBA.\
721  setValue(0xff0000ff - (0xff*fp.FrameTransparency)//100)
722  self.frame_color_y.orderedRGBA.\
723  setValue(0x00ff00ff - (0xff*fp.FrameTransparency)//100)
724  self.frame_color_z.orderedRGBA.\
725  setValue(0x0000ffff - (0xff*fp.FrameTransparency)//100)
726 
727  elif prop == "ShaftLength" and hasattr(fp, "ShaftLength"):
728  self.frame_shaft.vertexProperty.getValue().vertex.\
729  set1Value(1, 0, fp.ShaftLength, 0)
730  if hasattr(fp, "FrameArrowheadLength"):
731  self.frame_arrowhead_translation.translation.setValue(
732  0, fp.ShaftLength + fp.FrameArrowheadLength/2, 0)
733  if not fp.ShowFrameArrowheads and hasattr(fp, "DistanceToAxis"):
734  self.label_translations[0].translation.setValue(
735  0, fp.ShaftLength + fp.DistanceToAxis, 0)
736 
737  elif prop == "FrameArrowheadLength" and \
738  hasattr(fp, "FrameArrowheadLength"):
739  self.frame_arrowhead_cone.height.setValue(fp.FrameArrowheadLength)
740  if hasattr(fp, "ShaftLength"):
741  self.frame_arrowhead_translation.translation.setValue(
742  0, fp.ShaftLength + fp.FrameArrowheadLength/2, 0)
743  if fp.ShowFrameArrowheads and hasattr(fp, "DistanceToAxis"):
744  self.label_translations[0].translation.setValue(
745  0, fp.FrameArrowheadLength/2 + fp.DistanceToAxis, 0)
746 
747  elif prop == "ShaftWidth" and hasattr(fp, "ShaftWidth"):
748  self.frame_drawstyle.lineWidth.setValue(fp.ShaftWidth)
749 
750  elif prop == "FrameArrowheadRadius" and \
751  hasattr(fp, "FrameArrowheadRadius"):
752  self.frame_arrowhead_cone.bottomRadius.setValue(
753  fp.FrameArrowheadRadius)
754 
755  elif prop == "ShowFrameArrowheads" and \
756  hasattr(fp, "ShowFrameArrowheads"):
757  if fp.ShowFrameArrowheads:
758  self.frame_arrowhead.whichChild.setValue(coin.SO_SWITCH_ALL)
759  if hasattr(fp, "FrameArrowheadLength") and \
760  hasattr(fp, "DistanceToAxis"):
761  self.label_translations[0].translation.setValue(
762  0, fp.FrameArrowheadLength/2 + fp.DistanceToAxis, 0)
763  else:
764  self.frame_arrowhead.whichChild.setValue(coin.SO_SWITCH_NONE)
765  if hasattr(fp, "ShaftLength") and \
766  hasattr(fp, "DistanceToAxis"):
767  self.label_translations[0].translation.setValue(
768  0, fp.ShaftLength + fp.DistanceToAxis, 0)
769 
770  elif prop == "ShowFrameLabels" and hasattr(fp, "ShowFrameLabels"):
771  for label in self.labels[:3]:
772  if fp.ShowFrameLabels:
773  label.whichChild.setValue(coin.SO_SWITCH_ALL)
774  else:
775  label.whichChild.setValue(coin.SO_SWITCH_NONE)
776 
777  # Changes to the labels
778  elif prop == "Subscription" and hasattr(fp, "Subscription"):
779  for l in self.label_texts:
780  l.string.setValues(2, 1, [fp.Subscription])
781 
782  elif prop == "Superscription" and hasattr(fp, "Superscription"):
783  for l in self.label_texts:
784  l.string.setValues(0, 1, [fp.Superscription])
785 
786  elif prop == "FontFamily" and hasattr(fp, "FontFamily"):
787  if fp.FontFamily == "SERIF":
788  self.font.family.setValue(self.font.SERIF)
789  if fp.FontFamily == "SANS":
790  self.font.family.setValue(self.font.SANS)
791  if fp.FontFamily == "TYPEWRITER":
792  self.font.family.setValue(self.font.TYPEWRITER)
793 
794  elif prop == "FontStyle" and hasattr(fp, "FontStyle"):
795  if fp.FontStyle == "NONE":
796  self.font.style.setValue(self.font.NONE)
797  if fp.FontStyle == "BOLD":
798  self.font.style.setValue(self.font.BOLD)
799  if fp.FontStyle == "ITALIC":
800  self.font.style.setValue(self.font.ITALIC)
801  if fp.FontStyle == "BOLD ITALIC":
802  self.font.style.setValue(self.font.BOLD | self.font.ITALIC)
803 
804  elif prop == "FontSize" and hasattr(fp, "FontSize"):
805  self.font.size.setValue(fp.FontSize)
806 
807  elif prop == "DistanceToAxis" and hasattr(fp, "DistanceToAxis") and \
808  hasattr(fp, "ShowFrameArrowheads"):
809  if fp.ShowFrameArrowheads and hasattr(fp, "FrameArrowheadLength"):
810  self.label_translations[0].translation.setValue(
811  0, fp.FrameArrowheadLength/2 + fp.DistanceToAxis, 0)
812  elif hasattr(fp, "ShaftLength"):
813  self.label_translations[0].translation.setValue(
814  0, fp.ShaftLength + fp.DistanceToAxis, 0)
815 
816 
825 
826  def onChanged(self, vp, prop):
827  if prop == "Visibility":
828  if vp.Visibility:
829  self.visualisations.whichChild.setValue(coin.SO_SWITCH_ALL)
830  else:
831  self.visualisations.whichChild.setValue(coin.SO_SWITCH_NONE)
832 
833 
841 
842  def claimChildren(self):
843  if hasattr(self, "fp") and self.fp:
844  return self.fp.Group
845  return []
846 
847 
855 
856  def canDropObject(self, obj):
857  if hasattr(obj, "Proxy") and \
858  (obj.Proxy.__class__.__name__ == "RobRotationProxy" or
859  obj.Proxy.__class__.__name__ == "RobTranslationProxy"):
860  return True
861  return False
862 
863 
870 
871  def getIcon(self):
872  return path.join(PATH_TO_ICONS, "RobRotation.png")
873 
874 
886 
887  def __getstate__(self):
888  return None
889 
890 
896 
897  def __setstate__(self, state):
898  pass
899 
900 
907 
908  def setProperties(self, vp):
909  # hide unnecessary view properties
910  vp.setEditorMode("DisplayMode", 2)
911 
912 
926 
927  def doubleClicked(self, vp):
928  # Switch to the Task View if a RobotPanel is already opened
929  if self.panel:
930  FreeCADGui.Control.showTaskView()
931 
932  # Try to open new RobotPanel
933  else:
934  # Load the QDialog from a file and name it after this object
935  new_form = [FreeCADGui.PySideUic.loadUi(path.join(PATH_TO_UI,
936  "AnimationJoint.ui"))]
937  new_form[0].setWindowTitle(vp.Object.Label)
938 
939  # Create a control panel and try to show it
940  self.panel = RobotPanel([vp.Object], new_form)
941  try:
942  FreeCADGui.Control.showDialog(self.panel)
943  except RuntimeError as e:
944  # Reset the panel
945  self.panel = None
946 
947  # Find all RobRotation/RobTranslation feature python objects
948  # with a reference to a RobotPanel
949  robot_joints = []
950  for obj in FreeCAD.ActiveDocument.Objects:
951  if hasattr(obj, "Proxy") and \
952  (obj.Proxy.__class__.__name__
953  == "RobRotationProxy"
954  or obj.Proxy.__class__.__name__
955  == "RobTranslationProxy"):
956  if obj.ViewObject.Proxy.panel is not None:
957  robot_joints.append(obj)
958 
959  if len(robot_joints) > 0:
960  # Close opened Trajecotry panel
961  robot_joints[0].ViewObject.Proxy.panel.reject()
962 
963  # Load the QDialog form for each RobRotation/RobTranslation
964  # which had a reference to the panel
965  forms = []
966  for joint in robot_joints:
967  form = FreeCADGui.PySideUic.loadUi(
968  path.join(PATH_TO_UI, "AnimationJoint.ui"))
969  form.setWindowTitle(joint.Label)
970  forms.append(form)
971 
972  # Load one more QDialog for this RobRotation
973  forms.append(new_form[0])
974 
975  # Add this RobRotation to the list of robot_joints
976  robot_joints.append(vp.Object)
977 
978  # Add a reference to the new panel to view providers
979  # of all robot_joints
980  self.panel = RobotPanel(robot_joints, forms)
981  for joint in robot_joints:
982  joint.ViewObject.Proxy.panel = self.panel
983  FreeCADGui.Control.showDialog(self.panel)
984  return True
985 
986  # Diffeerent Task panel is opened, inform the user
987  else:
988  QMessageBox.warning(
989  None,
990  'Error while opening RobotPanel',
991  "A different panel is already active.\n"
992  + "Close it before opening this one.")
993  FreeCADGui.Control.showTaskView()
994  return True
995 
996 
1006 
1007  def setupContextMenu(self, vp, menu):
1008  menu.clear()
1009  action = menu.addAction("Check joint range")
1010  action.triggered.connect(lambda f=self.doubleClicked,
1011  arg=vp: f(arg))
1012 
1013 
1021 
1022  def makeLabels(self):
1023  label_strings = ["X", "Y", "Z"]
1024  colors = [0xFF0000FF, 0x00FF00FF, 0x0000FFFF]
1025  self.label_texts = []
1027  # frame translation
1028  self.label_translations.append(coin.SoTranslation())
1029  # axis translation
1030  self.label_translations.append(coin.SoTranslation())
1031  self.labels = []
1032  for i in range(3):
1033  label_group = coin.SoSeparator()
1034  label_group.addChild(self.label_translations[0])
1035  frame_axis_color = coin.SoPackedColor()
1036  frame_axis_color.orderedRGBA.setValue(colors[i])
1037  label_group.addChild(frame_axis_color)
1038  self.label_texts.append(coin.SoText2())
1039  self.label_texts[i].string.setValues(
1040  0, 3, ["", label_strings[i], ""])
1041  self.label_texts[i].justification.setValue(
1042  self.label_texts[i].CENTER)
1043  self.label_texts[i].spacing.setValue(0.45)
1044  label_group.addChild(self.label_texts[i])
1045  self.labels.append(coin.SoSwitch())
1046  self.labels[i].addChild(label_group)
1047  return self.labels
1048 
1049 
1059 
1060  def makeFrame(self, frame_labels):
1061  # make a generic shaft from 0 in Y direction
1062  shaft_vertices = coin.SoVertexProperty()
1063  shaft_vertices.vertex.setNum(2)
1064  shaft_vertices.vertex.set1Value(0, 0, 0, 0)
1065  self.frame_shaft = coin.SoLineSet()
1066  self.frame_shaft.vertexProperty.setValue(shaft_vertices)
1067  self.frame_shaft.numVertices.setNum(1)
1068  self.frame_shaft.numVertices.setValue(2)
1069 
1070  # make a generic conic arrowhead oriented in Y axis direction and
1071  # move it at the end of the shaft
1072  self.frame_arrowhead_translation = coin.SoTranslation()
1073  self.frame_arrowhead_cone = coin.SoCone()
1074  self.frame_arrowhead = coin.SoSwitch()
1075  self.frame_arrowhead.addChild(self.frame_arrowhead_translation)
1076  self.frame_arrowhead.addChild(self.frame_arrowhead_cone)
1077 
1078  # make rotations to rotate prepared shaft and arrowhead for Y axis
1079  # direction also to X and Z
1080  rot_y2x = coin.SoRotation()
1081  rot_y2x.rotation.setValue(coin.SbRotation(coin.SbVec3f(0, 1, 0),
1082  coin.SbVec3f(1, 0, 0)))
1083  rot_y2z = coin.SoRotation()
1084  rot_y2z.rotation.setValue(coin.SbRotation(coin.SbVec3f(0, 1, 0),
1085  coin.SbVec3f(0, 0, 1)))
1086 
1087  # prepare colors for X,Y,Z which will correspond to R,G,B as customary
1088  self.frame_color_x = coin.SoPackedColor()
1089  self.frame_color_y = coin.SoPackedColor()
1090  self.frame_color_z = coin.SoPackedColor()
1091 
1092  # make complete colored and rotated arrows
1093  x_arrow = coin.SoSeparator()
1094  x_arrow.addChild(rot_y2x)
1095  x_arrow.addChild(self.frame_color_x)
1096  x_arrow.addChild(self.frame_shaft)
1097  x_arrow.addChild(self.frame_arrowhead)
1098  x_arrow.addChild(frame_labels[0])
1099  y_arrow = coin.SoSeparator()
1100  y_arrow.addChild(self.frame_color_y)
1101  y_arrow.addChild(self.frame_shaft)
1102  y_arrow.addChild(self.frame_arrowhead)
1103  y_arrow.addChild(frame_labels[1])
1104  z_arrow = coin.SoSeparator()
1105  z_arrow.addChild(rot_y2z)
1106  z_arrow.addChild(self.frame_color_z)
1107  z_arrow.addChild(self.frame_shaft)
1108  z_arrow.addChild(self.frame_arrowhead)
1109  z_arrow.addChild(frame_labels[2])
1110 
1111  # prepare draw style to control shaft width
1112  self.frame_drawstyle = coin.SoDrawStyle()
1113 
1114  # make complete frame and it to shaded display mode
1115  separated_frame = coin.SoSeparator()
1116  separated_frame.addChild(self.frame_drawstyle)
1117  separated_frame.addChild(x_arrow)
1118  separated_frame.addChild(y_arrow)
1119  separated_frame.addChild(z_arrow)
1120 
1121  return separated_frame
1122 
1123 
1124 
1130 
1131 class RobRotationCommand(object):
1132 
1133 
1140 
1141  def GetResources(self):
1142  return {'Pixmap': path.join(PATH_TO_ICONS, "RobRotationCmd.png"),
1143  'MenuText': "RobRotation",
1144  'ToolTip': "Create RobRotation instance."}
1145 
1146 
1153 
1154  def Activated(self):
1155  doc = FreeCAD.ActiveDocument
1156  a = doc.addObject("App::DocumentObjectGroupPython", "RobRotation")
1157  RobRotationProxy(a)
1158  if FreeCAD.GuiUp:
1159  ViewProviderRobRotationProxy(a.ViewObject)
1160  doc.recompute()
1161  return
1162 
1163 
1172 
1173  def IsActive(self):
1174  if FreeCAD.ActiveDocument is None:
1175  return False
1176  else:
1177  return True
1178 
1179 
1180 if FreeCAD.GuiUp:
1181  # Add command to FreeCAD Gui when importing this module in InitGui
1182  FreeCADGui.addCommand('RobRotationCommand', RobRotationCommand())
def getIcon(self)
Method called by FreeCAD to supply an icon for the Tree View.
Definition: RobRotation.py:871
font
A SoFontStyle font for axes labels.
Definition: RobRotation.py:675
frame_arrowhead
A SoSwitch translated cone for frame axes.
def __setstate__(self, state)
Necessary method to avoid errors when trying to restore unserializable objects.
Definition: RobRotation.py:897
def is_ValidRotation(self, timestamps=[], thetas=[], rotation=None)
Method to check if a rotation is valid.
Definition: RobRotation.py:459
def canDropObject(self, obj)
Method called by FreeCAD to ask if an object obj can be dropped into a Group.
Definition: RobRotation.py:856
def __setstate__(self, state)
Necessary method to avoid errors when trying to restore unserializable objects.
Definition: RobRotation.py:569
def addObserver()
Adds an AnimateDocumentObserver between FreeCAD's document observers safely.
fp
A DocumentObjectGroupPython associated with the proxy.
Definition: RobRotation.py:396
def __getstate__(self)
Necessary method to avoid errors when trying to save unserializable objects.
Definition: RobRotation.py:556
frame_color_z
A SoPackedColor blue color for an Z axis.
def doubleClicked(self, vp)
Method called by FreeCAD when RobRotation is double-clicked in the Tree View.
Definition: RobRotation.py:927
def onChanged(self, vp, prop)
Method called after RobRotation.ViewObject was changed.
Definition: RobRotation.py:826
def find_timestamp_indices_and_weights(self, fp)
Method to find weighted timestamps indices corresponding to a given time.
Definition: RobRotation.py:513
panel
A RobotPanel if one is active or None.
Definition: RobRotation.py:642
def Activated(self)
Method used as a callback when the toolbar button or the menu item is clicked.
def __init__(self, fp)
Initialization method for RobRotationProxy.
Definition: RobRotation.py:94
frame_color_y
A SoPackedColor green color for an Y axis.
labels
A list of SoSwitches containing colored translated labels.
def makeLabels(self)
Method which makes Coin3D labels to be displayed in the FreeCAD View.
label_texts
A list of SoText2s labels denoting all axes and an origin.
def makeFrame(self, frame_labels)
Method which makes a Coin3D frame to show a current pose in a RobRotation.
Proxy class for Gui.ViewProviderDocumentObject RobRotation.ViewObject.
Definition: RobRotation.py:589
def __init__(self, vp)
Initialization method for ViewProviderRobRotationProxy.
Definition: RobRotation.py:656
Proxy class for a DocumentObjectGroupPython RobRotation instance.
Definition: RobRotation.py:71
frame_color_x
A SoPackedColor red color for an X axis.
Class specifying Animate workbench's RobRotation button/command.
frame
A SoSeparator with a coordinate frame made from 3 RGB arrows.
Definition: RobRotation.py:680
frame_drawstyle
A SoDrawStyle controlling frame axes shaft line width.
visualisations
A SoSwitch with all visualisations (frame & rotation axis).
Definition: RobRotation.py:683
def onDocumentRestored(self, fp)
Method called when document is restored to make sure everything is as it was.
Definition: RobRotation.py:212
bool updated
A bool - True if a property was changed by a class and not user.
Definition: RobRotation.py:79
def setProperties(self, fp)
Method to set properties during initialization or document restoration.
Definition: RobRotation.py:226
label_translations
A list of SoTranslations moving labels.
def setProperties(self, vp)
Method to hide unused properties.
Definition: RobRotation.py:908
def is_rotation_property(self, prop)
Method to check that a property describes a rotation.
Definition: RobRotation.py:439
frame_arrowhead_translation
A SoTranslation moving frame arrowheads.
def setupContextMenu(self, vp, menu)
Method called by the FreeCAD to customize a RobRotation context menu.
Class providing funcionality to a RobRotation panel inside the TaskView.
Definition: RobotPanel.py:51
def IsActive(self)
Method to specify when the toolbar button and the menu item are enabled.
def claimChildren(self)
Method called by FreeCAD to retrieve assigned children.
Definition: RobRotation.py:842
tf_object2world
A SoTransform transformation from object to world frame.
Definition: RobRotation.py:672
def GetResources(self)
Method used by FreeCAD to retrieve resources to use for this command.
frame_shaft
A SoLineSet shaft for frame axes.
frame_arrowhead_cone
A SoCone arrowhead cone for frame axes.
def __getstate__(self)
Necessary method to avoid errors when trying to save unserializable objects.
Definition: RobRotation.py:887
def change_joint_sequence(self, joint_sequence)
Method used to change a RobRotation's joint variable sequence.
Definition: RobRotation.py:420
def attach(self, vp)
Method called by FreeCAD after initialization to attach Coin3D constructs.
Definition: RobRotation.py:670
def updateData(self, fp, prop)
Method called after DocumentObjectGroupPython RobRotation was changed.
Definition: RobRotation.py:703
def onChanged(self, fp, prop)
Method called after DocumentObjectGroupPython RobRotation was changed.
Definition: RobRotation.py:111
def execute(self, fp)
Method called when recomputing a DocumentObjectGroupPython.
Definition: RobRotation.py:167