Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions edg/electronics_model/SvgPcbTemplateBlock.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ def _svgpcb_refdes_of(self, block_ref: List[str]) -> Tuple[str, int]:
block_path = self._svgpcb_pathname_data.append_block(*block_ref)
candidate_blocks = [block for block in self._svgpcb_netlist.blocks
if block.full_path.startswith(block_path)]
assert len(candidate_blocks) == 1
assert len(candidate_blocks) > 0
Copy link

Copilot AI Nov 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The assertion change from == 1 to > 0 allows multiple matching blocks but then unconditionally uses candidate_blocks[0]. This could mask issues where multiple blocks exist but different ones are expected. Consider adding a warning or selecting based on specific criteria when multiple blocks are found.

Copilot uses AI. Check for mistakes.
refdes = candidate_blocks[0].refdes
assert isinstance(refdes, str)
assert refdes is not None
Expand All @@ -60,19 +60,19 @@ def _svgpcb_refdes_of(self, block_ref: List[str]) -> Tuple[str, int]:

def _svgpcb_footprint_block_path_of(self, block_ref: List[str]) -> TransformUtil.Path:
"""Infrastructure method, given the name of a container block, returns the block path of the footprint block.
Asserts there is exactly one."""
Picks the first one, which is assumed to be the main / anchor device."""
block_path = self._svgpcb_pathname_data.append_block(*block_ref)
candidate_blocks = [block for block in self._svgpcb_netlist.blocks
if block.full_path.startswith(block_path)]
assert len(candidate_blocks) == 1
assert len(candidate_blocks) > 0
Copy link

Copilot AI Nov 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to the previous assertion, this change allows multiple blocks but always returns the first one. The updated docstring says 'Picks the first one, which is assumed to be the main / anchor device' but this assumption may not always hold. Consider adding validation or making the selection more explicit.

Copilot uses AI. Check for mistakes.
return candidate_blocks[0].full_path

def _svgpcb_footprint_of(self, path: TransformUtil.Path) -> str:
"""Infrastructure method, returns the footprint for the output of _svgpcb_footprint_block_path_of.
If _svgpcb_footprint_block_path_of returned a value, this will return the footprint; otherwise crashes."""
candidate_blocks = [block for block in self._svgpcb_netlist.blocks
if block.full_path == path]
assert len(candidate_blocks) == 1
assert len(candidate_blocks) > 0
return self._svgpcb_footprint_to_svgpcb(candidate_blocks[0].footprint)

def _svgpcb_pin_of(self, block_ref: List[str], pin_ref: List[str]) -> str:
Expand Down
96 changes: 67 additions & 29 deletions edg/parts/Neopixel.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,22 @@

@abstract_block_default(lambda: Ws2812b)
class Neopixel(Light, Block):
"""Abstract base class for Neopixel-type LEDs including the Vdd/Gnd/Din/Dout interface."""
"""Abstract base class for individually-addressable, serially-connected Neopixel-type
(typically RGB) LEDs and defines the pwr/gnd/din/dout interface."""
def __init__(self) -> None:
super().__init__()
self.vdd = self.Port(VoltageSink.empty(), [Power])
self.pwr = self.Port(VoltageSink.empty(), [Power])
self.vdd = self.pwr # deprecated alias
self.gnd = self.Port(Ground.empty(), [Common])
self.din = self.Port(DigitalSink.empty(), [Input])
self.dout = self.Port(DigitalSource.empty(), optional=True)


class Ws2812b(Neopixel, FootprintBlock, JlcPart):
"""5050-size Neopixel RGB. Specifically does NOT need extra filtering capacitors."""
def __init__(self) -> None:
super().__init__()
self.vdd.init_from(VoltageSink(
self.pwr.init_from(VoltageSink(
voltage_limits=(3.7, 5.3) * Volt,
current_draw=(0.6, 0.6 + 12*3) * mAmp,
))
Expand All @@ -30,15 +33,15 @@ def __init__(self) -> None:
# note that a more restrictive input_threshold_abs of (1.5, 2.3) was used previously
))
self.dout.init_from(DigitalSource.from_supply(
self.gnd, self.vdd,
self.gnd, self.pwr,
current_limits=0*mAmp(tol=0),
))

def contents(self) -> None:
self.footprint(
'D', 'LED_SMD:LED_WS2812B_PLCC4_5.0x5.0mm_P3.2mm',
{
'1': self.vdd,
'1': self.pwr,
'2': self.dout,
'3': self.gnd,
'4': self.din
Expand All @@ -54,25 +57,23 @@ def contents(self) -> None:
self.assign(self.actual_basic_part, False)


class Sk6812Mini_E(Neopixel, FootprintBlock):
"""SK6812MINI-E reverse-mount Neopixel RGB LED, commonly used for keyboard lighting.
Note: while listed as JLC C5149201, it seems non-stocked and is standard assembly only."""
class Sk6812Mini_E_Device(InternalSubcircuit, JlcPart, FootprintBlock):
def __init__(self) -> None:
super().__init__()
self.vdd.init_from(VoltageSink(
self.vdd = self.Port(VoltageSink(
voltage_limits=(3.7, 5.5) * Volt,
current_draw=(1, 1 + 12*3) * mAmp, # 1 mA static type + up to 12mA/ch
))
self.gnd.init_from(Ground())
self.din.init_from(DigitalSink.from_supply(
self.gnd = self.Port(Ground())
self.din = self.Port(DigitalSink.from_supply(
self.gnd, self.vdd,
voltage_limit_tolerance=(-0.5, 0.5),
input_threshold_factor=(0.3, 0.7),
))
self.dout.init_from(DigitalSource.from_supply(
self.dout = self.Port(DigitalSource.from_supply(
self.gnd, self.vdd,
current_limits=0*mAmp(tol=0),
))
), optional=True)

def contents(self) -> None:
self.footprint(
Expand All @@ -86,26 +87,39 @@ def contents(self) -> None:
mfr='Opsco Optoelectronics', part='SK6812MINI-E',
datasheet='https://cdn-shop.adafruit.com/product-files/4960/4960_SK6812MINI-E_REV02_EN.pdf'
)
self.assign(self.lcsc_part, 'C5149201')
self.assign(self.actual_basic_part, False)


class Sk6805_Ec15(Neopixel, JlcPart, FootprintBlock):
"""SK6805-EC15 Neopixel RGB LED in 1.5x1.5 (0606)."""
class Sk6812Mini_E(Neopixel):
"""Reverse-mount (through-board) Neopixel RGB LED, commonly used for keyboard lighting."""
def __init__(self) -> None:
super().__init__()
self.vdd.init_from(VoltageSink(
self.device = self.Block(Sk6812Mini_E_Device())
self.cap = self.Block(DecouplingCapacitor(0.1*uFarad(tol=0.2)))
self.connect(self.gnd, self.device.gnd, self.cap.gnd)
self.connect(self.pwr, self.device.vdd, self.cap.pwr)
self.connect(self.din, self.device.din)
self.connect(self.dout, self.device.dout)


class Sk6805_Ec15_Device(InternalSubcircuit, JlcPart, FootprintBlock):
def __init__(self) -> None:
super().__init__()
self.vdd = self.Port(VoltageSink(
voltage_limits=(3.7, 5.5) * Volt,
current_draw=(1, 1 + 5*3) * mAmp, # 1 mA static type + up to 5mA/ch
))
self.gnd.init_from(Ground())
self.din.init_from(DigitalSink.from_supply(
self.gnd = self.Port(Ground())
self.din = self.Port(DigitalSink.from_supply(
self.gnd, self.vdd,
voltage_limit_tolerance=(-0.5, 0.5),
input_threshold_factor=(0.3, 0.7),
))
self.dout.init_from(DigitalSource.from_supply(
self.dout = self.Port(DigitalSource.from_supply(
self.gnd, self.vdd,
current_limits=0*mAmp(tol=0),
))
), optional=True)

def contents(self) -> None:
self.footprint(
Expand All @@ -123,24 +137,35 @@ def contents(self) -> None:
self.assign(self.actual_basic_part, False)


class Sk6812_Side_A(Neopixel, FootprintBlock):
"""SK6812-SIDE-A side-emitting Neopixel LED."""
class Sk6805_Ec15(Neopixel):
"""0606-size (1.5mm x 1.5mm) size Neopixel RGB LED."""
def __init__(self) -> None:
super().__init__()
self.vdd.init_from(VoltageSink(
self.device = self.Block(Sk6805_Ec15_Device())
self.cap = self.Block(DecouplingCapacitor(0.1*uFarad(tol=0.2)))
self.connect(self.gnd, self.device.gnd, self.cap.gnd)
self.connect(self.pwr, self.device.vdd, self.cap.pwr)
self.connect(self.din, self.device.din)
self.connect(self.dout, self.device.dout)


class Sk6812_Side_A_Device(InternalSubcircuit, FootprintBlock):
def __init__(self) -> None:
super().__init__()
self.vdd = self.Port(VoltageSink(
voltage_limits=(3.5, 5.5) * Volt,
current_draw=(1, 1 + 12*3) * mAmp, # 1 mA static type + up to 12mA/ch
))
self.gnd.init_from(Ground())
self.din.init_from(DigitalSink.from_supply(
self.gnd = self.Port(Ground())
self.din = self.Port(DigitalSink.from_supply(
self.gnd, self.vdd,
voltage_limit_tolerance=(-0.5, 0.5),
input_threshold_factor=(0.3, 0.7),
))
self.dout.init_from(DigitalSource.from_supply(
self.dout = self.Port(DigitalSource.from_supply(
self.gnd, self.vdd,
current_limits=0*mAmp(tol=0),
))
), optional=True)

def contents(self) -> None:
self.footprint(
Expand All @@ -157,14 +182,27 @@ def contents(self) -> None:
# potentially footprint-compatible with C2890037


class Sk6812_Side_A(Neopixel):
"""Side-emitting Neopixel LED, including used for keyboard edge lighting."""
def __init__(self) -> None:
super().__init__()
self.device = self.Block(Sk6812_Side_A_Device())
self.cap = self.Block(DecouplingCapacitor(0.1*uFarad(tol=0.2)))
self.connect(self.gnd, self.device.gnd, self.cap.gnd)
self.connect(self.pwr, self.device.vdd, self.cap.pwr)
self.connect(self.din, self.device.din)
self.connect(self.dout, self.device.dout)


class NeopixelArray(Light, GeneratorBlock):
"""An array of Neopixels"""
@init_in_parent
def __init__(self, count: IntLike):
super().__init__()
self.din = self.Port(DigitalSink.empty(), [Input])
self.dout = self.Port(DigitalSource.empty(), [Output], optional=True)
self.vdd = self.Port(VoltageSink.empty(), [Power])
self.pwr = self.Port(VoltageSink.empty(), [Power])
self.vdd = self.pwr # deprecated alias
self.gnd = self.Port(Ground.empty(), [Common])

self.count = self.ArgParameter(count)
Expand All @@ -178,7 +216,7 @@ def generate(self):
for led_i in range(self.get(self.count)):
led = self.led[str(led_i)] = self.Block(Neopixel())
self.connect(last_signal_pin, led.din)
self.connect(self.vdd, led.vdd)
self.connect(self.pwr, led.pwr)
self.connect(self.gnd, led.gnd)
last_signal_pin = led.dout
self.connect(self.dout, last_signal_pin)
Expand Down
Loading
Loading