From 0c4d4398e123bdf5b2b3043a6befca7a8a08ee21 Mon Sep 17 00:00:00 2001 From: Nicolas Predella Date: Wed, 20 May 2026 14:43:59 +0300 Subject: [PATCH] search menus and refactor --- core/nodeslicer/nodes.py | 117 ++++++--------------------- infra/prusaslicer_profiles/cache.py | 16 ++-- infra/prusaslicer_profiles/models.py | 70 ++++++++-------- tests/test_profiles.py | 2 +- 4 files changed, 69 insertions(+), 136 deletions(-) diff --git a/core/nodeslicer/nodes.py b/core/nodeslicer/nodes.py index e13f06f..85cc992 100644 --- a/core/nodeslicer/nodes.py +++ b/core/nodeslicer/nodes.py @@ -10,7 +10,7 @@ from typing import Any, TypeAlias import bpy import numpy as np from mathutils import Euler -from .bpy_ns_types import NfIntProperty, NfStringProperty, NfEnumProperty, NfBoolProperty +from .bpy_ns_types import NfIntProperty, NfStringProperty, NfEnumProperty, NfBoolProperty, notify_tree_changed from ...registry import register_class from .. import slice @@ -18,7 +18,8 @@ from ..slice import Pather, slice_at_range, SlicePaths from ...infra.blender.blender_mesh_capture import SlicingCollection, SlicingObject, capture_mesh_edges, SlicingGroup from ...infra.blender.blender_bridge import get_all_children from ...infra.blender.blender_slice_nodes_generic import SliceNode, resolve_input -from .bpy_ns_types import notify_tree_changed +from ...infra.prusaslicer_profiles.cache import LocalCache +from ...infra.prusaslicer_profiles.models import Profile from ...infra._3mf_writer._3mf import prepare_3mf class SliceIntrinsics: @@ -108,101 +109,33 @@ class SLICENODE_PrusaslicerConf(SliceNode): notify_tree_changed(self, context) - def get_printers(self, context): + def search_printers(self, _context, edit_text): self.ensure_profiles_loaded() - cache = self.get_cache() - items = [('NONE', "None", "No printer selected", 'NONE', 0)] - for i, (k, p) in enumerate(cache.printer_profiles.items(), 1): - items.append((k, p.id, p.path.name, 'PRINTER', i)) + cache: LocalCache = self.get_cache() + items: list[str] = [p.id for k, p in cache.printer_profiles.items() if edit_text.lower() in p.id.lower()] return items - def get_filaments(self, context): + def search_conf(self, edit_text: str, conf_type: str) -> list[str]: + if not (printer_id := self.printer_id): return [] + self.ensure_profiles_loaded() cache = self.get_cache() - items = [('NONE', "None", "No filament selected", 'NONE', 0)] - - printer_id = self.printer_id - if printer_id == 'NONE': - return items + if not (printer := cache.printer_profiles.get("printer:" + printer_id)): return [] - printer = cache.printer_profiles.get(printer_id) - if not printer: - return items + profiles: list[Profile] = printer.compatible_filament_profiles if conf_type == "filaments" else printer.compatible_print_profiles - idx = 1 - for k in printer.compatible_profiles: - if k.startswith("filament:"): - p = cache.profiles[k] - items.append((k, p.id, p.path.name, 'FILAMENT', idx)) - idx += 1 + items: list[str] = [p.id for p in profiles if edit_text.lower() in p.id.lower()] return items - def get_prints(self, context): - self.ensure_profiles_loaded() - cache = self.get_cache() - items = [('NONE', "None", "No print selected", 'NONE', 0)] - - printer_id = self.printer_id - if printer_id == 'NONE': - return items + def search_filaments(self, _context, edit_text) -> list[str]: + return self.search_conf(edit_text, conf_type="filaments") - printer = cache.printer_profiles.get(printer_id) - if not printer: - return items + def search_prints(self, _context, edit_text) -> list[str]: + return self.search_conf(edit_text, conf_type="prints") - idx = 1 - for k in printer.compatible_profiles: - if k.startswith("print:"): - p = cache.profiles[k] - items.append((k, p.id, p.path.name, 'PRINT', idx)) - idx += 1 - return items - - printer_id: NfStringProperty(default='NONE', update=update_profiles) # pyright: ignore[reportInvalidTypeForm] - filament_id: NfStringProperty(default='NONE') # pyright: ignore[reportInvalidTypeForm] - print_id: NfStringProperty(default='NONE') # pyright: ignore[reportInvalidTypeForm] - - def _get_enum_idx(self, items, value): - for item in items: - if item[0] == value: - return item[4] - return 0 - - def _set_enum_idx(self, items, index, prop_name): - for item in items: - if item[4] == index: - setattr(self, prop_name, item[0]) - break - - def get_printer_enum(self): return self._get_enum_idx(self.get_printers(None), self.printer_id) - def set_printer_enum(self, value): self._set_enum_idx(self.get_printers(None), value, 'printer_id') - - def get_filament_enum(self): return self._get_enum_idx(self.get_filaments(None), self.filament_id) - def set_filament_enum(self, value): self._set_enum_idx(self.get_filaments(None), value, 'filament_id') - - def get_print_enum(self): return self._get_enum_idx(self.get_prints(None), self.print_id) - def set_print_enum(self, value): self._set_enum_idx(self.get_prints(None), value, 'print_id') - - printer_enum: NfEnumProperty( - name="Printer", - items=get_printers, - get=get_printer_enum, - set=set_printer_enum - ) # pyright: ignore[reportInvalidTypeForm] - - filament_enum: NfEnumProperty( - name="Filament", - items=get_filaments, - get=get_filament_enum, - set=set_filament_enum - ) # pyright: ignore[reportInvalidTypeForm] - - print_enum: NfEnumProperty( - name="Print", - items=get_prints, - get=get_print_enum, - set=set_print_enum - ) # pyright: ignore[reportInvalidTypeForm] + printer_id: NfStringProperty(name="Printer", default='NONE', update=update_profiles, search=search_printers) # pyright: ignore[reportInvalidTypeForm] + filament_id: NfStringProperty(name="Filament", default='NONE', search=search_filaments) # pyright: ignore[reportInvalidTypeForm] + print_id: NfStringProperty(name="Print", default='NONE', search=search_prints) # pyright: ignore[reportInvalidTypeForm] def init(self, context): self.add_output("SLICESOCK_PrusaslicerConf", "Config") @@ -210,10 +143,10 @@ class SLICENODE_PrusaslicerConf(SliceNode): def draw_buttons(self, context, layout): assert layout self.ensure_profiles_loaded() - layout.prop(self, "printer_enum") + layout.prop(self, "printer_id") if self.printer_id != 'NONE': - layout.prop(self, "filament_enum") - layout.prop(self, "print_enum") + layout.prop(self, "filament_id") + layout.prop(self, "print_id") def evaluate_output(self, socket_name: str, **_kwargs) -> Any: self.ensure_profiles_loaded() @@ -222,9 +155,9 @@ class SLICENODE_PrusaslicerConf(SliceNode): cache = self.get_cache() return { - 'printer': self.printer_id, - 'filament': [self.filament_id], - 'print': self.print_id, + 'printer': "printer:" + self.printer_id, + 'filament': ["filament:" + self.filament_id], + 'print': "print:" + self.print_id, 'cache': cache } diff --git a/infra/prusaslicer_profiles/cache.py b/infra/prusaslicer_profiles/cache.py index 50d096d..31a2a4a 100644 --- a/infra/prusaslicer_profiles/cache.py +++ b/infra/prusaslicer_profiles/cache.py @@ -61,25 +61,19 @@ class LocalCache: # Recalculate inheritance for all profiles for profile in self.display_profiles.values(): - profile.generate_inherited_confs(self.profiles) + profile.generate_inherited_conf(self.profiles) return changed, added, deleted def evaluate_compatibility(self, enabled_printers: set[str], enabled_vendors: set[str]): """Evaluates compatibility expressions for all active profiles.""" - for k, printer in self.printer_profiles.items(): - if k not in enabled_printers: - continue + for k in enabled_printers: + printer = self.printer_profiles["printer:" + k] - # Combine vendors with empty string for default/generic profiles active_vendors = enabled_vendors | {''} active_filaments = {k: p for k, p in self.filament_profiles.items() if p.vendor in active_vendors} - compats = { - k: p.compatibility_expression - for k, p in (active_filaments | self.print_profiles).items() - } - printer.evaluate_compatibility(compats) + printer.evaluate_compatibility(active_filaments | self.print_profiles) def generate_conf_writer( self, @@ -103,9 +97,9 @@ class LocalCache: all_keys.discard('cooling_slowdown_logic') for key in all_keys: + from .fields import search_db key_props = search_db.get(key) if not key_props: - print(f"Warning: Key '{key}' unrecognized in fields database.") continue default_val = key_props.get('default', '') diff --git a/infra/prusaslicer_profiles/models.py b/infra/prusaslicer_profiles/models.py index c968ab9..e712467 100644 --- a/infra/prusaslicer_profiles/models.py +++ b/infra/prusaslicer_profiles/models.py @@ -1,54 +1,52 @@ from __future__ import annotations import tempfile -from dataclasses import dataclass, field from pathlib import Path from typing import Any, TYPE_CHECKING if TYPE_CHECKING: from .parser import ExprNode -@dataclass class Profile: - key: str - category: str - path: Path - has_header: bool - conf_dict: dict[str, Any] - - id: str = field(init=False) - vendor: str = "" - all_conf_dict: dict[str, Any] = field(default_factory=dict) - compatible_profiles: list[str] = field(default_factory=list) - compatibility_expression: ExprNode | None = None - - def __post_init__(self): + def __init__( + self, + key: str, + category: str, + path: Path, + has_header: bool, + conf_dict: dict[str, Any] + ): + self.key = key + self.category = category + self.path = path + self.has_header = has_header + self.conf_dict = conf_dict + self.id = self.key.split(':')[1] if len(self.key.split(':')) > 1 else self.key + self.vendor = "" + self.all_conf_dict: dict[str, Any] = {} + self.compatibility_expression: ExprNode | None = None - def evaluate_compatibility(self, compats: dict[str, ExprNode | None]): - self.compatible_profiles = [] - if self.category.startswith('printer:'): - return - - for key, compat in compats.items(): - if not compat: - self.compatible_profiles.append(key) - continue - if compat.eval(self.all_conf_dict): - self.compatible_profiles.append(key) - - print(f'Evaled {self.id}') + self.compatible_profiles: list[Profile] = [] - def generate_inherited_confs(self, all_profiles: dict[str, Profile]): + @property + def compatible_print_profiles(self) -> list[Profile]: + return [k for k in self.compatible_profiles if k.category == 'print'] + + @property + def compatible_filament_profiles(self) -> list[Profile]: + return [k for k in self.compatible_profiles if k.category == 'filament'] + + def generate_inherited_conf(self, all_profiles: dict[str, Profile]): + """Calculates the full configuration dictionary by resolving inheritance.""" from .utils import generate_conf from .parser import Parser self.all_conf_dict = generate_conf(all_profiles, self.key) - + self.vendor = self.all_conf_dict.get('filament_vendor', '') + if self.category == 'printer': nozzle_diameters = self.all_conf_dict.get('nozzle_diameter', '0.4') self.all_conf_dict['num_extruders'] = str(len(nozzle_diameters.split(','))) - - self.vendor = self.all_conf_dict.get('filament_vendor', '') if exp := self.all_conf_dict.get('compatible_printers_condition'): try: @@ -56,6 +54,14 @@ class Profile: except Exception as e: print(f'Expression parsing failed for {self.id}: {exp} - {e}') + def evaluate_compatibility(self, profiles: dict[str, Profile]): + """Checks which of the provided profiles are compatible with this one.""" + self.compatible_profiles = [] + for key, p in profiles.items(): + compat = p.compatibility_expression + if not compat or compat.eval(self.all_conf_dict): + self.compatible_profiles += [p] + class ConfigWriter: def __init__(self, conf: dict[str, str | list[str]]) -> None: self.config_dict: dict[str, str | list[str]] = conf diff --git a/tests/test_profiles.py b/tests/test_profiles.py index f682373..9dcfef0 100644 --- a/tests/test_profiles.py +++ b/tests/test_profiles.py @@ -37,7 +37,7 @@ class TestPrusaSlicerProfiles(unittest.TestCase): profile = self.cache.profiles[filament_keys[0]] self.assertTrue(len(profile.all_conf_dict) > 0) - self.assertTrue(len(profile.vendor) > 0 or profile.all_conf_dict.get('filament_vendor') is None) + self.assertTrue(profile.vendor or profile.all_conf_dict.get('filament_vendor') is None) def test_fields_database(self): """Verify that the fields database is accessible and searchable."""