Skip to content

Commit c279086

Browse files
Merge branch 'develop' into bih_depth_limit
2 parents 426d339 + 05d3f69 commit c279086

8 files changed

Lines changed: 752 additions & 48 deletions

File tree

scripts/user/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
*.dot
2+
*.json
23
*.pdf
34
*.png

scripts/user/orange-csg-to-dot.py

Lines changed: 77 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
"""
55
Convert an ORANGE CSG JSON representation to a GraphViz or Mermaid input.
66
7+
Note that ``test/orange/g4org/Converter.test.cc`` can be used to generate
8+
the ``.csg.json`` files.
9+
710
.. example::
811
912
To run from a raw CSG tree that you've copied to your clipboard::
@@ -24,11 +27,50 @@
2427
from typing import Any
2528

2629

30+
def visit_csg_tree(tree: list, root_node: int, visited_nodes: set, visited_edges: set):
31+
"""
32+
Visit all nodes and edges in the CSG tree starting from root_node.
33+
34+
Populates visited_nodes and visited_edges sets with reachable elements.
35+
"""
36+
if root_node >= len(tree):
37+
return
38+
39+
stack = [root_node]
40+
41+
while stack:
42+
node_idx = stack.pop()
43+
if node_idx in visited_nodes or node_idx >= len(tree):
44+
continue
45+
46+
visited_nodes.add(node_idx)
47+
node = tree[node_idx]
48+
49+
if isinstance(node, str):
50+
# True/false literal - no children
51+
continue
52+
53+
(nodetype, value) = node
54+
55+
if isinstance(value, list):
56+
# Joined node - visit all children
57+
for child_idx in value:
58+
visited_edges.add((node_idx, child_idx))
59+
stack.append(child_idx)
60+
elif nodetype in "=~":
61+
# Aliased/negated node - visit child
62+
visited_edges.add((node_idx, value))
63+
stack.append(value)
64+
# Surface nodes (nodetype == "S") have no tree children
65+
66+
2767
class DotGenerator:
2868
def __init__(self, f, args):
2969
self.f = f
3070
self.write = f.write
3171
self.vol_edges = []
72+
self.visited_nodes = set()
73+
self.visited_edges = set()
3274

3375
def __enter__(self):
3476
self.write("""\
@@ -47,9 +89,10 @@ def __exit__(self, type, value, traceback):
4789
self.write(f"volume{i:02d} -> {i:02d}\n")
4890
self.write("}\n}\n")
4991

50-
def write_node(self, i: int, label: str):
51-
label = f' [label="{label}"]' if label else ""
52-
self.write(f"{i:02d}{label}\n")
92+
def write_node(self, i: int, label: str, is_visited: bool):
93+
label_attr = f' [label="{label}"' if label else " ["
94+
style = "" if is_visited else ", style=dashed, color=gray"
95+
self.write(f"{i:02d}{label_attr}{style}]\n")
5396

5497
@contextmanager
5598
def write_volumes(self):
@@ -68,15 +111,18 @@ def write_volume(self, i: int, label: str):
68111
self.write(f"volume{i:02d}{label}\n")
69112
self.vol_edges.append(i)
70113

71-
def write_edge(self, i: int, e: int):
72-
self.write(f"{i:02d} -> {e:02d};\n")
114+
def write_edge(self, i: int, e: int, is_visited: bool):
115+
style = "" if is_visited else " [style=dashed, color=gray]"
116+
self.write(f"{i:02d} -> {e:02d}{style};\n")
73117

74118

75119
class MermaidGenerator:
76120
def __init__(self, f, args):
77121
self.f = f
78122
self.write = f.write
79123
self.vol_edges = []
124+
self.visited_nodes = set()
125+
self.visited_edges = set()
80126

81127
def __enter__(self):
82128
self.write("flowchart TB\n")
@@ -86,9 +132,10 @@ def __exit__(self, type, value, traceback):
86132
for i in self.vol_edges:
87133
self.write(f" v{i:02d} <--> n{i:02d}\n")
88134

89-
def write_node(self, i: int, label: str):
90-
label = f'["{label}"]' if label else ""
91-
self.write(f" n{i:02d}{label}\n")
135+
def write_node(self, i: int, label: str, is_visited: bool):
136+
label_attr = f'["{label}"]' if label else ""
137+
style = "" if is_visited else ":::unvisited"
138+
self.write(f" n{i:02d}{label_attr}{style}\n")
92139

93140
@contextmanager
94141
def write_volumes(self):
@@ -103,11 +150,12 @@ def write_volume(self, i: int, label: str):
103150
self.write(f" v{i:02d}{label}\n")
104151
self.vol_edges.append(i)
105152

106-
def write_edge(self, i: int, e: int):
107-
self.write(f" n{i:02d} --> n{e:02d}\n")
153+
def write_edge(self, i: int, e: int, is_visited: bool):
154+
arrow = "-->" if is_visited else "-.->"
155+
self.write(f" n{i:02d} {arrow} n{e:02d}\n")
108156

109157

110-
def write_tree(gen: DotGenerator, csg_unit: dict[str, Any], args):
158+
def write_tree(gen, csg_unit: dict[str, Any], args):
111159
tree = csg_unit["tree"]
112160
metadata = csg_unit["metadata"] or repeat(None)
113161
get_surface = (
@@ -120,6 +168,8 @@ def write_tree(gen: DotGenerator, csg_unit: dict[str, Any], args):
120168
if isinstance(node, str):
121169
# True literal
122170
node = (node.upper(), node)
171+
if labels is None:
172+
labels = []
123173

124174
(nodetype, value) = node
125175

@@ -134,15 +184,18 @@ def write_tree(gen: DotGenerator, csg_unit: dict[str, Any], args):
134184
labels.append(nodetype)
135185

136186
label = r"\n".join(labels)
137-
gen.write_node(i, label)
187+
is_visited = i in gen.visited_nodes
188+
gen.write_node(i, label, is_visited)
138189

139190
if isinstance(value, list):
140191
# Joined
141192
for v in value:
142-
gen.write_edge(i, v)
193+
is_edge_visited = (i, v) in gen.visited_edges
194+
gen.write_edge(i, v, is_edge_visited)
143195
elif nodetype in "=~":
144196
# Aliased/negated
145-
gen.write_edge(i, value)
197+
is_edge_visited = (i, value) in gen.visited_edges
198+
gen.write_edge(i, value, is_edge_visited)
146199

147200

148201
def write_volumes(gen, volumes: dict[str, Any]):
@@ -177,16 +230,19 @@ def run(infile, outfile, gencls, args):
177230
)
178231
sys.exit(1)
179232
else:
180-
# Raw CSG tree, no metadata
181-
csg_unit = {
182-
"tree": tree,
183-
"metadata": None,
184-
"label": "CSG tree",
185-
}
233+
# Assume input is a single CSG unit copied from a csg.json file
234+
csg_unit = tree
186235

187236
with gencls(outfile, args) as gen:
188-
write_tree(gen, csg_unit, args)
237+
# Visit all nodes and edges that volumes depend on
189238
if vols := csg_unit.get("volumes"):
239+
csg_tree = csg_unit["tree"]
240+
for vol in vols:
241+
root = vol["csg_node"]
242+
visit_csg_tree(csg_tree, root, gen.visited_nodes, gen.visited_edges)
243+
244+
write_tree(gen, csg_unit, args)
245+
if vols:
190246
write_volumes(gen, vols)
191247

192248

src/orange/g4org/PhysicalVolumeConverter.cc

Lines changed: 13 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
#include <deque>
1010
#include <iostream>
11+
#include <variant>
1112
#include <G4LogicalVolume.hh>
1213
#include <G4PVPlacement.hh>
1314
#include <G4ReplicaNavigation.hh>
@@ -23,6 +24,7 @@
2324
#include "corecel/sys/TypeDemangler.hh"
2425
#include "geocel/GeantGeoParams.hh"
2526
#include "orange/inp/Import.hh"
27+
#include "orange/transform/NoTransformation.hh"
2628
#include "orange/transform/TransformIO.hh"
2729

2830
#include "LogicalVolumeConverter.hh"
@@ -128,15 +130,21 @@ PhysicalVolumeConverter::~PhysicalVolumeConverter() = default;
128130
//---------------------------------------------------------------------------//
129131
auto PhysicalVolumeConverter::operator()(arg_type g4world) -> result_type
130132
{
131-
CELER_EXPECT(!g4world.GetRotation());
132-
CELER_EXPECT(g4world.GetTranslation() == G4ThreeVector(0, 0, 0));
133-
134133
ScopedProfiling profile_this{"g4org-convert"};
135134
ScopedMem record_mem("orange.convert-geant");
136135

137136
CELER_LOG(status) << "Converting Geant4 geometry elements to ORANGE input";
138137
ScopedTimeLog scoped_time;
139138

139+
if (auto tf = data_->make_transform.variant(g4world.GetTranslation(),
140+
g4world.GetRotation());
141+
!std::holds_alternative<NoTransformation>(tf))
142+
{
143+
CELER_LOG(warning)
144+
<< "Ignoring transformation " << StreamableVariant{tf}
145+
<< " on top-level Geant4 volume '" << g4world.GetName() << "'";
146+
}
147+
140148
// Construct world volume
141149
Builder impl{data_.get(), {}};
142150
auto world = impl.make_pv(0, g4world);
@@ -158,24 +166,8 @@ PhysicalVolumeConverter::Builder::make_pv(int depth,
158166
result.id = this->data->geo.geant_to_id(g4pv);
159167

160168
// Get transform
161-
result.transform = [&]() -> VariantTransform {
162-
auto const& g4trans = g4pv.GetObjectTranslation();
163-
if (g4pv.GetFrameRotation())
164-
{
165-
// Get the child-to-parent rotation and do another check for the
166-
// identity matrix (parameterized volumes often have one)
167-
auto const& rot = g4pv.GetObjectRotationValue();
168-
if (!rot.isIdentity())
169-
{
170-
return this->data->make_transform(g4trans, rot);
171-
}
172-
}
173-
if (g4trans[0] != 0 || g4trans[1] != 0 || g4trans[2] != 0)
174-
{
175-
return this->data->make_transform(g4trans);
176-
}
177-
return NoTransformation{};
178-
}();
169+
result.transform = this->data->make_transform.variant(
170+
g4pv.GetObjectTranslation(), g4pv.GetObjectRotation());
179171

180172
// Convert logical volume
181173
auto* g4lv = g4pv.GetLogicalVolume();

src/orange/g4org/Transformer.hh

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,10 @@ class Transformer
6464
// Convert an affine transform
6565
inline Transformation operator()(G4AffineTransform const& at) const;
6666

67+
// Construct dynamically
68+
inline VariantTransform
69+
variant(G4ThreeVector const& t, G4RotationMatrix const* rot) const;
70+
6771
private:
6872
//// DATA ////
6973

@@ -143,6 +147,29 @@ auto Transformer::operator()(G4AffineTransform const& affine) const
143147
scale_.to<Real3>(affine.NetTranslation())};
144148
}
145149

150+
//---------------------------------------------------------------------------//
151+
/*!
152+
* Create a transform from a translation and optional rotation.
153+
*/
154+
auto Transformer::variant(G4ThreeVector const& trans,
155+
G4RotationMatrix const* rot) const -> VariantTransform
156+
{
157+
if (rot)
158+
{
159+
// Do another check for the identity matrix (parameterized volumes
160+
// often have one)
161+
if (!rot->isIdentity())
162+
{
163+
return (*this)(trans, *rot);
164+
}
165+
}
166+
if (trans[0] != 0 || trans[1] != 0 || trans[2] != 0)
167+
{
168+
return (*this)(trans);
169+
}
170+
return NoTransformation{};
171+
}
172+
146173
//---------------------------------------------------------------------------//
147174
/*!
148175
* Convert a ThreeVector.

0 commit comments

Comments
 (0)