Skip to content

Commit db6e9b1

Browse files
committed
move dotfile and showgraph into baserobot
1 parent 1c4bdf3 commit db6e9b1

File tree

2 files changed

+489
-244
lines changed

2 files changed

+489
-244
lines changed

roboticstoolbox/robot/BaseRobot.py

Lines changed: 246 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from copy import deepcopy
1111
from functools import lru_cache
1212
from typing import (
13+
IO,
1314
TYPE_CHECKING,
1415
Any,
1516
Callable,
@@ -49,6 +50,7 @@
4950
from roboticstoolbox.backends.PyPlot import PyPlot, PyPlot2
5051
from roboticstoolbox.backends.PyPlot.EllipsePlot import EllipsePlot
5152

53+
5254
if TYPE_CHECKING:
5355
from matplotlib.cm import Color # pragma nocover
5456
else:
@@ -2838,3 +2840,247 @@ def teach(
28382840
return env
28392841

28402842
# --------------------------------------------------------------------- #
2843+
2844+
# --------------------------------------------------------------------- #
2845+
# --------- Utility Methods ------------------------------------------- #
2846+
# --------------------------------------------------------------------- #
2847+
2848+
def showgraph(self, display_graph: bool = True, **kwargs) -> Union[None, str]:
2849+
"""
2850+
Display a link transform graph in browser
2851+
2852+
``robot.showgraph()`` displays a graph of the robot's link frames
2853+
and the ETS between them. It uses GraphViz dot.
2854+
2855+
The nodes are:
2856+
- Base is shown as a grey square. This is the world frame origin,
2857+
but can be changed using the ``base`` attribute of the robot.
2858+
- Link frames are indicated by circles
2859+
- ETS transforms are indicated by rounded boxes
2860+
2861+
The edges are:
2862+
- an arrow if `jtype` is False or the joint is fixed
2863+
- an arrow with a round head if `jtype` is True and the joint is
2864+
revolute
2865+
- an arrow with a box head if `jtype` is True and the joint is
2866+
prismatic
2867+
2868+
Edge labels or nodes in blue have a fixed transformation to the
2869+
preceding link.
2870+
2871+
Parameters
2872+
----------
2873+
display_graph
2874+
Open the graph in a browser if True. Otherwise will return the
2875+
file path
2876+
etsbox
2877+
Put the link ETS in a box, otherwise an edge label
2878+
jtype
2879+
Arrowhead to node indicates revolute or prismatic type
2880+
static
2881+
Show static joints in blue and bold
2882+
2883+
Examples
2884+
--------
2885+
>>> import roboticstoolbox as rtb
2886+
>>> panda = rtb.models.URDF.Panda()
2887+
>>> panda.showgraph()
2888+
2889+
.. image:: ../figs/panda-graph.svg
2890+
:width: 600
2891+
2892+
See Also
2893+
--------
2894+
:func:`dotfile`
2895+
2896+
"""
2897+
2898+
# Lazy import
2899+
import tempfile
2900+
import subprocess
2901+
import webbrowser
2902+
2903+
# create the temporary dotfile
2904+
dotfile = tempfile.TemporaryFile(mode="w")
2905+
self.dotfile(dotfile, **kwargs)
2906+
2907+
# rewind the dot file, create PDF file in the filesystem, run dot
2908+
dotfile.seek(0)
2909+
pdffile = tempfile.NamedTemporaryFile(suffix=".pdf", delete=False)
2910+
subprocess.run("dot -Tpdf", shell=True, stdin=dotfile, stdout=pdffile)
2911+
2912+
# open the PDF file in browser (hopefully portable), then cleanup
2913+
if display_graph: # pragma nocover
2914+
webbrowser.open(f"file://{pdffile.name}")
2915+
else:
2916+
return pdffile.name
2917+
2918+
def dotfile(
2919+
self,
2920+
filename: Union[str, IO[str]],
2921+
etsbox: bool = False,
2922+
ets: L["full", "brief"] = "full",
2923+
jtype: bool = False,
2924+
static: bool = True,
2925+
):
2926+
"""
2927+
Write a link transform graph as a GraphViz dot file
2928+
2929+
The file can be processed using dot:
2930+
% dot -Tpng -o out.png dotfile.dot
2931+
2932+
The nodes are:
2933+
- Base is shown as a grey square. This is the world frame origin,
2934+
but can be changed using the ``base`` attribute of the robot.
2935+
- Link frames are indicated by circles
2936+
- ETS transforms are indicated by rounded boxes
2937+
2938+
The edges are:
2939+
- an arrow if `jtype` is False or the joint is fixed
2940+
- an arrow with a round head if `jtype` is True and the joint is
2941+
revolute
2942+
- an arrow with a box head if `jtype` is True and the joint is
2943+
prismatic
2944+
2945+
Edge labels or nodes in blue have a fixed transformation to the
2946+
preceding link.
2947+
2948+
Note
2949+
----
2950+
If ``filename`` is a file object then the file will *not*
2951+
be closed after the GraphViz model is written.
2952+
2953+
Parameters
2954+
----------
2955+
file
2956+
Name of file to write to
2957+
etsbox
2958+
Put the link ETS in a box, otherwise an edge label
2959+
ets
2960+
Display the full ets with "full" or a brief version with "brief"
2961+
jtype
2962+
Arrowhead to node indicates revolute or prismatic type
2963+
static
2964+
Show static joints in blue and bold
2965+
2966+
See Also
2967+
--------
2968+
:func:`showgraph`
2969+
2970+
"""
2971+
2972+
if isinstance(filename, str):
2973+
file = open(filename, "w")
2974+
else:
2975+
file = filename
2976+
2977+
header = r"""digraph G {
2978+
graph [rankdir=LR];
2979+
"""
2980+
2981+
def draw_edge(link, etsbox, jtype, static):
2982+
# draw the edge
2983+
if jtype:
2984+
if link.isprismatic:
2985+
edge_options = 'arrowhead="box", arrowtail="inv", dir="both"'
2986+
elif link.isrevolute:
2987+
edge_options = 'arrowhead="dot", arrowtail="inv", dir="both"'
2988+
else:
2989+
edge_options = 'arrowhead="normal"'
2990+
else:
2991+
edge_options = 'arrowhead="normal"'
2992+
2993+
if link.parent is None:
2994+
parent = "BASE"
2995+
else:
2996+
parent = link.parent.name
2997+
2998+
if etsbox:
2999+
# put the ets fragment in a box
3000+
if not link.isjoint and static:
3001+
node_options = ', fontcolor="blue"'
3002+
else:
3003+
node_options = ""
3004+
3005+
try:
3006+
file.write(
3007+
' {}_ets [shape=box, style=rounded, label="{}"{}];\n'.format(
3008+
link.name,
3009+
link.ets.__str__(q=f"q{link.jindex}"),
3010+
node_options,
3011+
)
3012+
)
3013+
except UnicodeEncodeError: # pragma nocover
3014+
file.write(
3015+
' {}_ets [shape=box, style=rounded, label="{}"{}];\n'.format(
3016+
link.name,
3017+
link.ets.__str__(q=f"q{link.jindex}")
3018+
.encode("ascii", "ignore")
3019+
.decode("ascii"),
3020+
node_options,
3021+
)
3022+
)
3023+
3024+
file.write(" {} -> {}_ets;\n".format(parent, link.name))
3025+
file.write(
3026+
" {}_ets -> {} [{}];\n".format(link.name, link.name, edge_options)
3027+
)
3028+
else:
3029+
# put the ets fragment as an edge label
3030+
if not link.isjoint and static:
3031+
edge_options += 'fontcolor="blue"'
3032+
if ets == "full":
3033+
estr = link.ets.__str__(q=f"q{link.jindex}")
3034+
elif ets == "brief":
3035+
if link.jindex is None:
3036+
estr = ""
3037+
else:
3038+
estr = f"...q{link.jindex}"
3039+
else:
3040+
return
3041+
try:
3042+
file.write(
3043+
' {} -> {} [label="{}", {}];\n'.format(
3044+
parent,
3045+
link.name,
3046+
estr,
3047+
edge_options,
3048+
)
3049+
)
3050+
except UnicodeEncodeError: # pragma nocover
3051+
file.write(
3052+
' {} -> {} [label="{}", {}];\n'.format(
3053+
parent,
3054+
link.name,
3055+
estr.encode("ascii", "ignore").decode("ascii"),
3056+
edge_options,
3057+
)
3058+
)
3059+
3060+
file.write(header)
3061+
3062+
# add the base link
3063+
file.write(" BASE [shape=square, style=filled, fillcolor=gray]\n")
3064+
3065+
# add the links
3066+
for link in self:
3067+
# draw the link frame node (circle) or ee node (doublecircle)
3068+
if link in self.ee_links:
3069+
# end-effector
3070+
node_options = 'shape="doublecircle", color="blue", fontcolor="blue"'
3071+
else:
3072+
node_options = 'shape="circle"'
3073+
3074+
file.write(" {} [{}];\n".format(link.name, node_options))
3075+
3076+
draw_edge(link, etsbox, jtype, static)
3077+
3078+
for gripper in self.grippers:
3079+
for link in gripper.links:
3080+
file.write(" {} [shape=cds];\n".format(link.name))
3081+
draw_edge(link, etsbox, jtype, static)
3082+
3083+
file.write("}\n")
3084+
3085+
if isinstance(filename, str):
3086+
file.close() # noqa

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy