velxio/frontend/src/components/velxio-components/FlipFlopElements.ts

106 lines
4.3 KiB
TypeScript
Raw Normal View History

feat: expand SPICE component catalog (fases 9 + 10) Adds 44 SPICE mappers, 58 custom metadata entries, and 12 visual Web Components covering logic gates, transistors, op-amps, regulators, sources, electromechanical parts and integrated-circuit packaging. Fase 9 — component catalog expansion ------------------------------------ - 7 logic gates (AND/OR/NAND/NOR/XOR/XNOR + NOT) as SPICE B-sources - 8 multi-input gates (AND/OR/NAND/NOR with 3 and 4 inputs) - 9 transistors: 5 BJTs (incl. PNP 2N3906/BC557) + 4 MOSFETs (incl. P-channel IRF9540/FQP27P06). NMOS refactored from Level=3 W=0.1 (hangs ngspice) to Level=1 with sane W/L - 5 op-amps: LM358, LM741, TL072, LM324 with per-chip saturation rails + opamp-ideal - 4 linear regulators (7805, 7812, 7905, LM317) with dropout - 3 batteries (9V, AA, coin-cell) with realistic ESR - Signal generator (sine / square / DC) - 2 Schottky diodes (1N5817, 1N5819) + photodiode (lux-driven current source) Fase 10 — electromechanical + ICs --------------------------------- - Relay (SPDT): coil + L + S-switch with native hysteresis + flyback diode, inverted-control trick for the NC contact - Optocouplers 4N25 and PC817 (LED + CCCS with CTR=0.5 / 1.0) - 7 74HC ICs as DIP-14 packages emitting 4 or 6 B-sources per component (first mapper pattern emitting multiple device cards) - 3 flip-flops (D, T, JK) — digital-sim only (edge detection is not representable in ngspice .op) - L293D dual H-bridge motor driver Infrastructure -------------- - scripts/component-overrides.json gains a _customComponents[] array that lets new Velxio-only parts survive metadata regeneration (previously applyOverrides() could only patch wokwi-elements components that had already been scanned) - scripts/generate-component-metadata.ts injects custom entries before the patch loop - New ComponentCategory values: 'logic', 'analog', 'electromech' - frontend/src/components/DynamicComponent.tsx PASSIVE tracing extended from just ['resistor','resistor-us'] to 9 two-terminal passives with per-part pin name maps - New CI workflow test-circuit.yml runs the sandbox on push/PR - frontend-tests.yml regenerates metadata and fails if committed JSON is stale - Documented 2 new ngspice gotchas in circuit-emulation-gotchas.md: unicode in netlist titles silently hangs the parser, and MOSFET Level=3 + W=0.1m causes .op to hang - 164/164 sandbox tests passing in ~9 s (was 88 pre-fase-9) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 06:44:18 +07:00
/**
* FlipFlopElements.ts Custom Web Components for edge-triggered flip-flops.
*
* Digital-simulation-only (no SPICE mapper real edge detection requires
* time-domain `ddt()` which doesn't work in `.op`).
*
* Tags defined:
* wokwi-flip-flop-d D-type (Q D on rising CLK)
* wokwi-flip-flop-t T-type (Q toggles when T=1 on rising CLK)
* wokwi-flip-flop-jk JK (hold/set/reset/toggle on rising CLK)
*
* Pin layouts (80 × 64 px):
* D-FF: D (0, 16) CLK (0, 48) Q (80, 16) Qbar (80, 48)
* T-FF: T (0, 16) CLK (0, 48) Q (80, 16) Qbar (80, 48)
* JK-FF: J (0, 12) K (0, 36) CLK (0, 56) Q (80, 16) Qbar (80, 48)
*/
const FILL = '#eef3fa';
const STROKE = '#2a2a2a';
const LEAD = '#555555';
const LABEL = '#333333';
const STYLE = ':host{display:inline-block;line-height:0}';
const W = 80;
const H = 64;
function ffSvg(inputLabels: Array<{ label: string; y: number; isClk?: boolean }>, partLabel: string): string {
const inputLeads = inputLabels.map(inp => `
<line x1="0" y1="${inp.y}" x2="14" y2="${inp.y}" stroke="${LEAD}" stroke-width="2"/>
${inp.isClk
? `<!-- Clock triangle (inverter-like marker) -->
<polygon points="14,${inp.y - 4} 20,${inp.y} 14,${inp.y + 4}" fill="none" stroke="${STROKE}" stroke-width="1"/>`
: ''}
<text x="2" y="${inp.y - 2}" font-family="sans-serif" font-size="6" fill="${LABEL}">${inp.label}</text>
`).join('');
return `
<style>${STYLE}</style>
<svg width="${W}" height="${H}" xmlns="http://www.w3.org/2000/svg">
<!-- Body -->
<rect x="14" y="6" width="${W - 28}" height="${H - 12}" rx="3" fill="${FILL}" stroke="${STROKE}" stroke-width="1.5"/>
${inputLeads}
<!-- Outputs -->
<line x1="${W - 14}" y1="16" x2="${W}" y2="16" stroke="${LEAD}" stroke-width="2"/>
<line x1="${W - 14}" y1="48" x2="${W}" y2="48" stroke="${LEAD}" stroke-width="2"/>
<text x="${W - 22}" y="14" font-family="sans-serif" font-size="6" fill="${LABEL}">Q</text>
<text x="${W - 26}" y="46" font-family="sans-serif" font-size="6" fill="${LABEL}"></text>
<!-- Part label -->
<text x="${W / 2}" y="${H / 2 + 4}" text-anchor="middle" font-family="sans-serif" font-size="10" fill="${LABEL}" font-weight="bold">${partLabel}</text>
</svg>`;
}
class DFlipFlopElement extends HTMLElement {
readonly pinInfo = [
{ name: 'D', x: 0, y: 16, number: 1, signals: [] as string[] },
{ name: 'CLK', x: 0, y: 48, number: 2, signals: [] as string[] },
{ name: 'Q', x: W, y: 16, number: 3, signals: [] as string[] },
{ name: 'Qbar', x: W, y: 48, number: 4, signals: [] as string[] },
];
constructor() {
super();
this.attachShadow({ mode: 'open' }).innerHTML = ffSvg(
[{ label: 'D', y: 16 }, { label: 'CLK', y: 48, isClk: true }],
'D-FF',
);
}
}
class TFlipFlopElement extends HTMLElement {
readonly pinInfo = [
{ name: 'T', x: 0, y: 16, number: 1, signals: [] as string[] },
{ name: 'CLK', x: 0, y: 48, number: 2, signals: [] as string[] },
{ name: 'Q', x: W, y: 16, number: 3, signals: [] as string[] },
{ name: 'Qbar', x: W, y: 48, number: 4, signals: [] as string[] },
];
constructor() {
super();
this.attachShadow({ mode: 'open' }).innerHTML = ffSvg(
[{ label: 'T', y: 16 }, { label: 'CLK', y: 48, isClk: true }],
'T-FF',
);
}
}
class JKFlipFlopElement extends HTMLElement {
readonly pinInfo = [
{ name: 'J', x: 0, y: 12, number: 1, signals: [] as string[] },
{ name: 'K', x: 0, y: 36, number: 2, signals: [] as string[] },
{ name: 'CLK', x: 0, y: 56, number: 3, signals: [] as string[] },
{ name: 'Q', x: W, y: 16, number: 4, signals: [] as string[] },
{ name: 'Qbar', x: W, y: 48, number: 5, signals: [] as string[] },
];
constructor() {
super();
this.attachShadow({ mode: 'open' }).innerHTML = ffSvg(
[{ label: 'J', y: 12 }, { label: 'K', y: 36 }, { label: 'CLK', y: 56, isClk: true }],
'JK-FF',
);
}
}
if (!customElements.get('wokwi-flip-flop-d')) customElements.define('wokwi-flip-flop-d', DFlipFlopElement);
if (!customElements.get('wokwi-flip-flop-t')) customElements.define('wokwi-flip-flop-t', TFlipFlopElement);
if (!customElements.get('wokwi-flip-flop-jk')) customElements.define('wokwi-flip-flop-jk', JKFlipFlopElement);
export {};