From 9fb44435acb31ee8c04a92e161e0baa4a80f385b Mon Sep 17 00:00:00 2001 From: Plom Heller Date: Tue, 5 May 2026 02:51:36 +0200 Subject: [PATCH] Count alternates as proper (if dependent) Designs, fix Box item counts. --- bricksplom.py | 159 +++++++++++++++++++++++++++----------------------- 1 file changed, 85 insertions(+), 74 deletions(-) diff --git a/bricksplom.py b/bricksplom.py index 0806ec4..2239992 100755 --- a/bricksplom.py +++ b/bricksplom.py @@ -148,15 +148,32 @@ class Color(Textfiled, Lookupable): class Design(Textfiled, Lookupable): 'Shape and texture configurations with descriptions and equalities.' + alternate_to: Optional[Self] = None def __init__( self, id_: str, - description: str + description='' ) -> None: self.id_ = id_ - self.description = description - self.alternates: set[str] = set() + self._description = description + self.alternate_ids: set[str] = set() + + @property + def description( + self + ) -> str: + 'Description text wherever found between self and .alternate_to.' + return (f'={self.alternate_to.id_}: {self.alternate_to.description}' + if self.alternate_to + else self._description) + + @property + def all_ids( + self + ) -> tuple[str, ...]: + 'Own .id_ plus (sorted) .alternate_ids.' + return tuple([self.id_] + sorted(list(self.alternate_ids))) @classmethod def from_textfile( @@ -175,31 +192,21 @@ class Design(Textfiled, Lookupable): if char_type == CHAR_DESIGN_ALT: alts[body] = alts.get(body, set()) alts[body].add(design_id) + collected[design_id] = cls(design_id) else: # == CHAR_DESIGN_DESC collected[design_id] = cls(design_id, body) - for id_, alternatives in alts.items(): - collected[id_].alternates = alternatives + for id_, alternate_ids in alts.items(): + collected[id_].alternate_ids = alternate_ids + for alt_id in alternate_ids: + collected[alt_id].alternate_to = collected[id_] return collected def raw( self ) -> str: - return '\n'.join([f'{self.id_:>6} _{self.description}'] - + [f'{a:>6} ={self.id_}' for a in self.alternates]) - - @classmethod - def possibly_by_alt( - cls, - id_queried: str, - designs: dict[str, Self] - ) -> Optional[Self]: - 'In designs find one of id_queried, by way of alternates if necessary.' - if id_queried in designs: - return designs[id_queried] - for design in designs.values(): - if id_queried in design.alternates: - return design - return None + return f'{self.id_:>6} ' + (f'={self.alternate_to.id_}' + if self.alternate_to + else f'_{self._description}') class Piece(Textfiled, WithDb, Lookupable): @@ -246,10 +253,11 @@ class Piece(Textfiled, WithDb, Lookupable): self ) -> str: assert self._db - design = Design.possibly_by_alt(self.design_id, self._db.designs) - color = self._db.colors[self.color_id] + 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_:>7} {self.design_id:>6} {design.description} ({str(color).strip()}){comment}' + return (f'{self.id_:>7} {self.design_id:>6} ' + f'{design.description} ({color}){comment}') class Collection(Textfiled, WithDb, Lookupable): @@ -396,43 +404,48 @@ class Box(WithDb, Lookupable): def __init__( self, id_: str, - designs: tuple[str, ...], + collection: Collection, **kwargs ) -> None: - self.id_ = id_ - self.designs = designs super().__init__(**kwargs) + assert self._db + self.id_ = id_ + self._collection = collection + designed_listings: list[tuple[Design, list[PieceListing]]] = [] + for listing in self._collection.piece_listings_flat(): + design = self._db.designs[self._db.pieces[listing[1]].design_id] + design = design.alternate_to or design + if not (designed_listings and designed_listings[-1][0] == design): + designed_listings += [(design, []), ] + designed_listings[-1][1] += [listing] + self.designs_to_listings = tuple((d, tuple(ls)) + for d, ls in designed_listings) def __str__( self ) -> str: - return f'{self.id_:>2} {",".join(self.designs)}' + return (f'{self.id_:>2} ' + + ', '.join('/'.join(design.all_ids) + for design, _ in self.designs_to_listings)) def show( self, ) -> str: assert self._db lines = [] - for design_id in self.designs: - design = self._db.designs[design_id] - lines += [f'=== {design_id:>6}: {design.description} ==='] - for piece in [p for p in self._db.pieces.values() - if p.design_id in {design.id_} | design.alternates]: - count_pieces = 0 - color = self._db.colors[piece.color_id] - for listings in [c.piece_listings_flat() - for c in self._db.collections.values()]: - for count in [t[0] for t in listings if t[1] == piece.id_]: - count_pieces += count - lines += [f'{count_pieces:>2}× {piece.id_:>7} / {color}'] + for design, listings in self.designs_to_listings: + lines += [ + f'=== {" / ".join(design.all_ids)}: {design.description} ==='] + for count, piece_id, comment in listings: + color = self._db.colors[self._db.pieces[piece_id].color_id] + lines += [f'{count:>2}× {piece_id:>7} / {color} # {comment}'] return '\n'.join(lines) def raw( self ) -> str: 'Call .raw() of base Collection.' - assert self._db - return self._db.collections[f'{BOX_PREFIX}{self.id_}'].raw() + return self._collection.raw() class BricksDb: @@ -456,19 +469,10 @@ class BricksDb: ) -> dict[str, Box]: 'Parsed from BOX_PREFIX-prefixed entries in .collections.' collected = {} - for coll in [c for c in self.collections.values() - if c.id_.startswith(BOX_PREFIX)]: - box_id = coll.id_[len(BOX_PREFIX):] - assert box_id - boxed_designs: list[str] = [] - for piece_id in [t[1] for t in coll.piece_listings_flat()]: - design = Design.possibly_by_alt( - self.pieces[piece_id].design_id, - self.designs) - assert design - if [design.id_] != boxed_designs[-1:]: - boxed_designs += [design.id_] - collected[box_id] = Box(box_id, tuple(boxed_designs), db=self) + for collection in [c for c in self.collections.values() + if c.id_.startswith(BOX_PREFIX)]: + box_id = collection.id_[len(BOX_PREFIX):] + collected[box_id] = Box(box_id, collection, db=self) return collected def _check_consistencies_between_tables( @@ -482,22 +486,15 @@ class BricksDb: if t[1] not in self.pieces]: fails += [f'missing Piece of ID: {piece_id}'] - # check all designs listed in boxes recorded in designs - for design_ids in [b.designs for b in self.boxes().values()]: - for design_id in [d_id for d_id in design_ids - if d_id not in self.designs]: - fails += [f'missing Design of ID: {design_id}'] - # check all pieces' designs recorded in designs for d_id in [p.design_id for p in self.pieces.values() - if not Design.possibly_by_alt(p.design_id, self.designs)]: + if p.design_id not in self.designs]: fails += [f'missing Design of ID: {d_id}'] # check all recorded designs have matching pieces (at least via alts) - for design_id, alts in [(k, v.alternates) - for k, v in self.designs.items()]: - if not [id_ for id_ in {design_id} | alts - if id_ in [p.design_id for p in self.pieces.values()]]: + for design_id in self.designs: + if not [p for p in self.pieces.values() + if p.design_id == design_id]: fails += [f'missing Pieces for design of ID: {design_id}'] # check all pieces' colors are recorded @@ -543,14 +540,28 @@ class BricksDb: self, piece_id: str, ) -> tuple[str, str]: - 'For Piece of piece_id, print bosition in .boxes() and description.' - design = Design.possibly_by_alt(self.pieces[piece_id].design_id, - self.designs) - assert design is not None - description = design.description - box = [b for b in self.boxes().values() if design.id_ in b.designs][0] - idx_in_box = box.designs.index(design.id_) + 1 - return f'{box.id_:>2}:{idx_in_box:>2}', description + 'For Piece of piece_id, print position in .boxes() and description.' + design = self.designs[self.pieces[piece_id].design_id] + owning_box: Optional[Box] = None + for box in self.boxes().values(): + for idx_in_box in [ + idx for idx, d_to_ls in enumerate(box.designs_to_listings) + if piece_id in [listing[1] for listing in d_to_ls[1]]]: + owning_box = box + break + if owning_box: + break + if not owning_box: + for box in self.boxes().values(): + for idx_in_box in [ + idx for idx, t in enumerate(box.designs_to_listings) + if design.id_ in t[0].all_ids]: + owning_box = box + break + if owning_box: + break + assert owning_box + return f'{box.id_:>2}:{idx_in_box:>2}', design.description def print( self -- 2.30.2