home · contact · privacy
To --ldraw=verify add comparison of explicit studs count to estimate from ldraw geome...
authorPlom Heller <plom@plomlompom.com>
Tue, 21 Jul 2026 23:03:40 +0000 (01:03 +0200)
committerPlom Heller <plom@plomlompom.com>
Tue, 21 Jul 2026 23:03:40 +0000 (01:03 +0200)
bricksplom.py

index 67ca7f2f5f1a2bf7e7cb7168c7ce080ae8f5d85b..821ff16e7ec3a304807192f99d19f091161f95d3 100755 (executable)
@@ -12,7 +12,19 @@ PATH_SETS = 'sets.txt'
 PATH_COLORS = 'colors.txt'
 PATH_DESIGNS = 'designs.txt'
 PATH_BRICKS = 'bricks.txt'
+
 NAME_ENV_LDRAW = 'PATH_LDRAW'
+LDRAW_STUDS = {
+    'stud.dat',
+    'studa.dat',
+    'stud2.dat',
+    'stud2a.dat',
+    'stud6a.dat',
+    'stud10.dat',
+    'stud15.dat',
+    'hipstud.dat',
+    's/973s01.dat'
+}
 
 CHAR_NEWLINE = '\n'
 CHAR_SEP_TOKEN = ' '
@@ -665,6 +677,39 @@ class BricksDb:
             collected[box_id] = Box(box_id, bricks_set, db=self)
         return collected
 
+    def _ldraw_studs_count(
+            self,
+            filename: str
+            ) -> int:
+        path = self._path_ldraw.joinpath(filename)
+        if not path.exists():
+            path = self._path_ldraw.joinpath('..', 'p', filename)
+            if not path.exists():
+                raise Exception(f'invalid ldraw reference: {filename}')
+        studs_counted = 0
+        for line in path.read_text(encoding='utf8').split(CHAR_NEWLINE):
+            line = line.lstrip()
+            if not line.startswith('1'):
+                continue
+            toks = line.split(maxsplit=14)
+            target = toks[14].replace('\\', '/')
+            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]:
+                if do_ignore:
+                    break
+                for idx in range(len(row)):  # pylint: disable=C0200
+                    if do_ignore:
+                        break
+                    cell = row[idx]
+                    if cell != 0 and [True for jdx in range(len(row))
+                                      if jdx != idx and row[jdx] != 0]:
+                        do_ignore = True
+                        break
+            studs_counted += (int((not do_ignore) and target in LDRAW_STUDS)
+                              or self._ldraw_studs_count(target))
+        return studs_counted
+
     def _check_consistencies(
             self,
             verify_ldraw=False
@@ -704,15 +749,25 @@ class BricksDb:
                                 if v != 0]:
             fails += [f'invalid count for brick of ID: {brick_id} ({count})']
 
-        # hunt for dangling Design.ldraw references
         if verify_ldraw:
             for design in [
                     design for design in self.designs.values()
                     if (not design.alternate_to)
-                    and design.ldraw
-                    and not self._path_ldraw.joinpath(design.ldraw).exists()]:
-                fails += [f'unresolved ldraw reference for design of ID: '
-                          f'{design.id_} ({design.ldraw})']
+                    and design.ldraw and design.ldraw != '!']:
+                # hunt for dangling Design.ldraw references
+                if not self._path_ldraw.joinpath(design.ldraw).exists():
+                    fails += ['unresolved ldraw reference for design of ID: '
+                              f'{design.id_} ({design.ldraw})']
+                    continue
+                # compare n_studs calculations to explicit records
+                if design.n_studs <= -1:
+                    continue
+                studs_counted = self._ldraw_studs_count(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_} '
+                              f'({design.n_studs} vs {studs_counted} '
+                              f'as per {design.ldraw})']
 
         # print, and crash on, collected fails, if any
         for fail in fails: