mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-06-10 22:12:49 +00:00
Add codegen pipeline
This commit is contained in:
213
tools/validate_codegen.py
Normal file
213
tools/validate_codegen.py
Normal file
@@ -0,0 +1,213 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Validates generated PrintConfigDef code against the original PrintConfig.cpp.
|
||||
|
||||
Compares setting keys, types, defaults, enum values/labels, and metadata
|
||||
to ensure the codegen output is a faithful reproduction.
|
||||
"""
|
||||
|
||||
import re
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from collections import OrderedDict
|
||||
|
||||
|
||||
def parse_original_settings(cpp_path):
|
||||
"""Parse the original init_fff_params() into a dict of key -> properties."""
|
||||
with open(cpp_path, 'r', encoding='utf-8') as f:
|
||||
text = f.read()
|
||||
|
||||
m = re.search(r'void PrintConfigDef::init_fff_params\(\)(.*?)^\}', text, re.DOTALL | re.MULTILINE)
|
||||
if not m:
|
||||
print("ERROR: Could not find init_fff_params()")
|
||||
sys.exit(1)
|
||||
body = m.group(1)
|
||||
|
||||
settings = OrderedDict()
|
||||
current_key = None
|
||||
|
||||
for line in body.split('\n'):
|
||||
stripped = line.strip()
|
||||
|
||||
# Detect this->add("key", coType)
|
||||
add_match = re.search(r'this->add(?:_nullable)?\s*\(\s*"([^"]+)"\s*,\s*(co\w+)\s*\)', stripped)
|
||||
if add_match:
|
||||
current_key = add_match.group(1)
|
||||
co_type = add_match.group(2)
|
||||
# Last definition wins (handles duplicates)
|
||||
settings[current_key] = {
|
||||
'co_type': co_type,
|
||||
'has_default': False,
|
||||
'enum_values': 0,
|
||||
'enum_labels': 0,
|
||||
'has_enum_map': False,
|
||||
}
|
||||
continue
|
||||
|
||||
if current_key and current_key in settings:
|
||||
s = settings[current_key]
|
||||
if 'set_default_value' in stripped:
|
||||
s['has_default'] = True
|
||||
if re.search(r'enum_values\.(?:push_back|emplace_back)', stripped):
|
||||
s['enum_values'] += 1
|
||||
if re.search(r'enum_labels\.(?:push_back|emplace_back)', stripped):
|
||||
s['enum_labels'] += 1
|
||||
if 'enum_keys_map' in stripped and '=' in stripped:
|
||||
s['has_enum_map'] = True
|
||||
|
||||
return settings
|
||||
|
||||
|
||||
def parse_generated_settings(gen_path):
|
||||
"""Parse the generated PrintConfigDef code into a dict of key -> properties."""
|
||||
with open(gen_path, 'r', encoding='utf-8') as f:
|
||||
text = f.read()
|
||||
|
||||
settings = OrderedDict()
|
||||
current_key = None
|
||||
|
||||
for line in text.split('\n'):
|
||||
stripped = line.strip()
|
||||
|
||||
add_match = re.search(r'this->add(?:_nullable)?\s*\(\s*"([^"]+)"\s*,\s*(co\w+)\s*\)', stripped)
|
||||
if add_match:
|
||||
current_key = add_match.group(1)
|
||||
co_type = add_match.group(2)
|
||||
settings[current_key] = {
|
||||
'co_type': co_type,
|
||||
'has_default': False,
|
||||
'enum_values': 0,
|
||||
'enum_labels': 0,
|
||||
'has_enum_map': False,
|
||||
}
|
||||
continue
|
||||
|
||||
if current_key and current_key in settings:
|
||||
s = settings[current_key]
|
||||
if 'set_default_value' in stripped:
|
||||
s['has_default'] = True
|
||||
if 'enum_values.push_back' in stripped:
|
||||
s['enum_values'] += 1
|
||||
if 'enum_labels.push_back' in stripped:
|
||||
s['enum_labels'] += 1
|
||||
if 'enum_keys_map' in stripped and '=' in stripped:
|
||||
s['has_enum_map'] = True
|
||||
|
||||
return settings
|
||||
|
||||
|
||||
def main():
|
||||
orig_path = Path("src/libslic3r/PrintConfig.cpp")
|
||||
gen_path = Path("codegen/generated/PrintConfigDef_generated.cpp")
|
||||
|
||||
if not orig_path.exists() or not gen_path.exists():
|
||||
print("ERROR: Required files not found")
|
||||
sys.exit(1)
|
||||
|
||||
print("Parsing original...")
|
||||
orig = parse_original_settings(orig_path)
|
||||
print(f" {len(orig)} settings")
|
||||
|
||||
print("Parsing generated...")
|
||||
gen = parse_generated_settings(gen_path)
|
||||
print(f" {len(gen)} settings")
|
||||
|
||||
# Known exceptions: settings that exist in original but are commented out
|
||||
# or have runtime-generated enums
|
||||
known_exceptions = {
|
||||
'adaptive_layer_height', # Commented out in original
|
||||
'spaghetti_detector', # Commented out in original
|
||||
}
|
||||
# Settings with runtime-generated enum values (loop over MaterialType::all())
|
||||
runtime_enum_keys = {
|
||||
'filament_type', # enum_values from runtime loop
|
||||
}
|
||||
|
||||
# Compare
|
||||
errors = []
|
||||
warnings = []
|
||||
|
||||
# Missing keys
|
||||
orig_keys = set(orig.keys())
|
||||
gen_keys = set(gen.keys())
|
||||
|
||||
missing = orig_keys - gen_keys
|
||||
extra = gen_keys - orig_keys
|
||||
|
||||
if missing:
|
||||
real_missing = missing - known_exceptions
|
||||
if real_missing:
|
||||
errors.append(f"MISSING from generated ({len(real_missing)}): {sorted(real_missing)}")
|
||||
noted = missing & known_exceptions
|
||||
if noted:
|
||||
warnings.append(f"Known exceptions (commented out in original): {sorted(noted)}")
|
||||
if extra:
|
||||
warnings.append(f"EXTRA in generated ({len(extra)}): {sorted(extra)}")
|
||||
|
||||
# Compare shared keys
|
||||
shared = orig_keys & gen_keys
|
||||
type_mismatches = []
|
||||
default_mismatches = []
|
||||
enum_val_mismatches = []
|
||||
enum_lbl_mismatches = []
|
||||
enum_map_mismatches = []
|
||||
|
||||
for key in sorted(shared):
|
||||
o = orig[key]
|
||||
g = gen[key]
|
||||
|
||||
if o['co_type'] != g['co_type']:
|
||||
type_mismatches.append(f" {key}: orig={o['co_type']} gen={g['co_type']}")
|
||||
|
||||
if o['has_default'] != g['has_default'] and key not in known_exceptions:
|
||||
default_mismatches.append(f" {key}: orig={o['has_default']} gen={g['has_default']}")
|
||||
|
||||
if o['enum_values'] != g['enum_values'] and key not in runtime_enum_keys:
|
||||
enum_val_mismatches.append(f" {key}: orig={o['enum_values']} gen={g['enum_values']}")
|
||||
|
||||
if o['enum_labels'] != g['enum_labels']:
|
||||
enum_lbl_mismatches.append(f" {key}: orig={o['enum_labels']} gen={g['enum_labels']}")
|
||||
|
||||
if o['has_enum_map'] != g['has_enum_map']:
|
||||
enum_map_mismatches.append(f" {key}: orig={o['has_enum_map']} gen={g['has_enum_map']}")
|
||||
|
||||
# Report
|
||||
print("\n=== VALIDATION RESULTS ===\n")
|
||||
|
||||
if type_mismatches:
|
||||
errors.append(f"TYPE MISMATCHES ({len(type_mismatches)}):\n" + "\n".join(type_mismatches))
|
||||
|
||||
if default_mismatches:
|
||||
errors.append(f"DEFAULT MISMATCHES ({len(default_mismatches)}):\n" + "\n".join(default_mismatches))
|
||||
|
||||
if enum_val_mismatches:
|
||||
warnings.append(f"ENUM VALUE COUNT MISMATCHES ({len(enum_val_mismatches)}):\n" + "\n".join(enum_val_mismatches))
|
||||
|
||||
if enum_lbl_mismatches:
|
||||
warnings.append(f"ENUM LABEL COUNT MISMATCHES ({len(enum_lbl_mismatches)}):\n" + "\n".join(enum_lbl_mismatches))
|
||||
|
||||
if enum_map_mismatches:
|
||||
warnings.append(f"ENUM MAP MISMATCHES ({len(enum_map_mismatches)}):\n" + "\n".join(enum_map_mismatches))
|
||||
|
||||
if warnings:
|
||||
print("WARNINGS:")
|
||||
for w in warnings:
|
||||
print(f" {w}")
|
||||
print()
|
||||
|
||||
if errors:
|
||||
print("ERRORS:")
|
||||
for e in errors:
|
||||
print(f" {e}")
|
||||
print(f"\nValidation FAILED with {len(errors)} error(s)")
|
||||
sys.exit(1)
|
||||
else:
|
||||
print(f"All {len(shared)} shared settings validated successfully")
|
||||
if extra:
|
||||
print(f" ({len(extra)} extra settings from axis expansion)")
|
||||
print("\nValidation PASSED")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user