44"""
55Convert 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::
2427from 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+
2767class 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
75119class 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
148201def 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
0 commit comments