AnasSarkiz/magnetic-shutter-remote-r8
A USB‑C rechargeable battery-powered ESP32‑C3 wireless controller with Li‑ion charging, protected 3.3‑V buck‑boost regulation, thermal/safety monitoring, status LEDs, buttons, programming UART, crystal clock, and 2.4‑GHz antenna matching network.
- Version
- 0.4.0-prototype
- License
- unset
- Stars
- 0
scripts/review-pcb-mesh-indexing.py
"""Independently compare native and indexed GLB triangle attribute streams.
Mesh-local coordinates/attributes retain exact encoded bytes; the separate
native model review validates board-world anchors and assembly transforms.
"""
import argparse,hashlib,json,struct
from pathlib import Path
import numpy as np
COMPONENT_BYTES={5120:1,5121:1,5122:2,5123:2,5125:4,5126:4}
ELEMENT_COUNTS={'SCALAR':1,'VEC2':2,'VEC3':3,'VEC4':4,'MAT4':16}
INDEX_DTYPES={5121:'u1',5123:'<u2',5125:'<u4'}
def read_glb(path):
raw=path.read_bytes()
if struct.unpack_from('<III',raw)!= (0x46546c67,2,len(raw)):
raise ValueError('Invalid GLB header')
length,kind=struct.unpack_from('<II',raw,12)
if kind!=0x4e4f534a:raise ValueError('Missing JSON chunk')
document=json.loads(raw[20:20+length]);offset=20+length
binary_length,kind=struct.unpack_from('<II',raw,offset)
if kind!=0x004e4942:raise ValueError('Missing binary chunk')
return document,raw[offset+8:offset+8+binary_length],raw
def accessor_rows(accessor_index,context):
document,binary,_=context;accessor=document['accessors'][accessor_index]
if 'sparse' in accessor:raise ValueError('Sparse accessor needs explicit proof support')
view=document['bufferViews'][accessor['bufferView']]
if view['buffer']!=0:raise ValueError('External binary buffer')
width=COMPONENT_BYTES[accessor['componentType']]*ELEMENT_COUNTS[accessor['type']]
rows=np.ndarray((accessor['count'],width),dtype='u1',buffer=binary,
offset=view.get('byteOffset',0)+accessor.get('byteOffset',0),
strides=(view.get('byteStride',width),1))
return accessor,rows
def indices(primitive,context):
if 'indices' not in primitive:
accessor,_=accessor_rows(primitive['attributes']['POSITION'],context)
return np.arange(accessor['count'])
accessor,rows=accessor_rows(primitive['indices'],context)
return np.frombuffer(rows.copy().tobytes(),dtype=INDEX_DTYPES[accessor['componentType']])
def image_hashes(context):
document,binary,_=context;result=[]
for image in document.get('images',[]):
if 'bufferView' not in image:raise ValueError('External image needs explicit proof support')
view=document['bufferViews'][image['bufferView']];start=view.get('byteOffset',0)
result.append(hashlib.sha256(binary[start:start+view['byteLength']]).hexdigest())
return sorted(result)
def effective_material(primitive,document):
material=dict(document.get('materials',[])[primitive['material']]) if 'material' in primitive else {}
material.setdefault('alphaMode','OPAQUE');material.setdefault('alphaCutoff',.5)
material.setdefault('doubleSided',False);material.setdefault('emissiveFactor',[0,0,0])
if material.get('name')=='':material.pop('name')
pbr=dict(material.get('pbrMetallicRoughness',{}));pbr.setdefault('baseColorFactor',[1,1,1,1])
pbr.setdefault('metallicFactor',1);pbr.setdefault('roughnessFactor',1);material['pbrMetallicRoughness']=pbr
return material
def compare(native_path,indexed_path):
native=read_glb(native_path);indexed=read_glb(indexed_path);a=native[0];b=indexed[0]
if len(a['meshes'])!=len(b['meshes']):raise ValueError('Mesh coverage changed')
if image_hashes(native)!=image_hashes(indexed):raise ValueError('Embedded image bytes changed')
rows=[]
for before,after in zip(a['meshes'],b['meshes']):
if before.get('name')!=after.get('name') or len(before['primitives'])!=len(after['primitives']):
raise ValueError('Mesh identity or primitive coverage changed')
for first,second in zip(before['primitives'],after['primitives']):
if first.get('mode',4)!=4 or second.get('mode',4)!=4:raise ValueError('Require triangles')
if set(first['attributes'])!=set(second['attributes']):raise ValueError('Attribute coverage changed')
if effective_material(first,a)!=effective_material(second,b):raise ValueError('Effective material changed')
first_indices=indices(first,native);second_indices=indices(second,indexed)
if len(first_indices)!=len(second_indices) or len(first_indices)%3:raise ValueError('Triangle count changed')
streams={}
for semantic in sorted(first['attributes']):
first_accessor,first_rows=accessor_rows(first['attributes'][semantic],native)
second_accessor,second_rows=accessor_rows(second['attributes'][semantic],indexed)
for field in ['componentType','type']:
if first_accessor[field]!=second_accessor[field]:raise ValueError('Vertex encoding changed')
if first_accessor.get('normalized',False)!=second_accessor.get('normalized',False):raise ValueError('Normalization changed')
expanded_before=first_rows[first_indices].tobytes();expanded_after=second_rows[second_indices].tobytes()
if expanded_before!=expanded_after:raise ValueError(f'Triangle attribute bytes changed: {before.get("name")}/{semantic}')
streams[semantic]=hashlib.sha256(expanded_before).hexdigest()
rows.append({'mesh':before.get('name'),'triangles':len(first_indices)//3,'expanded_attribute_sha256':streams,'exactly_equal':True})
return {'native_sha256':hashlib.sha256(native[2]).hexdigest(),'indexed_sha256':hashlib.sha256(indexed[2]).hexdigest(),'native_bytes':len(native[2]),'indexed_bytes':len(indexed[2]),'all_triangle_attribute_streams_exactly_equal':True,'embedded_images_exactly_equal':True,'primitives':rows}
if __name__=='__main__':
parser=argparse.ArgumentParser();parser.add_argument('--directory',type=Path,required=True);parser.add_argument('--output',type=Path,required=True);args=parser.parse_args()
reports=[]
for path in sorted(args.directory.glob('*.native.glb')):
reports.append({'fragment':path.name,**compare(path,path.with_name(path.name.replace('.native.glb','.indexed.glb')))})
if len(reports)!=8:raise ValueError('Require all eight actual fragments')
args.output.write_text(json.dumps({'fragments':reports,'all_exact':True},indent=2)+'\n')
print('All eight fragments retain every triangle and exact vertex attributes/images')