home · contact · privacy
Some refactoring.
authorPlom Heller <plom@plomlompom.com>
Sat, 8 Aug 2026 23:06:02 +0000 (01:06 +0200)
committerPlom Heller <plom@plomlompom.com>
Sat, 8 Aug 2026 23:06:02 +0000 (01:06 +0200)
src/bricksplom/ldraw.py

index ae283d827d4201be2c8663c54caa4bf52e53930f..1a6fcfc92d365f2251dcc092f96912e40d76e913 100644 (file)
@@ -3,7 +3,7 @@
 # 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
 
@@ -23,7 +23,7 @@ LDRAW_STUDS = {
 
 LdrawRefsTree = tuple[Path, '_SubRefs']
 _SubRefs = tuple[tuple[str, LdrawRefsTree], ...]
-_N_TOKS_PER_REFLINE = 15
+_N_TOKS_PER_REFLINE = 14
 _N_DIMS = 3
 
 
@@ -37,6 +37,57 @@ class _XYZ(NamedTuple):
     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.'
 
@@ -65,10 +116,8 @@ class LdrawDb:
             ) -> 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(
@@ -98,9 +147,9 @@ class LdrawDb:
                 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
@@ -123,66 +172,36 @@ class LdrawDb:
             ) -> _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(