Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
F
freecad_x3d
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Wiki
Wiki
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Commits
Issue Boards
Open sidebar
Elphel
freecad_x3d
Commits
b57ed3ac
Commit
b57ed3ac
authored
Dec 11, 2015
by
Andrey Filippov
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Implemented using colored faces in STEP models to mark orientation of symmetrical objects
parent
e27c2935
Changes
1
Show whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
891 additions
and
0 deletions
+891
-0
x3d_step_assy_color_match.py
x3d_step_assy_color_match.py
+891
-0
No files found.
x3d_step_assy_color_match.py
0 → 100644
View file @
b57ed3ac
from
__future__
import
division
from
__future__
import
print_function
'''
# Copyright (C) 2015, Elphel.inc.
# File: x3d_step_assy_color_match.py
# Generate x3d model from STEP parts models and STEP assembly
# by matching each solid in the assembly to the parts.
# Work in progress, not yet handles parts with symmetries
#
# Uses code from https://gist.github.com/hyOzd/2b38adff6a04e1613622
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http:#www.gnu.org/licenses/>.
@author: Andrey Filippov
@copyright: 2015 Elphel, Inc.
@license: GPLv3.0+
@contact: andrey@elphel.coml
@deffield updated: Updated
'''
__author__
=
"Andrey Filippov"
__copyright__
=
"Copyright 2015, Elphel, Inc."
__license__
=
"GPL"
__version__
=
"3.0+"
__maintainer__
=
"Andrey Filippov"
__email__
=
"andrey@elphel.com"
__status__
=
"Development"
import
FreeCAD
import
FreeCADGui
# just to update cosole output (change to threads later?)
import
Part
import
os
import
time
import
pickle
import
math
import
xml.etree.ElementTree
as
et
from
xml.dom
import
minidom
from
FreeCAD
import
Base
ROOT_DIR
=
'~/parts/0393/export'
DIR_LIST
=
[
"parts"
,
"subassy_flat"
]
INFO_DIR
=
"info"
X3D_DIR
=
"x3d"
X3D_EXT
=
".x3d"
INFO_EXT
=
".pickle"
PRECISION
=
0.0001
PRECISION_INSIDE
=
0.03
COLOR_PER_VERTEX
=
True
if
ROOT_DIR
[
0
]
==
"~"
:
ROOT_DIR
=
os
.
path
.
join
(
os
.
path
.
expanduser
(
'~'
),
ROOT_DIR
[
2
:])
def
get_step_list
(
dir_list
):
step_files
=
[]
for
rpath
in
dir_list
:
apath
=
os
.
path
.
join
(
ROOT_DIR
,
rpath
)
step_files
+=
[
os
.
path
.
join
(
rpath
,
f
)
for
f
in
os
.
listdir
(
apath
)
if
os
.
path
.
isfile
(
os
.
path
.
join
(
apath
,
f
))
and
f
.
endswith
((
".step"
,
".stp"
))]
return
step_files
def
vector_to_tuple
(
v
):
return
((
v
.
x
,
v
.
y
,
v
.
z
))
def
repair_solids_from_shells
(
shape
):
solids
=
shape
.
Solids
new_solids
=
[]
for
sh
in
shape
.
Shells
:
#find same shell in solids
for
sld
in
solids
:
if
sh
.
isEqual
(
sld
.
Shells
[
0
]):
new_solids
.
append
(
sld
)
break
else
:
new_solids
.
append
(
Part
.
Solid
(
sh
))
return
new_solids
#Find Vertex indices with maximal/minima X,Y,Z to check orientation(Still does not check for holes - Add them somehow?
def
verticesToCheck
(
solid
):
l
=
[[],[],[],[],[],[],[],[],[]]
for
v
in
solid
.
Vertexes
:
l
[
0
]
.
append
(
v
.
X
)
l
[
1
]
.
append
(
v
.
Y
)
l
[
2
]
.
append
(
v
.
Z
)
l
[
3
]
.
append
(
v
.
X
+
v
.
Y
)
l
[
4
]
.
append
(
v
.
X
-
v
.
Y
)
l
[
5
]
.
append
(
v
.
X
+
v
.
Z
)
l
[
6
]
.
append
(
v
.
X
-
v
.
Z
)
l
[
7
]
.
append
(
v
.
Y
+
v
.
Z
)
l
[
8
]
.
append
(
v
.
Y
-
v
.
Z
)
sind
=
set
()
for
lst
in
l
:
sind
.
add
(
lst
.
index
(
min
(
lst
)))
sind
.
add
(
lst
.
index
(
max
(
lst
)))
lv
=
[]
for
vi
in
sind
:
v
=
solid
.
Vertexes
[
vi
]
lv
.
append
((
v
.
X
,
v
.
Y
,
v
.
Z
))
return
lv
def
create_file_info_nogui
(
shape
,
fname
=
""
):
objects
=
[]
#repairing open shells
solids
=
shape
.
Solids
if
len
(
solids
)
!=
len
(
shape
.
Shells
):
print
(
"Repairing open shells that are not solids for
%
s"
%
(
fname
))
solids
=
repair_solids_from_shells
(
shape
)
fromShell
=
True
else
:
fromShell
=
False
for
s
in
(
solids
):
pp
=
s
.
PrincipalProperties
objects
.
append
({
"rpath"
:
fname
,
"shell"
:
fromShell
,
"volume"
:
s
.
Volume
,
"area"
:
s
.
Area
,
"center"
:
vector_to_tuple
(
s
.
CenterOfMass
),
"principal"
:
{
'RadiusOfGyration'
:
pp
[
'RadiusOfGyration'
],
'FirstAxisOfInertia'
:
vector_to_tuple
(
pp
[
'FirstAxisOfInertia'
]),
'SecondAxisOfInertia'
:
vector_to_tuple
(
pp
[
'SecondAxisOfInertia'
]),
'ThirdAxisOfInertia'
:
vector_to_tuple
(
pp
[
'ThirdAxisOfInertia'
]),
'Moments'
:
pp
[
'Moments'
],
'SymmetryPoint'
:
pp
[
'SymmetryPoint'
],
'SymmetryAxis'
:
pp
[
'SymmetryAxis'
]},
"vertices"
:
verticesToCheck
(
s
)
})
# return objects
return
(
objects
,
solids
)
def
create_file_info
(
freecadObjects
,
fname
=
""
):
if
not
"Gui"
in
dir
(
FreeCAD
):
return
create_file_info_nogui
(
freecadObjects
,
fname
)
# progress_bar = Base.ProgressIndicator()
# Count all shells in all objects
numShells
=
0
for
o
in
freecadObjects
:
if
hasattr
(
o
,
"Shape"
):
numShells
+=
len
(
o
.
Shape
.
Shells
)
txt
=
""
if
fname
:
txt
+=
" in "
+
fname
# progress_bar.start("Generating objects%s to export to X3D ..."%(txt), len(freecadObjects))
objects
=
[]
"""
>>> len(o0.ViewObject.DiffuseColor)
284
>>> len(o0.Shape.Faces)
284
"""
allSolids
=
[]
for
o
in
freecadObjects
:
if
hasattr
(
o
,
"Shape"
):
shape
=
o
.
Shape
#repairing open shells
solids
=
shape
.
Solids
if
len
(
solids
)
!=
len
(
shape
.
Shells
):
print
(
"Repairing open shells that are not solids for
%
s"
%
(
fname
))
solids
=
repair_solids_from_shells
(
shape
)
fromShell
=
True
else
:
fromShell
=
False
# get all colors for faces in this object (normally just one Shell/Solid
color_set
=
set
()
if
o
.
ViewObject
:
for
clr
in
o
.
ViewObject
.
DiffuseColor
:
# colors are one per face
color_set
.
add
(
clr
)
col_list
=
list
(
color_set
)
col_dict
=
{}
# index for each color (reverse to list)
for
i
,
clr
in
enumerate
(
col_list
):
col_dict
[
clr
]
=
i
#Calculate per-color centers for each object (normally each object has just one Solid/Shell
dc
=
o
.
ViewObject
.
DiffuseColor
if
(
len
(
dc
)
==
1
)
and
(
len
(
o
.
Shape
.
Faces
)
>
1
):
dc
=
dc
*
len
(
o
.
Shape
.
Faces
)
colorCenters
=
[[
0.0
,
0.0
,
0.0
,
0.0
]
for
c
in
col_list
]
# SX,SY,SZ,S0
# for clr,face in zip(o.ViewObject.DiffuseColor, o.Shape.Faces):
for
clr
,
face
in
zip
(
dc
,
o
.
Shape
.
Faces
):
clr_index
=
col_dict
[
clr
]
m
=
face
.
Area
c
=
face
.
CenterOfMass
# Vector
colorCenters
[
clr_index
][
0
]
+=
c
.
x
*
m
colorCenters
[
clr_index
][
1
]
+=
c
.
y
*
m
colorCenters
[
clr_index
][
2
]
+=
c
.
z
*
m
colorCenters
[
clr_index
][
3
]
+=
m
# print ("%s: cx=%f, cy=%f, cz=%f, m=%f"%(fname, c.x,c.y,c.z,m))
color_center_area
=
{}
for
clr
in
col_dict
:
clr_index
=
col_dict
[
clr
]
color_center_area
[
clr
]
=
{
"center"
:(
colorCenters
[
clr_index
][
0
]
/
colorCenters
[
clr_index
][
3
],
colorCenters
[
clr_index
][
1
]
/
colorCenters
[
clr_index
][
3
],
colorCenters
[
clr_index
][
2
]
/
colorCenters
[
clr_index
][
3
]),
"area"
:
colorCenters
[
clr_index
][
3
]}
# print ("color_center_area[%s] = %s"%(str(clr), str(color_center_area[clr])))
for
i
,
s
in
enumerate
(
solids
):
pp
=
s
.
PrincipalProperties
object
=
{
"rpath"
:
fname
,
"shell"
:
fromShell
,
"volume"
:
s
.
Volume
,
"area"
:
s
.
Area
,
"center"
:
vector_to_tuple
(
s
.
CenterOfMass
),
"principal"
:
{
'RadiusOfGyration'
:
pp
[
'RadiusOfGyration'
],
'FirstAxisOfInertia'
:
vector_to_tuple
(
pp
[
'FirstAxisOfInertia'
]),
'SecondAxisOfInertia'
:
vector_to_tuple
(
pp
[
'SecondAxisOfInertia'
]),
'ThirdAxisOfInertia'
:
vector_to_tuple
(
pp
[
'ThirdAxisOfInertia'
]),
'Moments'
:
pp
[
'Moments'
],
'SymmetryPoint'
:
pp
[
'SymmetryPoint'
],
'SymmetryAxis'
:
pp
[
'SymmetryAxis'
]},
"vertices"
:
verticesToCheck
(
s
)
}
if
i
==
0
:
object
[
"colorCenters"
]
=
color_center_area
objects
.
append
(
object
)
allSolids
.
append
(
s
)
# progress_bar.next() # True) # True - enable ESC to abort
# progress_bar.stop()
# return {"objects":objects,"solids":solids}
return
(
objects
,
allSolids
)
def
get_info_files_nogui
(
dir_list
=
DIR_LIST
):
start_time
=
time
.
time
()
sl
=
get_step_list
(
dir_list
=
dir_list
)
# print ("Step files:")
# for i,f in enumerate (sl):
# print("%d: %s"%(i,f))
if
not
INFO_DIR
in
os
.
listdir
(
ROOT_DIR
):
os
.
mkdir
(
os
.
path
.
join
(
ROOT_DIR
,
INFO_DIR
))
todo_list
=
[]
for
f
in
sl
:
fname
,
_
=
os
.
path
.
splitext
(
os
.
path
.
basename
(
f
))
# print("%s -> %s"%(f,fname))
if
not
os
.
path
.
isfile
(
os
.
path
.
join
(
ROOT_DIR
,
INFO_DIR
,
fname
+
INFO_EXT
)):
todo_list
.
append
(
f
)
# for i,f in enumerate (todo_list):
# print("%d: %s"%(i,f))
for
i
,
f
in
enumerate
(
todo_list
):
apath
=
os
.
path
.
join
(
ROOT_DIR
,
f
)
rslt_path
=
os
.
path
.
join
(
ROOT_DIR
,
INFO_DIR
,
os
.
path
.
splitext
(
os
.
path
.
basename
(
f
))[
0
]
+
INFO_EXT
)
print
(
"
%
d: Reading
%
s @
%
f"
%
(
i
,
apath
,
time
.
time
()
-
start_time
),
end
=
"..."
)
shape
=
Part
.
read
(
apath
)
print
(
" got
%
d solids @
%
f"
%
(
len
(
shape
.
Solids
),
time
.
time
()
-
start_time
))
objects
,
_
=
create_file_info_nogui
(
shape
,
f
)
print
(
objects
)
pickle
.
dump
(
objects
,
open
(
rslt_path
,
"wb"
))
# Now read all pickled data:
info_dict
=
{}
for
f
in
sl
:
name
=
os
.
path
.
splitext
(
os
.
path
.
basename
(
f
))[
0
]
info_path
=
os
.
path
.
join
(
ROOT_DIR
,
INFO_DIR
,
name
+
INFO_EXT
)
info_dict
[
name
]
=
pickle
.
load
(
open
(
info_path
,
"rb"
))
#Put largest element as the [0] index
for
k
in
info_dict
:
if
len
(
info_dict
[
k
])
>
1
:
o
=
info_dict
[
k
]
print
(
k
,
len
(
o
),
o
)
vols
=
[
s
[
"volume"
]
for
s
in
o
]
mi
=
vols
.
index
(
max
(
vols
))
print
(
"Largest solid is number
%
d"
%
(
mi
))
if
mi
>
0
:
o
.
insert
(
0
,
o
.
pop
(
mi
))
return
info_dict
def
get_info_files
(
dir_list
=
DIR_LIST
):
if
not
"Gui"
in
dir
(
FreeCAD
):
return
get_info_files_nogui
(
dir_list
)
start_time
=
time
.
time
()
sl
=
get_step_list
(
dir_list
=
dir_list
)
if
not
INFO_DIR
in
os
.
listdir
(
ROOT_DIR
):
os
.
mkdir
(
os
.
path
.
join
(
ROOT_DIR
,
INFO_DIR
))
todo_list
=
[]
for
f
in
sl
:
fname
,
_
=
os
.
path
.
splitext
(
os
.
path
.
basename
(
f
))
if
not
os
.
path
.
isfile
(
os
.
path
.
join
(
ROOT_DIR
,
INFO_DIR
,
fname
+
INFO_EXT
)):
todo_list
.
append
(
f
)
for
i
,
f
in
enumerate
(
todo_list
):
apath
=
os
.
path
.
join
(
ROOT_DIR
,
f
)
rslt_path
=
os
.
path
.
join
(
ROOT_DIR
,
INFO_DIR
,
os
.
path
.
splitext
(
os
.
path
.
basename
(
f
))[
0
]
+
INFO_EXT
)
print
(
"
%
d: Reading
%
s @
%
f"
%
(
i
,
apath
,
time
.
time
()
-
start_time
),
end
=
"..."
)
# Prepare data
FreeCAD
.
loadFile
(
apath
)
doc
=
FreeCAD
.
activeDocument
()
doc
.
Label
=
fname
# x3d_objects = prepareX3dExport(doc.Objects, step_file) # step_file needed just for progress bar
# exportX3D(x3d_objects, x3dFile, colorPerVertex)
# shape = Part.read(apath)
print
(
" got
%
d objects @
%
f"
%
(
len
(
doc
.
Objects
),
time
.
time
()
-
start_time
))
objects
,
_
=
create_file_info
(
doc
.
Objects
,
f
)
FreeCAD
.
closeDocument
(
doc
.
Name
)
FreeCADGui
.
updateGui
()
# print (objects)
pickle
.
dump
(
objects
,
open
(
rslt_path
,
"wb"
))
# Now read all pickled data:
info_dict
=
{}
for
f
in
sl
:
name
=
os
.
path
.
splitext
(
os
.
path
.
basename
(
f
))[
0
]
info_path
=
os
.
path
.
join
(
ROOT_DIR
,
INFO_DIR
,
name
+
INFO_EXT
)
info_dict
[
name
]
=
pickle
.
load
(
open
(
info_path
,
"rb"
))
#Put largest element as the [0] index
for
k
in
info_dict
:
if
len
(
info_dict
[
k
])
>
1
:
o
=
info_dict
[
k
]
print
(
k
,
len
(
o
),
o
)
vols
=
[
s
[
"volume"
]
for
s
in
o
]
mi
=
vols
.
index
(
max
(
vols
))
print
(
"Largest solid is number
%
d"
%
(
mi
))
if
mi
>
0
:
o
.
insert
(
0
,
o
.
pop
(
mi
))
return
info_dict
def
findPartsTransformations
(
solids
,
objects
,
candidates
,
info_dict
,
insidePrecision
=
PRECISION_INSIDE
,
precision
=
PRECISION
):
"""
@param candidates - list (per assembly element) of dictionaries indexed by part name (usually just one) one,
containing list of colors (tuples) for which there is an area match between assembly element and a part.
Build element part frame from colors first, then (if not enough) use inertial directions
"""
transformations
=
[]
for
i
,
s
in
enumerate
(
solids
):
tolerance
=
insidePrecision
*
s
.
BoundBox
.
DiagonalLength
# Or should it be fraction of the translation distance?
trans
=
[]
print
(
"
%
d findPartsTransformations:"
%
(
i
))
for
cand_name
in
candidates
[
i
]:
co
=
info_dict
[
cand_name
][
0
]
# First solid in the candidate part file
try
:
colorCenters
=
co
[
'colorCenters'
]
except
:
colorCenters
=
{}
matrix_part
=
ppToMatrix
(
co
[
'principal'
],
co
[
'center'
],
colorCenters
,
candidates
[
i
][
cand_name
],
0
,
precision
)
# Now try 4 orientations (until the first match).
# TODO - process parts with rotational axis (that allows certain, but not any rotation)
matrix_part_inverse
=
matrix_part
.
inverse
()
# get color properties of a solid
try
:
colorCenters
=
objects
[
i
][
'colorCenters'
]
except
:
colorCenters
=
{}
for
orient
in
range
(
4
):
matrix_assy
=
ppToMatrix
(
s
.
PrincipalProperties
,
s
.
CenterOfMass
,
colorCenters
,
candidates
[
i
][
cand_name
],
orient
,
precision
)
matrix_part_assy
=
matrix_assy
.
multiply
(
matrix_part_inverse
)
for
j
,
v
in
enumerate
(
co
[
'vertices'
]):
if
not
s
.
isInside
(
matrix_part_assy
.
multiply
(
FreeCAD
.
Vector
(
v
)),
tolerance
,
True
):
# print("%d: %s Failed on orientation %d vertice #%d (%f, %f,%f)"%(i,cand_name, orient, j, v[0],v[1],v[2]))
break
else
:
print
(
"
%
d:
%
s - got transformation with orientation
%
d"
%
(
i
,
cand_name
,
orient
))
trans
.
append
(
matrix_part_assy
)
break
else
:
print
(
"Could not find match for part
%
s, trying manually around that vertex"
%
(
cand_name
))
# Seems to be a bug FreeCAD does not recognize seemingly perfect match even with huge tolerance
# Will try manually around that point to find inside one
try_vectors
=
((
-
1
,
-
1
,
-
1
),
(
-
1
,
-
1
,
0
),
(
-
1
,
-
1
,
1
),
(
-
1
,
0
,
-
1
),
(
-
1
,
0
,
0
),
(
-
1
,
0
,
1
),
(
-
1
,
1
,
-
1
),
(
-
1
,
1
,
0
),
(
-
1
,
1
,
1
),
(
0
,
-
1
,
-
1
),
(
0
,
-
1
,
0
),
(
0
,
-
1
,
1
),
(
0
,
0
,
-
1
),
(
0
,
0
,
1
),
(
0
,
1
,
-
1
),
(
0
,
1
,
0
),
(
0
,
1
,
1
),
(
1
,
-
1
,
-
1
),
(
1
,
-
1
,
0
),
(
1
,
-
1
,
1
),
(
1
,
0
,
-
1
),
(
1
,
0
,
0
),
(
1
,
0
,
1
),
(
1
,
1
,
-
1
),
(
1
,
1
,
0
),
(
1
,
1
,
1
))
for
orient
in
range
(
4
):
matrix_assy
=
ppToMatrix
(
s
.
PrincipalProperties
,
s
.
CenterOfMass
,
colorCenters
,
candidates
[
i
][
cand_name
],
orient
,
precision
)
matrix_part_assy
=
matrix_assy
.
multiply
(
matrix_part_inverse
)
for
j
,
v
in
enumerate
(
co
[
'vertices'
]):
if
not
s
.
isInside
(
matrix_part_assy
.
multiply
(
FreeCAD
.
Vector
(
v
)),
tolerance
,
True
):
for
tv
in
try_vectors
:
mv
=
FreeCAD
.
Vector
(
v
[
0
]
+
tv
[
0
]
*
tolerance
,
v
[
1
]
+
tv
[
1
]
*
tolerance
,
v
[
2
]
+
tv
[
2
]
*
tolerance
)
if
s
.
isInside
(
matrix_part_assy
.
multiply
(
mv
),
tolerance
,
True
):
break
# got it!
else
:
break
# no luck
else
:
print
(
"
%
d:
%
s - finally got transformation with orientation
%
d"
%
(
i
,
cand_name
,
orient
))
trans
.
append
(
matrix_part_assy
)
break
else
:
trans
.
append
(
None
)
# so Transformations have same structure as candidates
print
(
"*** Could not find match for part
%
s"
%
(
cand_name
))
transformations
.
append
(
trans
)
return
transformations
def
colorMatchCandidate
(
assy_object
,
candidates
,
info_dict
,
precision
=
PRECISION
):
"""
@return dictionary partName -> list of colors (as 3-tuples)
"""
colored_candidates
=
{}
if
candidates
:
assy_color_center_area
=
assy_object
[
"colorCenters"
]
cand_matches
=
[]
for
candidate
in
candidates
:
matched_colors
=
[]
info_cand
=
info_dict
[
candidate
][
0
]
# only first solid in a part
# print ("info_cand=",info_cand)
for
color
in
assy_color_center_area
:
assy_area
=
assy_color_center_area
[
color
][
"area"
]
# print ("color: %s, assy_area = %f"%(str(color), assy_area))
try
:
part_area
=
info_cand
[
"colorCenters"
][
color
][
"area"
]
# print ("color: %s, part_area = %f"%(str(color), part_area))
if
abs
(
part_area
-
assy_area
)
<
precision
*
assy_area
:
matched_colors
.
append
(
color
)
except
:
pass
cand_matches
.
append
(
matched_colors
)
max_match
=
max
([
len
(
a
)
for
a
in
cand_matches
])
for
candidate
,
colors
in
zip
(
candidates
,
cand_matches
):
if
len
(
colors
)
==
max_match
:
colored_candidates
[
candidate
]
=
colors
return
colored_candidates
#components=scan_step_parts.findComponents("/home/andrey/parts/0393/export/nc393_07_flat_noassy.stp")
def
findComponents
(
assembly
,
precision_inside
=
PRECISION_INSIDE
,
precision
=
PRECISION
):
"""
@param assembly - may be file path (different treatment for Gui/no-Gui, Shape or doc.Objects
"""
start_time
=
time
.
time
()
print
(
"Getting parts database"
)
info_dict
=
get_info_files
()
aname
=
""
if
isinstance
(
assembly
,
(
str
,
unicode
)):
assembly_path
=
assembly
aname
,
_
=
os
.
path
.
splitext
(
os
.
path
.
basename
(
assembly_path
))
if
not
"Gui"
in
dir
(
FreeCAD
):
print
(
"Reading assembly file
%
s @
%
f"
%
(
assembly_path
,
time
.
time
()
-
start_time
),
end
=
"..."
)
assembly
=
Part
.
read
(
assembly_path
)
print
(
" got
%
d solids @
%
f"
%
(
len
(
assembly
.
Solids
),
time
.
time
()
-
start_time
))
else
:
FreeCAD
.
loadFile
(
assembly_path
)
doc
=
FreeCAD
.
activeDocument
()
doc
.
Label
=
aname
print
(
" got
%
d objects @
%
f"
%
(
len
(
doc
.
Objects
),
time
.
time
()
-
start_time
))
assembly
=
doc
.
Objects
# assuming assembly is doc.Objects
if
isinstance
(
assembly
,
Part
.
Shape
):
objects
,
solids
=
create_file_info_nogui
(
shape
,
aname
)
# shape = assembly
else
:
objects
,
solids
=
create_file_info
(
assembly
,
aname
)
# print (objects)
candidates
=
[]
for
i
,
o
in
enumerate
(
objects
):
# try:
# FreeCADGui.updateGui()
# except:
# pass
# print (i,o)
this_candidates
=
[]
rg
=
o
[
'principal'
][
'RadiusOfGyration'
]
rgp
=
precision
*
math
.
sqrt
(
rg
[
0
]
**
2
+
rg
[
1
]
**
2
+
rg
[
2
]
**
2
)
vp
=
o
[
'volume'
]
*
precision
ap
=
o
[
'area'
]
*
precision
for
n
in
info_dict
:
co
=
info_dict
[
n
][
0
]
if
((
abs
(
o
[
'volume'
]
-
co
[
'volume'
])
<
vp
)
and
(
abs
(
o
[
'area'
]
-
co
[
'area'
])
<
ap
)
and
(
abs
(
rg
[
0
]
-
co
[
'principal'
][
'RadiusOfGyration'
][
0
])
<
ap
)
and
(
abs
(
rg
[
1
]
-
co
[
'principal'
][
'RadiusOfGyration'
][
1
])
<
ap
)
and
(
abs
(
rg
[
2
]
-
co
[
'principal'
][
'RadiusOfGyration'
][
2
])
<
ap
)):
this_candidates
.
append
(
n
)
# Filter candidates by number of color areas matched
colored_candidates
=
colorMatchCandidate
(
o
,
this_candidates
,
info_dict
,
precision
)
try
:
num_ass_obj_colors
=
len
(
o
[
"colorCenters"
])
except
:
num_ass_obj_colors
=
0
# print ("%d :colors: %d candidates: %s, colored_candidates: %s"%(i,num_ass_obj_colors, str(this_candidates), str(colored_candidates)))
candidates
.
append
(
colored_candidates
)
transformations
=
findPartsTransformations
(
solids
,
objects
,
candidates
,
info_dict
,
precision_inside
,
precision
)
#Each part can be in two orientations - check overlap after loading actual parts
return
{
"Solids"
:
solids
,
"objects"
:
objects
,
"candidates"
:
candidates
,
"transformations"
:
transformations
}
def
ortho3
(
v0
,
v1
):
v0
.
normalize
()
dv
=
FreeCAD
.
Vector
(
v0
)
.
multiply
(
v0
.
dot
(
v1
))
v1
=
v1
.
sub
(
dv
)
v1
.
normalize
()
v2
=
v0
.
cross
(
v1
)
v2
.
normalize
()
return
(
v0
,
v1
,
v2
)
def
ppToMatrix
(
pp
,
center
=
(
0
,
0
,
0
),
colorCenters
=
{},
# should have all the colors in a colors list "by design"
colors
=
[],
orient
=
0
,
precision
=
PRECISION
):
"""
@param pp - PrincipalProperties
@param center - Center of mass
@param colorCenters - dictionary indexed by colors, having center of color and area of each color (not used here)
@param colors - list of matched colors (tuples)
@param orient - 2-bit modifier for first and second axis of inertia (bit 0 - sign of the first axis, bit 1 - sign of the second)
orient will be overridden if there are some color vectors that define orientation
@param precision - multiplier for the radius of gyration to compare with color vectors
@return 4-matrix
"""
rg
=
pp
[
'RadiusOfGyration'
]
eps
=
math
.
sqrt
(
rg
[
0
]
**
2
+
rg
[
1
]
**
2
+
rg
[
2
]
**
2
)
*
precision
color_vectors
=
[]
t
=
FreeCAD
.
Vector
(
center
)
vectors
=
[]
for
color
in
colors
:
## print ("colorCenters=", colorCenters[color]['center']," area=",colorCenters[color]['area'])
color_vectors
.
append
(
FreeCAD
.
Vector
(
colorCenters
[
color
][
'center'
])
-
t
)
# print ("color_vectors=",color_vectors)
## print ("color_vectors=",color_vectors, "t=",t)
if
color_vectors
:
# find the longest one
lengths
=
[
v
.
Length
for
v
in
color_vectors
]
l
=
max
(
lengths
)
v
=
color_vectors
.
pop
(
lengths
.
index
(
l
))
if
l
>
eps
:
vectors
.
append
(
v
.
normalize
())
if
color_vectors
:
# now find the vector having maximal orthogonal component to v[0]
lengths
=
[
v
.
cross
(
vectors
[
0
])
.
Length
for
v
in
color_vectors
]
l
=
max
(
lengths
)
v
=
color_vectors
.
pop
(
lengths
.
index
(
l
))
if
l
>
eps
:
vectors
.
append
(
v
.
normalize
())
# print ("vectors=",vectors)
#use gyro axis (or two of them)
if
len
(
vectors
)
<
3
:
#insufficient color vectors
vgyro
=
[
FreeCAD
.
Vector
(
pp
[
"FirstAxisOfInertia"
]),
FreeCAD
.
Vector
(
pp
[
"SecondAxisOfInertia"
]),
FreeCAD
.
Vector
(
pp
[
"ThirdAxisOfInertia"
])]
if
(
orient
&
1
)
:
vgyro
[
0
]
.
multiply
(
-
1.0
)
if
(
orient
&
2
)
:
vgyro
[
1
]
.
multiply
(
-
1.0
)
#v0,v1,v2 = ortho3(v0,v1)
if
vgyro
[
2
]
.
dot
(
vgyro
[
0
]
.
cross
(
vgyro
[
1
]))
<
0
:
vgyro
[
2
]
.
multiply
(
-
1.0
)
## print ("vgyro=", vgyro)
if
not
vectors
:
vectors
=
[
vgyro
[
0
],
vgyro
[
1
],
vgyro
[
2
]]
else
:
# at least one vector is defined from colors, need one more
new_directions
=
[
False
,
False
,
False
]
new_length
=
len
(
vectors
)
if
len
(
vectors
)
<
2
:
# == 1, need one more
## print ("vgyro=",vgyro)
for
i
in
range
(
3
):
# filter parallel to existing
for
v
in
vectors
:
if
v
.
cross
(
vgyro
[
i
])
.
Length
<
eps
:
break
else
:
new_directions
[
i
]
=
True
new_length
+=
1
## print ("new_directions=",new_directions," new_length=",new_length)
if
new_length
>
2
:
# extras, filter more (perpendicular to axis of symmetry)
if
(
new_directions
[
0
]
or
new_directions
[
1
])
and
((
rg
[
0
]
-
rg
[
1
])
<
eps
):
if
new_directions
[
1
]:
new_directions
[
1
]
=
False
new_length
-=
1
if
new_directions
[
0
]
and
(
new_length
>
2
):
new_directions
[
0
]
=
False
new_length
-=
1
if
(
new_length
>
2
)
and
(
new_directions
[
1
]
or
new_directions
[
2
])
and
((
rg
[
1
]
-
rg
[
2
])
<
eps
):
if
new_directions
[
1
]:
new_directions
[
1
]
=
False
new_length
-=
1
if
new_directions
[
2
]
and
(
new_length
>
3
):
new_directions
[
2
]
=
False
new_length
-=
1
## print ("new_directions=",new_directions," new_length=",new_length)
# All good, add 1,2,3-rd and make ortho-normal
if
len
(
vectors
)
<
2
:
i
=
new_directions
.
index
(
True
)
vectors
.
append
((
vgyro
[
i
]
-
vectors
[
0
]
*
vectors
[
0
]
.
dot
(
vgyro
[
i
]))
.
normalize
())
# here we have 2 vectors, make a third
vectors
=
[
vectors
[
0
],
vectors
[
1
],
vectors
[
0
]
.
cross
(
vectors
[
1
])
.
normalize
()]
if
vectors
[
2
]
.
dot
(
vectors
[
0
]
.
cross
(
vectors
[
1
]))
<
0
:
vectors
[
2
]
.
multiply
(
-
1.0
)
# print ("Final vectors=",vectors)
return
FreeCAD
.
Matrix
(
vectors
[
0
]
.
x
,
vectors
[
1
]
.
x
,
vectors
[
2
]
.
x
,
t
.
x
,
vectors
[
0
]
.
y
,
vectors
[
1
]
.
y
,
vectors
[
2
]
.
y
,
t
.
y
,
vectors
[
0
]
.
z
,
vectors
[
1
]
.
z
,
vectors
[
2
]
.
z
,
t
.
z
,
0.0
,
0.0
,
0.0
,
1.0
)
def
list_parts_offsets
():
info_files
=
get_info_files
()
for
i
,
name
in
enumerate
(
info_files
):
for
j
,
o
in
enumerate
(
info_files
[
name
]):
d
=
math
.
sqrt
(
o
[
"center"
][
0
]
**
2
+
o
[
"center"
][
1
]
**
2
+
o
[
"center"
][
2
]
**
2
)
if
j
==
0
:
print
(
"
%3
i:"
%
(
i
),
end
=
""
)
else
:
print
(
" "
,
end
=
""
)
print
(
"
%
s offset =
%6.1
f"
%
(
name
,
d
))
# X3D Export
def
getShapeNode
(
vertices
,
faces
,
diffuseColor
=
None
,
main_color_index
=
0
,
colorPerVertex
=
True
):
"""Returns a <Shape> node for given mesh data.
vertices: list of vertice coordinates as `Vector` type
faces: list of tuple of vertice indexes and optionally a face color index ex: (1, 2, 3) or (1, 2, 3, 0)
diffuseColor: None or a list with 3*N color component values i the form of [R, G, B, R1, G1, B1, ...]
If only 3 color components are specified, they are applied to the whole shape, otherwise each vertex
is assigned color from the face color index
"""
shapeNode
=
et
.
Element
(
'Shape'
)
faceNode
=
et
.
SubElement
(
shapeNode
,
'IndexedFaceSet'
)
faceNode
.
set
(
'coordIndex'
,
' '
.
join
([
"
%
d
%
d
%
d -1"
%
face
[
0
:
3
]
for
face
in
faces
]))
if
diffuseColor
and
(
len
(
diffuseColor
)
>
3
):
# Multi-color
if
not
colorPerVertex
:
faceNode
.
set
(
'colorPerVertex'
,
'false'
)
faceNode
.
set
(
'colorIndex'
,
' '
.
join
([
"
%
d"
%
(
f
[
3
])
for
f
in
faces
]))
else
:
faceNode
.
set
(
'colorIndex'
,
' '
.
join
([
"
%
d
%
d
%
d -1"
%
(
f
[
3
],
f
[
3
],
f
[
3
])
for
f
in
faces
]))
coordinateNode
=
et
.
SubElement
(
faceNode
,
'Coordinate'
)
coordinateNode
.
set
(
'point'
,
' '
.
join
([
"
%
f
%
f
%
f"
%
(
p
.
x
,
p
.
y
,
p
.
z
)
for
p
in
vertices
]))
if
diffuseColor
:
if
len
(
diffuseColor
)
>
3
:
colorNode
=
et
.
SubElement
(
faceNode
,
'Color'
)
colorNode
.
set
(
'color'
,
' '
.
join
([
"
%
f"
%
(
c
)
for
c
in
diffuseColor
]))
appearanceNode
=
et
.
SubElement
(
shapeNode
,
'Appearance'
)
materialNode
=
et
.
SubElement
(
appearanceNode
,
'Material'
)
materialNode
.
set
(
'diffuseColor'
,
"
%
f
%
f
%
f"
%
tuple
(
diffuseColor
[
main_color_index
*
3
:
main_color_index
*
3
+
3
]))
return
shapeNode
def
exportX3D
(
objects
,
filepath
,
colorPerVertex
):
"""Export given list of objects to a X3D file.
Each object is a dictionary in this form:
{
points : [Vector, Vector...],
faces : [(pi, pi, pi, ci), ...], # pi: point index, ci - color index (optional)
color : [R, G, B,...] # number range is 0-1.0, exactly 3 elements for a single color, 3*N for per-vertex colors
}
"""
x3dNode
=
et
.
Element
(
'x3d'
)
x3dNode
.
set
(
'profile'
,
'Interchange'
)
x3dNode
.
set
(
'version'
,
'3.3'
)
sceneNode
=
et
.
SubElement
(
x3dNode
,
'Scene'
)
progress_bar
=
Base
.
ProgressIndicator
()
progress_bar
.
start
(
"Saving objects to X3D file
%
s ..."
%
(
filepath
),
len
(
objects
))
for
o
in
objects
:
shapeNode
=
getShapeNode
(
o
[
"points"
],
o
[
"faces"
],
o
[
"color"
],
o
[
"main_color_index"
],
colorPerVertex
)
sceneNode
.
append
(
shapeNode
)
progress_bar
.
next
()
# True) # True - enable ESC to abort
oneliner
=
et
.
tostring
(
x3dNode
)
reparsed
=
minidom
.
parseString
(
oneliner
)
with
open
(
filepath
,
"wr"
)
as
f
:
f
.
write
(
reparsed
.
toprettyxml
(
indent
=
" "
))
progress_bar
.
stop
()
def
prepareX3dExport
(
freecadObjects
,
fname
=
""
):
objects
=
[]
progress_bar
=
Base
.
ProgressIndicator
()
txt
=
""
if
fname
:
txt
+=
" in "
+
fname
progress_bar
.
start
(
"Generating objects
%
s to export to X3D ..."
%
(
txt
),
len
(
freecadObjects
))
for
o
in
freecadObjects
:
progress_bar
.
next
()
# True) # have to do it here as 'for' uses 'continue', True - enable ESC to abort
if
(
not
o
.
ViewObject
)
or
(
o
.
ViewObject
.
Visibility
):
if
hasattr
(
o
,
"Shape"
):
color_set
=
set
()
if
o
.
ViewObject
:
for
clr
in
o
.
ViewObject
.
DiffuseColor
:
color_set
.
add
(
clr
)
if
(
len
(
color_set
)
>
1
):
# process multi-color objects
col_list
=
list
(
color_set
)
col_dict
=
{}
# index for each color (reverse to list)
for
i
,
clr
in
enumerate
(
col_list
):
col_dict
[
clr
]
=
i
points
=
[]
# common for all faces
faces
=
[]
# flat list
colors
=
[]
color_areas
=
[
0.0
]
*
len
(
col_list
)
for
c
in
col_list
:
colors
+=
c
[
0
:
3
]
# only 3 first elements of 4
for
i
,
f
in
enumerate
(
o
.
Shape
.
Faces
):
mesh
=
f
.
tessellate
(
1
)
if
(
not
mesh
[
0
])
or
(
not
mesh
[
1
]):
continue
# some objects (such as Part:Circle)
color_index
=
col_dict
[
o
.
ViewObject
.
DiffuseColor
[
i
]]
#sometimes len(o.ViewObject.DiffuseColor[i]) ==1, but it will not get here
color_areas
[
color_index
]
+=
f
.
Area
delta
=
len
(
points
)
new_indices
=
[]
for
tf
in
mesh
[
1
]:
new_indices
.
append
((
tf
[
0
]
+
delta
,
tf
[
1
]
+
delta
,
tf
[
2
]
+
delta
,
color_index
))
# last element - color index
faces
+=
new_indices
points
+=
mesh
[
0
]
#find color with maximal area (will use in "Appearance")
main_color_index
=
color_areas
.
index
(
max
(
color_areas
))
objects
.
append
({
"points"
:
points
,
"faces"
:
faces
,
# Here - 2-d list of tuples
"color"
:
colors
,
# colors is a list of 3*n elements (n>1)
"main_color_index"
:
main_color_index
})
else
:
#same color for the whole object
if
o
.
ViewObject
:
colors
=
o
.
ViewObject
.
DiffuseColor
[
0
][
0
:
3
]
else
:
colors
=
[
0.7
,
0.7
,
0.3
]
mesh
=
o
.
Shape
.
tessellate
(
1
)
if
(
not
mesh
[
0
])
or
(
not
mesh
[
1
]):
continue
# some objects (such as Part:Circle)
# generate empty mesh, skip them
objects
.
append
({
"points"
:
mesh
[
0
],
"faces"
:
mesh
[
1
],
"color"
:
colors
,
# color is a list of 3 elements
"main_color_index"
:
0
})
progress_bar
.
stop
()
return
objects
def
generatePartsX3d
(
dir_list
=
DIR_LIST
,
colorPerVertex
=
COLOR_PER_VERTEX
):
info_dict
=
get_info_files
(
dir_list
)
# Will (re-) build info files if missing
step_list
=
get_step_list
(
dir_list
)
#relative to ROOT_DIR
if
not
X3D_DIR
in
os
.
listdir
(
ROOT_DIR
):
os
.
mkdir
(
os
.
path
.
join
(
ROOT_DIR
,
X3D_DIR
))
for
step_file
in
step_list
:
partName
,
_
=
os
.
path
.
splitext
(
os
.
path
.
basename
(
step_file
))
x3dFile
=
os
.
path
.
join
(
ROOT_DIR
,
X3D_DIR
,
partName
+
X3D_EXT
)
if
not
os
.
path
.
isfile
(
x3dFile
):
# Prepare data
FreeCAD
.
loadFile
(
os
.
path
.
join
(
ROOT_DIR
,
step_file
))
doc
=
FreeCAD
.
activeDocument
()
doc
.
Label
=
partName
x3d_objects
=
prepareX3dExport
(
doc
.
Objects
,
step_file
)
# step_file needed just for progress bar
exportX3D
(
x3d_objects
,
x3dFile
,
colorPerVertex
)
FreeCAD
.
closeDocument
(
doc
.
Name
)
FreeCADGui
.
updateGui
()
def
matrix4ToX3D
(
m
,
eps
=
0.000001
):
#assuming 3x3 matrix is pure rotational
axis
=
FreeCAD
.
Vector
(
m
.
A32
-
m
.
A23
,
m
.
A13
-
m
.
A31
,
m
.
A21
-
m
.
A12
)
r
=
axis
.
Length
# math.sqrt(axis.X**2 + axis.Y**2 + axis.Z**2)
tr
=
m
.
A11
+
m
.
A22
+
m
.
A33
theta
=
math
.
atan2
(
r
,
tr
-
1
)
if
r
>
eps
:
axis
.
normalize
()
else
:
#Based on Java code http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToAngle
if
abs
(
tr
-
3.0
)
<
eps
:
theta
=
0
axis
=
FreeCAD
.
Vector
(
1
,
0
,
0
)
else
:
theta
=
math
.
pi
sqr2
=
math
.
sqrt
(
2
)
xx
=
(
m
.
A11
+
1
)
/
2
;
yy
=
(
m
.
A22
+
1
)
/
2
;
zz
=
(
m
.
A33
+
1
)
/
2
;
xy
=
(
m
.
A12
+
m
.
A21
)
/
4
;
xz
=
(
m
.
A13
+
m
.
A31
)
/
4
;
yz
=
(
m
.
A23
+
m
.
A32
)
/
4
;
if
(
xx
>
yy
)
and
(
xx
>
zz
):
# m.A11 is the largest diagonal term
if
xx
<
eps
:
axis
=
FreeCAD
.
Vector
(
0
,
sqr2
,
sqr2
)
else
:
x
=
math
.
sqrt
(
xx
)
axis
=
FreeCAD
.
Vector
(
x
,
xy
/
x
,
xz
/
x
)
elif
yy
>
zz
:
# m.A22 is the largest diagonal term
if
yy
<
eps
:
axis
=
FreeCAD
.
Vector
(
sqr2
,
0.0
,
sqr2
)
else
:
y
=
math
.
sqrt
(
yy
)
axis
=
FreeCAD
.
Vector
(
xy
/
y
,
y
,
yz
/
y
)
else
:
# m.A33 is the largest diagonal term
if
zz
<
eps
:
axis
=
FreeCAD
.
Vector
(
sqr2
,
sqr2
,
0.0
)
else
:
z
=
math
.
sqrt
(
zz
)
axis
=
FreeCAD
.
Vector
(
xz
/
z
,
yz
/
z
,
z
)
return
{
"translation"
:
(
m
.
A14
,
m
.
A24
,
m
.
A34
),
"rotation"
:
(
axis
.
x
,
axis
.
y
,
axis
.
z
,
theta
)}
def
generateAssemblyX3d
(
assembly_path
,
components
=
None
,
dir_list
=
DIR_LIST
,
colorPerVertex
=
COLOR_PER_VERTEX
):
info_dict
=
get_info_files
(
dir_list
)
# Will (re-) build info files if missing
generatePartsX3d
(
dir_list
=
DIR_LIST
,
colorPerVertex
=
COLOR_PER_VERTEX
)
# Will only run if files are not there yet
if
not
components
:
components
=
findComponents
(
assembly_path
,
precision_inside
=
PRECISION_INSIDE
,
precision
=
PRECISION
)
assName
,
_
=
os
.
path
.
splitext
(
os
.
path
.
basename
(
assembly_path
))
x3dFile
=
os
.
path
.
join
(
ROOT_DIR
,
X3D_DIR
,
assName
+
X3D_EXT
)
# currently in the same directory as parts
x3dNode
=
et
.
Element
(
'x3d'
)
x3dNode
.
set
(
'profile'
,
'Interchange'
)
x3dNode
.
set
(
'version'
,
'3.3'
)
sceneNode
=
et
.
SubElement
(
x3dNode
,
'Scene'
)
# Including file with (manually created) NavInfo, Cameras, etc that should not be overwritten when regenerating assembly model
inlineNode
=
et
.
SubElement
(
sceneNode
,
'Inline'
)
inlineNode
.
set
(
'id'
,
assName
+
'_config'
)
inlineNode
.
set
(
'url'
,
assName
+
'_config'
+
X3D_EXT
)
inlineNode
.
set
(
'nameSpaceName'
,
assName
)
modelNode
=
et
.
SubElement
(
sceneNode
,
'Transform'
)
modelNode
.
set
(
'id'
,
'transform_'
+
assName
)
modelNode
.
set
(
'translation'
,
'
%
f
%
f
%
f'
%
(
0
,
0
,
0
))
modelNode
.
set
(
'rotation'
,
'
%
f
%
f
%
f
%
f'
%
(
0
,
0
,
0
,
0
))
defined_parts
=
{}
# for each defined part holds index (for ID generation)
for
i
,
component
in
enumerate
(
components
[
'objects'
]):
parts
=
components
[
'candidates'
][
i
]
transformations
=
components
[
'transformations'
][
i
]
# same structure as candidates, missing - 'None'
for
transformation
,
part
in
zip
(
transformations
,
parts
):
if
transformation
:
break
else
:
print
(
"Component
%
d does not have any matches, ignoring. Candidates:
%
s"
%
(
i
,
str
(
parts
)))
continue
# bbox=components['shape'].Shells[i].BoundBox
bbox
=
components
[
'solids'
][
i
]
.
BoundBox
bboxCenter
=
((
bbox
.
XMax
+
bbox
.
XMin
)
/
2
,(
bbox
.
YMax
+
bbox
.
YMin
)
/
2
,(
bbox
.
ZMax
+
bbox
.
ZMin
)
/
2
)
bboxSize
=
(
bbox
.
XMax
-
bbox
.
XMin
,
bbox
.
YMax
-
bbox
.
YMin
,
bbox
.
ZMax
-
bbox
.
ZMin
)
transform
=
matrix4ToX3D
(
transformation
)
rot
=
transform
[
'rotation'
]
print
(
"
%
d: Adding
%
s, rotation = (x=
%
f y=
%
f z=
%
f theta=
%
f)"
%
(
i
,
part
,
rot
[
0
],
rot
[
1
],
rot
[
2
],
rot
[
3
]))
if
part
in
defined_parts
:
defined_parts
[
part
]
+=
1
else
:
defined_parts
[
part
]
=
0
switchNode
=
et
.
SubElement
(
modelNode
,
'Switch'
)
switchNode
.
set
(
'id'
,
'switch_'
+
part
+
":"
+
str
(
defined_parts
[
part
]))
switchNode
.
set
(
'class'
,
'switch_'
+
part
)
switchNode
.
set
(
'whichChoice'
,
'0'
)
transformNode
=
et
.
SubElement
(
switchNode
,
'Transform'
)
transformNode
.
set
(
'id'
,
'transform_'
+
part
+
":"
+
str
(
defined_parts
[
part
]))
transformNode
.
set
(
'class'
,
'transform_'
+
part
)
transformNode
.
set
(
'translation'
,
'
%
f
%
f
%
f'
%
transform
[
'translation'
])
transformNode
.
set
(
'rotation'
,
'
%
f
%
f
%
f
%
f'
%
transform
[
'rotation'
])
groupNode
=
et
.
SubElement
(
transformNode
,
'Group'
)
groupNode
.
set
(
'id'
,
'group_'
+
part
+
":"
+
str
(
defined_parts
[
part
]))
groupNode
.
set
(
'class'
,
'group_'
+
part
)
groupNode
.
set
(
'bboxSize'
,
'
%
f
%
f
%
f'
%
bboxSize
)
groupNode
.
set
(
'bboxCenter'
,
'
%
f
%
f
%
f'
%
bboxCenter
)
if
defined_parts
[
part
]:
groupNode
.
set
(
'USE'
,
part
)
else
:
groupNode
.
set
(
'DEF'
,
part
)
inlineNode
=
et
.
SubElement
(
groupNode
,
'Inline'
)
inlineNode
.
set
(
'id'
,
'inline_'
+
part
+
":"
+
str
(
defined_parts
[
part
]))
inlineNode
.
set
(
'class'
,
'inline_'
+
part
)
# inlineNode.set('url',os.path.join(X3D_DIR,part + X3D_EXT))
inlineNode
.
set
(
'url'
,
part
+
X3D_EXT
)
inlineNode
.
set
(
'nameSpaceName'
,
part
)
oneliner
=
et
.
tostring
(
x3dNode
)
reparsed
=
minidom
.
parseString
(
oneliner
)
print
(
"Writing assembly to
%
s"
%
(
x3dFile
))
with
open
(
x3dFile
,
"wr"
)
as
f
:
f
.
write
(
reparsed
.
toprettyxml
(
indent
=
" "
))
def
run
():
get_info_files
()
#if __name__ == "__main__":
# run()
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment