# standard libs
from dataclasses import dataclass
from pathlib import Path
-from typing import Any, Callable, NamedTuple, Optional
+from typing import Any, Callable, NamedTuple, Optional, Self
# ourselves
from bricksplom.constants import CHAR_NEWLINE
LdrawRefsTree = tuple[Path, '_SubRefs']
_SubRefs = tuple[tuple[str, LdrawRefsTree], ...]
-_N_TOKS_PER_REFLINE = 15
+_N_TOKS_PER_REFLINE = 14
_N_DIMS = 3
z: float
+def _vectors_at(
+ toks: tuple[str, ...],
+ indices: tuple[int, ...]
+ ) -> tuple[_XYZ, ...]:
+ as_floats = tuple(float(tok) for tok in toks)
+ return tuple(_XYZ(*as_floats[idx * _N_DIMS:(idx + 1) * _N_DIMS])
+ for idx in indices)
+
+
+def _instructions_for(
+ path: Path,
+ collectables: tuple[str, ...]
+ ) -> tuple[tuple[str, str], ...]:
+ lines = []
+ for line in path.read_text(encoding='utf8').split(CHAR_NEWLINE):
+ toks = line.lstrip().split(maxsplit=1)
+ if toks and toks[0] in collectables:
+ assert len(toks) == 2
+ lines += [(toks[0], toks[1])]
+ return tuple(lines)
+
+
+@dataclass
+class _Transformation:
+ move: _XYZ
+ rotscal_x: _XYZ
+ rotscal_y: _XYZ
+ rotscal_z: _XYZ
+
+ def apply_to(
+ self,
+ vector: _XYZ
+ ) -> _XYZ:
+ 'Rotate, scale, translate vector.'
+ xyz = (
+ sum(rotscal[jdx] * vector[jdx] for jdx in range(_N_DIMS))
+ + self.move[idx]
+ for idx, rotscal in enumerate((self.rotscal_x,
+ self.rotscal_y,
+ self.rotscal_z)))
+ return _XYZ(*xyz)
+
+ @classmethod
+ def from_toks(
+ cls,
+ toks: tuple[str, ...]
+ ) -> Self:
+ 'Build from "1"-type geometry-transformation toks.'
+ return cls(*_vectors_at(toks, (0, 1, 2, 3)))
+
+
class LdrawDb:
'Connection to local LDRAW archive.'
) -> Any:
path = self.path(filename)
collected = []
- for line in path.read_text(encoding='utf8').split(CHAR_NEWLINE):
- toks = line.lstrip().split(maxsplit=_N_TOKS_PER_REFLINE - 1)
- if not (toks and toks[0] == '1'):
- continue
+ for _, line in _instructions_for(path, ('1',)):
+ toks = line.split(maxsplit=_N_TOKS_PER_REFLINE - 1)
assert len(toks) == _N_TOKS_PER_REFLINE
referenced = toks.pop().replace('\\', '/')
collected += collect(
ref: str,
toks: list[str]
) -> tuple[int]:
- matrix = tuple(tuple(toks[n:n+3]) for n in (5, 8, 11))
do_ignore = False
- for row in [[float(x) for x in row] for row in matrix]:
+ t = _Transformation.from_toks(tuple(toks[1:]))
+ for row in (t.rotscal_x, t.rotscal_y, t.rotscal_z):
if do_ignore:
break
for idx in range(len(row)): # pylint: disable=C0200
) -> _XYZ:
'For object at filename, calculate LDU sizes over all three axes.'
- @dataclass
- class _Transformation:
- move: _XYZ
- rotscal_x: _XYZ
- rotscal_y: _XYZ
- rotscal_z: _XYZ
-
- def apply_to(
- self,
- vector: _XYZ
- ) -> _XYZ:
- 'Rotate, scale, translate vector.'
- xyz = (
- sum(rotscal[jdx] * vector[jdx] for jdx in range(_N_DIMS))
- + self.move[idx]
- for idx, rotscal in enumerate((self.rotscal_x,
- self.rotscal_y,
- self.rotscal_z)))
- return _XYZ(*xyz)
-
@dataclass
class _MovingPoint:
start: _XYZ
transformations: list[_Transformation]
- def vectors_at(
- toks: tuple[str, ...],
- indices: tuple[int, ...]
- ) -> tuple[_XYZ, ...]:
- as_floats = tuple(float(tok) for tok in toks)
- return tuple(_XYZ(*as_floats[idx * _N_DIMS:(idx + 1) * _N_DIMS])
- for idx in indices)
-
def walk_collect(
walk: Callable[[], list[_MovingPoint]],
_: str,
toks: list[str]
) -> list[_MovingPoint]:
- vectors = vectors_at(tuple(toks[2:]), (0, 1, 2, 3))
+ transformation = _Transformation.from_toks(tuple(toks[1:]))
moving_points = walk()
for moving_point in moving_points:
- moving_point.transformations += [_Transformation(*vectors)]
+ moving_point.transformations += [transformation]
return moving_points
def walk_result(
path: Path,
collected: list[_MovingPoint]
) -> list[_MovingPoint]:
- collectables = {'2': (0, 1),
- '3': (0, 1, 2),
- '4': (0, 1, 2, 3),
- '5': (1, 3)}
- for line in path.read_text(encoding='utf8').split(CHAR_NEWLINE):
- toks = line.lstrip().split(maxsplit=1)
- if toks and toks[0] in collectables:
- collected += [
- _MovingPoint(xyz, [])
- for xyz in vectors_at(tuple(toks[1].split()[1:]),
- collectables[toks[0]])
- ]
+ pt_indices = {'2': (0, 1),
+ '3': (0, 1, 2),
+ '4': (0, 1, 2, 3),
+ '5': (1, 3)}
+ for key, line in _instructions_for(path, tuple(pt_indices.keys())):
+ collected += [
+ _MovingPoint(xyz, [])
+ for xyz in _vectors_at(tuple(line.split()[1:]),
+ pt_indices[key])
+ ]
return collected
def outer_bounds(