from typing import Callable, Optional, Self
# ourselves
from bricksplom.constants import CHAR_NEWLINE
-from bricksplom.ldraw import LdrawDb
+from bricksplom.ldraw import LdrawDb, LdrawRefsTree
CHAR_SEP_TOKEN = ' '
CHAR_COMMENT = '#'
class WithDb:
- 'Add db:Optional[BricksDB] field to __init__, setting ._db.'
+ 'Add db:Optional[BricksDB] field to __init__, setting .db.'
def __init__(
self,
db: Optional['BricksDb'] = None,
**kwargs
) -> None:
- self._db = db
+ self.db = db
super().__init__(**kwargs)
ldraw: str = ''
-class BrickDesign(Textfiled, Lookupable):
+class BrickDesign(Textfiled, WithDb, Lookupable):
'Shape and texture configurations with descriptions and equalities.'
_id_indent = 6
alternate_to: Optional[Self] = None
def __init__(
self,
id_: str,
- attrs: Optional[BrickDesignData] = None
+ attrs: Optional[BrickDesignData] = None,
+ **kwargs
) -> None:
self.id_ = id_
self.direct_attrs = attrs
self.alternate_ids: set[str] = set()
+ super().__init__(**kwargs)
def __getattribute__(self, key: str):
if key in BrickDesignData.__annotations__:
'Own .id_ plus (sorted) .alternate_ids.'
return tuple([self.id_] + sorted(list(self.alternate_ids)))
+ @classmethod
+ def formatters(
+ cls
+ ) -> dict[str, tuple[str, Callable[[Self], str]]]:
+ def ldraw_tree(
+ item: Self
+ ) -> str:
+ def walk(
+ lines: list[str],
+ filename: str,
+ refstree: LdrawRefsTree,
+ depth: int = 0
+ ) -> None:
+ indent = depth * 4 * ' '
+ path, subrefs = refstree
+ lines += [f'{indent}"{filename}": {path}']
+ for refname, subrefstree in subrefs.items():
+ walk(lines, refname, subrefstree, depth+1)
+ if not item.ldraw:
+ return 'no LDRAW file associated'
+ lines: list[str] = []
+ assert item.db is not None
+ walk(lines, item.ldraw, item.db.ldraw.tree(item.ldraw))
+ return CHAR_NEWLINE.join(lines)
+
+ return super().formatters() | {
+ 'ldraw_tree': ('tree of LDRAW references', ldraw_tree)}
+
@classmethod
def matchers(
cls
def from_textfile(
cls,
path: tuple[str, str],
+ db: Optional['BricksDb'] = None,
**_
) -> dict[str, Self]:
collected = {}
alt_id = body[1:]
alts[alt_id] = alts.get(alt_id, set())
alts[alt_id].add(design_id)
- collected[design_id] = cls(design_id)
+ collected[design_id] = cls(design_id, db=db)
else:
assert SEP_DESIGN_DESC in body
metadata, desc = body.split(SEP_DESIGN_DESC, maxsplit=1)
setattr(attrs, a_key, int(a_val_str))
else:
setattr(attrs, a_key, a_val_str)
- collected[design_id] = cls(design_id, attrs)
+ collected[design_id] = cls(design_id, attrs, db=db)
for id_, alternate_ids in alts.items():
collected[id_].alternate_ids = alternate_ids
for alt_id in alternate_ids:
def __str__(
self
) -> str:
- assert self._db
- design = self._db.designs[self.design_id]
- color = str(self._db.colors[self.color_id]).strip()
+ assert self.db
+ design = self.db.designs[self.design_id]
+ color = str(self.db.colors[self.color_id]).strip()
comment = f' # {self.comment}' if self.comment else ''
return (f'{self.id_indented()} '
f'{BrickDesign.indent_id(self.design_id)} '
self
) -> str:
def format_line(count, brick_id, comment) -> str:
- assert self._db
- brick = self._db.bricks[brick_id]
+ assert self.db
+ brick = self.db.bricks[brick_id]
design_id = brick.design_id
tail_comment = f' # {comment}' if comment else ''
- color = str(self._db.colors[self._db.bricks[brick_id].color_id]
+ color = str(self.db.colors[self.db.bricks[brick_id].color_id]
).lstrip().split(CHAR_SEP_TOKEN, maxsplit=1)[1]
box: Optional[Box] = None
- for i_box in self._db.boxes.values():
+ for i_box in self.db.boxes.values():
for idx_in_box in [
idx for idx, d_to_ls
in enumerate(i_box.designs_to_listings)
if box:
break
if not box:
- for i_box in self._db.boxes.values():
+ for i_box in self.db.boxes.values():
for idx_in_box in [
idx for idx, t
in enumerate(i_box.designs_to_listings)
return (
f'{count:>2}× {Brick.indent_id(brick_id)}:'
f'{BrickDesign.indent_id(design_id)} {box_listing} {color} '
- + self._db.designs[design_id].description + tail_comment)
+ + self.db.designs[design_id].description + tail_comment)
return f'{self}{CHAR_NEWLINE}{self._format_paginated(format_line)}'
**kwargs
) -> None:
super().__init__(**kwargs)
- assert self._db
+ assert self.db
self.id_ = id_
self._set = bricks_set
designed_listings: list[tuple[BrickDesign, list[BrickListing]]] = []
for listing in self.brick_listings_flat():
- design = self._db.designs[self._db.bricks[listing[1]].design_id]
+ design = self.db.designs[self.db.bricks[listing[1]].design_id]
design = design.alternate_to or design
if not (designed_listings and designed_listings[-1][0] == design):
designed_listings += [(design, []), ]
def show(
self,
) -> str:
- assert self._db
+ assert self.db
lines = []
for design, listings in self.designs_to_listings:
lines += [
f'=== {" / ".join(design.all_ids)}: {design.description} ===']
for count, brick_id, comment in listings:
- color = self._db.colors[self._db.bricks[brick_id].color_id]
+ color = self.db.colors[self.db.bricks[brick_id].color_id]
lines += [f'{count:>2}× {Brick.indent_id(brick_id)} / {color} '
f'# {comment}']
return CHAR_NEWLINE.join(lines)
kwargs = {'db': self} if issubclass(cls, WithDb) else {}
setattr(self, name, cls.from_textfile((path_tables, f'{name}.txt'),
**kwargs))
- self._ldraw = LdrawDb(Path(path_ldraw))
+ self.ldraw = LdrawDb(Path(path_ldraw))
self._ldraw_modes = ldraw_modes.split(CHAR_SEP_LDRAW_MODES)
if LDRAW_MODE_FILL_PATHS in self._ldraw_modes:
for design in [design for design in self.designs.values()
if not (design.alternate_to or design.ldraw)]:
filename = f'{design.id_}.dat'
- if self._ldraw.path(filename).exists():
+ if self.ldraw.path(filename).exists():
assert design.direct_attrs is not None
design.direct_attrs.ldraw = filename
self._check_consistencies()
if d.n_studs != 1]:
assert design.direct_attrs is not None
design.direct_attrs.n_studs\
- = self._ldraw.count_studs(design.ldraw)
+ = self.ldraw.count_studs(design.ldraw)
@property
def boxes(
for design in self._designs_ldrawed():
# hunt for dangling Design.ldraw references
if LDRAW_MODE_VERIFY_PATHS in self._ldraw_modes:
- if not self._ldraw.path(design.ldraw).exists():
+ if not self.ldraw.path(design.ldraw).exists():
fails += ['unresolved ldraw reference for design of ID: '
f'{design.id_} ({design.ldraw})']
continue
if LDRAW_MODE_VERIFY_N_STUDS in self._ldraw_modes:
if design.n_studs <= -1:
continue
- studs_counted = self._ldraw.count_studs(design.ldraw)
+ studs_counted = self.ldraw.count_studs(design.ldraw)
if design.n_studs != studs_counted:
fails += ['n_studs count differs between record and ldraw '
f'calculation for design of ID: {design.id_} '