EDA Skillpack
Public Made by Adomby adom
All skills for all EDA capabilities across the Adom ecosystem.
Add shared footprint-aware component hero and LED appearance skills #1
Component heroes need to expose the footprint underneath the physical model so an engineer can inspect terminal alignment and pin 1. Add shared EDA skills for component heroes and LED appearance, plus a footprint-reference GLB generator. This implements the existing wiki-component reference-art contract in the EDA skillpack rather than leaving it as ESC-specific instructions.
The generator appends separately named nonphysical teal dashed copper boundaries (including drilled boundaries), translucent silk, and symbol-mapped signal labels. It preserves the original binary geometry, materials, animation and punctual lights, attaches the art under the existing presentation transform, and records source GLB, footprint, mapping, font and generator hashes. Unsupported geometry fails explicitly. Hero helpers are excluded from board-use assets.
The LED skill covers optional off/lit variants, restrained opt-in light behavior, lens sides, MPN separation, non-coplanar display skins and native/Babylon verification. STEP colors are explicitly distinguished from GLB lights. AI-added geometry and optical approximations must be disclosed.
Validation: generated the 40 existing ESC component-page heroes and checked original binary/mesh/material/animation preservation against their actual source GLBs. Published top and underside views were rendered in the wiki Babylon viewer. The audit found three footprints without visible silk contours; those actual footprint files were repaired without changing their copper pads, parsed through KiCad 10.0.5 SVG export, and their heroes regenerated. This work does not qualify unknown upstream physical models as manufacturer accurate.
Runtime dependencies for the generator: Python 3, fontTools and shapely; NumPy is used for arc construction. A font path is explicit. The companion AI Flow proposal adds structural checking and invokes these skills early in library work. Please merge and publish the skillpack so other Adom users receive the same workflow.
Diff Skip to comments
@@ -1,47 +1,51 @@⋯ 44 unchanged lines ⋯ - [eda-design-summary-video](skills/eda-design-summary-video/SKILL.md): Make a narrated evidence-based video of PCB placement, routing, validation and native 3D comparison. Worked example: [Astra independent placement, routing and 3D comparison](https://wiki.adom.inc/adom/codex/files/docs/BQ-PLACEMENT-ROUTING-3D.md). Example metrics are not acceptance targets for other boards.++- [Inspectable component heroes](skills/eda-component-hero/SKILL.md): dashed copper, translucent silk, pin names and pin1 evidence; clean board variants.+- [LED appearance](skills/eda-led-appearance/SKILL.md): off/lit variants, restrained light, animation and z-fighting prevention.+@@ -1,60 +1,64 @@⋯ 57 unchanged lines ⋯ ## Independent Astra ESC source and measurements [Independent placement, routing and copper pours](docs/examples/astra-esc-independent/README.md) includes Python and C++ source, the clean fixture, accepted plans and native evidence. The algorithms illustrate reusable methods; the board-specific policy must be reviewed for each new design. These resources are available to any AI.++- [Inspectable component heroes](skills/eda-component-hero/SKILL.md): dashed copper, translucent silk, pin names and pin1 evidence; clean board variants.+- [LED appearance](skills/eda-led-appearance/SKILL.md): off/lit variants, restrained light, animation and z-fighting prevention.+@@ -0,0 +1,25 @@+---+name: eda-component-hero+description: Create and verify component wiki GLB heroes with teal dashed copper-pad boundaries, translucent silkscreen, signal labels, visible pin1 and preserved MPN; separate annotated heroes from clean EDA board models.+user-invocable: true+---+# Inspectable component heroes++Shared EDA implementation of the existing wiki-component readable 3D reference artwork contract. Reuse the existing exact component page and preserve upstream files, source attribution, visibility and other contributors' changes. Board net names and instance references do not belong in global component artwork.++In the verified Z-up footprint seating frame, add separately named NONPHYSICAL helper nodes:++- Copper-pad perimeters: teal dashed outlines, alpha0.50. Use actual copper geometry, including pad rotation, custom polygons and plated-hole boundaries; never substitute mask or paste.+- Silkscreen contours: alpha0.30. Preserve pin1/polarity features. Omit uninstantiated REF** text and hidden properties. Explicitly report unsupported primitives.+- Pin labels: alpha0.50, actual signal names resolved from the symbol/canonical pin-to-pad mapping. Preserve active-low semantics; use unique pad numbers for unnamed passive terminals. Place labels outside the body, oriented to avoid overlap.++Keep physical terminals, model markers and virtual MPN separate from footprint art. A marker on an internal module IC is not automatically module pin1. Compare physical pin1, numbered copper and silk marker from top, oblique and underside views; do not infer orientation from a pleasing hero pose. Unknown alignment remains a finding. Apply any hero presentation rotation to the ENTIRE chip+art assembly, including animations. Do not rotate artwork separately.++The helper in tools/component-reference-overlay.py appends geometry to the original GLB binary buffer and preserves existing materials, animation and punctual lights. Its input is the original-frame or coherently posed hero, the exact KiCad footprint, explicit pin-map JSON and a supplied font. It is a display generator, not a native library validator. Preserve source hashes, transforms, opacity, geometry coverage, tool revision and limitations beside the output. Fail visibly on unsupported geometry.++Reference art belongs ONLY in the wiki hero. All board-use STEP/GLB/LOD assets exclude footprint outlines, labels and silk; the board supplies these. Keep the reviewed MPN and physical terminals in board assets. Preserve originals and cache both products on the existing component page.++Inspect actual rendered pixels, not just manifests: top, underside, oblique, pad1 corner and dense pin labels. Verify alpha, dashed appearance, MPN, body/pad alignment and no z-fighting. Load the published file in the wiki Babylon viewer, reload, and check that the expected hash/revision persists. After any downstream board/model change, refresh BOTH the native 2D editor and linked 3D view, preserving user work and avoiding stale windows.++For LEDs, read ../eda-led-appearance/SKILL.md. AI Flow should invoke these early in library review, record detailed component inspections and use a short overview in the final video.+@@ -0,0 +1,19 @@+---+name: eda-led-appearance+description: Create reusable LED off/lit STEP and GLB variants, emissive lens materials, restrained datasheet-informed Babylon spot lights, toggleable scene groups and gentle hero animation; prevent z-fighting and preserve MPN and polarity.+user-invocable: true+---+# LED appearance and light++Reuse the exact LED component page. Keep original manufacturer assets and source provenance. State whether the body is manufacturer CAD, a generic package or independently generated. Verify polarity and emitted color from the exact datasheet, not the filename or body tint.++Publish clean board-use off and lit variants plus an annotated wiki hero. Name separate scene groups for physical chip, MPN, emission and light state. Default to the user's chosen state; retain off variants. STEP supports colored geometry/assemblies, not glTF lights or a portable interactive toggle. A capable viewer may toggle named GLB groups; that capability must be verified rather than assumed for every EDA.++Prefer recoloring existing optical faces when feasible. If a separate colored/emissive skin is necessary, do NOT place coplanar or near-coplanar faces on the original lens. Offset along each actual outward face normal, including sides, by a tested separation that survives STEP tessellation, glTF float serialization and native depth precision. The ESC revision uses0.01mm outward clearance and0.005mm skin thickness, explicitly an illustrative dimensional addition, not a universal rule. Place virtual MPN above the resulting visible surface and recheck it; a lens fix must not hide the inscription. Preserve solder contacts, seating and polarity geometry. Verify source/skin clearance numerically and orbit both near and at whole-board scale in native KiCad and Babylon; report the tested scales and remaining limits.++Use restrained emissive materials and, when requested, one finite-range KHR_lights_punctual spot per LED. Derive viewing-angle interpretation from the datasheet: full half-intensity angle is not simply the glTF outer cone. Document the chosen cone approximation, emitted color, intensity, units, range and illustration gain. This is not calibrated optical or circuit simulation. Do not add a second spill light by default; emissive side faces often suffice, and a board with many lights needs measured scene-performance checks.++A component hero can gently dim/re-light and rotate once, then settle lit and still. Retain static board variants. Keep animation/light extensions intact when adding footprint reference artwork; test the published viewer's actual support. Label all AI-created skins, MPNs and light approximations with input/source hashes and reproducible code. Cache STEP, GLB, off/lit choices and provenance on the existing component page.++After model changes refresh BOTH native board editor and 3D viewer; inspect actual pixels and avoid stale model caches. Use eda-component-hero for dashed pads, translucent silk and signal labels, which stay out of board-use models.+@@ -0,0 +1,185 @@+#!/usr/bin/env python3+"""Add nonphysical footprint reference artwork to a Z-up component GLB.+Original geometry buffers and source files are preserved. Units: GLB metres,+KiCad footprint millimetres with Y down. Glyph outlines use a supplied TrueType font.+"""+import argparse,json,re,math,struct,hashlib+from pathlib import Path+from fontTools.ttLib import TTFont+from fontTools.pens.basePen import BasePen+class Atom(str):pass++def parse(s):+ stack=[];root=None+ for t in re.findall(r'"(?:\\.|[^"\\])*"|[()]|[^\s()]+',s):+ if t=='(':+ a=[]+ if stack:stack[-1].append(a)+ else:root=a+ stack.append(a)+ elif t==')':stack.pop()+ else:stack[-1].append(json.loads(t) if t.startswith('"') else Atom(t))+ assert not stack+ return root+def dump(n):+ return '('+' '.join(map(dump,n))+')' if isinstance(n,list) else str(n) if isinstance(n,Atom) else json.dumps(n,ensure_ascii=False)+def fields(n,key):return [x for x in n if isinstance(x,list) and x and x[0]==key]+def field(n,key,default=None):return next(iter(fields(n,key)),default)+def xy(n,key):return tuple(map(float,field(n,key)[1:3]))+def circle(c,r,start=0,span=2*math.pi,count=48):return [(c[0]+r*math.cos(start+span*i/count),c[1]+r*math.sin(start+span*i/count)) for i in range(count+1)]+def rounded(w,h,r):+ out=[]+ for cx,cy,a in [(w/2-r,h/2-r,0),(-w/2+r,h/2-r,math.pi/2),(-w/2+r,-h/2+r,math.pi),(w/2-r,-h/2+r,math.pi*1.5)]:out+=circle((cx,cy),r,a,math.pi/2,8)+ return out+[out[0]]+def dashed(points,on=.07,off=.045):+ out=[];distance=0+ for a,b in zip(points,points[1:]):+ length=math.dist(a,b);pos=0+ while pos<length-1e-12:+ phase=distance%(on+off);take=min(length-pos,(on-phase) if phase<on else (on+off-phase))+ if take<1e-10:distance+=1e-9;continue+ if phase<on:+ out.append([(a[0]+(b[0]-a[0])*u/length,a[1]+(b[1]-a[1])*u/length) for u in [pos,pos+take]])+ distance+=take;pos+=take+ return out+class Flatten(BasePen):+ def __init__(self,g):super().__init__(g);self.paths=[];self.path=[]+ def _moveTo(self,p):self.path=[p]+ def _lineTo(self,p):self.path.append(p)+ def _curveToOne(self,a,b,c):+ p=self._getCurrentPoint()+ for i in range(1,9):+ t=i/8;self.path.append(tuple((1-t)**3*p[j]+3*(1-t)**2*t*a[j]+3*(1-t)*t*t*b[j]+t**3*c[j] for j in range(2)))+ def _qCurveToOne(self,a,b):+ p=self._getCurrentPoint()+ for i in range(1,9):+ t=i/8;self.path.append(tuple((1-t)**2*p[j]+2*(1-t)*t*a[j]+t*t*b[j] for j in range(2)))+ def _closePath(self):self.path.append(self.path[0]);self.paths.append(self.path);self.path=[]+ def _endPath(self):self.paths.append(self.path);self.path=[]+def main():+ ap=argparse.ArgumentParser();ap.add_argument('--glb',type=Path,required=True);ap.add_argument('--footprint',type=Path,required=True);ap.add_argument('--library',type=Path,required=True);ap.add_argument('--font',type=Path,required=True);ap.add_argument('--out',type=Path,required=True);args=ap.parse_args()+ fp=parse(args.footprint.read_text());library=json.loads(args.library.read_text());pin_names={p['number']:p['name'] for p in library['symbol']['pins']};pad_names={}+ for c in library['connect']:+ name=pin_names[c['pin']]+ if c['pad'] in pad_names:assert pad_names[c['pad']]==name,'Ambiguous pad name mapping'+ pad_names[c['pad']]=name+ font=TTFont(args.font);glyphs=font.getGlyphSet();cmap=font.getBestCmap();em=font['head'].unitsPerEm+ def textpaths(text,at,height):+ paths=[];cursor=0+ for ch in text:+ name=cmap.get(ord(ch));assert name,repr(ch)+ pen=Flatten(glyphs);glyphs[name].draw(pen);paths.extend([[(x+cursor,y) for x,y in line] for line in pen.paths]);cursor+=glyphs[name].width+ scale=height/(em*.75)+ return [[(at[0]+(x-cursor/2)*scale,at[1]+(y-em*.35)*scale) for x,y in line] for line in paths]+ pads=[];padlines=[];silk=[];labels=[]+ for p in fields(fp,'pad'):+ if not any(x in field(p,'layers')[1:] for x in ['F.Cu','*.Cu']):continue+ number=p[1];at=xy(p,'at');size=xy(p,'size');angle=math.radians(-float(field(p,'at')[3]) if len(field(p,'at'))>3 else 0);shape=p[3]+ if shape=='roundrect':r=min(size)*float(field(p,'roundrect_rratio')[1]);outline=rounded(*size,r)+ elif shape=='rect':outline=rounded(*size,0)+ elif shape=='circle':outline=circle((0,0),size[0]/2)+ elif shape=='oval':outline=rounded(*size,min(size)/2)+ elif shape=='custom':+ from shapely.geometry import Polygon,Point,LineString,box+ from shapely.ops import unary_union+ shapes=[]+ anchor=field(field(p,'options',[]),'anchor',['anchor','rect'])[1]+ shapes.append(Point(0,0).buffer(size[0]/2) if anchor=='circle' else box(-size[0]/2,-size[1]/2,size[0]/2,size[1]/2))+ for pr in field(p,'primitives',[])[1:]:+ width=float(field(pr,'width',['width',0])[1])+ if pr[0]=='gr_poly':+ pts=[tuple(map(float,q[1:3])) for q in field(pr,'pts')[1:]];shapes.append(Polygon(pts).buffer(width/2))+ elif pr[0]=='gr_line':shapes.append(LineString([xy(pr,'start'),xy(pr,'end')]).buffer(width/2))+ elif pr[0]=='gr_circle':shapes.append(Point(*xy(pr,'center')).buffer(math.dist(xy(pr,'center'),xy(pr,'end'))+width/2))+ else:raise ValueError('Unsupported custom copper primitive '+pr[0])+ merged=unary_union(shapes);assert merged.geom_type=='Polygon','Disconnected custom copper pad requires explicit review';outline=list(merged.exterior.coords)+ else:raise ValueError('Unsupported pad shape '+shape)+ def transform(q):return (at[0]+q[0]*math.cos(angle)-q[1]*math.sin(angle),-(at[1]+q[0]*math.sin(angle)+q[1]*math.cos(angle)))+ outline=list(map(transform,outline));padlines+=dashed(outline)+ drill=field(p,'drill',[])+ if drill:+ dims=[float(v) for v in drill[1:] if not isinstance(v,list) and v!='oval'];off=field(drill,'offset',['offset',0,0]);center=(float(off[1]),float(off[2]))+ hole=rounded(dims[0],dims[1],min(dims)/2) if drill[1]=='oval' else circle((0,0),dims[0]/2)+ hole=[transform((x+center[0],y+center[1])) for x,y in hole];padlines+=dashed(hole)+ bounds=([min(q[i] for q in outline) for i in range(2)],[max(q[i] for q in outline) for i in range(2)])+ raw_name=pad_names.get(number,number) or number;+ if not raw_name:raise ValueError('No electrical mapping for copper pad '+number)+ active_low=bool(re.fullmatch(r'~\{[^}]+\}',raw_name));display_name=raw_name[2:-1] if active_low else raw_name+ unnamed_passive=len(library['symbol']['pins'])==2 and all(q['electrical_type']=='passive' for q in library['symbol']['pins']) and raw_name in ['', '~', number]+ if unnamed_passive:display_name=number+ if not display_name:raise ValueError('Missing signal name for pad '+number)+ glyphs_local=textpaths(display_name,(0,0),.17);points=[q for line in glyphs_local for q in line];xlo=min(q[0] for q in points);xhi=max(q[0] for q in points)+ if active_low:+ bar_y=max(q[1] for q in points)+.025;glyphs_local.append([(xlo,bar_y),(xhi,bar_y)])+ padcenters=[xy(q,'at') for q in fields(fp,'pad') if any(v in field(q,'layers')[1:] for v in ['F.Cu','*.Cu'])]+ maxx=max(abs(v[0]) for v in padcenters) or 1;maxy=max(abs(v[1]) for v in padcenters) or 1+ vertical=abs(at[1])/maxy > abs(at[0])/maxx+.05+ if vertical:+ # Radial labels outside top/bottom rows, with text along the outward axis.+ glyphs_local=[[(-y,x) for x,y in line] for line in glyphs_local];points=[q for line in glyphs_local for q in line];xlo=min(q[0] for q in points);xhi=max(q[0] for q in points)+ dx=at[0];label_y=(bounds[1][1]+.32-min(q[1] for q in points)) if at[1]<0 else bounds[0][1]-.32-max(q[1] for q in points)+ else:+ dx=(bounds[0][0]-.32-xhi) if at[0]<0 else (bounds[1][0]+.32-xlo);label_y=-at[1]+ if abs(at[0])<.01 and abs(at[1])<.01:+ ys=[-xy(q,'at')[1]+sign*xy(q,'size')[1]/2 for q in fields(fp,'pad') for sign in [-1,1]];dx=0;label_y=min(ys)-.65+ labelat=(dx,label_y);labels += [[(x+dx,y+label_y) for x,y in line] for line in glyphs_local]+ pads.append({'number':number,'drill_boundary_drawn':bool(drill),'signal_name':raw_name,'display_name':display_name,'pad_number_fallback':unnamed_passive,'active_low_overbar':active_low,'center_mm':[at[0],-at[1]],'copper_size_mm':size,'copper_outline_bounds_mm':bounds,'label_position_mm':labelat})+ for p in fp:+ if not isinstance(p,list) or field(p,'layer')!=['layer','F.SilkS']:continue+ kind=p[0]+ if kind=='fp_line':silk.append([(x,-y) for x,y in [xy(p,'start'),xy(p,'end')]])+ elif kind=='fp_circle':+ c=xy(p,'center');e=xy(p,'end');silk.append([(x,-y) for x,y in circle(c,math.dist(c,e))])+ elif kind=='fp_rect':+ a,b=xy(p,'start'),xy(p,'end');silk.append([(x,-y) for x,y in [a,(b[0],a[1]),b,(a[0],b[1]),a]])+ elif kind=='fp_poly':+ points=[tuple(map(float,q[1:3])) for q in field(p,'pts')[1:]];silk.append([(x,-y) for x,y in points+[points[0]]])+ elif kind=='fp_arc':+ import numpy as np+ a,m,z=[np.array(xy(p,k)) for k in ['start','mid','end']];center=np.linalg.solve(2*np.array([m-a,z-a]),np.array([m@m-a@a,z@z-a@a]));rad=float(np.linalg.norm(a-center));t0,t1,t2=[math.atan2(*(v-center)[::-1]) for v in [a,m,z]];span=(t2-t0)%(2*math.pi)+ if (t1-t0)%(2*math.pi)>span:span-=2*math.pi+ silk.append([(x,-y) for x,y in circle(center,rad,t0,span,48)])+ elif kind=='property':+ # Identity placeholders/hidden properties are not physical footprint silk.+ if field(p,'hide') or 'hide' in p or p[1]=='Reference' or p[2] in ['REF**','REF*','${REFERENCE}']:continue+ at=xy(p,'at');effects=field(p,'effects');height=float(field(field(effects,'font'),'size')[2]);silk+=textpaths(p[2],(at[0],-at[1]),height)+ elif kind=='fp_text':+ if 'hide' in p or (p[1]=='reference' and p[2] in ['REF**','REF*','${REFERENCE}']):continue+ at=xy(p,'at');effects=field(p,'effects');height=float(field(field(effects,'font'),'size')[2]);silk+=textpaths(p[2],(at[0],-at[1]),height)+ else:raise ValueError('Unsupported silk primitive '+kind)+ b=args.glb.read_bytes();jn,jt=struct.unpack_from('<II',b,12);d=json.loads(b[20:20+jn]);bn,bt=struct.unpack_from('<II',b,20+jn);assert bt==0x004e4942+ original=b[28+jn:28+jn+bn];binary=bytearray(original);assert len(d['buffers'])==1 and 'uri' not in d['buffers'][0]+ ext=d.setdefault('extensionsUsed',[])+ if 'KHR_materials_unlit' not in ext:ext.append('KHR_materials_unlit')+ parent={'name':'Adom reference artwork (nonphysical)','children':[],'extras':{'physical':False,'purpose':'footprint copper outlines, silkscreen and pad identifiers','sourceFootprint':args.footprint.name,'units':'metre','upAxis':'Z'}}+ parentid=len(d['nodes']);d['nodes'].append(parent)+ roots=d['scenes'][d.get('scene',0)]['nodes'];poses=[i for i in roots if d['nodes'][i].get('name','').startswith('Adom.Hero.')]+ if len(poses)==1:d['nodes'][poses[0]].setdefault('children',[]).append(parentid)+ elif not poses:roots.append(parentid)+ else:raise ValueError('Ambiguous hero presentation transform')+ def add(name,paths,color,alpha,width):+ vertices=[];z=-.00002+ for path in paths:+ for a,c in zip(path,path[1:]):+ length=math.dist(a,c)+ if length<1e-10:continue+ nx=-(c[1]-a[1])/length*width/2;ny=(c[0]-a[0])/length*width/2+ quad=[(a[0]+nx,a[1]+ny),(a[0]-nx,a[1]-ny),(c[0]-nx,c[1]-ny),(c[0]+nx,c[1]+ny)]+ for i in [0,1,2,0,2,3]:vertices.append((quad[i][0]/1000,quad[i][1]/1000,z))+ if not vertices:return+ binary.extend(b'\0'*((-len(binary))%4));offset=len(binary);raw=b''.join(struct.pack('<fff',*v) for v in vertices);binary.extend(raw)+ bv=len(d['bufferViews']);d['bufferViews'].append({'buffer':0,'byteOffset':offset,'byteLength':len(raw),'target':34962})+ ac=len(d['accessors']);d['accessors'].append({'bufferView':bv,'componentType':5126,'count':len(vertices),'type':'VEC3','min':[min(v[i] for v in vertices) for i in range(3)],'max':[max(v[i] for v in vertices) for i in range(3)]})+ material=len(d['materials']);d['materials'].append({'name':name,'pbrMetallicRoughness':{'baseColorFactor':color+[alpha],'metallicFactor':0,'roughnessFactor':1},'alphaMode':'BLEND' if alpha<1 else 'OPAQUE','doubleSided':True,'extensions':{'KHR_materials_unlit':{}},'extras':{'referenceArtwork':True,'opacity':alpha}})+ mesh=len(d['meshes']);d['meshes'].append({'name':name,'primitives':[{'attributes':{'POSITION':ac},'material':material,'mode':4}]})+ node=len(d['nodes']);d['nodes'].append({'name':name,'mesh':mesh,'extras':{'physical':False}});parent['children'].append(node)+ add('Footprint pads - teal dashed outlines - 50 percent opacity',padlines,[0,.902,.863],.5,.015)+ add('Silkscreen reference lines - 30 percent opacity',silk,[.92,.96,1],.3,.012)+ add('Signal names - 50 percent opacity',labels,[.92,.96,1],.5,.009)+ d['buffers'][0]['byteLength']=len(binary);assert binary[:len(original)]==original+ j=json.dumps(d,separators=(',',':')).encode();j+=b' '*((-len(j))%4);binary.extend(b'\0'*((-len(binary))%4))+ out=struct.pack('<III',0x46546c67,2,28+len(j)+len(binary))+struct.pack('<II',len(j),0x4e4f534a)+j+struct.pack('<II',len(binary),0x004e4942)+binary;args.out.write_bytes(out)+ report={'font_file':args.font.name,'font_sha256':hashlib.sha256(args.font.read_bytes()).hexdigest(),'generator_sha256':hashlib.sha256(Path(__file__).read_bytes()).hexdigest(),'source_glb_sha256':hashlib.sha256(b).hexdigest(),'footprint_sha256':hashlib.sha256(args.footprint.read_bytes()).hexdigest(),'derivative_sha256':hashlib.sha256(out).hexdigest(),'manufacturer_geometry_buffer_prefix_unchanged':True,'units':'metre','upAxis':'Z','plane_z_mm':-.02,'pad_outline':'copper perimeter, not solder mask or paste','dash_mm':.07,'gap_mm':.045,'silkscreen_path_count':len(silk),'silkscreen_opacity':.3,'pad_outline_opacity':.5,'pad_outline_color':'teal #00E6DC','uninstantiated_reference_placeholder':'omitted from component display','pad_name_opacity':.5,'pad_names':'signal names resolved through canonical pin-to-pad mapping; active-low overbars preserved','library_sha256':hashlib.sha256(args.library.read_bytes()).hexdigest(),'pads':pads,'overlay_nodes':len(parent['children']),'limitations':['Nonphysical reference lines, not a manufacturing layer or manufacturer STEP geometry.','Silkscreen text follows source text/placement but uses the supplied display font.','Physical body can occlude artwork on the seating plane; inspect underside or hide the body.']}+ args.out.with_suffix('.overlay.json').write_text(json.dumps(report,indent=2)+'\n');print(json.dumps(report))+if __name__=='__main__':main()+
Comments
Log in to comment.
No comments yet.