Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Full conversion #2

Open
wants to merge 46 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
cc321cd
Add files via upload
abbyxh Jul 23, 2024
2a6756d
Update index.js
abbyxh Jul 23, 2024
b6dcade
Update config_ild_trial.json
abbyxh Jul 23, 2024
1fe8fcb
Update config_ild_trial.json
abbyxh Jul 23, 2024
0215c4f
changes
abbyxh Jul 23, 2024
4fd3bc1
changes
abbyxh Jul 23, 2024
38d5fbf
cahnges
abbyxh Jul 23, 2024
1963a27
change name test
abbyxh Jul 23, 2024
8ac05be
changes
abbyxh Jul 23, 2024
31881f9
Merge branch 'main' of https://github.com/abbyxh/root2gltf
abbyxh Jul 23, 2024
9d09b97
change
abbyxh Jul 23, 2024
d56c302
TEST
abbyxh Jul 23, 2024
4c194dd
is this working
abbyxh Jul 23, 2024
75f7c3c
completed detector changes
abbyxh Jul 23, 2024
b996379
Finished
abbyxh Jul 23, 2024
0cf2042
Added xml -> gltf detector conversion python scripts so that the full…
Jul 30, 2024
8dda039
Combined the .xml -> .root converter with the .root -> .gltf converte…
Jul 30, 2024
49bdb02
Added python script to get a dictionary of the layers in a given xml …
Aug 1, 2024
f33a7aa
Added a 'maxDepth' variable that can be used if you don't want to sho…
Aug 2, 2024
78bf31f
Created a processing function to turn the original tree dictionary in…
Aug 2, 2024
c262670
Tidied up the configfile generator python script so it could be added…
Aug 5, 2024
2e71168
Finished the 'conversion_xml2gltf_autoConfig.py' script to convert th…
Aug 8, 2024
027db15
Moved python files to 'bin' and added folders for root and gltf files…
Aug 8, 2024
a5a7979
Finished adding in 'inputs' that are given to the user throughout the…
Aug 8, 2024
a963223
Added in more detectors (some xml files seem unable to work currently…
Aug 9, 2024
568ed7b
Was able to add in an option for users to define parts of the detecto…
Aug 12, 2024
4265929
Added some comments on how the python scripts work as well as includi…
Aug 12, 2024
ad6ddb8
Changed the RegEx in index.js to be a different flag 'i' so that the …
Aug 12, 2024
0ff7ecd
Have been working out a way to allow users to add their own colours a…
Aug 15, 2024
c01dce2
Managed to fix up the colour changing code so that a user can put the…
Aug 16, 2024
b960abc
Functionised the colour changing code and made it so that if colourin…
Aug 16, 2024
fdec236
Added in a way for users to add the ILD colors to the automatic confi…
Aug 19, 2024
06f886b
Fixed a bug that added the subparts of the subdetector to the previou…
Aug 21, 2024
ed814ae
Made edits to the new code and edited the readme file to setup for me…
Sep 4, 2024
e683f9c
Create root_folder.txt
abbyxh Sep 4, 2024
3bb8b87
Create gltf.txt
abbyxh Sep 4, 2024
68ad178
Add files via upload
abbyxh Sep 5, 2024
dca3085
Add files via upload
abbyxh Sep 5, 2024
97fd933
Add files via upload
abbyxh Sep 5, 2024
07b9d9b
Add files via upload
abbyxh Sep 5, 2024
391414b
Add files via upload
abbyxh Sep 5, 2024
07b2013
Add files via upload
abbyxh Sep 5, 2024
2699afa
Add files via upload
abbyxh Sep 5, 2024
fadf0d1
Add files via upload
abbyxh Sep 5, 2024
bfb540e
Delete gltf_files/gltf.txt
abbyxh Sep 5, 2024
891e550
Delete root_files/root_folder.txt
abbyxh Sep 5, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,39 @@ Three variables are expected in the configuration file:
the phoenix menu
* `childrenToHide` stems of names of the subparts to be removed
* `maxLevel` maximum depth of detail

## Converting xml2gltf

The conversion_xml2gltf_autoConfig.py python script provides a way for an xml file to be converted into a gltf file while providing an initial or automatic configuration file that the user is able to alter if they would like to.

To run the python script in the bin folder with only a compact file:

```bash
python conversion_xml2gltf_autoConfig.py -cm <detector.xml>
```

The script can also be run by inputing a configuration file, a root file or both. The converter automatically saves the files root, gltf and config files in automatic folders named:

* `root_files` -root files can choose to be deleted at the end of the run if the user wishes to
* `gltf_files`
* `configs` -config files are automatically created if one is not provided but the run can be ended early if the user wants to edit this file

The user can input where they'd like to save the files:

```bash
python conversion_xml2gltf_autoConfig.py -cm <your-detector-file.xml>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
python conversion_xml2gltf_autoConfig.py -cm <your-detector-file.xml>
```bash
python conversion_xml2gltf_autoConfig.py -cm <your-detector-file.xml>

-r_in <input-root-file.root>
-r_out <output-root-file-loactaion.root>
-cn_in <input-config-file.json>
-cn_out <output-config-file-loactaion.json>
-g <output-gltf-file-location.gltf>
```

The user can also define how many layers of the detector they would like to see, what colour scheme they would like to use- 'ild' or leave blank for purple -and if they would like to hide any parts of the detector:

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
```bash

```bash
python conversion_xml2gltf_autoConfig.py -cm <detector.xml>
-d <maximum-depth-of-detector-layers>
-c <default-colours>
-hide <detector-parts-to-hide>
```
20 changes: 10 additions & 10 deletions bin/GLTFExporter.js
Original file line number Diff line number Diff line change
Expand Up @@ -1278,10 +1278,9 @@ class GLTFWriter {
console.warn( 'GLTFExporter: Use MeshStandardMaterial or MeshBasicMaterial for best results.' );

}

// pbrMetallicRoughness.baseColorFactor
const color = material.color.toArray().concat( [ material.opacity ] );

if ( ! equalArray( color, [ 1, 1, 1, 1 ] ) ) {

materialDef.pbrMetallicRoughness.baseColorFactor = color;
Expand Down Expand Up @@ -1664,12 +1663,13 @@ class GLTFWriter {
}

const isMultiMaterial = Array.isArray( mesh.material );

if ( isMultiMaterial && geometry.groups.length === 0 ) return null;

const materials = isMultiMaterial ? mesh.material : [ mesh.material ];
const groups = isMultiMaterial ? geometry.groups : [ { materialIndex: 0, start: undefined, count: undefined } ];

const groups = isMultiMaterial ? geometry.groups : [ { materialIndex: 0, start: undefined, count: undefined } ];

for ( let i = 0, il = groups.length; i < il; i ++ ) {

const primitive = {
Expand All @@ -1688,7 +1688,6 @@ class GLTFWriter {
if ( groups[ i ].start !== undefined || groups[ i ].count !== undefined ) {

cacheKey += ':' + groups[ i ].start + ':' + groups[ i ].count;

}

if ( cache.attributes.has( cacheKey ) ) {
Expand All @@ -1698,6 +1697,7 @@ class GLTFWriter {
} else {

primitive.indices = this.processAccessor( geometry.index, geometry, groups[ i ].start, groups[ i ].count );

cache.attributes.set( cacheKey, primitive.indices );

}
Expand All @@ -1707,7 +1707,7 @@ class GLTFWriter {
}

const material = this.processMaterial( materials[ groups[ i ].materialIndex ] );

if ( material !== null ) primitive.material = material;

primitives.push( primitive );
Expand Down Expand Up @@ -2012,7 +2012,7 @@ class GLTFWriter {
for ( let i = 0, l = object.children.length; i < l; i ++ ) {

const child = object.children[ i ];

if ( child.visible || options.onlyVisible === false ) {

const nodeIndex = this.processNode( child );
Expand Down Expand Up @@ -2066,19 +2066,19 @@ class GLTFWriter {
for ( let i = 0, l = scene.children.length; i < l; i ++ ) {

const child = scene.children[ i ];

if ( child.visible || options.onlyVisible === false ) {

const nodeIndex = this.processNode( child );

if ( nodeIndex !== null ) nodes.push( nodeIndex );

}

}

if ( nodes.length > 0 ) sceneDef.nodes = nodes;

this.serializeUserData( scene, sceneDef );

}
Expand Down
128 changes: 128 additions & 0 deletions bin/configfile_generator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
#!/usr/bin/env python
# coding: utf-8

from dd4hep import Detector
import re
from argparse import ArgumentParser
import pprint
import json

def main():
parser = ArgumentParser()

parser.add_argument(
"--compact", help="DD4hep compact description xml", required=True
)
parser.add_argument(
"--max_depth", help="Maximum traversal depth of the detector tree", default=10, type=int,
)
parser.add_argument(
"--config_path", help="Location of produced config file", default='nothing', type=str,
)
parser.add_argument(
"--hide_list", help="List of detector geometries that aren't shown", default='', type=str, nargs='+'
)
parser.add_argument(
"--coloring", help="Default colours for detector (choose ild or leave blank)", default='', type=str
)
args = parser.parse_args()

## Gets the detector geometry
theDetector = Detector.getInstance()
theDetector.fromXML(args.compact)
start = theDetector.world()

## Runs through the detector to make a tree dictionary of the detector parts
detector_dict = tree(start, 0, args.max_depth)
## Processes the detector tree to make a usable config file
subPart_processed, hidden_children = post_processing(detector_dict, list(detector_dict.keys()), '|'.join(args.hide_list[0].split(", ")), args.coloring)
## Produce a config.json file using the edited detector tree
produce_config(subPart_processed, hidden_children, args.config_path)

def process_name(raw_name):
## Changes any numbers for parts of a detector into .* (which in regex can mean any ending)
name = re.sub(r"\d+", ".*", raw_name)
return name

def add_ild_colors(subdetector):
subdetector_dict = {"Tube": [1,0.7,0.5],
"BeamPipe": [0,1,0],
"Vac": [0,0,0],
"VXD": [0.1,0.5,0.5],
"FTD": [0.39,0.1,0.57],
"BeamCal": [1,1,1],
"SIT": [0.86,0.86,0.86],
"SET": [0.86, 0.86, 0.86],
"TPC": [0.96,0.95,0],
"Ecal": [0.48,0.95,0],
"Hcal": [0.76,0.76,0.19],
"Yoke": [0.09,0.76,0.76],
"Coil": [0.28,0.28,0.86],
"Fcal": [0.67, 0.66, 0.67]
}
for s in subdetector_dict:
find_subdetector = re.search(f'{s}', f'{subdetector}',re.IGNORECASE)
if find_subdetector: return subdetector_dict[s]
return [0.57,0.63,0.81]


def tree(detElement, depth, maxDepth):
## Creates a detector tree using the geometry while also being able to set a max depth that the config file reaches
nd = {}
depth += 1
children = detElement.children()
for raw_name, child in children:
if depth > maxDepth:
tree(child, depth, maxDepth)
else:
dictionary = tree(child, depth, maxDepth)
nd.update({raw_name: dictionary})
return nd

def post_processing(obj, main_parts, hidden, coloring, subParts={}, sublist= [], hide_children= []):
## Processes the tree dictionary to make a usable config file
for k, v in obj.items():
if k in main_parts:
## Look for hidden children that we want to add to the hidden_children list and ignore
y = re.search(f'{hidden}', f'{k}', re.IGNORECASE)
if y == None:
## Removes envelopes from being featured in the final geometry
sublist = [f'({k}_(?!envelope))\\w+|({k}(?!_))\w+']
outer_list = []
outer_list.append(sublist)
outer_list.append(0.8)

## Adds automatic ILD coloring if the user asks
if coloring == "ild":
color = add_ild_colors(k)
outer_list.append(color)

subParts.update({str(k): outer_list})
post_processing(v, main_parts, hidden, coloring, subParts, sublist)

else:
hide_children.append(f'{k}')
sublist = []
post_processing(v, main_parts, hidden, coloring, subParts, sublist, hide_children)

else:
k_new = process_name(f"{k}\\w+")
## The function ignores components with common names that can often be in multiple detectors: module, stave, layer...
x = re.search("module|stave|layer|Calorimeter|component", k_new, re.IGNORECASE)
if k_new not in sublist and x == None:
sublist.append(f'{k_new}')
post_processing(v, main_parts, hidden, coloring, subParts, sublist, hide_children)
return subParts, hide_children

def produce_config(subParts, hidden, config_path):
## Create the final dictionary that will be converted into a config.json file
final_dict = {"childrenToHide": hidden,
"subParts": subParts,
"maxLevel": 3}

pprint.pprint(final_dict)
with open(config_path, "w") as outfile:
json.dump(final_dict, outfile, indent=4)

if __name__ == '__main__':
main()
134 changes: 134 additions & 0 deletions bin/conversion_xml2gltf_autoConfig.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@

import argparse
import ROOT
import subprocess
import os

def main():
parser = argparse.ArgumentParser(description='Convert detector')
parser.add_argument('-cm', '--compact_files', help='Compact file location(s)',
default='', type=str)
parser.add_argument('-cn_in', '--config_file_in', help='Json file of detector structure to be given',
default='', type=str)
parser.add_argument('-cn_out', '--config_file_out', help='Automatic json file of detector structure file path out',
default='', type=str)
parser.add_argument('-r_in', '--in_root', help='Input root file path (if a root file has already been obtained)',
default='', type=str)
parser.add_argument('-r_out', '--out_root', help='Converted root file path',
default='', type=str)
parser.add_argument('-g', '--out_gltf', help='Converted gltf file path',
default='', type=str)
parser.add_argument('-d', '--depth', help='Level of layers in detector to consider in the conversion',
default=10, type=int)
parser.add_argument('-c', '--color', help='Default colours for detector (choose ild or leave blank)',
default='', type=str)
parser.add_argument('-hide', '--hidden_children', help='Parts of the detector to be hidden',
default=['thereisnodetectorparthere'], type=str, nargs='+')
args = parser.parse_args()

## Loop for multiple compact files (currently not working)
##for cfile in args.compact_files:
## For if both a root and config file are given
if args.in_root and args.config_file_in:
config_file, root_path = args.config_file_in, args.in_root

## For if only a root file is given
elif args.in_root and not args.config_file_in:
config_file = automatic_config(args.config_file_out, args.compact_files, args.depth, args.color, args.hidden_children)
root_path = args.in_root

## For if only a config file is given
elif not args.in_root and args.config_file_in:
root_path = root_convert(args.compact_files, args.out_root, args.depth)
config_file = args.config_file_in

## For if neither file is given
else:
root_path = root_convert(args.compact_files, args.out_root, args.depth)
config_file = automatic_config(args.config_file_out, args.compact_files, args.depth, args.color, args.hidden_children)

gltf_convert(config_file, args.out_gltf, root_path)

def root_convert(cfile, out_path, visibility):
## Converts an xml file to root file
print('INFO: Converting following compact file(s):')
print(' ' + cfile)

ROOT.gSystem.Load('libDDCore')
description = ROOT.dd4hep.Detector.getInstance()
description.fromXML(cfile)

## Sets the number of layers visible in the root file
ROOT.gGeoManager.SetVisLevel(visibility)
ROOT.gGeoManager.SetVisOption(0)
## Sets an automatic root path if one isn't given
root_path = determine_outpath(out_path, cfile, 'root')
ROOT.gGeoManager.Export(root_path)

return root_path

def automatic_config(config_out, cfile, depth, color, hide):
## Creates an automatic configuaration file if one isn't given using the 'configfile_generator.py'
## Determines automatic outpath of config file
config_file = determine_outpath(config_out, cfile, "json")
subprocess.run(["python", "configfile_generator.py", "--compact", f'{cfile}', '--max_depth', f'{depth}', '--config_path', f'{config_file}', '--hide_list', ', '.join(hide), '--coloring', f'{color}'])

## Asks the user if they would like to exit the run to edit the automatic config file
config_edit = input(f'Would you like to exit and edit the automatic cofiguration file {config_file}? [y/n]')
program_questions(config_file, config_edit, "c")

return config_file


def gltf_convert(config, gltf, root):
## Converts the root file into a gltf file
## Determines the outpath of the gltf file if one isn't given
gltf_path = determine_outpath(gltf, root, "gltf")
subprocess.run(["node", ".", "-c", f'{config}', "-o", f'{gltf_path}', f'{root}'])

## Asks the user if they would like to remove the root file
rmv_file = input(f'Would you like to remove the {root} file? [y/n]')
program_questions(root, rmv_file, 'r')

def determine_outpath(out_path, file, ending):
## Generates the automatic out_paths for the files if one isn't provided
if out_path == '':
counter = 0
slash_list = []
for i in file:
counter += 1
if i == '/': slash_list.append(counter)
if i == '.': dot = counter

if not slash_list:
path = f'../{ending}_files/{file[:dot-1]}.{ending}'
else:
last_slash = max(slash_list)
if ending == 'json': path = f'../configs/{file[last_slash:dot-1]}.{ending}'
else: path = f'../{ending}_files/{file[last_slash:dot-1]}.{ending}'
else: path = out_path
return path

def program_questions(file, response, r_c):
## Asks questions to user as described above
while True:
if response == 'y':
if r_c == 'r':
print(f'Removing {file} file')
os.remove(file)
break
elif r_c == 'c':
print('Exiting converter')
exit()
elif response == 'n':
if r_c == 'r':
print(f'{file} file kept')
elif r_c == 'c':
print('Producing gltf file:')
break
else:
print("Incorrect response given (choose y/n)")
response = input()

if __name__ == '__main__':
main()
Loading