mirror of
https://github.com/Ultimaker/Cura.git
synced 2025-07-24 23:23:57 -06:00
203 lines
9.7 KiB
Python
203 lines
9.7 KiB
Python
import json
|
|
import re
|
|
from pathlib import Path
|
|
from typing import Iterator
|
|
|
|
from ..diagnostic import Diagnostic
|
|
from .linter import Linter
|
|
from ..replacement import Replacement
|
|
|
|
|
|
class Definition(Linter):
|
|
""" Finds issues in definition files, such as overriding default parameters """
|
|
def __init__(self, file: Path, settings: dict) -> None:
|
|
super().__init__(file, settings)
|
|
self._definitions = {}
|
|
self._definition_name = None
|
|
self._experimental_settings = []
|
|
self._loadDefinitionFiles(file)
|
|
self._content = self._file.read_text()
|
|
self._loadExperimentalSettings()
|
|
self._loadBasePrinterSettings()
|
|
|
|
@property
|
|
def base_def(self):
|
|
if "fdmextruder" in self._definitions:
|
|
return "fdmextruder"
|
|
return "fdmprinter"
|
|
|
|
def check(self) -> Iterator[Diagnostic]:
|
|
if self._settings["checks"].get("diagnostic-definition-redundant-override", False):
|
|
for check in self.checkRedefineOverride():
|
|
yield check
|
|
|
|
if self._settings["checks"].get("diagnostic-material-temperature-defined", False):
|
|
for check in self.checkMaterialTemperature():
|
|
yield check
|
|
|
|
if self._settings["checks"].get("diagnostic-definition-experimental-setting", False):
|
|
for check in self.checkExperimentalSetting():
|
|
yield check
|
|
|
|
# Add other which will yield Diagnostic's
|
|
# TODO: A check to determine if the user set value is with the min and max value defined in the parent and doesn't trigger a warning
|
|
# TODO: A check if the key exist in the first place
|
|
# TODO: Check if the model platform exist
|
|
|
|
yield
|
|
|
|
def checkRedefineOverride(self) -> Iterator[Diagnostic]:
|
|
""" Checks if definition file overrides its parents settings with the same value. """
|
|
definition = self._definitions[self._definition_name]
|
|
if "overrides" in definition and self._definition_name not in ("fdmprinter", "fdmextruder"):
|
|
for key, value_dict in definition["overrides"].items():
|
|
is_redefined, child_key, child_value, parent, inherited_by= self._isDefinedInParent(key, value_dict, definition['inherits'])
|
|
if is_redefined:
|
|
redefined = re.compile(r'.*(\"' + key + r'\"[\s\:\S]*?)\{[\s\S]*?\},?')
|
|
found = redefined.search(self._content)
|
|
# TODO: Figure out a way to support multiline fixes in the PR review GH Action, for now suggest no fix to ensure no ill-formed json are created
|
|
# see: https://github.com/platisd/clang-tidy-pr-comments/issues/37
|
|
if len(found.group().splitlines()) > 1:
|
|
replacements = []
|
|
else:
|
|
replacements = [Replacement(
|
|
file = self._file,
|
|
offset = found.span(1)[0],
|
|
length = len(found.group()),
|
|
replacement_text = "")]
|
|
|
|
yield Diagnostic(
|
|
file = self._file,
|
|
diagnostic_name = "diagnostic-definition-redundant-override",
|
|
message = f"Overriding {key} with the same value ({child_key}: {child_value}) as defined in parent definition: {inherited_by}",
|
|
level = "Warning",
|
|
offset = found.span(0)[0],
|
|
replacements = replacements
|
|
)
|
|
|
|
def checkMaterialTemperature(self) -> Iterator[Diagnostic]:
|
|
"""Checks if definition file has material tremperature defined within them"""
|
|
definition = self._definitions[self._definition_name]
|
|
if "overrides" in definition and self._definition_name not in ("fdmprinter", "fdmextruder"):
|
|
for key, value_dict in definition["overrides"].items():
|
|
if "temperature" in key and "material" in key:
|
|
|
|
redefined = re.compile(r'.*(\"' + key + r'\"[\s\:\S]*?)\{[\s\S]*?\},?')
|
|
found = redefined.search(self._content)
|
|
if len(found.group().splitlines()) > 1:
|
|
replacements = []
|
|
else:
|
|
replacements = [Replacement(
|
|
file=self._file,
|
|
offset=found.span(1)[0],
|
|
length=len(found.group()),
|
|
replacement_text="")]
|
|
|
|
yield Diagnostic(
|
|
file=self._file,
|
|
diagnostic_name="diagnostic-material-temperature-defined",
|
|
message=f"Overriding {key} as it belongs to material temperature catagory and shouldn't be placed in machine definitions",
|
|
level="Warning",
|
|
offset=found.span(0)[0],
|
|
replacements=replacements
|
|
)
|
|
|
|
def checkExperimentalSetting(self) -> Iterator[Diagnostic]:
|
|
"""Checks if definition uses experimental settings"""
|
|
definition = self._definitions[self._definition_name]
|
|
if "overrides" in definition and self._definition_name not in ("fdmprinter", "fdmextruder"):
|
|
for setting in definition["overrides"]:
|
|
if setting in self._experimental_settings:
|
|
redefined = re.compile(setting)
|
|
found = redefined.search(self._content)
|
|
yield Diagnostic(
|
|
file=self._file,
|
|
diagnostic_name="diagnostic-definition-experimental-setting",
|
|
message=f"Setting {setting} is still experimental and should not be used in default profiles",
|
|
level="Warning",
|
|
offset=found.span(0)[0]
|
|
)
|
|
|
|
def _loadDefinitionFiles(self, definition_file) -> None:
|
|
""" Loads definition file contents into self._definitions. Also load parent definition if it exists. """
|
|
definition_name = Path(definition_file.stem).stem
|
|
|
|
if not definition_file.exists() or definition_name in self._definitions:
|
|
return
|
|
|
|
if self._definition_name is None:
|
|
self._definition_name = definition_name
|
|
|
|
# Load definition file into dictionary
|
|
self._definitions[definition_name] = json.loads(definition_file.read_text())
|
|
|
|
# Load parent definition if it exists
|
|
if "inherits" in self._definitions[definition_name]:
|
|
if self._definitions[definition_name]['inherits'] in ("fdmextruder", "fdmprinter"):
|
|
parent_file = definition_file.parent.parent.joinpath("definitions", f"{self._definitions[definition_name]['inherits']}.def.json")
|
|
else:
|
|
parent_file = definition_file.parent.joinpath(f"{self._definitions[definition_name]['inherits']}.def.json")
|
|
self._loadDefinitionFiles(parent_file)
|
|
|
|
def _isDefinedInParent(self, key, value_dict, inherits_from):
|
|
if self._ignore(key, "diagnostic-definition-redundant-override"):
|
|
return False, None, None, None, None
|
|
if "overrides" not in self._definitions[inherits_from]:
|
|
return self._isDefinedInParent(key, value_dict, self._definitions[inherits_from]["inherits"])
|
|
|
|
parent = self._definitions[inherits_from]["overrides"]
|
|
if key not in self._definitions[self.base_def]["overrides"]:
|
|
is_number = False
|
|
else:
|
|
is_number = self._definitions[self.base_def]["overrides"][key]["type"] in ("float", "int")
|
|
for child_key, child_value in value_dict.items():
|
|
if key in parent:
|
|
if child_key in ("default_value", "value"):
|
|
check_values = [cv for cv in [parent[key].get("default_value", None), parent[key].get("value", None)] if cv is not None]
|
|
else:
|
|
check_values = [parent[key].get(child_key, None)]
|
|
for check_value in check_values:
|
|
if is_number and child_key in ("default_value", "value"):
|
|
try:
|
|
v = str(float(child_value))
|
|
except:
|
|
v = child_value
|
|
try:
|
|
cv = str(float(check_value))
|
|
except:
|
|
cv = check_value
|
|
else:
|
|
v = child_value
|
|
cv = check_value
|
|
if v == cv:
|
|
return True, child_key, child_value, parent, inherits_from
|
|
|
|
if "inherits" in parent:
|
|
return self._isDefinedInParent(key, value_dict, parent["inherits"])
|
|
return False, None, None, None, None
|
|
|
|
def _loadExperimentalSettings(self):
|
|
try:
|
|
self._experimental_settings = self._definitions[self.base_def]["settings"]["experimental"]["children"].keys()
|
|
except:
|
|
pass
|
|
|
|
def _loadBasePrinterSettings(self):
|
|
settings = {}
|
|
for k, v in self._definitions[self.base_def]["settings"].items():
|
|
self._getSetting(k, v, settings)
|
|
self._definitions[self.base_def] = {"overrides": settings}
|
|
|
|
def _getSetting(self, name, setting, settings) -> None:
|
|
if "children" in setting:
|
|
for childname, child in setting["children"].items():
|
|
self._getSetting(childname, child, settings)
|
|
settings |= {name: setting}
|
|
|
|
def _ignore(self, key: dict, type_of_check: str) -> bool:
|
|
if f"{type_of_check}-ignore" in self._settings:
|
|
filters = [re.compile(f) for f in self._settings[f"{type_of_check}-ignore"]]
|
|
for f in filters:
|
|
if f.match(key):
|
|
return True
|
|
return False
|