Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
31 changes: 24 additions & 7 deletions SofaRegressionProgram/SofaRegressionProgram.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@
import SofaRuntime # importing SofaRuntime will add the py3 loader to the scene loaders
import tools.RegressionSceneList as RegressionSceneList
import tools.RegressionWorker as RegressionWorker
from tools.RegressionHelper import writeMessage
from tools.RegressionHelper import writeMessage, writeWarning

regression_file_extension = ".regression-tests"

class RegressionProgram:
def __init__(self, input_folders, filter = None, disable_progress_bar = False, verbose = 1, nbr_jobs = 1, logs_output = None):
def __init__(self, input_folders, filter = None, reg_type = 'ALL', disable_progress_bar = False, verbose = 1, nbr_jobs = 1, logs_output = None):
"""Initialize the RegressionProgram

Args:
Expand All @@ -37,6 +37,7 @@ def __init__(self, input_folders, filter = None, disable_progress_bar = False, v
self.legacy_mode = False
self.nbr_jobs = RegressionWorker.resolve_nbr_jobs(nbr_jobs)
self.logs_output = logs_output
self.reg_type = reg_type

err_logs_stream = None
if self.logs_output is not None :
Expand All @@ -46,9 +47,10 @@ def __init__(self, input_folders, filter = None, disable_progress_bar = False, v
for directory in input_folders :
for root, dirs, files in os.walk(directory):
for file in files:
if file.endswith(regression_file_extension):
file_path = os.path.join(root, file)
file_path = os.path.join(root, file)

#Warning lazy or in the end, if not lazy then this breaks
if file.endswith(regression_file_extension) and (reg_type == 'ALL' or RegressionSceneList.RegressionSceneList.RegressionType[reg_type].value[0] in file) :
scene_list = RegressionSceneList.RegressionSceneList(file_path, filter, self.disable_progress_bar, verbose, self.nbr_jobs)

if err_logs_stream is not None:
Expand All @@ -60,6 +62,9 @@ def __init__(self, input_folders, filter = None, disable_progress_bar = False, v
print("", file=err_logs_stream)

self.scene_sets.append(scene_list)
elif file.endswith(regression_file_extension):
writeWarning(f"Regression file {file_path} skipped because of selected regression type {reg_type}", self.verbose)

finally:
if self.logs_output is not None :
with open(Path(self.logs_output) / "parse_errors_logs.txt", 'w', encoding="utf-8") as summary_file:
Expand Down Expand Up @@ -120,8 +125,11 @@ def compare_all_sets_references(self):
return self.run_all_sets("compare", "Compare All sets")

def replay_references(self, id_scene, id_set=0):
scene_list = self.scene_sets[id_set]
scene_list.replay_references(id_scene)
if(self.scene_sets[id_set].regression_type is not None and self.scene_sets[id_set].regression_type.value[1].is_replay_available()):
scene_list = self.scene_sets[id_set]
scene_list.replay_references(id_scene)
else:
raise ValueError(f"Replay is not available for regression type {self.scene_sets[id_set].regression_type}")



Expand All @@ -140,6 +148,15 @@ def make_parser():
default=[],
type=str)

regression_type_choices = ['ALL', *(reg_type.name for reg_type in RegressionSceneList.RegressionSceneList.RegressionType)]
parser.add_argument('--regression-type',
dest='reg_type',
choices=regression_type_choices,
help=f"The regression type from {regression_type_choices}. Default value is ALL.",
type=str,
default='ALL')


parser.add_argument('--filter',
dest='filter',
help="A regex filter to select scenes to test (e.g., '^demo.*.scn$')",
Expand Down Expand Up @@ -222,7 +239,7 @@ def make_parser():

# 2- Process file
if args.input:
reg_prog = RegressionProgram(args.input, args.filter, args.progress_bar_is_disabled, verbose, args.jobs, logs_output = args.output)
reg_prog = RegressionProgram(args.input, args.filter, args.reg_type, args.progress_bar_is_disabled, verbose, args.jobs, logs_output = args.output)
else:
parser.print_help()
exit("Error: Argument is required ! Quitting.")
Expand Down
75 changes: 73 additions & 2 deletions SofaRegressionProgram/tools/ReferenceFileIO.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ def read_JSON_reference_file(file_path):
keyframes = []
for key in decoded_array:
keyframes.append(float(key))

return decoded_array, keyframes

# --------------------------------------------------
Expand All @@ -109,7 +109,7 @@ def read_legacy_reference(filename, mechanical_object):
if line.startswith("T="):
current_time = float(line.split("=", 1)[1])
times.append(current_time)

# Positions
elif line.startswith("X="):
if current_time is None:
Expand Down Expand Up @@ -137,3 +137,74 @@ def read_legacy_reference(filename, mechanical_object):
)

return times, values

# --------------------------------------------------
# Helper: read the legacy topology reference format
# --------------------------------------------------
# Written by the former C++ WriteTopology component, one block per timestep:
# T= <time>
# Edges= <nbr>
# <raw edges list, 2 ints each, space separated, on one line (blank if nbr==0)>
# Triangles= <nbr>
# <raw triangles list, 3 ints each, space separated, on one line (blank if nbr==0)>
# Quads= <nbr>
# <raw quads list, 4 ints each, space separated, on one line (blank if nbr==0)>
# Tetrahedra= <nbr>
# <raw tetrahedra list, 4 ints each, space separated, on one line (blank if nbr==0)>
# Hexahedra= <nbr>
# <raw hexahedra list, 8 ints each, space separated, on one line (blank if nbr==0)>
_legacy_topology_categories = (
("Edges=", "edges", 2),
("Triangles=", "triangles", 3),
("Quads=", "quads", 4),
("Tetrahedra=", "tetrahedra", 4),
("Hexahedra=", "hexahedra", 8),
)


def read_legacy_topology_reference(filename):
times = []
values = []
current_entry = None

with gzip.open(filename, "rt") as f:
for line in f:
stripped = line.strip()
if not stripped:
continue

if stripped.startswith("T="):
if current_entry is not None:
values.append(current_entry)
times.append(float(stripped.split("=", 1)[1]))
current_entry = {key: [] for _, key, _ in _legacy_topology_categories}
continue

for label, key, arity in _legacy_topology_categories:
if not stripped.startswith(label):
continue

nbr = int(stripped.split("=", 1)[1].strip())
if nbr > 0:
raw = next(f).split()
if len(raw) != nbr * arity:
raise ValueError(
f"Legacy topology reference corrupted in {filename}: "
f"expected {nbr * arity} values for '{key}', got {len(raw)}"
)
current_entry[key] = [
tuple(int(v) for v in raw[i * arity:(i + 1) * arity])
for i in range(nbr)
]
break

if current_entry is not None:
values.append(current_entry)

if len(times) != len(values):
raise RuntimeError(
f"Legacy topology reference corrupted in {filename}: "
f"{len(times)} times vs {len(values)} entries"
)

return times, values
Loading