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
tests/cam/test_r9_filled_via_process.py
"""Keep intentional filled/capped ground vias distinct from unsafe pad overlaps."""
import copy,importlib.util,json,sys,unittest
from pathlib import Path
ROOT=Path(__file__).resolve().parents[2]
SPEC=importlib.util.spec_from_file_location('r9_process_test',ROOT/'scripts/r9-filled-via-process.py')
PROCESS=importlib.util.module_from_spec(SPEC);sys.modules[SPEC.name]=PROCESS;SPEC.loader.exec_module(PROCESS)
G=PROCESS.GEOMETRY
class FilledViaProcessTest(unittest.TestCase):
def setUp(self):
self.circuit=json.loads((ROOT/'dist/index/circuit.json').read_text())
self.process=json.loads((ROOT/'fabrication/r9-process.json').read_text())
def test_exact_ground_vias_are_conditionally_classified(self):
copper,drills,_,ids=PROCESS.classify_process(self.circuit,self.process)
self.assertEqual(len(ids),4)
for drill in (d for d in drills if d.id in ids):
pad=next(c for c in copper if c.id==drill.filled_capped_pad_id)
self.assertEqual(G.drill_copper_rule(drill,pad),(None,'required_filled_capped_barrel_in_pad'))
unfilled=copy.copy(drill);unfilled.filled_capped_pad_id=None
self.assertEqual(G.drill_copper_rule(unfilled,pad),(.2,'project_unrelated_drill_copper'))
self.assertFalse(self.process['intentional_via_in_pad']['supplier_processed_preview_confirmed'])
def test_soldermask_plugging_is_not_filled_and_capped(self):
self.process['intentional_via_in_pad']['required_process']='soldermask_plugged'
with self.assertRaisesRegex(ValueError,'epoxy filling'):PROCESS.classify_process(self.circuit,self.process)
def test_unlisted_pad_via_is_rejected(self):
via=copy.deepcopy(next(e for e in self.circuit if e['type']=='pcb_via'))
via.update({'pcb_via_id':'unlisted_ground_via','x':0,'y':-10.5})
self.circuit.append(via)
with self.assertRaisesRegex(ValueError,'Unqualified via-in-pad'):PROCESS.classify_process(self.circuit,self.process)
def test_wrong_drill_size_is_rejected(self):
via=next(e for e in self.circuit if e['type']=='pcb_via' and e['x']==-.5 and e['y']==-11)
via['hole_diameter']=.2
with self.assertRaisesRegex(ValueError,'0.3/0.45'):PROCESS.classify_process(self.circuit,self.process)
def test_ordinary_via_must_have_requested_geometry(self):
via=copy.deepcopy(next(e for e in self.circuit if e['type']=='pcb_via'))
via.update({'pcb_via_id':'ordinary_test_via','x':-20,'y':0})
self.circuit.append(via)
via['outer_diameter']=.6
with self.assertRaisesRegex(ValueError,'Nonconforming through-via'):PROCESS.classify_process(self.circuit,self.process)
def test_blind_via_is_rejected(self):
via=copy.deepcopy(next(e for e in self.circuit if e['type']=='pcb_via'))
via.update({'pcb_via_id':'ordinary_test_via','x':-20,'y':0})
self.circuit.append(via)
via['layers']=['top','inner1']
with self.assertRaisesRegex(ValueError,'Nonconforming through-via'):PROCESS.classify_process(self.circuit,self.process)
def test_missing_declared_via_is_rejected(self):
self.circuit=[e for e in self.circuit if not(e['type']=='pcb_via' and e['x']==-.5 and e['y']==-11)]
with self.assertRaisesRegex(ValueError,'missing or duplicated'):PROCESS.classify_process(self.circuit,self.process)
def test_different_net_cannot_be_classified_as_ground(self):
copper,drills,_,ids=PROCESS.classify_process(self.circuit,self.process)
drill=next(d for d in drills if d.id in ids)
pad=next(c for c in copper if c.id==drill.filled_capped_pad_id)
altered=copy.copy(drill);altered.net='unrelated_signal'
with self.assertRaisesRegex(ValueError,'Invalid filled/capped'):G.drill_copper_rule(altered,pad)
class SourceTraceAttributionTest(unittest.TestCase):
def test_unkeyed_single_port_via_uses_canonical_ground_membership(self):
circuit=[{'type':'source_port','source_port_id':'ground_port','subcircuit_connectivity_map_key':'ground'},
{'type':'source_trace','source_trace_id':'via_connection','connected_source_port_ids':['ground_port']},
{'type':'pcb_via','pcb_via_id':'via','source_trace_id':'via_connection','x':0,'y':0,'hole_diameter':.3,'outer_diameter':.45,'layers':['top','bottom']}]
copper,drills,_=G.make_features(circuit)
self.assertEqual(drills[0].net,'ground')
self.assertTrue(all(c.net=='ground' for c in copper))
def test_unkeyed_trace_cannot_merge_different_resolved_nets(self):
circuit=[{'type':'source_port','source_port_id':'ground_port','subcircuit_connectivity_map_key':'ground'},
{'type':'source_port','source_port_id':'signal_port','subcircuit_connectivity_map_key':'signal'},
{'type':'source_trace','source_trace_id':'bad_connection','connected_source_port_ids':['ground_port','signal_port']}]
with self.assertRaisesRegex(ValueError,'Conflicting source net'):G.make_features(circuit)
def test_unknown_port_does_not_acquire_a_guessed_net(self):
circuit=[{'type':'source_trace','source_trace_id':'unresolved','connected_source_port_ids':['unknown']},
{'type':'pcb_via','pcb_via_id':'via','source_trace_id':'unresolved','x':0,'y':0,'hole_diameter':.3,'outer_diameter':.45,'layers':['top','bottom']}]
_,drills,_=G.make_features(circuit)
self.assertIsNone(drills[0].net)
if __name__=='__main__':unittest.main()