├── MANIFEST.in
├── .gitignore
├── xacro4sdf
├── __init__.py
├── xml_format.py
├── common.xmacro
└── xacro4sdf.py
├── test
├── test_custom_usage.py
├── model3.sdf.xmacro
├── model2.sdf.xmacro
├── model.sdf.xmacro
├── model2.sdf
├── model.sdf
├── model3.sdf
└── custom.sdf
├── setup.py
├── .github
└── workflows
│ └── python-publish.yml
├── LICENSE
└── README.md
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include xacro4sdf/common.xmacro
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | build/
2 | dist/
3 | xacro4sdf.egg-info/
4 | *.pyc
5 |
--------------------------------------------------------------------------------
/xacro4sdf/__init__.py:
--------------------------------------------------------------------------------
1 | from xacro4sdf.xacro4sdf import xacro4sdf_main
--------------------------------------------------------------------------------
/test/test_custom_usage.py:
--------------------------------------------------------------------------------
1 | #!/bin/python3
2 | from xacro4sdf.xacro4sdf import XMLMacro
3 |
4 | xmacro=XMLMacro()
5 | #case1 parse from file
6 | xmacro.set_xml_file("model.sdf.xmacro")
7 | custom_property={"rplidar_a2_h":0.8}
8 | xmacro.generate(custom_property)
9 | xmacro.set_static(True)
10 | xmacro.to_file("custom.sdf",banner_info="test_custom_usage.py")
--------------------------------------------------------------------------------
/test/model3.sdf.xmacro:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | 0 0 0.03 0 0 0
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/test/model2.sdf.xmacro:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | 0 0 0.02 0 0 0
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 | # -*- coding: utf-8 -*-
3 | from setuptools import setup, find_packages
4 |
5 | from os import path
6 | this_directory = path.abspath(path.dirname(__file__))
7 | with open(path.join(this_directory, 'README.md'), encoding='utf-8') as f:
8 | long_description = f.read()
9 |
10 | setup(
11 | name='xacro4sdf',
12 | version='2.1.0',
13 | author='Zhenpeng Ge',
14 | author_email='zhenpeng.ge@qq.com',
15 | url='https://github.com/gezp/xacro4sdf',
16 | description='a simple XML macro script for sdf, like ros/xacro which is desiged for urdf.',
17 | long_description=long_description,
18 | long_description_content_type='text/markdown',
19 | license='MIT',
20 | keywords=['sdf','sdformat','xacro', 'gazebo', 'ignition'],
21 | include_package_data=True,
22 | packages=find_packages(),
23 | entry_points={
24 | 'console_scripts': [
25 | 'xacro4sdf=xacro4sdf:xacro4sdf_main',
26 | ]
27 | }
28 | )
29 |
--------------------------------------------------------------------------------
/.github/workflows/python-publish.yml:
--------------------------------------------------------------------------------
1 | # This workflows will upload a Python Package using Twine when a release is created
2 | # For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries
3 |
4 | name: Upload Python Package
5 |
6 | on:
7 | release:
8 | types: [created]
9 |
10 | jobs:
11 | deploy:
12 |
13 | runs-on: ubuntu-latest
14 |
15 | steps:
16 | - uses: actions/checkout@v2
17 | - name: Set up Python
18 | uses: actions/setup-python@v2
19 | with:
20 | python-version: '3.x'
21 | - name: Install dependencies
22 | run: |
23 | python -m pip install --upgrade pip
24 | pip install setuptools wheel twine
25 | - name: Build and publish
26 | env:
27 | TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
28 | TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
29 | run: |
30 | python setup.py sdist bdist_wheel
31 | twine upload dist/*
32 |
--------------------------------------------------------------------------------
/test/model.sdf.xmacro:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | 0 0 0.02 0 0 0
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 gezp
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/test/model2.sdf:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | 0 0 0.02 0 0 0
12 | 0.5
13 |
14 | 0.037083333333333336
15 | 0
16 | 0
17 | 0.03333333333333334
18 | 0
19 | 0.017083333333333336
20 |
21 |
22 |
23 |
24 |
25 | model://rplidar_a2/meshes/rplidar_a2.dae
26 |
27 |
28 |
29 |
30 |
31 |
32 | model://rplidar_a2/meshes/rplidar_a2.dae
33 |
34 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/test/model.sdf:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | 0 0 0.02 0 0 0
12 | 0.5
13 |
14 | 0.01041666666666667
15 | 0
16 | 0
17 | 0.008333333333333335
18 | 0
19 | 0.005416666666666668
20 |
21 |
22 |
23 |
24 |
25 | model://rplidar_a2/meshes/rplidar_a2.dae
26 |
27 |
28 |
29 |
30 |
31 |
32 | model://rplidar_a2/meshes/rplidar_a2.dae
33 |
34 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/test/model3.sdf:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | 0 0 0.03 0 0 0
12 | 0.5
13 |
14 | 0.037083333333333336
15 | 0
16 | 0
17 | 0.03333333333333334
18 | 0
19 | 0.017083333333333336
20 |
21 |
22 |
23 |
24 |
25 | model://rplidar_a2/meshes/rplidar_a2.dae
26 |
27 |
28 |
29 |
30 |
31 |
32 | model://rplidar_a2/meshes/rplidar_a2.dae
33 |
34 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/test/custom.sdf:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | true
10 |
11 |
12 | 0 0 0.02 0 0 0
13 | 0.5
14 |
15 | 0.1404166666666667
16 | 0
17 | 0
18 | 0.13333333333333336
19 | 0
20 | 0.060416666666666674
21 |
22 |
23 |
24 |
25 |
26 | model://rplidar_a2/meshes/rplidar_a2.dae
27 |
28 |
29 |
30 |
31 |
32 |
33 | model://rplidar_a2/meshes/rplidar_a2.dae
34 |
35 |
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/xacro4sdf/xml_format.py:
--------------------------------------------------------------------------------
1 | import xml.dom.minidom
2 |
3 | ###########################################################################################################
4 | # Better pretty printing of xml
5 | # Taken from http://ronrothman.com/public/leftbraned/xml-dom-minidom-toprettyxml-and-silly-whitespace/
6 |
7 | def fixed_writexml(self, writer, indent="", addindent="", newl=""):
8 | # indent = current indentation
9 | # addindent = indentation to add to higher levels
10 | # newl = newline string
11 | writer.write(indent + "<" + self.tagName)
12 |
13 | attrs = self._get_attributes()
14 | a_names = sorted(attrs.keys())
15 |
16 | for a_name in a_names:
17 | writer.write(" %s=\"" % a_name)
18 | xml.dom.minidom._write_data(writer, attrs[a_name].value)
19 | writer.write("\"")
20 | if self.childNodes:
21 | if len(self.childNodes) == 1 \
22 | and self.childNodes[0].nodeType == xml.dom.minidom.Node.TEXT_NODE:
23 | writer.write(">")
24 | self.childNodes[0].writexml(writer, "", "", "")
25 | writer.write("%s>%s" % (self.tagName, newl))
26 | return
27 | writer.write(">%s" % newl)
28 | for node in self.childNodes:
29 | # skip whitespace-only text nodes
30 | if node.nodeType == xml.dom.minidom.Node.TEXT_NODE and \
31 | (not node.data or node.data.isspace()):
32 | continue
33 | node.writexml(writer, indent + addindent, addindent, newl)
34 | writer.write("%s%s>%s" % (indent, self.tagName, newl))
35 | else:
36 | writer.write("/>%s" % newl)
37 |
38 | # replace minidom's function with ours
39 | xml.dom.minidom.Element.writexml = fixed_writexml
40 | ###################################################################################################
41 |
--------------------------------------------------------------------------------
/xacro4sdf/common.xmacro:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | ${m}
6 |
7 | ${m*(3*r*r+l*l)/12}
8 | 0
9 | 0
10 | ${m*(3*r*r+l*l)/12}
11 | 0
12 | ${m*r*r/2}
13 |
14 |
15 |
16 |
17 | ${m}
18 |
19 | ${m*(y*y+z*z)/12}
20 | 0
21 | 0
22 | ${m*(x*x+z*z)/12}
23 | 0
24 | ${m*(x*x+y*y)/12}
25 |
26 |
27 |
28 |
29 | ${m}
30 |
31 | ${2*m*r*r/5}
32 | 0
33 | 0
34 | ${2*m*r*r/5}
35 | 0
36 | ${2*m*r*r/5}
37 |
38 |
39 |
40 |
41 |
42 |
43 | ${r}
44 | ${l}
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 | ${x} ${y} ${z}
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 | ${r}
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 | ${uri}
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 | ${uri}
78 |
79 |
80 |
81 |
82 |
83 |
84 | ${uri}
85 |
86 |
87 |
88 |
89 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # xacro4sdf
2 |
3 |   
4 |
5 | **[xmacro](https://github.com/gezp/xmacro) is recommended to replace `xacro4sdf`, which is a refactor version of `xacro4sdf`. and `xacro4sdf` won't be maintained in the future.**
6 |
7 | `xacro4sdf` is a simple tool to define and parse XML macro for [sdf (sdformat)](http://sdformat.org/), you can use `xacro4sdf` to write modularized SDF xml (not nest model)
8 |
9 | * xacro4sdf is similar, but different from [ros/xacro](https://github.com/ros/xacro) which is desiged for urdf.
10 | * xacro4sdf is simpler, but easier to use.
11 |
12 | > Reference: [ros/xacro](https://github.com/ros/xacro)
13 | >
14 | > * With Xacro, you can construct shorter and more readable XML files by using macros that expand to larger XML expressions.
15 |
16 | **Attention:**
17 |
18 | * `` is departed after version2.0.0, you can use the tag with version1.2.6.
19 |
20 | ## 1. Usage
21 |
22 | **Installation**
23 |
24 | ```bash
25 | #install by pip
26 | pip install xacro4sdf
27 | # or install from source code
28 | # git clone https://github.com/gezp/xacro4sdf.git
29 | # cd xacro4sdf && sudo python3 setup.py install
30 | ```
31 |
32 | **create model.sdf.xmacro file**
33 |
34 | * refer to files in `test/` (for example `test/model.sdf.xmacro`)
35 |
36 | **generate model.sdf**
37 |
38 | ```bash
39 | #cd test
40 | xacro4sdf model.sdf.xmacro
41 | ```
42 |
43 | * it will generate model.sdf (the result should be same as test/model.sdf)
44 | * more examples can be found `test` folder.
45 |
46 | **summary of XML Tags**
47 |
48 | * definition of property and macro : core function
49 | * `` and ``
50 | * include
51 | * `` :include definition of property and macro from other xmacro file
52 | * use of property and macro:
53 | * `${xxx}` : use of property ,it's very useful to use math expressions.
54 | * `` : use of macro, it's very useful for modular modeling.
55 |
56 | > Tip:
57 | >
58 | > * the xacro defination (`` , `` and ``) must be child node of root node `` .
59 |
60 | ## 2. Features
61 |
62 | * Properties
63 | * Macros
64 | * Math expressions
65 | * Include
66 |
67 | ### 2.1. Properties
68 |
69 | Properties are named values that can be inserted anywhere into the XML document
70 |
71 | **xacro definition**
72 |
73 | ```xml
74 |
75 |
76 |
77 |
78 | ```
79 |
80 | **generated xml**
81 |
82 | ```xml
83 |
84 | ```
85 |
86 | ### 2.2. Macros
87 |
88 | The main feature of `xacro4sdf` is macros.
89 |
90 | Define macros with the macro tag ``, then specify the macro name and a list of parameters. The list of parameters should be whitespace separated.
91 |
92 | The usage of Macros is to define `` which will be replaced with `` block according to the param `name`.
93 |
94 | **xacro definition**
95 |
96 | ```xml
97 |
98 |
99 |
100 | ${m}
101 |
102 | ${m*(y*y+z*z)/12}
103 | 0
104 | 0
105 | ${m*(x*x+z*z)/12}
106 | 0
107 | ${m*(x*x+z*z)/12}
108 |
109 |
110 |
111 |
112 | 0 0 0.02 0 0 0
113 |
114 |
115 | ```
116 |
117 | **generated xml**
118 |
119 | ```xml
120 |
121 | 0 0 0.02 0 0 0
122 | 0.2
123 |
124 | 0.0008333333333333335
125 | 0
126 | 0
127 | 0.002166666666666667
128 | 0
129 | 0.002166666666666667
130 |
131 |
132 | ```
133 |
134 | * only support simple parameters (string and number),but block parameters isn't supported.
135 | * it's supported to use other `xacro_macro` in `xacro_define_macro` which is recursive definition.
136 |
137 | > it's not recommended to define macro recursively (only support <=5).
138 |
139 | ### 2.3. Math expressions
140 |
141 | * within dollared-braces `${xxxx}`, you can also write simple math expressions.
142 | * refer to examples of **Properties** and **Macros**
143 | * it's implemented by calling `eval()` in python, so it's unsafe for some cases.
144 |
145 | ### 2.4. Including other xmacro files
146 |
147 | **definition include**
148 |
149 | You can include other xmacro files using the `` tag ,include other xmacro files according to param `uri`.
150 |
151 | * it will only include the definition of properties with tag `` and macros with tag ``.
152 |
153 | ```xml
154 |
155 |
156 | ```
157 |
158 | * The uri for `model` means to search file in a list of folders which are defined by environment variable `IGN_GAZEBO_RESOURCE_PATH` and `GAZEBO_MODEL_PATH`
159 | * The uri for `file` means to open the file directly.
160 | * it try to open the file with relative path `simple_car/model.sdf.xmacro` .
161 | * you can also try to open file with absolute path `/simple_car/model.sdf.xmacro` with uri `file:///simple_car/model.sdf.xmacro`.
162 |
163 | > Tips:
164 | >
165 | > * `` supports to include recursively.
166 | > * Don't use same name for xacro definition (the param `name` of `` and ``) , otherwise the priority of xacro definition need be considered.
167 | > * Be carefully when using `` and ``
168 |
169 | ### 2.5 pre-defined common.xmacro
170 |
171 | ```xml
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 | ```
184 |
185 | * you can directly use these macros in your xmacro file.
186 |
187 | ## 3. Custom usage in python
188 |
189 | you can use xacro4sdf in python easily
190 |
191 | ```python
192 | from xacro4sdf.xacro4sdf import XMLMacro
193 |
194 | xmacro=XMLMacro()
195 | #case1 parse from file
196 | xmacro.set_xml_file(inputfile)
197 | xmacro.generate()
198 | xmacro.to_file(outputfile)
199 |
200 | #case2 parse from xml string
201 | xmacro.set_xml_string(xml_str)
202 | xmacro.generate()
203 | xmacro.to_file(outputfile)
204 |
205 | #case3 generate to string
206 | xmacro.set_xml_file(inputfile)
207 | xmacro.generate()
208 | xmacro.to_string()
209 |
210 | #case4 custom property
211 | xmacro.set_xml_file(inputfile)
212 | # use custom property dictionary to overwrite global property
213 | # defined by (only in 'inputfile')
214 | kv={"rplidar_a2_h":0.8}
215 | xmacro.generate(custom_property=kv)
216 | xmacro.to_file(outputfile)
217 |
218 | #case5 set model static
219 | xmacro.set_xml_file(inputfile)
220 | xmacro.generate()
221 | xmacro.set_static(True)
222 | xmacro.to_file(outputfile)
223 |
224 | ```
225 |
226 | ## 4. Maintainer and License
227 |
228 | maintainer : Zhenpeng Ge, zhenpeng.ge@qq.com
229 |
230 | `xacro4sdf` is provided under MIT License.
231 |
--------------------------------------------------------------------------------
/xacro4sdf/xacro4sdf.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 | # -*- coding: utf-8 -*-
3 | import os
4 | import sys
5 | import re
6 | import xml.dom.minidom
7 | import xacro4sdf.xml_format
8 |
9 |
10 | def try2number(str):
11 | try:
12 | return float(str)
13 | except ValueError:
14 | return str
15 |
16 | class XMLMacro:
17 | def __init__(self):
18 | # variables for xml parse
19 | self.local_property_dict={}
20 | self.global_property_dict={}
21 | self.macro_params_dict={}
22 | self.macro_content_dict={}
23 | self.xmacro_paths=[]
24 | # variables for xml info
25 | self.filename=""
26 | self.dirname=""
27 | self.in_doc=None
28 | self.out_doc=None
29 | self.parse_flag=False
30 | #init
31 | if os.getenv("IGN_GAZEBO_RESOURCE_PATH") is not None:
32 | self.xmacro_paths=self.xmacro_paths+os.getenv("IGN_GAZEBO_RESOURCE_PATH").split(":")
33 | if os.getenv("GAZEBO_MODEL_PATH") is not None:
34 | self.xmacro_paths=self.xmacro_paths+os.getenv("GAZEBO_MODEL_PATH").split(":")
35 | if os.getenv("XACRO4SDF_MODEL_PATH") is not None:
36 | self.xmacro_paths=self.xmacro_paths+os.getenv("XACRO4SDF_MODEL_PATH").split(":")
37 |
38 | ####private funtion
39 | # returh a absolute path.
40 | def __parse_uri(self,uri,current_dirname):
41 | #current_dirname is needed for 'file://'
42 | result = ""
43 | splited_str=uri.split("://")
44 | if len(splited_str)!=2:
45 | return result
46 | #get absolute path according to uri
47 | if splited_str[0] == "file":
48 | #to absolute path
49 | if(not os.path.isabs(splited_str[1])):
50 | splited_str[1]=os.path.join(current_dirname,splited_str[1])
51 | if os.path.isfile(splited_str[1]):
52 | result = os.path.abspath(splited_str[1])
53 | elif splited_str[0] == "model":
54 | for tmp_path in self.xmacro_paths:
55 | tmp_path = os.path.join(tmp_path,splited_str[1])
56 | if(os.path.isfile(tmp_path)):
57 | result = tmp_path
58 | break
59 | return result
60 |
61 | def __get_xacro(self,doc):
62 | # only find in ...
63 | root = doc.documentElement
64 | for node in root.childNodes:
65 | if node.nodeType == xml.dom.Node.ELEMENT_NODE:
66 | if node.tagName == 'xacro_define_property':
67 | name = node.getAttribute("name")
68 | self.global_property_dict[name] = try2number(node.getAttribute("value"))
69 | elif node.tagName == 'xacro_define_macro':
70 | name = node.getAttribute("name")
71 | self.macro_params_dict[name] = node.getAttribute("params").split(' ')
72 | self.macro_content_dict[name] = node.toxml()
73 |
74 | def __get_include_xacro_recursively(self,doc,dirname):
75 | root = doc.documentElement
76 | for node in root.childNodes:
77 | if node.nodeType == xml.dom.Node.ELEMENT_NODE:
78 | if node.tagName == 'xacro_include_definition':
79 | uri = node.getAttribute("uri")
80 | filepath = self.__parse_uri(uri,dirname)
81 | if filepath != "":
82 | tmp_doc = xml.dom.minidom.parse(filepath)
83 | #get xacro from child recursively
84 | self.__get_include_xacro_recursively(tmp_doc,os.path.dirname(filepath))
85 | #get xacro from file
86 | self.__get_xacro(tmp_doc)
87 | else:
88 | print("Error: not find xacro_include_definition uri",uri)
89 | sys.exit(1)
90 |
91 | def __remove_definition_xacro_node(self,doc):
92 | root = doc.documentElement
93 | for node in root.childNodes:
94 | if node.nodeType == xml.dom.Node.ELEMENT_NODE:
95 | if node.tagName == 'xacro_define_property' \
96 | or node.tagName == 'xacro_define_macro' \
97 | or node.tagName == 'xacro_include_definition':
98 | root.removeChild(node)
99 |
100 | def __re_eval_fn(self,obj):
101 | result = eval(obj.group(1), self.global_property_dict, self.local_property_dict)
102 | return str(result)
103 |
104 | def __eval_text(self,xml_str):
105 | pattern = re.compile(r'[$][{](.*?)[}]', re.S)
106 | return re.sub(pattern, self.__re_eval_fn, xml_str)
107 |
108 | def __replace_macro_node(self,node):
109 | parent = node.parentNode
110 | if not node.hasAttribute("name"):
111 | print("Error: check block,not find parameter name!")
112 | sys.exit(1)
113 | name = node.getAttribute("name")
114 | # get xml string
115 | xml_str = self.macro_content_dict[name]
116 | # get local table
117 | self.local_property_dict.clear()
118 | for param in self.macro_params_dict[name]:
119 | self.local_property_dict[param] = try2number(node.getAttribute(param))
120 | # replace macro(insert and remove)
121 | xml_str = self.__eval_text(xml_str)
122 | new_node = xml.dom.minidom.parseString(xml_str).documentElement
123 | for cc in list(new_node.childNodes):
124 | parent.insertBefore(cc, node)
125 | parent.removeChild(node)
126 |
127 |
128 |
129 | ####public funtion
130 | def set_xml_file(self,filepath):
131 | self.filename=filepath
132 | self.dirname=os.path.dirname(os.path.abspath(filepath))
133 | self.in_doc = xml.dom.minidom.parse(filepath)
134 | self.parse_flag=False
135 |
136 | def set_xml_string(self,xml_str):
137 | self.dirname=os.path.dirname(os.path.abspath(__file__))
138 | self.in_doc = xml.dom.minidom.parse(xml_str)
139 | self.parse_flag=False
140 |
141 | def parse(self):
142 | # get xacro defination from in_doc(store macro's defination to dictionary)
143 | # get common xacro (lowest priority,it can be overwrited)
144 | common_xacro_path = os.path.join(os.path.dirname(os.path.abspath(__file__)) ,'common.xmacro')
145 | self.__get_xacro(xml.dom.minidom.parse(common_xacro_path))
146 | # get inlcude xacro recursively (the priority depends on the order of tag)
147 | self.__get_include_xacro_recursively(self.in_doc,self.dirname)
148 | # get current xacro (highest priority)
149 | self.__get_xacro(self.in_doc)
150 | # remove xacro defination (,,)
151 | self.__remove_definition_xacro_node(self.in_doc)
152 | self.parse_flag=True
153 |
154 | def generate(self,custom_property:dict=None):
155 | if self.in_doc is None:
156 | self.out_doc=None
157 | return
158 | if not self.parse_flag:
159 | self.parse()
160 | # generate out_doc by relapcing xacro macro
161 | # replace global xacro property (process by string regular expression operations)
162 | self.local_property_dict.clear()
163 | if custom_property is not None:
164 | self.local_property_dict=custom_property.copy()
165 | xml_str = self.in_doc.documentElement.toxml()
166 | xml_str = self.__eval_text(xml_str)
167 | self.out_doc = xml.dom.minidom.parseString(xml_str)
168 | # replace xacro macro
169 | for _ in range(5):
170 | nodes = self.out_doc.getElementsByTagName("xacro_macro")
171 | if nodes.length != 0:
172 | for node in list(nodes):
173 | self.__replace_macro_node(node)
174 | else:
175 | break
176 | #check
177 | if self.out_doc.getElementsByTagName("xacro_macro").length != 0:
178 | print("Error:The recursive depth of macro defination only support <=5")
179 | self.out_doc=None
180 | return
181 |
182 | def set_static(self,is_static):
183 | if self.out_doc is None:
184 | return False
185 | root=self.out_doc.documentElement
186 | # model node
187 | nodes=root.getElementsByTagName('model')
188 | if len(nodes)==0:
189 | return False
190 | model=nodes[0]
191 | nodes=model.getElementsByTagName('static')
192 | # static node
193 | static = None
194 | if len(nodes)==0:
195 | static=self.out_doc.createElement('static')
196 | model.insertBefore(static,model.firstChild)
197 | static.appendChild(self.out_doc.createTextNode('false'))
198 | else:
199 | static=nodes[0]
200 | # set value
201 | static.firstChild.data = "true" if is_static else "false"
202 | return True
203 |
204 | def to_string(self):
205 | if self.out_doc is None:
206 | return None
207 | return self.out_doc.documentElement.toxml()
208 |
209 | def to_file(self,filepath,banner_info=None):
210 | # auto-generated banner
211 | # reference: https://github.com/ros/xacro/blob/noetic-devel/src/xacro/__init__.py
212 | if self.out_doc is None:
213 | return None
214 | src = "python script"
215 | if self.filename != "":
216 | src = self.filename
217 | if banner_info is not None:
218 | src=banner_info
219 | banner = [xml.dom.minidom.Comment(c) for c in
220 | [" %s " % ('=' * 83),
221 | " | This document was autogenerated by xacro4sdf from %-26s | " % src,
222 | " | EDITING THIS FILE BY HAND IS NOT RECOMMENDED %-30s | " % "",
223 | " %s " % ('=' * 83)]]
224 | first = self.out_doc.firstChild
225 | for comment in banner:
226 | self.out_doc.insertBefore(comment, first)
227 | #write to file
228 | try:
229 | with open(filepath, 'w', encoding='UTF-8') as f:
230 | self.out_doc.writexml(f, indent='', addindent='\t', newl='\n', encoding='UTF-8')
231 | except Exception as err:
232 | print('to_file error:{0}'.format(err))
233 |
234 | def xacro4sdf_main():
235 | if(len(sys.argv) < 2):
236 | print("Usage: xacro4sdf (the name of inputfile must be xxx.xmacro)")
237 | return -1
238 | inputfile = sys.argv[1]
239 | outputfile = os.path.splitext(inputfile)[0]
240 | #check
241 | if os.path.splitext(inputfile)[1] != '.xmacro' and os.path.splitext(inputfile)[1] != '.xacro':
242 | print("Error: the name of inputfile must be xxx.xmacro")
243 | return -2
244 | #warn
245 | if os.path.splitext(inputfile)[1] == '.xacro':
246 | print("Warning: inputfile with xxx.xmacro is recommanded to show the difference from ros/xacro")
247 | #process
248 | xmacro=XMLMacro()
249 | xmacro.set_xml_file(inputfile)
250 | xmacro.generate()
251 | xmacro.to_file(outputfile)
252 | return 0
253 |
254 |
255 | if __name__ == '__main__':
256 | xacro4sdf_main()
--------------------------------------------------------------------------------