Skip to content

Commit ebb0f89

Browse files
nimrod-gileadicopybara-github
authored andcommitted
Add abstract methods to mjcf.Element to make it a bit friendlier to type-safe code.
PiperOrigin-RevId: 437225654 Change-Id: I259920123295eff2db8ce6c05d3d36ab8ee938a0
1 parent 2a77562 commit ebb0f89

File tree

1 file changed

+232
-0
lines changed

1 file changed

+232
-0
lines changed

dm_control/mjcf/base.py

Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,235 @@ class Element(metaclass=abc.ABCMeta):
2929
tree, all references held automatically become invalid.
3030
"""
3131
__slots__ = []
32+
33+
@abc.abstractmethod
34+
def get_init_stack(self):
35+
"""Gets the stack trace where this element was first initialized."""
36+
37+
@abc.abstractmethod
38+
def get_last_modified_stacks_for_all_attributes(self):
39+
"""Gets a dict of stack traces where each attribute was last modified."""
40+
41+
@abc.abstractmethod
42+
def is_same_as(self, other):
43+
"""Checks whether another element is semantically equivalent to this one.
44+
45+
Two elements are considered equivalent if they have the same
46+
specification (i.e. same tag appearing in the same context), the same
47+
attribute values, and all of their children are equivalent. The ordering
48+
of non-repeated children is not important for this comparison, while
49+
the ordering of repeated children are important only amongst the same
50+
type* of children. In other words, for two bodies to be considered
51+
equivalent, their child sites must appear in the same order, and their
52+
child geoms must appear in the same order, but permutations between sites
53+
and geoms are disregarded. (The only exception is in tendon definition,
54+
where strict ordering of all children is necessary for equivalence.)
55+
56+
*Note that the notion of "same type" in this function is very loose:
57+
for example different actuator element subtypes are treated as separate
58+
types when children ordering is considered. Therefore, two <actuator>
59+
elements might be considered equivalent even though they result in different
60+
orderings of `mjData.ctrl` when compiled. As it stands, this function
61+
is designed primarily as a testing aid and should not be used to guarantee
62+
that models are actually identical.
63+
64+
Args:
65+
other: An `mjcf.Element`
66+
67+
Returns:
68+
`True` if `other` element is semantically equivalent to this one.
69+
"""
70+
71+
@property
72+
@abc.abstractmethod
73+
def tag(self):
74+
pass
75+
76+
@property
77+
@abc.abstractmethod
78+
def spec(self):
79+
pass
80+
81+
@property
82+
@abc.abstractmethod
83+
def parent(self):
84+
pass
85+
86+
@property
87+
@abc.abstractmethod
88+
def namescope(self):
89+
pass
90+
91+
@property
92+
@abc.abstractmethod
93+
def root(self):
94+
pass
95+
96+
@abc.abstractmethod
97+
def prefixed_identifier(self, prefix_root):
98+
pass
99+
100+
@property
101+
@abc.abstractmethod
102+
def full_identifier(self):
103+
"""Fully-qualified identifier used for this element in the generated XML."""
104+
105+
@abc.abstractmethod
106+
def find(self, namespace, identifier):
107+
"""Finds an element with a particular identifier.
108+
109+
This function allows the direct access to an arbitrarily deeply nested
110+
child element by name, without the need to manually traverse through the
111+
object tree. The `namespace` argument specifies the kind of element to
112+
find. In most cases, this corresponds to the element's XML tag name.
113+
However, if an element has multiple specialized tags, then the namespace
114+
corresponds to the tag name of the most general element of that kind.
115+
For example, `namespace='joint'` would search for `<joint>` and
116+
`<freejoint>`, while `namespace='actuator'` would search for `<general>`,
117+
`<motor>`, `<position>`, `<velocity>`, and `<cylinder>`.
118+
119+
Args:
120+
namespace: A string specifying the namespace being searched. See the
121+
docstring above for explanation.
122+
identifier: The identifier string of the desired element.
123+
124+
Returns:
125+
An `mjcf.Element` object, or `None` if an element with the specified
126+
identifier is not found.
127+
128+
Raises:
129+
ValueError: if either `namespace` or `identifier` is not a string, or if
130+
`namespace` is not a valid namespace.
131+
"""
132+
133+
@abc.abstractmethod
134+
def find_all(self, namespace,
135+
immediate_children_only=False, exclude_attachments=False):
136+
"""Finds all elements of a particular kind.
137+
138+
The `namespace` argument specifies the kind of element to
139+
find. In most cases, this corresponds to the element's XML tag name.
140+
However, if an element has multiple specialized tags, then the namespace
141+
corresponds to the tag name of the most general element of that kind.
142+
For example, `namespace='joint'` would search for `<joint>` and
143+
`<freejoint>`, while `namespace='actuator'` would search for `<general>`,
144+
`<motor>`, `<position>`, `<velocity>`, and `<cylinder>`.
145+
146+
Args:
147+
namespace: A string specifying the namespace being searched. See the
148+
docstring above for explanation.
149+
immediate_children_only: (optional) A boolean, if `True` then only
150+
the immediate children of this element are returned.
151+
exclude_attachments: (optional) A boolean, if `True` then elements
152+
belonging to attached models are excluded.
153+
154+
Returns:
155+
A list of `mjcf.Element`.
156+
157+
Raises:
158+
ValueError: if `namespace` is not a valid namespace.
159+
"""
160+
161+
@abc.abstractmethod
162+
def enter_scope(self, scope_identifier):
163+
"""Finds the root element of the given scope and returns it.
164+
165+
This function allows the access to a nested scope that is a child of this
166+
element. The `scope_identifier` argument specifies the path to the child
167+
scope element.
168+
169+
Args:
170+
scope_identifier: The path of the desired scope element.
171+
172+
Returns:
173+
An `mjcf.Element` object, or `None` if a scope element with the
174+
specified path is not found.
175+
"""
176+
177+
@abc.abstractmethod
178+
def get_attribute_xml_string(self, attribute_name, prefix_root=None):
179+
pass
180+
181+
@abc.abstractmethod
182+
def get_attributes(self):
183+
pass
184+
185+
@abc.abstractmethod
186+
def set_attributes(self, **kwargs):
187+
pass
188+
189+
@abc.abstractmethod
190+
def get_children(self, element_name):
191+
pass
192+
193+
@abc.abstractmethod
194+
def add(self, element_name, **kwargs):
195+
"""Add a new child element to this element.
196+
197+
Args:
198+
element_name: The tag of the element to add.
199+
**kwargs: Attributes of the new element being created.
200+
201+
Raises:
202+
ValueError: If the 'element_name' is not a valid child, or if an invalid
203+
attribute is specified in `kwargs`.
204+
205+
Returns:
206+
An `mjcf.Element` corresponding to the newly created child element.
207+
"""
208+
209+
@abc.abstractmethod
210+
def remove(self, affect_attachments=False):
211+
"""Removes this element from the model."""
212+
213+
@property
214+
@abc.abstractmethod
215+
def is_removed(self):
216+
pass
217+
218+
@abc.abstractmethod
219+
def all_children(self):
220+
pass
221+
222+
@abc.abstractmethod
223+
def to_xml(self, prefix_root=None, debug_context=None):
224+
"""Generates an etree._Element corresponding to this MJCF element.
225+
226+
Args:
227+
prefix_root: (optional) A `NameScope` object to be treated as root
228+
for the purpose of calculating the prefix.
229+
If `None` then no prefix is included.
230+
debug_context: (optional) A `debugging.DebugContext` object to which
231+
the debugging information associated with the generated XML is written.
232+
This is intended for internal use within PyMJCF; users should never need
233+
manually pass this argument.
234+
235+
Returns:
236+
An etree._Element object.
237+
"""
238+
239+
@abc.abstractmethod
240+
def to_xml_string(self, prefix_root=None,
241+
self_only=False, pretty_print=True, debug_context=None):
242+
"""Generates an XML string corresponding to this MJCF element.
243+
244+
Args:
245+
prefix_root: (optional) A `NameScope` object to be treated as root
246+
for the purpose of calculating the prefix.
247+
If `None` then no prefix is included.
248+
self_only: (optional) A boolean, whether to generate an XML corresponding
249+
only to this element without any children.
250+
pretty_print: (optional) A boolean, whether to the XML string should be
251+
properly indented.
252+
debug_context: (optional) A `debugging.DebugContext` object to which
253+
the debugging information associated with the generated XML is written.
254+
This is intended for internal use within PyMJCF; users should never need
255+
manually pass this argument.
256+
257+
Returns:
258+
A string.
259+
"""
260+
261+
@abc.abstractmethod
262+
def resolve_references(self):
263+
pass

0 commit comments

Comments
 (0)