component
NCD0402G1
Public Made by Adomby adom
Foshan NationStar Optoelectronics LED Indication - Discrete, 0402. Pinout cross-checked by two models against the datasheet, KiCad symbol/footprint/3D.
master
John Lauer
Add optional LED on/off STEP and emissive GLB variants with independent MPN group and provenance
9231724
1mo ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960
import sys,json,pathlib,struct,hashlib,subprocess,math
B=pathlib.Path(__file__).resolve().parent; A=B.parent/'all-marked';sys.path.insert(0,str(B.parent.parent/'cad-python'))
import cadquery as cq
from OCP.TDocStd import TDocStd_Document
from OCP.TCollection import TCollection_ExtendedString
from OCP.STEPCAFControl import STEPCAFControl_Reader,STEPCAFControl_Writer
from OCP.XCAFDoc import XCAFDoc_DocumentTool,XCAFDoc_ColorType
from OCP.TDataStd import TDataStd_Name
from OCP.Quantity import Quantity_Color,Quantity_TOC_RGB
from OCP.STEPControl import STEPControl_AsIs
def sha(p):return hashlib.sha256(p.read_bytes()).hexdigest()
def readglb(p):
d=p.read_bytes();n=struct.unpack_from('<I',d,12)[0];return json.loads(d[20:20+n]),d[20+n:]
def writeglb(p,j,rest):
raw=json.dumps(j,separators=(',',':')).encode();raw+=b' '*((-len(raw))%4);p.write_bytes(struct.pack('<III',0x46546c67,2,20+len(raw)+len(rest))+struct.pack('<II',len(raw),0x4e4f534a)+raw+rest)
def descendants(j,node):
yield node
for i in node.get('children',[]):yield from descendants(j,j['nodes'][i])
for slug,color,rgb in [('ncd0402g1','green',[.015,1,.07]),('ncd0402b1','blue',[.025,.14,1])]:
d=B/slug;d.mkdir(exist_ok=True);src=A/slug/(slug+'-mpn.step');out=d/(slug+'-mpn-led-on.step')
doc=TDocStd_Document(TCollection_ExtendedString('BinXCAF'));r=STEPCAFControl_Reader();r.SetColorMode(True);r.SetNameMode(True);r.ReadFile(str(src));assert r.Transfer(doc)
original=cq.importers.importStep(str(src)).val();plates=[]
for face in original.Faces():
bb=face.BoundingBox()
if face.geomType()=='PLANE' and face.Area()>.01 and .4999<=bb.zmax<=.50001 and bb.zmin>=.18 and face.normalAt().z>=-.1:
normal=face.normalAt();skin=cq.Solid.extrudeLinear(face.outerWire(),face.innerWires(),normal.multiply(.0002)).translate(normal.multiply(.00005));plates.append(skin)
assert len(plates)>=5,len(plates)
shape=cq.Compound.makeCompound(plates)
st=XCAFDoc_DocumentTool.ShapeTool_s(doc.Main());ct=XCAFDoc_DocumentTool.ColorTool_s(doc.Main());label=st.AddShape(shape.wrapped,False);TDataStd_Name.Set_s(label,TCollection_ExtendedString('Adom.LED.Emission'));ct.SetColor(label,Quantity_Color(*rgb,Quantity_TOC_RGB),XCAFDoc_ColorType.XCAFDoc_ColorSurf)
w=STEPCAFControl_Writer();w.SetNameMode(True);w.SetColorMode(True);assert w.Transfer(doc,STEPControl_AsIs);w.Write(str(out))
cq.Assembly(shape,name='Adom.LED.Emission',color=cq.Color(*rgb)).export(str(d/(slug+'-led-overlay.step')))
glb=out.with_suffix('.glb');subprocess.run(['step2glb','convert',str(out),'-o',str(glb)],check=True)
j,rest=readglb(glb);found=[]
for node in j['nodes']:
name=node.get('name','')
if name=='Adom.LED.Emission':
node['extras']={'adomRole':'led-emission','defaultEnabled':True,'emittingColor':color,'illustrative':True};found.append(name)
for p in [p for n in descendants(j,node) if 'mesh' in n for p in j['meshes'][n['mesh']]['primitives']]:
mi=len(j['materials']);m={'name':'Adom.LED.Emissive.'+color,'doubleSided':True,'pbrMetallicRoughness':{'baseColorFactor':rgb+[1],'metallicFactor':0,'roughnessFactor':.4},'emissiveFactor':rgb};j['materials'].append(m);p['material']=mi
elif name=='Adom.LaserEtch.MPN':node['extras']={'adomRole':'mpn-marking','defaultEnabled':True};found.append(name)
assert len(set(found))==2,found
# Fit a smooth glTF cone to the datasheet's full half-intensity angle 130deg.
outer=math.radians(85);inner=math.acos(math.cos(outer)+(math.cos(math.radians(65))-math.cos(outer))/math.sqrt(.5))
intensity=.000075 if color=='green' else .000015
light={'name':'Adom.LED.Light','type':'spot','color':rgb,'intensity':intensity,'range':.015,'spot':{'innerConeAngle':inner,'outerConeAngle':outer},'extras':{'viewingAngleFwhmDegrees':130,'datasheetTypicalCandelaAt5mA':.15 if color=='green' else .03,'illustrationGain':.0005,'profile':'Two-angle smooth cosine approximation, not measured far-field distribution'}}
j.setdefault('extensions',{})['KHR_lights_punctual']={'lights':[light]};j.setdefault('extensionsUsed',[]).append('KHR_lights_punctual')
li=len(j['nodes']);j['nodes'].append({'name':'Adom.LED.Light','translation':[0,0,.0005004],'rotation':[1,0,0,0],'extensions':{'KHR_lights_punctual':{'light':0}},'extras':{'adomRole':'led-light'}})
for sc in j['scenes']:
roots=[i for i in sc['nodes'] if j['nodes'][i].get('name')=='Adom.LED.Emission'];assert roots
gi=len(j['nodes']);j['nodes'].append({'name':'Adom.LED.State','children':roots+[li],'extras':{'adomRole':'led-state','defaultEnabled':True}});sc['nodes']=[i for i in sc['nodes'] if i not in roots]+[gi]
j.setdefault('asset',{})['extras']={'adomDisplayVariants':{'led':'on','mpn':'on'},'notice':'Illustrative LED state, not circuit simulation or calibrated photometry. Toggle named node visibility in a capable viewer.'};writeglb(glb,j,rest)
off=d/(slug+'-mpn-led-off.glb');jo=json.loads(json.dumps(j));lit={i for i,n in enumerate(jo['nodes']) if n.get('name')=='Adom.LED.State'}
for sc in jo['scenes']:sc['nodes']=[i for i in sc['nodes'] if i not in lit]
for n in jo['nodes']:
if 'children' in n:n['children']=[i for i in n['children'] if i not in lit]
jo['asset']['extras']['adomDisplayVariants']['led']='off';writeglb(off,jo,rest)
record={'mpn':slug.upper(),'emittingColor':color,'sourceStep':str(src),'sourceSha256':sha(src),'classification':'AI-authored illustrative light overlay on KiCad-derived generic LED body; manufacturer body fidelity not established','generator':'Codex Astra; build.py with CadQuery/OpenCascade and glTF emissive materials','lightGeometryMm':{'skinThickness':.0002,'outwardClearance':.00005,'method':'Extrude actual upper-lens planar faces along their outward normals','faceCount':len(plates)},'punctualLight':light,'linearRgbIllustration':rgb,'defaultLED':'on','defaultMPN':'on','groups':found,'physicalLimitation':'Illustrative emission, not a calibrated optical or circuit simulation. GLB includes a spot light; STEP has colored geometry only. The lens skin adds at most 0.0002mm to XY bounds. Original dimensions and polarity geometry unchanged.','files':{p.name:sha(p) for p in d.iterdir() if p.suffix in ['.step','.glb']}}
(d/'led-provenance.json').write_text(json.dumps(record,indent=2));print('READY',slug,flush=True)