home · contact · privacy
Allow selective ignorance of LDRAW guesses. master
authorPlom Heller <plom@plomlompom.com>
Sun, 9 Aug 2026 04:50:52 +0000 (06:50 +0200)
committerPlom Heller <plom@plomlompom.com>
Sun, 9 Aug 2026 04:50:52 +0000 (06:50 +0200)
src/bricksplom/misc.py

index cbad122fedbdde46a8719303b5a34508b1e37083..39e22e91627e8141e81c9614cc86c668bc8a5936 100644 (file)
@@ -2,9 +2,9 @@
 
 # standard libs
 from abc import ABC, abstractmethod
-from dataclasses import dataclass
+from dataclasses import dataclass, field as dc_field
 from pathlib import Path
-from typing import Callable, Optional, Self
+from typing import Any, Callable, Optional, Self
 # ourselves
 from bricksplom.constants import CHAR_NEWLINE
 from bricksplom.ldraw import LdrawDb, LdrawPathFailure, LdrawRefsTree
@@ -269,6 +269,29 @@ class BrickDesignData:
     n_studs: int = -1
     description: str = '?'
     ldraw: str = ''
+    _secured: set = dc_field(default_factory=set)
+
+    @classmethod
+    def sortables(
+            cls
+            ) -> dict[str, Any]:
+        'Any .__annotations__ whose key does not start with an underscore.'
+        return {k: v for k, v in cls.__annotations__.items()
+                if not k.startswith('_')}
+
+    def secure(
+            self,
+            attr_key: str
+            ) -> None:
+        'Declare knowledge of attribute as certain (overpower ldraw guesses).'
+        self._secured.add(attr_key)
+
+    def secured(
+            self,
+            attr_key: str
+            ) -> bool:
+        'Is knowledge of attribute certain (to owerpower ldraw guesses)?'
+        return attr_key in self._secured
 
 
 class BrickDesign(Textfiled, WithDb, Lookupable):
@@ -288,7 +311,7 @@ class BrickDesign(Textfiled, WithDb, Lookupable):
         super().__init__(**kwargs)
 
     def __getattribute__(self, key: str):
-        if key in BrickDesignData.__annotations__:
+        if key in BrickDesignData.__annotations__ or key == 'secured':
             if self.direct_attrs:
                 attrs = self.direct_attrs
             else:
@@ -365,7 +388,7 @@ class BrickDesign(Textfiled, WithDb, Lookupable):
             return 'by ' + ATTR_DESCS[attr_name], match
         return super().matchers() | {
             f'{attr_name}{CHAR_ATTR_EQ}': attr_matcher(attr_name)
-            for attr_name, attr_type in BrickDesignData.__annotations__.items()
+            for attr_name, attr_type in BrickDesignData.sortables().items()
             if attr_type is int}
 
     @classmethod
@@ -388,7 +411,7 @@ class BrickDesign(Textfiled, WithDb, Lookupable):
         return super().sorters()\
             | {TOK_SORT_BOX: cls._by_box_sorter('design')}\
             | {attr_name: attr_sorter(attr_name)
-               for attr_name in BrickDesignData.__annotations__}
+               for attr_name in BrickDesignData.sortables().keys()}
 
     @classmethod
     def from_textfile(
@@ -416,6 +439,9 @@ class BrickDesign(Textfiled, WithDb, Lookupable):
                 for attr in metadata.split(SEP_DESIGN_ATTR):
                     assert CHAR_ATTR_EQ in attr
                     a_key, a_val_str = attr.split(CHAR_ATTR_EQ, maxsplit=1)
+                    if a_val_str[:1] == CHAR_ATTR_EQ:
+                        a_val_str = a_val_str[1:]
+                        attrs.secure((a_key))
                     assert a_key in annos
                     if annos[a_key] is int:
                         assert a_val_str.isdigit(), a_val_str
@@ -436,7 +462,7 @@ class BrickDesign(Textfiled, WithDb, Lookupable):
         if self.alternate_to:
             return f'{raw}{CHAR_DESIGN_ALT}{self.alternate_to.id_}'
         attrs = []
-        for attr_key in [k for k in BrickDesignData.__annotations__
+        for attr_key in [k for k in BrickDesignData.sortables()
                          if k != 'description']:
             attr_val = getattr(self, attr_key)
             if attr_val != getattr(BrickDesignData, attr_key):
@@ -828,9 +854,9 @@ class BricksDb:
                               f'{design.id_} ({design.ldraw})']
                     continue
             # compare n_studs calculations to explicit records
-            if LDRAW_MODE_VERIFY_N_STUDS in self._ldraw_modes:
-                if design.n_studs <= -1:
-                    continue
+            if LDRAW_MODE_VERIFY_N_STUDS in self._ldraw_modes\
+                    and design.n_studs > -1\
+                    and not design.secured('n_studs'):
                 studs_counted = self.ldraw.count_studs(design.ldraw)
                 if design.n_studs != studs_counted:
                     fails += ['n_studs count differs between record and ldraw '