mirror of
https://github.com/SoftFever/OrcaSlicer.git
synced 2025-06-26 09:25:26 -06:00
Add check for obsolete keys in profiles (#9955)
* feat: Add check for obsolete keys in filament profiles and improve error handling * feat: Enhance error handling in machine profile checks and filament name consistency * feat: Add option to check for obsolete keys in profile validation * feat: Clarify help message for obsolete keys check in filament profiles
This commit is contained in:
parent
8aec3f69e5
commit
5c42b396e0
2 changed files with 150 additions and 51 deletions
|
@ -401,11 +401,15 @@ In addition to the Orca validator, you should run the `orca_extra_profile_check.
|
||||||
python ./orca_extra_profile_check.py
|
python ./orca_extra_profile_check.py
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
You can also enable or disable specific checks:
|
You can also enable or disable specific checks:
|
||||||
|
|
||||||
|
- `--help`: displays help information
|
||||||
- `--vendor` (optional): checks only the specified vendor. If omitted, all vendors are checked.
|
- `--vendor` (optional): checks only the specified vendor. If omitted, all vendors are checked.
|
||||||
- `--check-filaments` (enabled by default): checks `compatible_printers` fields in filament profiles
|
- `--check-filaments` (enabled by default): checks `compatible_printers` fields in filament profiles
|
||||||
- `--check-materials`: checks default material names in machine profiles
|
- `--check-materials`: checks default material names in machine profiles
|
||||||
|
- `--check-obsolete-keys`: checks for obsolete keys in profiles
|
||||||
|
|
||||||
|
|
||||||
#### Sample usage with all checks enabled
|
#### Sample usage with all checks enabled
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,37 @@ import json
|
||||||
import argparse
|
import argparse
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
OBSOLETE_KEYS = {
|
||||||
|
"acceleration", "scale", "rotate", "duplicate", "duplicate_grid",
|
||||||
|
"bed_size", "print_center", "g0", "wipe_tower_per_color_wipe",
|
||||||
|
"support_sharp_tails", "support_remove_small_overhangs", "support_with_sheath",
|
||||||
|
"tree_support_collision_resolution", "tree_support_with_infill",
|
||||||
|
"max_volumetric_speed", "max_print_speed", "support_closing_radius",
|
||||||
|
"remove_freq_sweep", "remove_bed_leveling", "remove_extrusion_calibration",
|
||||||
|
"support_transition_line_width", "support_transition_speed", "bed_temperature",
|
||||||
|
"bed_temperature_initial_layer", "can_switch_nozzle_type", "can_add_auxiliary_fan",
|
||||||
|
"extra_flush_volume", "spaghetti_detector", "adaptive_layer_height",
|
||||||
|
"z_hop_type", "z_lift_type", "bed_temperature_difference", "long_retraction_when_cut",
|
||||||
|
"retraction_distance_when_cut", "extruder_type", "internal_bridge_support_thickness",
|
||||||
|
"extruder_clearance_max_radius", "top_area_threshold", "reduce_wall_solid_infill",
|
||||||
|
"filament_load_time", "filament_unload_time", "smooth_coefficient",
|
||||||
|
"overhang_totally_speed", "silent_mode", "overhang_speed_classic"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Utility functions for printing messages in different colors.
|
||||||
|
def print_error(msg):
|
||||||
|
print(f"\033[91m[ERROR]\033[0m {msg}") # Red
|
||||||
|
|
||||||
|
def print_warning(msg):
|
||||||
|
print(f"\033[93m[WARNING]\033[0m {msg}") # Yellow
|
||||||
|
|
||||||
|
def print_info(msg):
|
||||||
|
print(f"\033[94m[INFO]\033[0m {msg}") # Blue
|
||||||
|
|
||||||
|
def print_success(msg):
|
||||||
|
print(f"\033[92m[SUCCESS]\033[0m {msg}") # Green
|
||||||
|
|
||||||
|
|
||||||
# Add helper function for duplicate key detection.
|
# Add helper function for duplicate key detection.
|
||||||
def no_duplicates_object_pairs_hook(pairs):
|
def no_duplicates_object_pairs_hook(pairs):
|
||||||
seen = {}
|
seen = {}
|
||||||
|
@ -37,17 +68,17 @@ def check_filament_compatible_printers(vendor_folder):
|
||||||
# Use custom hook to detect duplicates.
|
# Use custom hook to detect duplicates.
|
||||||
data = json.load(fp, object_pairs_hook=no_duplicates_object_pairs_hook)
|
data = json.load(fp, object_pairs_hook=no_duplicates_object_pairs_hook)
|
||||||
except ValueError as ve:
|
except ValueError as ve:
|
||||||
print(f"Duplicate key error in {file_path}: {ve}")
|
print_error(f"Duplicate key error in {file_path}: {ve}")
|
||||||
error += 1
|
error += 1
|
||||||
continue
|
continue
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error processing {file_path}: {e}")
|
print_error(f"Error processing {file_path}: {e}")
|
||||||
error += 1
|
error += 1
|
||||||
continue
|
continue
|
||||||
|
|
||||||
profile_name = data['name']
|
profile_name = data['name']
|
||||||
if profile_name in profiles:
|
if profile_name in profiles:
|
||||||
print(f"Duplicated profile {profile_name}: {file_path}")
|
print_error(f"Duplicated profile {profile_name}: {file_path}")
|
||||||
error += 1
|
error += 1
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
@ -76,10 +107,10 @@ def check_filament_compatible_printers(vendor_folder):
|
||||||
try:
|
try:
|
||||||
compatible_printers = get_inherit_property(profile, "compatible_printers")
|
compatible_printers = get_inherit_property(profile, "compatible_printers")
|
||||||
if not compatible_printers or (isinstance(compatible_printers, list) and not compatible_printers):
|
if not compatible_printers or (isinstance(compatible_printers, list) and not compatible_printers):
|
||||||
print(f"'compatible_printers' missing in {profile['file_path']}")
|
print_error(f"'compatible_printers' missing in {profile['file_path']}")
|
||||||
error += 1
|
error += 1
|
||||||
except ValueError as ve:
|
except ValueError as ve:
|
||||||
print(f"Unable to parse {profile['file_path']}: {ve}")
|
print_error(f"Unable to parse {profile['file_path']}: {ve}")
|
||||||
error += 1
|
error += 1
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
@ -109,7 +140,7 @@ def load_available_filament_profiles(profiles_dir, vendor_name):
|
||||||
if "name" in data:
|
if "name" in data:
|
||||||
profiles.add(data["name"])
|
profiles.add(data["name"])
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error loading filament profile {file_path}: {e}")
|
print_error(f"Error loading filament profile {file_path}: {e}")
|
||||||
|
|
||||||
return profiles
|
return profiles
|
||||||
|
|
||||||
|
@ -124,13 +155,14 @@ def check_machine_default_materials(profiles_dir, vendor_name):
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
int: Number of missing filament references found
|
int: Number of missing filament references found
|
||||||
|
int: the number of warnings found (0 or 1)
|
||||||
"""
|
"""
|
||||||
error_count = 0
|
error_count = 0
|
||||||
machine_dir = profiles_dir / vendor_name / "machine"
|
machine_dir = profiles_dir / vendor_name / "machine"
|
||||||
|
|
||||||
if not machine_dir.exists():
|
if not machine_dir.exists():
|
||||||
print(f"No machine profiles found for vendor: {vendor_name}")
|
print_warning(f"No machine profiles found for vendor: {vendor_name}")
|
||||||
return 0
|
return 0, 1
|
||||||
|
|
||||||
# Load available filament profiles
|
# Load available filament profiles
|
||||||
vendor_filaments = load_available_filament_profiles(profiles_dir, vendor_name)
|
vendor_filaments = load_available_filament_profiles(profiles_dir, vendor_name)
|
||||||
|
@ -153,7 +185,7 @@ def check_machine_default_materials(profiles_dir, vendor_name):
|
||||||
if isinstance(default_materials, list):
|
if isinstance(default_materials, list):
|
||||||
for material in default_materials:
|
for material in default_materials:
|
||||||
if material not in all_available_filaments:
|
if material not in all_available_filaments:
|
||||||
print(f"Missing filament profile: '{material}' referenced in {file_path.relative_to(profiles_dir)}")
|
print_error(f"Missing filament profile: '{material}' referenced in {file_path.relative_to(profiles_dir)}")
|
||||||
error_count += 1
|
error_count += 1
|
||||||
else:
|
else:
|
||||||
# Handle semicolon-separated list of materials in a string
|
# Handle semicolon-separated list of materials in a string
|
||||||
|
@ -161,43 +193,51 @@ def check_machine_default_materials(profiles_dir, vendor_name):
|
||||||
for material in default_materials.split(";"):
|
for material in default_materials.split(";"):
|
||||||
material = material.strip()
|
material = material.strip()
|
||||||
if material and material not in all_available_filaments:
|
if material and material not in all_available_filaments:
|
||||||
print(f"Missing filament profile: '{material}' referenced in {file_path.relative_to(profiles_dir)}")
|
print_error(f"Missing filament profile: '{material}' referenced in {file_path.relative_to(profiles_dir)}")
|
||||||
error_count += 1
|
error_count += 1
|
||||||
else:
|
else:
|
||||||
# Single material in a string
|
# Single material in a string
|
||||||
if default_materials not in all_available_filaments:
|
if default_materials not in all_available_filaments:
|
||||||
print(f"Missing filament profile: '{default_materials}' referenced in {file_path.relative_to(profiles_dir)}")
|
print_error(f"Missing filament profile: '{default_materials}' referenced in {file_path.relative_to(profiles_dir)}")
|
||||||
error_count += 1
|
error_count += 1
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error processing machine profile {file_path}: {e}")
|
print_error(f"Error processing machine profile {file_path}: {e}")
|
||||||
error_count += 1
|
error_count += 1
|
||||||
|
|
||||||
return error_count
|
return error_count, 0
|
||||||
|
|
||||||
def check_filament_name_consistency(profiles_dir, vendor_name):
|
def check_filament_name_consistency(profiles_dir, vendor_name):
|
||||||
"""
|
"""
|
||||||
Make sure filament profile names match in both vendor json and subpath files.
|
Make sure filament profile names match in both vendor json and subpath files.
|
||||||
Filament profiles work only if the name in <vendor>.json matches the name in sub_path file,
|
Filament profiles work only if the name in <vendor>.json matches the name in sub_path file,
|
||||||
or if it's one of the sub_path file's `renamed_from`.
|
or if it's one of the sub_path file's `renamed_from`.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
profiles_dir (Path): Base profiles directory
|
||||||
|
vendor_name (str): Vendor name
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
int: Number of errors found
|
||||||
|
int: Number of warnings found (0 or 1)
|
||||||
"""
|
"""
|
||||||
error_count = 0
|
error_count = 0
|
||||||
vendor_dir = profiles_dir / vendor_name
|
vendor_dir = profiles_dir / vendor_name
|
||||||
vendor_file = profiles_dir / (vendor_name + ".json")
|
vendor_file = profiles_dir / (vendor_name + ".json")
|
||||||
|
|
||||||
if not vendor_file.exists():
|
if not vendor_file.exists():
|
||||||
print(f"No profiles found for vendor: {vendor_name} at {vendor_file}")
|
print_warning(f"No profiles found for vendor: {vendor_name} at {vendor_file}")
|
||||||
return 0
|
return 0, 1
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with open(vendor_file, 'r', encoding='UTF-8') as fp:
|
with open(vendor_file, 'r', encoding='UTF-8') as fp:
|
||||||
data = json.load(fp)
|
data = json.load(fp)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error loading vendor profile {vendor_file}: {e}")
|
print_error(f"Error loading vendor profile {vendor_file}: {e}")
|
||||||
return 1
|
return 1, 0
|
||||||
|
|
||||||
if 'filament_list' not in data:
|
if 'filament_list' not in data:
|
||||||
return 0
|
return 0, 0
|
||||||
|
|
||||||
for child in data['filament_list']:
|
for child in data['filament_list']:
|
||||||
name_in_vendor = child['name']
|
name_in_vendor = child['name']
|
||||||
|
@ -205,7 +245,7 @@ def check_filament_name_consistency(profiles_dir, vendor_name):
|
||||||
sub_file = vendor_dir / sub_path
|
sub_file = vendor_dir / sub_path
|
||||||
|
|
||||||
if not sub_file.exists():
|
if not sub_file.exists():
|
||||||
print(f"Missing sub profile: '{sub_path}' declared in {vendor_file.relative_to(profiles_dir)}")
|
print_error(f"Missing sub profile: '{sub_path}' declared in {vendor_file.relative_to(profiles_dir)}")
|
||||||
error_count += 1
|
error_count += 1
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
@ -213,7 +253,7 @@ def check_filament_name_consistency(profiles_dir, vendor_name):
|
||||||
with open(sub_file, 'r', encoding='UTF-8') as fp:
|
with open(sub_file, 'r', encoding='UTF-8') as fp:
|
||||||
sub_data = json.load(fp)
|
sub_data = json.load(fp)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error loading profile {sub_file}: {e}")
|
print_error(f"Error loading profile {sub_file}: {e}")
|
||||||
error_count += 1
|
error_count += 1
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
@ -227,10 +267,10 @@ def check_filament_name_consistency(profiles_dir, vendor_name):
|
||||||
if name_in_vendor in renamed_from:
|
if name_in_vendor in renamed_from:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
print(f"Filament name mismatch: required '{name_in_vendor}' in {vendor_file.relative_to(profiles_dir)} but found '{name_in_sub}' in {sub_file.relative_to(profiles_dir)}, and none of its `renamed_from` matches the required name either")
|
print_error(f"Filament name mismatch: required '{name_in_vendor}' in {vendor_file.relative_to(profiles_dir)} but found '{name_in_sub}' in {sub_file.relative_to(profiles_dir)}, and none of its `renamed_from` matches the required name either")
|
||||||
error_count += 1
|
error_count += 1
|
||||||
|
|
||||||
return error_count
|
return error_count, 0
|
||||||
|
|
||||||
def check_filament_id(vendor, vendor_folder):
|
def check_filament_id(vendor, vendor_folder):
|
||||||
"""
|
"""
|
||||||
|
@ -251,11 +291,11 @@ def check_filament_id(vendor, vendor_folder):
|
||||||
# Use custom hook to detect duplicates.
|
# Use custom hook to detect duplicates.
|
||||||
data = json.load(fp, object_pairs_hook=no_duplicates_object_pairs_hook)
|
data = json.load(fp, object_pairs_hook=no_duplicates_object_pairs_hook)
|
||||||
except ValueError as ve:
|
except ValueError as ve:
|
||||||
print(f"Duplicate key error in {file_path}: {ve}")
|
print_error(f"Duplicate key error in {file_path}: {ve}")
|
||||||
error += 1
|
error += 1
|
||||||
continue
|
continue
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error processing {file_path}: {e}")
|
print_error(f"Error processing {file_path}: {e}")
|
||||||
error += 1
|
error += 1
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
@ -266,52 +306,107 @@ def check_filament_id(vendor, vendor_folder):
|
||||||
|
|
||||||
if len(filament_id) > 8:
|
if len(filament_id) > 8:
|
||||||
error += 1
|
error += 1
|
||||||
print(f"Filament id too long \"{filament_id}\": {file_path}")
|
print_error(f"Filament id too long \"{filament_id}\": {file_path}")
|
||||||
|
|
||||||
return error
|
return error
|
||||||
|
|
||||||
|
def check_obsolete_keys(profiles_dir, vendor_name):
|
||||||
|
"""
|
||||||
|
Check for obsolete keys in all filament profiles for a vendor.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
profiles_dir (Path): Base profiles directory
|
||||||
|
vendor_name (str): Vendor name
|
||||||
|
obsolete_keys (set): Set of obsolete key names to check
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
int: Number of obsolete keys found
|
||||||
|
"""
|
||||||
|
error_count = 0
|
||||||
|
vendor_path = profiles_dir / vendor_name / "filament"
|
||||||
|
|
||||||
|
if not vendor_path.exists():
|
||||||
|
return 0
|
||||||
|
|
||||||
|
for file_path in vendor_path.rglob("*.json"):
|
||||||
|
try:
|
||||||
|
with open(file_path, "r", encoding="UTF-8") as fp:
|
||||||
|
data = json.load(fp)
|
||||||
|
except Exception as e:
|
||||||
|
print_warning(f"Error reading profile {file_path.relative_to(profiles_dir)}: {e}")
|
||||||
|
error_count += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
for key in data.keys():
|
||||||
|
if key in OBSOLETE_KEYS:
|
||||||
|
print_warning(f"Obsolete key: '{key}' found in {file_path.relative_to(profiles_dir)}")
|
||||||
|
error_count += 1
|
||||||
|
|
||||||
|
return error_count
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
print("Checking profiles ...")
|
parser = argparse.ArgumentParser(
|
||||||
parser = argparse.ArgumentParser(description="Check profiles for issues")
|
description="Check 3D printer profiles for common issues",
|
||||||
parser.add_argument("--vendor", type=str, required=False, help="Vendor name")
|
formatter_class=argparse.ArgumentDefaultsHelpFormatter
|
||||||
parser.add_argument("--check-filaments", default=True, action="store_true", help="Check compatible_printers in filament profiles")
|
)
|
||||||
|
parser.add_argument("--vendor", type=str, help="Specify a single vendor to check")
|
||||||
|
parser.add_argument("--check-filaments", action="store_true", help="Check 'compatible_printers' in filament profiles")
|
||||||
parser.add_argument("--check-materials", action="store_true", help="Check default materials in machine profiles")
|
parser.add_argument("--check-materials", action="store_true", help="Check default materials in machine profiles")
|
||||||
|
parser.add_argument("--check-obsolete-keys", action="store_true", help="Warn if obsolete keys are found in filament profiles")
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
print_info("Checking profiles ...")
|
||||||
|
|
||||||
script_dir = Path(__file__).resolve().parent
|
script_dir = Path(__file__).resolve().parent
|
||||||
profiles_dir = script_dir.parent / "resources" / "profiles"
|
profiles_dir = script_dir.parent / "resources" / "profiles"
|
||||||
checked_vendor_count = 0
|
checked_vendor_count = 0
|
||||||
errors_found = 0
|
errors_found = 0
|
||||||
|
warnings_found = 0
|
||||||
if args.vendor:
|
|
||||||
|
def run_checks(vendor_name):
|
||||||
|
nonlocal errors_found, warnings_found, checked_vendor_count
|
||||||
|
vendor_path = profiles_dir / vendor_name
|
||||||
|
|
||||||
if args.check_filaments or not (args.check_materials and not args.check_filaments):
|
if args.check_filaments or not (args.check_materials and not args.check_filaments):
|
||||||
errors_found += check_filament_compatible_printers(profiles_dir / args.vendor / "filament")
|
errors_found += check_filament_compatible_printers(vendor_path / "filament")
|
||||||
|
|
||||||
if args.check_materials:
|
if args.check_materials:
|
||||||
errors_found += check_machine_default_materials(profiles_dir, args.vendor)
|
new_errors, new_warnings = check_machine_default_materials(profiles_dir, vendor_name)
|
||||||
errors_found += check_filament_name_consistency(profiles_dir, args.vendor)
|
errors_found += new_errors
|
||||||
errors_found += check_filament_id(args.vendor, profiles_dir / args.vendor / "filament")
|
warnings_found += new_warnings
|
||||||
|
|
||||||
|
if args.check_obsolete_keys:
|
||||||
|
warnings_found += check_obsolete_keys(profiles_dir, vendor_name)
|
||||||
|
|
||||||
|
new_errors, new_warnings = check_filament_name_consistency(profiles_dir, vendor_name)
|
||||||
|
errors_found += new_errors
|
||||||
|
warnings_found += new_warnings
|
||||||
|
|
||||||
|
errors_found += check_filament_id(vendor_name, vendor_path / "filament")
|
||||||
checked_vendor_count += 1
|
checked_vendor_count += 1
|
||||||
|
|
||||||
|
if args.vendor:
|
||||||
|
run_checks(args.vendor)
|
||||||
else:
|
else:
|
||||||
for vendor_dir in profiles_dir.iterdir():
|
for vendor_dir in profiles_dir.iterdir():
|
||||||
if not vendor_dir.is_dir():
|
if not vendor_dir.is_dir() or vendor_dir.name == "OrcaFilamentLibrary":
|
||||||
continue
|
continue
|
||||||
errors_found += check_filament_name_consistency(profiles_dir, vendor_dir.name)
|
run_checks(vendor_dir.name)
|
||||||
errors_found += check_filament_id(vendor_dir.name, vendor_dir / "filament")
|
|
||||||
# skip "OrcaFilamentLibrary" folder
|
|
||||||
if vendor_dir.name == "OrcaFilamentLibrary":
|
|
||||||
continue
|
|
||||||
if args.check_filaments or not (args.check_materials and not args.check_filaments):
|
|
||||||
errors_found += check_filament_compatible_printers(vendor_dir / "filament")
|
|
||||||
if args.check_materials:
|
|
||||||
errors_found += check_machine_default_materials(profiles_dir, vendor_dir.name)
|
|
||||||
checked_vendor_count += 1
|
|
||||||
|
|
||||||
|
# ✨ Output finale in stile "compilatore"
|
||||||
|
print("\n==================== SUMMARY ====================")
|
||||||
|
print_info(f"Checked vendors : {checked_vendor_count}")
|
||||||
if errors_found > 0:
|
if errors_found > 0:
|
||||||
print(f"Errors found in {errors_found} profile files")
|
print_error(f"Files with errors : {errors_found}")
|
||||||
exit(-1)
|
|
||||||
else:
|
else:
|
||||||
print(f"Checked {checked_vendor_count} vendor files")
|
print_success("Files with errors : 0")
|
||||||
exit(0)
|
if warnings_found > 0:
|
||||||
|
print_warning(f"Files with warnings : {warnings_found}")
|
||||||
|
else:
|
||||||
|
print_success("Files with warnings : 0")
|
||||||
|
print("=================================================")
|
||||||
|
|
||||||
|
exit(-1 if errors_found > 0 else 0)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue