2727def discover_time_series (
2828 paths : list [Path ],
2929 pattern : str = r"\.t(\d+)\.(vtk|vtp|vtu)$" ,
30- ) -> list [tuple [int , Path ]]:
30+ ) -> tuple [ list [tuple [int , Path ]], bool ]:
3131 """Discover and sort time-series VTK files by extracted time index.
3232
3333 Args:
3434 paths: List of paths to VTK files
3535 pattern: Regex with one group for time step number (default matches .t123.vtk)
3636
3737 Returns:
38- Sorted list of (time_step, path) tuples. If no match, returns [(0, p) for p in paths].
38+ (time_series, pattern_matched): Sorted list of (time_step, path) tuples, and
39+ a flag True if at least one path matched the pattern. If no path matches,
40+ time_series is [(0, p) for p in paths] and pattern_matched is False.
3941 """
4042 time_series : list [tuple [int , Path ]] = []
4143 regex = re .compile (pattern , re .IGNORECASE )
44+ pattern_matched = False
4245 for p in paths :
4346 match = regex .search (p .name )
4447 if match :
4548 time_series .append ((int (match .group (1 )), Path (p )))
49+ pattern_matched = True
4650 else :
4751 time_series .append ((0 , Path (p )))
4852 time_series .sort (key = lambda x : (x [0 ], str (x [1 ])))
49- return time_series
53+ return time_series , pattern_matched
5054
5155
5256AppearanceKind = Literal ["solid" , "anatomy" , "colormap" ]
@@ -141,17 +145,24 @@ def run(self) -> str:
141145 raise ValueError ("vtk_files must not be empty" )
142146
143147 # Discover time series
144- time_series = discover_time_series (
148+ time_series , pattern_matched = discover_time_series (
145149 self .vtk_files , pattern = self .time_series_pattern
146150 )
147151 time_steps = [t for t , _ in time_series ]
148152 time_codes = [float (t ) for t in time_steps ]
149153 paths_ordered = [p for _ , p in time_series ]
150154 n_frames = len (paths_ordered )
151155
156+ # Multiple files but no pattern match: treat as static scene (all at time 0, no time samples)
157+ is_static_merge = n_frames > 1 and not pattern_matched
158+
152159 self .log_info ("Input: %d file(s), time steps: %s" , n_frames , time_steps [:5 ])
153160 if n_frames > 5 :
154161 self .log_info (" ... and %d more" , n_frames - 5 )
162+ if is_static_merge :
163+ self .log_info (
164+ "Filenames do not match time-series pattern; outputting static scene (no time samples)"
165+ )
155166 self .log_info ("Output: %s" , self .output_usd )
156167
157168 settings = ConversionSettings (
@@ -163,7 +174,7 @@ def run(self) -> str:
163174 separate_objects_by_cell_type = self .separate_by_cell_type ,
164175 up_axis = self .up_axis ,
165176 times_per_second = self .times_per_second ,
166- use_time_samples = True ,
177+ use_time_samples = not is_static_merge ,
167178 )
168179
169180 converter = VTKToUSDConverter (settings )
@@ -181,13 +192,22 @@ def run(self) -> str:
181192 material = default_material ,
182193 extract_surface = self .extract_surface ,
183194 )
195+ elif is_static_merge :
196+ stage = converter .convert_files_static (
197+ paths_ordered ,
198+ self .output_usd ,
199+ mesh_name = self .mesh_name ,
200+ material = default_material ,
201+ extract_surface = self .extract_surface ,
202+ )
184203 else :
204+ # Load mesh sequence once for both validation and conversion (avoids double I/O)
205+ mesh_sequence = [
206+ read_vtk_file (p , extract_surface = self .extract_surface )
207+ for p in paths_ordered
208+ ]
185209 # Optional: validate topology consistency across frames
186210 try :
187- mesh_sequence = [
188- read_vtk_file (p , extract_surface = self .extract_surface )
189- for p in paths_ordered
190- ]
191211 report = validate_time_series_topology (mesh_sequence )
192212 if report .get ("topology_changes" ):
193213 self .log_warning (
@@ -197,13 +217,12 @@ def run(self) -> str:
197217 except Exception as e :
198218 self .log_debug ("Time series validation skipped: %s" , e )
199219
200- stage = converter .convert_sequence (
201- paths_ordered ,
220+ stage = converter .convert_mesh_data_sequence (
221+ mesh_sequence ,
202222 self .output_usd ,
203223 mesh_name = self .mesh_name ,
204224 time_codes = time_codes ,
205225 material = default_material ,
206- extract_surface = self .extract_surface ,
207226 )
208227
209228 # Post-process: apply chosen appearance to all meshes under /World/Meshes
@@ -215,6 +234,9 @@ def run(self) -> str:
215234 self .log_warning ("No mesh prims found under /World/Meshes" )
216235 return str (self .output_usd )
217236
237+ # Static merge has no time samples; pass None so only default time is used
238+ appearance_time_codes = None if is_static_merge else time_codes
239+
218240 self .log_info (
219241 "Applying appearance '%s' to %d mesh(es)" , self .appearance , len (mesh_paths )
220242 )
@@ -225,7 +247,7 @@ def run(self) -> str:
225247 str (self .output_usd ),
226248 mesh_path ,
227249 self .solid_color ,
228- time_codes = time_codes ,
250+ time_codes = appearance_time_codes ,
229251 bind_vertex_color_material = True ,
230252 )
231253
@@ -249,7 +271,6 @@ def run(self) -> str:
249271 self .log_warning (
250272 "No color primvar found for %s; skip colormap" , mesh_path
251273 )
252- primvar = self .colormap_primvar
253274 continue
254275 self .log_info (
255276 "Applying colormap to %s from primvar %s" , mesh_path , primvar
0 commit comments