RobTranslation.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 
90 
91  def __init__(self, fp):
92  # Add (and preset) properties
93  self.setProperties(fp)
94  fp.Proxy = self
95 
96 
107 
108  def onChanged(self, fp, prop):
109  # Ignore updates to ranges
110  if self.updated:
111  self.updated = False
112  return
113 
114  # Control allowed theta range limits
115  elif prop == "dMinimum" and hasattr(fp, "dMaximum"):
116  self.updated = True
117  fp.dMaximum = (fp.dMaximum, fp.dMinimum, float("inf"), 1)
118  elif prop == "dMaximum" and hasattr(fp, "dMinimum"):
119  self.updated = True
120  fp.dMinimum = (fp.dMinimum, -float("inf"), fp.dMaximum, 1)
121 
122  # Check that a translation has valid format
123  elif self.is_translation_property(prop) and \
124  hasattr(fp, "dMaximum") and \
125  hasattr(fp, "dMinimum") and \
126  hasattr(self, "fp"):
127  trans_valid = self.is_ValidTranslation(fp.Timestamps, fp.dSequence)
128  if trans_valid != fp.ValidTranslation:
129  fp.ValidTranslation = trans_valid
130 
131  elif prop == "Placement":
132  # Propagate the Placement updates down the chain
133  if hasattr(fp, "Group") and len(fp.Group) != 0:
134  for child in fp.Group:
135  child.ParentFramePlacement = fp.Placement
136  child.purgeTouched()
137 
138  # Display animated objects in a pose specified by the rotation
139  # and current time
140  if hasattr(fp, "AnimatedObjects") and len(fp.AnimatedObjects) != 0:
141  for o in fp.AnimatedObjects:
142  o.Placement = fp.Placement
143  o.purgeTouched()
144 
145  elif prop == "ParentFramePlacement":
146  # If parent frame changed, recompute placement
147  fp.Placement = fp.ParentFramePlacement.multiply(
148  fp.ObjectPlacement)
149 
150 
160 
161  def execute(self, fp):
162  # Check that joint is not controlled by a RobotPanel
163  if not fp.RobotPanelActive:
164  # Check that current translation has valid format
165  if not fp.ValidTranslation:
166  FreeCAD.Console.PrintWarning(fp.Name +
167  ".execute(): Translation " +
168  "is not in a valid format.\n")
169  return
170 
171  # Update d according to current time and translation
172  indices, weights = self.find_timestamp_indices_and_weights(fp)
173 
174  fp.d = fp.dOffset + (weights[0]*fp.dSequence[indices[0]]
175  + weights[1]*fp.dSequence[indices[1]])
176 
177  # DH transformation
178  T_theta = FreeCAD.Placement(FreeCAD.Vector(0, 0, 0),
179  FreeCAD.Rotation(FreeCAD.Vector(0, 0, 1),
180  fp.theta))
181  T_d = FreeCAD.Placement(FreeCAD.Vector(0, 0, fp.d),
182  FreeCAD.Rotation(FreeCAD.Vector(0, 0, 1), 0))
183  T_a = FreeCAD.Placement(FreeCAD.Vector(fp.a, 0, 0),
184  FreeCAD.Rotation(FreeCAD.Vector(1, 0, 0), 0))
185  T_alpha = FreeCAD.Placement(FreeCAD.Vector(0, 0, 0),
186  FreeCAD.Rotation(FreeCAD.Vector(1, 0, 0),
187  fp.alpha))
188 
189  fp.ObjectPlacement = T_theta.multiply(T_d.multiply(
190  T_a.multiply(T_alpha)))
191 
192  # Placement update
193  fp.Placement = fp.ParentFramePlacement.multiply(fp.ObjectPlacement)
194 
195 
204 
205  def onDocumentRestored(self, fp):
206  fp.ViewObject.Proxy.setProperties(fp.ViewObject)
207  self.setProperties(fp)
208 
209  # supporting methods-------------------------------------------------------
210 
218 
219  def setProperties(self, fp):
220  # Add (and preset) properties
221  # Animation properties
222  if not hasattr(fp, "ValidTranslation"):
223  fp.addProperty("App::PropertyBool", "ValidTranslation", "General",
224  "This property records if rotation was changed."
225  ).ValidTranslation = False
226  if not hasattr(fp, "RobotPanelActive"):
227  fp.addProperty("App::PropertyBool", "RobotPanelActive", "General",
228  "This property records if robot panel is active."
229  ).RobotPanelActive = False
230  if not hasattr(fp, "AnimatedObjects"):
231  fp.addProperty("App::PropertyLinkListGlobal", "AnimatedObjects",
232  "General", "Objects that will be animated.")
233  if not hasattr(fp, "Interpolate"):
234  fp.addProperty("App::PropertyBool", "Interpolate", "General",
235  "Interpolate RobTranslation between timestamps."
236  ).Interpolate = True
237  if not hasattr(fp, "AllowServer"):
238  fp.addProperty("App::PropertyBool", "AllowServer", "General",
239  "Should this object allow a Server object to "
240  + "change it.").AllowServer = True
241  if not hasattr(fp, "AllowControl"):
242  fp.addProperty("App::PropertyBool", "AllowControl", "General",
243  "Should this object allow a Control object "
244  + " to change it."
245  ).AllowControl = True
246  if not hasattr(fp, "Time"):
247  fp.addProperty("App::PropertyFloat", "Time", "General",
248  "Animation time in seconds.").Time = 0
249  if not hasattr(fp, "ParentFramePlacement"):
250  fp.addProperty("App::PropertyPlacement", "ParentFramePlacement",
251  "General", "Current placement of a Parent Frame.")
252  if not hasattr(fp, "ObjectPlacement"):
253  fp.addProperty("App::PropertyPlacement", "ObjectPlacement",
254  "General",
255  "Current Object placement in a Parent Frame.")
256 
257  # DH parameters
258  if not hasattr(fp, "d"):
259  fp.addProperty("App::PropertyFloat", "d", "d-hParameters",
260  "Displacement along Z axis.").d = 0
261  if not hasattr(fp, "dMaximum"):
262  fp.addProperty("App::PropertyFloatConstraint", "dMaximum",
263  "d-hParameters", "Upper limit of displacement"
264  + " along Z axis."
265  ).dMaximum = (1000, 0, float("inf"), 1)
266  elif hasattr(fp, "dMinimum"):
267  fp.dMaximum = (fp.dMaximum, fp.dMinimum, float("inf"), 1)
268  if not hasattr(fp, "dMinimum"):
269  fp.addProperty("App::PropertyFloatConstraint", "dMinimum",
270  "d-hParameters", "Lower limit of displacement"
271  + " alon Z axis."
272  ).dMinimum = (0, -float("inf"), 1000, 1)
273  elif hasattr(fp, "dMaximum"):
274  fp.dMinimum = (fp.dMinimum, -float("inf"), fp.dMaximum, 1)
275  if not hasattr(fp, "dOffset"):
276  fp.addProperty("App::PropertyFloat", "dOffset",
277  "d-hParameters", "Offset of displacement"
278  + " along Z axis.").dOffset = 0
279  if not hasattr(fp, "a"):
280  fp.addProperty("App::PropertyFloat", "a", "d-hParameters",
281  "Displacement along X axis.").a = 0
282  if not hasattr(fp, "alpha"):
283  fp.addProperty("App::PropertyFloat", "alpha", "d-hParameters",
284  "Rotation angle about X axis in degrees.").alpha = 0
285  if not hasattr(fp, "theta"):
286  fp.addProperty("App::PropertyFloat", "theta", "d-hParameters",
287  "Rotation angle about X axis in degrees.").theta = 0
288 
289  # Frame properties
290  if not hasattr(fp, "ShowFrame"):
291  fp.addProperty("App::PropertyBool", "ShowFrame", "Frame",
292  "Show a frame for current pose."
293  ).ShowFrame = True
294  if not hasattr(fp, "FrameTransparency"):
295  fp.addProperty("App::PropertyPercent", "FrameTransparency",
296  "Frame", "Transparency of the frame in percents."
297  ).FrameTransparency = 0
298  if not hasattr(fp, "ShowFrameArrowheads"):
299  fp.addProperty("App::PropertyBool", "ShowFrameArrowheads", "Frame",
300  "Show arrowheads for frame axis arrow's."
301  ).ShowFrameArrowheads = True
302  if not hasattr(fp, "FrameArrowheadLength"):
303  fp.addProperty("App::PropertyFloatConstraint",
304  "FrameArrowheadLength", "Frame",
305  "Frame axis arrow's arrowhead length.\n"
306  + "Range is < 1.0 | 1e6 >."
307  ).FrameArrowheadLength = (10, 1.0, 1e6, 1)
308  else:
309  fp.FrameArrowheadLength = (fp.FrameArrowheadLength, 1.0, 1e6, 1)
310  if not hasattr(fp, "FrameArrowheadRadius"):
311  fp.addProperty("App::PropertyFloatConstraint",
312  "FrameArrowheadRadius", "Frame",
313  "Frame axis arrow's arrowhead bottom radius.\n"
314  + "Range is < 0.5 | 1e6 >."
315  ).FrameArrowheadRadius = (5, 0.5, 1e6, 0.5)
316  else:
317  fp.FrameArrowheadRadius = (fp.FrameArrowheadRadius, 0.5, 1e6, 0.5)
318  if not hasattr(fp, "ShaftLength"):
319  fp.addProperty("App::PropertyFloatConstraint", "ShaftLength",
320  "Frame", "Frame axis arrow's shaft length.\n"
321  + "Range is < 1.0 | 1e6 >."
322  ).ShaftLength = (20, 1.0, 1e6, 1)
323  else:
324  fp.ShaftLength = (fp.ShaftLength, 1.0, 1e6, 1)
325  if not hasattr(fp, "ShaftWidth"):
326  fp.addProperty("App::PropertyFloatConstraint", "ShaftWidth",
327  "Frame", "Frame axis arrow's shaft width.\n"
328  + "Range is < 1.0 | 64 >."
329  ).ShaftWidth = (4, 1.0, 64, 1)
330  else:
331  fp.ShaftWidth = (fp.ShaftWidth, 1.0, 64, 1)
332  if not hasattr(fp, "ShowFrameLabels"):
333  fp.addProperty("App::PropertyBool", "ShowFrameLabels",
334  "Frame", "Show label for frame axes."
335  ).ShowFrameLabels = True
336 
337  # Label properties
338  if not hasattr(fp, "FontSize"):
339  fp.addProperty("App::PropertyIntegerConstraint", "FontSize",
340  "Labels", "Label font size.\n"
341  + "Range is < 1 | 100 >."
342  ).FontSize = (10, 1, 100, 1)
343  else:
344  fp.FontSize = (fp.FontSize, 1, 100, 1)
345  if not hasattr(fp, "DistanceToAxis"):
346  fp.addProperty("App::PropertyFloatConstraint", "DistanceToAxis",
347  "Labels", "Distance from label to its axis.\n"
348  + "Range is < 0.5 | 1e6 >."
349  ).DistanceToAxis = (5, 0.5, 1e6, 0.5)
350  else:
351  fp.DistanceToAxis = (fp.DistanceToAxis, 0.5, 1e6, 0.5)
352  if not hasattr(fp, "Subscription"):
353  fp.addProperty("App::PropertyString", "Subscription", "Labels",
354  "Subscription added to an axis name."
355  ).Subscription = ""
356  if not hasattr(fp, "Superscription"):
357  fp.addProperty("App::PropertyString", "Superscription", "Labels",
358  "Superscription added to an axis name."
359  ).Superscription = ""
360  if not hasattr(fp, "FontFamily"):
361  fp.addProperty("App::PropertyEnumeration", "FontFamily",
362  "Labels", "Label font family."
363  ).FontFamily = ["SERIF", "SANS", "TYPEWRITER"]
364  if not hasattr(fp, "FontStyle"):
365  fp.addProperty("App::PropertyEnumeration", "FontStyle",
366  "Labels", "Label font style."
367  ).FontStyle = ["NONE", "BOLD", "ITALIC",
368  "BOLD ITALIC"]
369 
370  # Placement properties
371  if not hasattr(fp, "Placement"):
372  fp.addProperty("App::PropertyPlacement", "Placement", "Base",
373  "Current placement for animated objects in "
374  + "world frame.")
375 
376  # Rotation properties
377  if not hasattr(fp, "Timestamps"):
378  fp.addProperty("App::PropertyFloatList", "Timestamps",
379  "Translation", "Timestamps at which we define\n"
380  + "translation.")
381  if not hasattr(fp, "dSequence"):
382  fp.addProperty("App::PropertyFloatList", "dSequence",
383  "Translation", "Displacements along Z axis.")
384 
385  # Add feature python
386  self.fp = fp
387 
388  # Make some properties read-only
389  fp.setEditorMode("ObjectPlacement", 1)
390  fp.setEditorMode("ParentFramePlacement", 1)
391  fp.setEditorMode("d", 1)
392 
393  # Hide some properties
394  fp.setEditorMode("Placement", 2)
395  fp.setEditorMode("ValidTranslation", 2)
396  fp.setEditorMode("RobotPanelActive", 2)
397 
398  import AnimateDocumentObserver
400 
401 
410 
411  def change_joint_sequence(self, joint_sequence):
412  # Check that RobTranslation has a correct format and load it
413  if self.is_ValidTranslation(translation=joint_sequence):
414  self.fp.Timestamps = joint_sequence["Timestamps"]
415  self.fp.dSequence = joint_sequence["dSequence"]
416  else:
417  FreeCAD.Console.PrintError("Invalid joint sequence!")
418 
419 
429 
430  def is_translation_property(self, prop):
431  return prop in ["Timestamps", "dSequence"]
432 
433 
449 
450  def is_ValidTranslation(self, timestamps=[], ds=[], translation=None):
451  # Check all keys are included and record lengths of their lists
452  if translation is not None and isinstance(translation, dict):
453  for key in ["Timestamps", "dSequence"]:
454  if key not in translation.keys():
455  FreeCAD.Console.PrintWarning("Translation misses key " +
456  key + ".\n")
457  return False
458  timestamps = translation["Timestamps"]
459  ds = translation["dSequence"]
460 
461  # Check that all lists have the same length
462  if len(timestamps) == 0 or \
463  (len(timestamps) != 0 and len(timestamps) != len(ds)):
464  FreeCAD.Console.PrintWarning("Translation has lists with "
465  + "inconsistent or zero "
466  + "lengths.\n")
467  return False
468 
469  # Check timestamps correspond to list of increasing values
470  if any([timestamps[i] >= timestamps[i+1]
471  for i in range(len(timestamps)-1)]):
472  FreeCAD.Console.PrintWarning("Translation 'Timestamps' is not "
473  + "list of increasing values.\n")
474  return False
475 
476  if not all([self.fp.dMinimum <= d <= self.fp.dMaximum for d in ds]):
477  FreeCAD.Console.PrintWarning("Translation 'dSequence' elements"
478  + " are out of d range.\n")
479  return False
480 
481  return True
482 
483 
502 
504  # Retrieve indices corresponding to current time
505  # If the time is before the first Timestamp use the first Timestamp
506  if fp.Time <= fp.Timestamps[0]:
507  indices = [0, 0]
508  weights = [1, 0]
509 
510  # If the time is after the last Timpestamp use the last Timestamp
511  elif fp.Time >= fp.Timestamps[-1]:
512  indices = [-1, -1]
513  weights = [1, 0]
514 
515  # If time is in the range of Timesteps
516  else:
517  # Find the index of the closest higher value
518  indices = [bisect(fp.Timestamps, fp.Time)]
519  # Add the previous index
520  indices.insert(0, indices[0]-1)
521  weights = [fp.Timestamps[indices[1]] - fp.Time,
522  fp.Time - fp.Timestamps[indices[0]]]
523  if not fp.Interpolate:
524  if weights[0] > weights[1]:
525  weights = [1, 0]
526  else:
527  weights = [0, 1]
528  else:
529  weights = [weights[0]/sum(weights), weights[1]/sum(weights)]
530 
531  return indices, weights
532 
533 
545 
546  def __getstate__(self):
547  return None
548 
549 
558 
559  def __setstate__(self, state):
560  return None
561 
562 
563 
578 
580 
581 
583 
584 
586 
587 
589 
590 
592 
593 
595 
596 
598 
599 
601 
602 
604 
605 
607 
608 
610 
611 
613 
614 
616 
617 
619 
620 
622 
623 
625 
626 
628 
629 
631 
632  panel = None
633  fp = None
634 
635  # standard methods---------------------------------------------------------
636 
645 
646  def __init__(self, vp):
647  self.setProperties(vp)
648  vp.Proxy = self
649 
650 
659 
660  def attach(self, vp):
661  # prepare transformation to keep pose corresponding to placement
662  self.tf_object2world = coin.SoTransform()
663 
664  labels = self.makeLabels()
665  self.font = coin.SoFontStyle()
666 
667  frame = self.makeFrame(labels[:3])
668  frame.insertChild(self.tf_object2world, 0)
669  frame.insertChild(self.font, 1)
670  self.frame = coin.SoSwitch()
671  self.frame.addChild(frame)
672 
673  self.visualisations = coin.SoSwitch()
674  self.visualisations.addChild(self.frame)
675  self.visualisations.whichChild.setValue(coin.SO_SWITCH_ALL)
676  vp.RootNode.addChild(self.visualisations)
677 
678  vp.Object.Proxy.setProperties(vp.Object)
679  self.setProperties(vp)
680  self.fp = vp.Object
681 
682 
692 
693  def updateData(self, fp, prop):
694  # Placement changes
695  if prop == "Placement" and hasattr(fp, "Placement"):
696  trans = fp.Placement.Base
697  rot = fp.Placement.Rotation
698  self.tf_object2world.translation.setValue((trans.x, trans.y,
699  trans.z))
700  self.tf_object2world.rotation.setValue(rot.Q)
701 
702  # Frame changes
703  elif prop == "ShowFrame" and hasattr(fp, "ShowFrame"):
704  if fp.ShowFrame:
705  self.frame.whichChild.setValue(coin.SO_SWITCH_ALL)
706  else:
707  self.frame.whichChild.setValue(coin.SO_SWITCH_NONE)
708 
709  elif prop == "FrameTransparency" and hasattr(fp, "FrameTransparency"):
710  self.frame_color_x.orderedRGBA.\
711  setValue(0xff0000ff - (0xff*fp.FrameTransparency)//100)
712  self.frame_color_y.orderedRGBA.\
713  setValue(0x00ff00ff - (0xff*fp.FrameTransparency)//100)
714  self.frame_color_z.orderedRGBA.\
715  setValue(0x0000ffff - (0xff*fp.FrameTransparency)//100)
716 
717  elif prop == "ShaftLength" and hasattr(fp, "ShaftLength"):
718  self.frame_shaft.vertexProperty.getValue().vertex.\
719  set1Value(1, 0, fp.ShaftLength, 0)
720  if hasattr(fp, "FrameArrowheadLength"):
721  self.frame_arrowhead_translation.translation.setValue(
722  0, fp.ShaftLength + fp.FrameArrowheadLength/2, 0)
723  if not fp.ShowFrameArrowheads and hasattr(fp, "DistanceToAxis"):
724  self.label_translations[0].translation.setValue(
725  0, fp.ShaftLength + fp.DistanceToAxis, 0)
726 
727  elif prop == "FrameArrowheadLength" and \
728  hasattr(fp, "FrameArrowheadLength"):
729  self.frame_arrowhead_cone.height.setValue(fp.FrameArrowheadLength)
730  if hasattr(fp, "ShaftLength"):
731  self.frame_arrowhead_translation.translation.setValue(
732  0, fp.ShaftLength + fp.FrameArrowheadLength/2, 0)
733  if fp.ShowFrameArrowheads and hasattr(fp, "DistanceToAxis"):
734  self.label_translations[0].translation.setValue(
735  0, fp.FrameArrowheadLength/2 + fp.DistanceToAxis, 0)
736 
737  elif prop == "ShaftWidth" and hasattr(fp, "ShaftWidth"):
738  self.frame_drawstyle.lineWidth.setValue(fp.ShaftWidth)
739 
740  elif prop == "FrameArrowheadRadius" and \
741  hasattr(fp, "FrameArrowheadRadius"):
742  self.frame_arrowhead_cone.bottomRadius.setValue(
743  fp.FrameArrowheadRadius)
744 
745  elif prop == "ShowFrameArrowheads" and \
746  hasattr(fp, "ShowFrameArrowheads"):
747  if fp.ShowFrameArrowheads:
748  self.frame_arrowhead.whichChild.setValue(coin.SO_SWITCH_ALL)
749  if hasattr(fp, "FrameArrowheadLength") and \
750  hasattr(fp, "DistanceToAxis"):
751  self.label_translations[0].translation.setValue(
752  0, fp.FrameArrowheadLength/2 + fp.DistanceToAxis, 0)
753  else:
754  self.frame_arrowhead.whichChild.setValue(coin.SO_SWITCH_NONE)
755  if hasattr(fp, "ShaftLength") and \
756  hasattr(fp, "DistanceToAxis"):
757  self.label_translations[0].translation.setValue(
758  0, fp.ShaftLength + fp.DistanceToAxis, 0)
759 
760  elif prop == "ShowFrameLabels" and hasattr(fp, "ShowFrameLabels"):
761  for label in self.labels[:3]:
762  if fp.ShowFrameLabels:
763  label.whichChild.setValue(coin.SO_SWITCH_ALL)
764  else:
765  label.whichChild.setValue(coin.SO_SWITCH_NONE)
766 
767  # Changes to the labels
768  elif prop == "Subscription" and hasattr(fp, "Subscription"):
769  for l in self.label_texts:
770  l.string.setValues(2, 1, [fp.Subscription])
771 
772  elif prop == "Superscription" and hasattr(fp, "Superscription"):
773  for l in self.label_texts:
774  l.string.setValues(0, 1, [fp.Superscription])
775 
776  elif prop == "FontFamily" and hasattr(fp, "FontFamily"):
777  if fp.FontFamily == "SERIF":
778  self.font.family.setValue(self.font.SERIF)
779  if fp.FontFamily == "SANS":
780  self.font.family.setValue(self.font.SANS)
781  if fp.FontFamily == "TYPEWRITER":
782  self.font.family.setValue(self.font.TYPEWRITER)
783 
784  elif prop == "FontStyle" and hasattr(fp, "FontStyle"):
785  if fp.FontStyle == "NONE":
786  self.font.style.setValue(self.font.NONE)
787  if fp.FontStyle == "BOLD":
788  self.font.style.setValue(self.font.BOLD)
789  if fp.FontStyle == "ITALIC":
790  self.font.style.setValue(self.font.ITALIC)
791  if fp.FontStyle == "BOLD ITALIC":
792  self.font.style.setValue(self.font.BOLD | self.font.ITALIC)
793 
794  elif prop == "FontSize" and hasattr(fp, "FontSize"):
795  self.font.size.setValue(fp.FontSize)
796 
797  elif prop == "DistanceToAxis" and hasattr(fp, "DistanceToAxis") and \
798  hasattr(fp, "ShowFrameArrowheads"):
799  if fp.ShowFrameArrowheads and hasattr(fp, "FrameArrowheadLength"):
800  self.label_translations[0].translation.setValue(
801  0, fp.FrameArrowheadLength/2 + fp.DistanceToAxis, 0)
802  elif hasattr(fp, "ShaftLength"):
803  self.label_translations[0].translation.setValue(
804  0, fp.ShaftLength + fp.DistanceToAxis, 0)
805 
806 
815 
816  def onChanged(self, vp, prop):
817  if prop == "Visibility":
818  if vp.Visibility:
819  self.visualisations.whichChild.setValue(coin.SO_SWITCH_ALL)
820  else:
821  self.visualisations.whichChild.setValue(coin.SO_SWITCH_NONE)
822 
823 
831 
832  def claimChildren(self):
833  if hasattr(self, "fp") and self.fp:
834  return self.fp.Group
835  return []
836 
837 
845 
846  def canDropObject(self, obj):
847  if hasattr(obj, "Proxy") and \
848  (obj.Proxy.__class__.__name__ == "RobRotationProxy" or
849  obj.Proxy.__class__.__name__ == "RobTranslationProxy"):
850  return True
851  return False
852 
853 
860 
861  def getIcon(self):
862  return path.join(PATH_TO_ICONS, "RobTranslation.png")
863 
864 
876 
877  def __getstate__(self):
878  return None
879 
880 
886 
887  def __setstate__(self, state):
888  pass
889 
890 
897 
898  def setProperties(self, vp):
899  # hide unnecessary view properties
900  vp.setEditorMode("DisplayMode", 2)
901 
902 
916 
917  def doubleClicked(self, vp):
918  # Switch to the Task View if a RobotPanel is already opened
919  if self.panel:
920  FreeCADGui.Control.showTaskView()
921 
922  # Try to open new RobotPanel
923  else:
924  # Load the QDialog from a file and name it after this object
925  new_form = [FreeCADGui.PySideUic.loadUi(path.join(PATH_TO_UI,
926  "AnimationJoint.ui"))]
927  new_form[0].setWindowTitle(vp.Object.Label)
928 
929  # Create a control panel and try to show it
930  self.panel = RobotPanel([vp.Object], new_form)
931  try:
932  FreeCADGui.Control.showDialog(self.panel)
933  except RuntimeError as e:
934  # Reset the panel
935  self.panel = None
936 
937  # Find all RobRotation/RobTranslation feature python objects
938  # with a reference to a RobotPanel
939  robot_joints = []
940  for obj in FreeCAD.ActiveDocument.Objects:
941  if hasattr(obj, "Proxy") and \
942  (obj.Proxy.__class__.__name__
943  == "RobRotationProxy"
944  or obj.Proxy.__class__.__name__
945  == "RobTranslationProxy"):
946  if obj.ViewObject.Proxy.panel is not None:
947  robot_joints.append(obj)
948 
949  if len(robot_joints) > 0:
950  # Close opened Trajecotry panel
951  robot_joints[0].ViewObject.Proxy.panel.reject()
952 
953  # Load the QDialog form for each RobRotation/RobTranslation
954  # which had a reference to the panel
955  forms = []
956  for joint in robot_joints:
957  form = FreeCADGui.PySideUic.loadUi(
958  path.join(PATH_TO_UI, "AnimationJoint.ui"))
959  form.setWindowTitle(joint.Label)
960  forms.append(form)
961 
962  # Load one more QDialog for this RobTranslation
963  forms.append(new_form[0])
964 
965  # Add this RobTranslation to the list of robot_joints
966  robot_joints.append(vp.Object)
967 
968  # Add a reference to the new panel to view providers
969  # of all robot_joints
970  self.panel = RobotPanel(robot_joints, forms)
971  for joint in robot_joints:
972  joint.ViewObject.Proxy.panel = self.panel
973  FreeCADGui.Control.showDialog(self.panel)
974  return True
975 
976  # Diffeerent Task panel is opened, inform the user
977  else:
978  QMessageBox.warning(
979  None,
980  'Error while opening RobotPanel',
981  "A different panel is already active.\n"
982  + "Close it before opening this one.")
983  FreeCADGui.Control.showTaskView()
984  return True
985 
986 
996 
997  def setupContextMenu(self, vp, menu):
998  menu.clear()
999  action = menu.addAction("Check joint range")
1000  action.triggered.connect(lambda f=self.doubleClicked,
1001  arg=vp: f(arg))
1002 
1003 
1011 
1012  def makeLabels(self):
1013  label_strings = ["X", "Y", "Z"]
1014  colors = [0xFF0000FF, 0x00FF00FF, 0x0000FFFF]
1015  self.label_texts = []
1017  # frame translation
1018  self.label_translations.append(coin.SoTranslation())
1019  # axis translation
1020  self.label_translations.append(coin.SoTranslation())
1021  self.labels = []
1022  for i in range(3):
1023  label_group = coin.SoSeparator()
1024  label_group.addChild(self.label_translations[0])
1025  frame_axis_color = coin.SoPackedColor()
1026  frame_axis_color.orderedRGBA.setValue(colors[i])
1027  label_group.addChild(frame_axis_color)
1028  self.label_texts.append(coin.SoText2())
1029  self.label_texts[i].string.setValues(
1030  0, 3, ["", label_strings[i], ""])
1031  self.label_texts[i].justification.setValue(
1032  self.label_texts[i].CENTER)
1033  self.label_texts[i].spacing.setValue(0.45)
1034  label_group.addChild(self.label_texts[i])
1035  self.labels.append(coin.SoSwitch())
1036  self.labels[i].addChild(label_group)
1037  return self.labels
1038 
1039 
1049 
1050  def makeFrame(self, frame_labels):
1051  # make a generic shaft from 0 in Y direction
1052  shaft_vertices = coin.SoVertexProperty()
1053  shaft_vertices.vertex.setNum(2)
1054  shaft_vertices.vertex.set1Value(0, 0, 0, 0)
1055  self.frame_shaft = coin.SoLineSet()
1056  self.frame_shaft.vertexProperty.setValue(shaft_vertices)
1057  self.frame_shaft.numVertices.setNum(1)
1058  self.frame_shaft.numVertices.setValue(2)
1059 
1060  # make a generic conic arrowhead oriented in Y axis direction and
1061  # move it at the end of the shaft
1062  self.frame_arrowhead_translation = coin.SoTranslation()
1063  self.frame_arrowhead_cone = coin.SoCone()
1064  self.frame_arrowhead = coin.SoSwitch()
1065  self.frame_arrowhead.addChild(self.frame_arrowhead_translation)
1066  self.frame_arrowhead.addChild(self.frame_arrowhead_cone)
1067 
1068  # make rotations to rotate prepared shaft and arrowhead for Y axis
1069  # direction also to X and Z
1070  rot_y2x = coin.SoRotation()
1071  rot_y2x.rotation.setValue(coin.SbRotation(coin.SbVec3f(0, 1, 0),
1072  coin.SbVec3f(1, 0, 0)))
1073  rot_y2z = coin.SoRotation()
1074  rot_y2z.rotation.setValue(coin.SbRotation(coin.SbVec3f(0, 1, 0),
1075  coin.SbVec3f(0, 0, 1)))
1076 
1077  # prepare colors for X,Y,Z which will correspond to R,G,B as customary
1078  self.frame_color_x = coin.SoPackedColor()
1079  self.frame_color_y = coin.SoPackedColor()
1080  self.frame_color_z = coin.SoPackedColor()
1081 
1082  # make complete colored and rotated arrows
1083  x_arrow = coin.SoSeparator()
1084  x_arrow.addChild(rot_y2x)
1085  x_arrow.addChild(self.frame_color_x)
1086  x_arrow.addChild(self.frame_shaft)
1087  x_arrow.addChild(self.frame_arrowhead)
1088  x_arrow.addChild(frame_labels[0])
1089  y_arrow = coin.SoSeparator()
1090  y_arrow.addChild(self.frame_color_y)
1091  y_arrow.addChild(self.frame_shaft)
1092  y_arrow.addChild(self.frame_arrowhead)
1093  y_arrow.addChild(frame_labels[1])
1094  z_arrow = coin.SoSeparator()
1095  z_arrow.addChild(rot_y2z)
1096  z_arrow.addChild(self.frame_color_z)
1097  z_arrow.addChild(self.frame_shaft)
1098  z_arrow.addChild(self.frame_arrowhead)
1099  z_arrow.addChild(frame_labels[2])
1100 
1101  # prepare draw style to control shaft width
1102  self.frame_drawstyle = coin.SoDrawStyle()
1103 
1104  # make complete frame and it to shaded display mode
1105  separated_frame = coin.SoSeparator()
1106  separated_frame.addChild(self.frame_drawstyle)
1107  separated_frame.addChild(x_arrow)
1108  separated_frame.addChild(y_arrow)
1109  separated_frame.addChild(z_arrow)
1110 
1111  return separated_frame
1112 
1113 
1114 
1120 
1122 
1123 
1130 
1131  def GetResources(self):
1132  return {'Pixmap': path.join(PATH_TO_ICONS, "RobTranslationCmd.png"),
1133  'MenuText': "RobTranslation",
1134  'ToolTip': "Create RobTranslation instance."}
1135 
1136 
1143 
1144  def Activated(self):
1145  doc = FreeCAD.ActiveDocument
1146  a = doc.addObject("App::DocumentObjectGroupPython", "RobTranslation")
1148  if FreeCAD.GuiUp:
1149  ViewProviderRobTranslationProxy(a.ViewObject)
1150  doc.recompute()
1151  return
1152 
1153 
1162 
1163  def IsActive(self):
1164  if FreeCAD.ActiveDocument is None:
1165  return False
1166  else:
1167  return True
1168 
1169 
1170 if FreeCAD.GuiUp:
1171  # Add command to FreeCAD Gui when importing this module in InitGui
1172  FreeCADGui.addCommand('RobTranslationCommand', RobTranslationCommand())
frame_drawstyle
A SoDrawStyle controlling frame axes shaft line width.
def __getstate__(self)
Necessary method to avoid errors when trying to save unserializable objects.
Class specifying Animate workbench's RobTranslation button/command.
tf_object2world
A SoTransform transformation from object to world frame.
def updateData(self, fp, prop)
Method called after DocumentObjectGroupPython RobTranslation was changed.
def Activated(self)
Method used as a callback when the toolbar button or the menu item is clicked.
frame_shaft
A SoLineSet shaft for frame axes.
fp
A DocumentObjectGroupPython associated with the proxy.
font
A SoFontStyle font for axes labels.
def onDocumentRestored(self, fp)
Method called when document is restored to make sure everything is as it was.
def addObserver()
Adds an AnimateDocumentObserver between FreeCAD's document observers safely.
def IsActive(self)
Method to specify when the toolbar button and the menu item are enabled.
frame_color_y
A SoPackedColor green color for an Y axis.
def __init__(self, fp)
Initialization method for RobTranslationProxy.
def onChanged(self, vp, prop)
Method called after RobTranslation.ViewObject was changed.
def GetResources(self)
Method used by FreeCAD to retrieve resources to use for this command.
def claimChildren(self)
Method called by FreeCAD to retrieve assigned children.
def makeLabels(self)
Method which makes Coin3D labels to be displayed in the FreeCAD View.
def canDropObject(self, obj)
Method called by FreeCAD to ask if an object obj can be dropped into a Group.
panel
A RobotPanel if one is active or None.
frame
A SoSeparator with a coordinate frame made from 3 RGB arrows.
frame_arrowhead
A SoSwitch translated cone for frame axes.
def setProperties(self, fp)
Method to set properties during initialization or document restoration.
frame_color_z
A SoPackedColor blue color for an Z axis.
def execute(self, fp)
Method called when recomputing a DocumentObjectGroupPython.
def makeFrame(self, frame_labels)
Method which makes a Coin3D frame to show a current pose in a RobTranslation.
def doubleClicked(self, vp)
Method called when RobTranslation is double-clicked in the Tree View.
def change_joint_sequence(self, joint_sequence)
Method used to change a RobTranslation's joint variable sequence.
def setProperties(self, vp)
Method to hide unused properties.
labels
A list of SoSwitches containing colored translated labels.
Proxy class for Gui.ViewProviderDocumentObject RobTranslation.ViewObject.
Proxy class for a DocumentObjectGroupPython RobTranslation instance.
def __setstate__(self, state)
Necessary method to avoid errors when trying to restore unserializable objects.
def __init__(self, vp)
Initialization method for ViewProviderRobTranslationProxy.
def is_translation_property(self, prop)
Method to check that a property describes a translation.
def getIcon(self)
Method called by FreeCAD to supply an icon for the Tree View.
def setupContextMenu(self, vp, menu)
Method called by the FreeCAD to customize a RobTranslation context menu.
label_translations
A list of SoTranslations moving labels.
frame_color_x
A SoPackedColor red color for an X axis.
def onChanged(self, fp, prop)
Method called after DocumentObjectGroupPython RobTranslation was changed.
Class providing funcionality to a RobRotation panel inside the TaskView.
Definition: RobotPanel.py:51
def is_ValidTranslation(self, timestamps=[], ds=[], translation=None)
Method to check if a translation is valid.
def find_timestamp_indices_and_weights(self, fp)
Method to find weighted timestamps indices corresponding to a given time.
frame_arrowhead_translation
A SoTranslation moving frame arrowheads.
def __setstate__(self, state)
Necessary method to avoid errors when trying to restore unserializable objects.
frame_arrowhead_cone
A SoCone arrowhead cone for frame axes.
label_texts
A list of SoText2s labels denoting all axes and an origin.
def __getstate__(self)
Necessary method to avoid errors when trying to save unserializable objects.
bool updated
A bool - True if a property was changed by a class and not user.
def attach(self, vp)
Method called by FreeCAD after initialization to attach Coin3D constructs.
visualisations
A SoSwitch with all visualisations (frame & rotation axis).