search menus and refactor
This commit is contained in:
parent
95911e6805
commit
0c4d4398e1
4 changed files with 69 additions and 136 deletions
|
|
@ -10,7 +10,7 @@ from typing import Any, TypeAlias
|
||||||
import bpy
|
import bpy
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from mathutils import Euler
|
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 ...registry import register_class
|
||||||
from .. import slice
|
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_mesh_capture import SlicingCollection, SlicingObject, capture_mesh_edges, SlicingGroup
|
||||||
from ...infra.blender.blender_bridge import get_all_children
|
from ...infra.blender.blender_bridge import get_all_children
|
||||||
from ...infra.blender.blender_slice_nodes_generic import SliceNode, resolve_input
|
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
|
from ...infra._3mf_writer._3mf import prepare_3mf
|
||||||
|
|
||||||
class SliceIntrinsics:
|
class SliceIntrinsics:
|
||||||
|
|
@ -108,101 +109,33 @@ class SLICENODE_PrusaslicerConf(SliceNode):
|
||||||
|
|
||||||
notify_tree_changed(self, context)
|
notify_tree_changed(self, context)
|
||||||
|
|
||||||
def get_printers(self, context):
|
def search_printers(self, _context, edit_text):
|
||||||
self.ensure_profiles_loaded()
|
self.ensure_profiles_loaded()
|
||||||
cache = self.get_cache()
|
cache: LocalCache = self.get_cache()
|
||||||
items = [('NONE', "None", "No printer selected", 'NONE', 0)]
|
items: list[str] = [p.id for k, p in cache.printer_profiles.items() if edit_text.lower() in p.id.lower()]
|
||||||
for i, (k, p) in enumerate(cache.printer_profiles.items(), 1):
|
|
||||||
items.append((k, p.id, p.path.name, 'PRINTER', i))
|
|
||||||
return items
|
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()
|
self.ensure_profiles_loaded()
|
||||||
cache = self.get_cache()
|
cache = self.get_cache()
|
||||||
items = [('NONE', "None", "No filament selected", 'NONE', 0)]
|
if not (printer := cache.printer_profiles.get("printer:" + printer_id)): return []
|
||||||
|
|
||||||
printer_id = self.printer_id
|
profiles: list[Profile] = printer.compatible_filament_profiles if conf_type == "filaments" else printer.compatible_print_profiles
|
||||||
if printer_id == 'NONE':
|
|
||||||
return items
|
|
||||||
|
|
||||||
printer = cache.printer_profiles.get(printer_id)
|
items: list[str] = [p.id for p in profiles if edit_text.lower() in p.id.lower()]
|
||||||
if not printer:
|
|
||||||
return items
|
|
||||||
|
|
||||||
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
|
|
||||||
return items
|
return items
|
||||||
|
|
||||||
def get_prints(self, context):
|
def search_filaments(self, _context, edit_text) -> list[str]:
|
||||||
self.ensure_profiles_loaded()
|
return self.search_conf(edit_text, conf_type="filaments")
|
||||||
cache = self.get_cache()
|
|
||||||
items = [('NONE', "None", "No print selected", 'NONE', 0)]
|
|
||||||
|
|
||||||
printer_id = self.printer_id
|
def search_prints(self, _context, edit_text) -> list[str]:
|
||||||
if printer_id == 'NONE':
|
return self.search_conf(edit_text, conf_type="prints")
|
||||||
return items
|
|
||||||
|
|
||||||
printer = cache.printer_profiles.get(printer_id)
|
printer_id: NfStringProperty(name="Printer", default='NONE', update=update_profiles, search=search_printers) # pyright: ignore[reportInvalidTypeForm]
|
||||||
if not printer:
|
filament_id: NfStringProperty(name="Filament", default='NONE', search=search_filaments) # pyright: ignore[reportInvalidTypeForm]
|
||||||
return items
|
print_id: NfStringProperty(name="Print", default='NONE', search=search_prints) # pyright: ignore[reportInvalidTypeForm]
|
||||||
|
|
||||||
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]
|
|
||||||
|
|
||||||
def init(self, context):
|
def init(self, context):
|
||||||
self.add_output("SLICESOCK_PrusaslicerConf", "Config")
|
self.add_output("SLICESOCK_PrusaslicerConf", "Config")
|
||||||
|
|
@ -210,10 +143,10 @@ class SLICENODE_PrusaslicerConf(SliceNode):
|
||||||
def draw_buttons(self, context, layout):
|
def draw_buttons(self, context, layout):
|
||||||
assert layout
|
assert layout
|
||||||
self.ensure_profiles_loaded()
|
self.ensure_profiles_loaded()
|
||||||
layout.prop(self, "printer_enum")
|
layout.prop(self, "printer_id")
|
||||||
if self.printer_id != 'NONE':
|
if self.printer_id != 'NONE':
|
||||||
layout.prop(self, "filament_enum")
|
layout.prop(self, "filament_id")
|
||||||
layout.prop(self, "print_enum")
|
layout.prop(self, "print_id")
|
||||||
|
|
||||||
def evaluate_output(self, socket_name: str, **_kwargs) -> Any:
|
def evaluate_output(self, socket_name: str, **_kwargs) -> Any:
|
||||||
self.ensure_profiles_loaded()
|
self.ensure_profiles_loaded()
|
||||||
|
|
@ -222,9 +155,9 @@ class SLICENODE_PrusaslicerConf(SliceNode):
|
||||||
|
|
||||||
cache = self.get_cache()
|
cache = self.get_cache()
|
||||||
return {
|
return {
|
||||||
'printer': self.printer_id,
|
'printer': "printer:" + self.printer_id,
|
||||||
'filament': [self.filament_id],
|
'filament': ["filament:" + self.filament_id],
|
||||||
'print': self.print_id,
|
'print': "print:" + self.print_id,
|
||||||
'cache': cache
|
'cache': cache
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -61,25 +61,19 @@ class LocalCache:
|
||||||
|
|
||||||
# Recalculate inheritance for all profiles
|
# Recalculate inheritance for all profiles
|
||||||
for profile in self.display_profiles.values():
|
for profile in self.display_profiles.values():
|
||||||
profile.generate_inherited_confs(self.profiles)
|
profile.generate_inherited_conf(self.profiles)
|
||||||
|
|
||||||
return changed, added, deleted
|
return changed, added, deleted
|
||||||
|
|
||||||
def evaluate_compatibility(self, enabled_printers: set[str], enabled_vendors: set[str]):
|
def evaluate_compatibility(self, enabled_printers: set[str], enabled_vendors: set[str]):
|
||||||
"""Evaluates compatibility expressions for all active profiles."""
|
"""Evaluates compatibility expressions for all active profiles."""
|
||||||
for k, printer in self.printer_profiles.items():
|
for k in enabled_printers:
|
||||||
if k not in enabled_printers:
|
printer = self.printer_profiles["printer:" + k]
|
||||||
continue
|
|
||||||
|
|
||||||
# Combine vendors with empty string for default/generic profiles
|
|
||||||
active_vendors = enabled_vendors | {''}
|
active_vendors = enabled_vendors | {''}
|
||||||
active_filaments = {k: p for k, p in self.filament_profiles.items() if p.vendor in active_vendors}
|
active_filaments = {k: p for k, p in self.filament_profiles.items() if p.vendor in active_vendors}
|
||||||
|
|
||||||
compats = {
|
printer.evaluate_compatibility(active_filaments | self.print_profiles)
|
||||||
k: p.compatibility_expression
|
|
||||||
for k, p in (active_filaments | self.print_profiles).items()
|
|
||||||
}
|
|
||||||
printer.evaluate_compatibility(compats)
|
|
||||||
|
|
||||||
def generate_conf_writer(
|
def generate_conf_writer(
|
||||||
self,
|
self,
|
||||||
|
|
@ -103,9 +97,9 @@ class LocalCache:
|
||||||
all_keys.discard('cooling_slowdown_logic')
|
all_keys.discard('cooling_slowdown_logic')
|
||||||
|
|
||||||
for key in all_keys:
|
for key in all_keys:
|
||||||
|
from .fields import search_db
|
||||||
key_props = search_db.get(key)
|
key_props = search_db.get(key)
|
||||||
if not key_props:
|
if not key_props:
|
||||||
print(f"Warning: Key '{key}' unrecognized in fields database.")
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
default_val = key_props.get('default', '')
|
default_val = key_props.get('default', '')
|
||||||
|
|
|
||||||
|
|
@ -1,61 +1,67 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
import tempfile
|
import tempfile
|
||||||
from dataclasses import dataclass, field
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, TYPE_CHECKING
|
from typing import Any, TYPE_CHECKING
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .parser import ExprNode
|
from .parser import ExprNode
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class Profile:
|
class Profile:
|
||||||
key: str
|
def __init__(
|
||||||
category: str
|
self,
|
||||||
path: Path
|
key: str,
|
||||||
has_header: bool
|
category: str,
|
||||||
conf_dict: dict[str, Any]
|
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
|
||||||
|
|
||||||
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):
|
|
||||||
self.id = self.key.split(':')[1] if len(self.key.split(':')) > 1 else self.key
|
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: list[Profile] = []
|
||||||
self.compatible_profiles = []
|
|
||||||
if self.category.startswith('printer:'):
|
|
||||||
return
|
|
||||||
|
|
||||||
for key, compat in compats.items():
|
@property
|
||||||
if not compat:
|
def compatible_print_profiles(self) -> list[Profile]:
|
||||||
self.compatible_profiles.append(key)
|
return [k for k in self.compatible_profiles if k.category == 'print']
|
||||||
continue
|
|
||||||
if compat.eval(self.all_conf_dict):
|
|
||||||
self.compatible_profiles.append(key)
|
|
||||||
|
|
||||||
print(f'Evaled {self.id}')
|
@property
|
||||||
|
def compatible_filament_profiles(self) -> list[Profile]:
|
||||||
|
return [k for k in self.compatible_profiles if k.category == 'filament']
|
||||||
|
|
||||||
def generate_inherited_confs(self, all_profiles: dict[str, Profile]):
|
def generate_inherited_conf(self, all_profiles: dict[str, Profile]):
|
||||||
|
"""Calculates the full configuration dictionary by resolving inheritance."""
|
||||||
from .utils import generate_conf
|
from .utils import generate_conf
|
||||||
from .parser import Parser
|
from .parser import Parser
|
||||||
|
|
||||||
self.all_conf_dict = generate_conf(all_profiles, self.key)
|
self.all_conf_dict = generate_conf(all_profiles, self.key)
|
||||||
|
self.vendor = self.all_conf_dict.get('filament_vendor', '')
|
||||||
|
|
||||||
if self.category == 'printer':
|
if self.category == 'printer':
|
||||||
nozzle_diameters = self.all_conf_dict.get('nozzle_diameter', '0.4')
|
nozzle_diameters = self.all_conf_dict.get('nozzle_diameter', '0.4')
|
||||||
self.all_conf_dict['num_extruders'] = str(len(nozzle_diameters.split(',')))
|
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'):
|
if exp := self.all_conf_dict.get('compatible_printers_condition'):
|
||||||
try:
|
try:
|
||||||
self.compatibility_expression = Parser(exp).parse()
|
self.compatibility_expression = Parser(exp).parse()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f'Expression parsing failed for {self.id}: {exp} - {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:
|
class ConfigWriter:
|
||||||
def __init__(self, conf: dict[str, str | list[str]]) -> None:
|
def __init__(self, conf: dict[str, str | list[str]]) -> None:
|
||||||
self.config_dict: dict[str, str | list[str]] = conf
|
self.config_dict: dict[str, str | list[str]] = conf
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ class TestPrusaSlicerProfiles(unittest.TestCase):
|
||||||
|
|
||||||
profile = self.cache.profiles[filament_keys[0]]
|
profile = self.cache.profiles[filament_keys[0]]
|
||||||
self.assertTrue(len(profile.all_conf_dict) > 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):
|
def test_fields_database(self):
|
||||||
"""Verify that the fields database is accessible and searchable."""
|
"""Verify that the fields database is accessible and searchable."""
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue