import math import bpy from ... import config from ...core import Check, Issue, contexts class ModifiersCheck(Check): id = "modifiers" label = "Modifiers" description = "No modifiers other than Subdivision Surface and Multiresolution." category = "Objects" contexts = contexts.ASSET - {contexts.CHAR_LINEART} fix_description = "Apply the modifiers that are not allowed" # Config key holding the allowed modifier types; subclasses change it. allowed_config_key = "ALLOWED_MODIFIERS" def run(self, ctx): allowed = getattr(config, self.allowed_config_key) for ob in ctx.objects(): names = [m.name for m in ob.modifiers if m.type not in allowed] if names: yield Issue.for_id( f"{ob.name}: {', '.join(names)}", ob, data={"modifiers": names}, ) def fix(self, ctx, issues): failed = [] for issue in issues: ob = issue.get_id() if ob is None: continue for name in issue.data["modifiers"]: try: with bpy.context.temp_override(object=ob, active_object=ob, selected_objects=[ob]): bpy.ops.object.modifier_apply(modifier=name) except RuntimeError as exc: failed.append(f"{ob.name} / {name}: {str(exc).strip()}") return failed class ApplyTransformCheck(Check): id = "apply_transform" label = "Transforms applied" description = "Rotation and scale (and location if set in config) of geometry objects are applied." category = "Objects" contexts = contexts.ASSET fix_description = "Apply the transforms (multi-user data is reported, not changed)" def run(self, ctx): tolerance = config.TRANSFORM_TOLERANCE channels = config.APPLY_TRANSFORM for ob in ctx.objects(*config.APPLY_TRANSFORM_TYPES): loc, rot, scale = ob.matrix_basis.decompose() problems = [] if channels.get("location") and loc.length > tolerance: problems.append("location") if channels.get("rotation") and min(rot.angle, math.tau - rot.angle) > tolerance: problems.append("rotation") if channels.get("scale") and any(abs(s - 1.0) > tolerance for s in scale): problems.append("scale") if problems: yield Issue.for_id(f"{ob.name}: {', '.join(problems)} not applied", ob) def fix(self, ctx, issues): failed = [] channels = config.APPLY_TRANSFORM for issue in issues: ob = issue.get_id() if ob is None: continue if ob.data is not None and ob.data.users > 1: failed.append(f"{ob.name}: data shared by {ob.data.users} users, apply by hand") continue try: with bpy.context.temp_override( object=ob, active_object=ob, selected_objects=[ob], selected_editable_objects=[ob] ): bpy.ops.object.transform_apply( location=bool(channels.get("location")), rotation=bool(channels.get("rotation")), scale=bool(channels.get("scale")), ) except RuntimeError as exc: failed.append(f"{ob.name}: {str(exc).strip()}") return failed class LightCameraCheck(Check): id = "lights_cameras" label = "Lights & cameras" description = "Asset files contain no lights or cameras." category = "Objects" contexts = contexts.ASSET fix_description = "Delete the lights and cameras" def run(self, ctx): for ob in ctx.objects(*config.FORBIDDEN_ASSET_OBJECT_TYPES): yield Issue.for_id(f"{ob.type.capitalize()} '{ob.name}' must be removed", ob) def fix(self, ctx, issues): for issue in issues: ob = issue.get_id() if ob is not None: bpy.data.objects.remove(ob, do_unlink=True)