Files
diy-layout-creator/diylc/.claude/skills/add-component/SKILL.md
T
Bane Stojkovic a743de4024 Controllers (#1080)
* initial

* more components

* fixes

* Update TreePanel.java

* fix boost converter

* Improve wrapping of component names

* Update ULN2003Driver.java

* Update NRF24L01Transceiver.java

* imp

* updates

* improvements

* more components

* arduino

* uno

* icon

* cleanup

* mega

* mega

* improveent

* nano

* esp boards

* esp

* esp32

* esp32

* Update ESP8266NodeMCU.java

* d1

* Update WemosD1Mini.java

* pi

* consolidate esp32

* pi pico updates

* pi zero

* Update RaspberryPiZero.java

* pi pico

* cleanup

* pi improvements

* consolidate pins

* antenna consolidation

* improve solder pads

* teensy

* headers

* teensy fix, test cleanup

* s3

* Update ESP32DevKit.java

* usb standardization

* pico w

* button standardization

* arduino improvements

* Update AbstractMakerBoard.java

* Update RaspberryPiZero.java

* Update RaspberryPi.java

* pi 3

* pi 5 improvements

* Update RaspberryPiZero.java

* fix tests

* esp pinout fix

* Update ESP32DevKit.java

* pin name improvements

* Create tier-1-modern-boards.md

* new versions

* pinouts

* nano improvements

* sanitize node names

* label improvements

* minima fixes

* arduino uno labels

* nano labels

* Update ArduinoNano.java

* mega

* mega

* comment out future stuff

* release prep

* Update RaspberryPiPico.java

* Refactor abstract class

* labeling improvement

* label offset fixes

* Update Teensy.java

* consolidate tests

* micro variants

* Update RaspberryPi.java

* displays tweaks

* display improvements

* neopix

* displays

* improvements

* led matrix

* strip

* led bar graph

* jewel

* tft

* tft

* Update maker-peripherals.md

* nokia

* led bar

* labels

* tft

* cleanup

* oled

* cleanup

* display enhancement

* displays

* neopixel

* displays

* displays

* new tft

* displays

* icon

* variants

* 2-digit 0.56 inch 7 segment

* Added ability to hide component categories in the component tree and toolbar from the Config menu

* led bar

* Update variants.xml

* color selection for bar graph

* Added 1.8" sd

* tft fixes

* Update WS2812BRing.java

* neopixel improvements

* netlist and node name improvements

* oled tweak

* cleanup

* mockito fix

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-10-09 09:38:09 +02:00

178 lines
8.7 KiB
Markdown

---
name: add-component
description: Add a new component type to the DIYLC library, or substantially modify an existing one. Use when asked to create a new part (resistor variant, board, jack, switch, pickup, symbol, shape), add editable properties to a component, change how a component draws or reports connectivity, or make a component rotatable/mirrorable.
---
# Adding a component to DIYLC
Components live in `diylc-library/src/main/java/org/diylc/components/`. They are discovered at
runtime by Reflections scanning `org.diylc` — **there is no registry to update**. A class becomes a
component by carrying `@ComponentDescriptor` and implementing `IDIYComponent` (in practice, by
extending one of the abstract bases).
## Before writing anything
1. **Find the closest existing component and read it end to end.** A new axial part follows
`passive/Resistor.java`; a new board follows `boards/VeroBoard.java`; a new schematic symbol
follows `AbstractSchematicLeadedSymbol` subclasses; a new jack follows `electromechanical/`.
The new class should read like a sibling of that file.
2. **Ask whether it is really a new class.** If the request is a revision or size variant of
something that already exists, prefer adding a `Version` (or similar enum) property to the
existing class over creating a parallel class. That keeps old project files loading and keeps the
toolbox from sprawling.
3. **Pick the right package** — `passive`, `semiconductors`, `electromechanical`, `boards`, `guitar`,
`tube`, `chassis`, `shapes`, `smd`, `connectivity`, `misc`, `autocreate`. The package should match
the `category` in the descriptor.
## Choosing a base class
| Base | Use for |
| --- | --- |
| `AbstractComponent<T>` | Anything that fits nothing else |
| `AbstractLabeledComponent<T>` | Adds name/value label rendering |
| `AbstractTransparentComponent<T>` | Anything with an alpha/transparency property |
| `AbstractLeadedComponent<T>` | Two-lead axial parts drawn between two points; implement `getDefaultLength()`, `getDefaultWidth()`, `getBodyShape()` |
| `AbstractRadialComponent<T>` | Radial (two leads out the same side) parts |
| `AbstractCurvedComponent<T>` | Curves and traces defined by control points |
| `AbstractAngledComponent<T>` | Parts placed by point and angle |
| `AbstractBoard` / `AbstractProtoBoard` / `AbstractVeroBoard` | Boards |
| `AbstractMultiPartComponent<T>` | Components made of several independently drawn sections |
| `Abstract3LegSymbol`, `AbstractDiodeSymbol`, `AbstractTubeSymbol`, `AbstractGuitarPickup`, `AbstractShape` | The obvious specialisations |
Extend the most specific base that fits. Do not reimplement behaviour a base already provides.
## The class
```java
/* ... GPL header copied verbatim from HEADER.txt ... */
package org.diylc.components.passive;
import ... // java.*, then third-party, then org.diylc.*
@ComponentDescriptor(name = "My Part", author = "Branislav Stojkovic", category = "Passive",
creationMethod = CreationMethod.POINT_BY_POINT, instanceNamePrefix = "X",
description = "Short sentence shown in the toolbox tooltip", zOrder = IDIYComponent.COMPONENT,
transformer = SimpleComponentTransformer.class, enableCache = true)
public class MyPart extends AbstractLeadedComponent<Resistance> {
private static final long serialVersionUID = 1L;
public static Size DEFAULT_WIDTH = new Size(1d / 2, SizeUnit.in);
public static Color BODY_COLOR = Color.decode("#82CFFD");
private Resistance value = null;
public MyPart() {
super();
this.bodyColor = BODY_COLOR;
}
@EditableProperty(validatorClass = PositiveMeasureValidator.class)
public Resistance getValue() {
return value;
}
public void setValue(Resistance value) {
this.value = value;
}
@Override
public void drawIcon(Graphics2D g2d, int width, int height) { ... }
public enum Version { // nested types last, just before the closing brace
...
}
}
```
Requirements the framework enforces:
- **No-argument public constructor**, and the class must be serializable with a
`private static final long serialVersionUID = 1L;`.
- `@ComponentDescriptor` needs `name`, `description`, `category`, `author`, `instanceNamePrefix` and
`zOrder` at minimum. Set `enableCache = true` for anything whose drawing is expensive and does not
depend on neighbouring components.
- Appearance constants are `public static` and deliberately **not final**, so they can be overridden
at runtime. Follow that.
- Nested types — a `Version` enum, a package or size enum, a small helper class — go at the **bottom
of the file**, after every method, so the outer class reads top to bottom without a block of enum
declarations in the middle of it.
## Editable properties
- Annotate the **getter** with `@EditableProperty`; a matching `setXYZ` must exist. The property name
comes from the getter unless you pass `name`.
- Use `sortOrder` when the order in the editor matters, `defaultable = false` for properties that
must not be remembered as a user default, and `validatorClass` (e.g.
`PositiveMeasureValidator`, `PositiveNonZeroMeasureValidator`) where a raw value would be invalid.
- Use the measure types from `org.diylc.core.measures` (`Size`, `Resistance`, `Capacitance`,
`Voltage`, `Power`, `Percentage`) rather than raw numbers — they carry units and format themselves.
- For a runtime-computed list of allowed values, add `@DynamicList(availableValueFunction = "getXyz")`
alongside `@EditableProperty`. (The `additionalOptions` example in `EditableProperty`'s own Javadoc
is stale — that attribute no longer exists.)
- Override `getValueForDisplay()` when the canvas label should combine several properties.
## Drawing
`draw(Graphics2D, ComponentState, boolean outlineMode, Project, IDrawingObserver)` runs on every
repaint, for every instance. Treat it as a hot path.
- Fetch strokes from `ObjectCache.getInstance().fetchBasicStroke(w)`, `fetchZoomableStroke(w)` or
`fetchStroke(w, dash, phase, cap)`. Do not allocate `new BasicStroke(...)` in `draw()`.
- Honour `outlineMode` and every `ComponentState` (`NORMAL`, `SELECTED`, `DRAGGING`) the way the
neighbouring components do.
- `drawIcon(g2d, width, height)` is a separate, hand-drawn miniature for the toolbox — it is not a
scaled call into `draw()`.
### Connectivity
If the component conducts, wrap the conductive drawing in
`drawingObserver.startTrackingContinuityArea(true)` / `stopTrackingContinuityArea()`, exactly as the
comparable component does. Getting this wrong silently corrupts continuity checks and the netlist
without any visible rendering difference. `startTracking()` / `stopTracking()` control whether
drawing contributes to the component's clickable area.
Also implement, where relevant:
- `getControlPointCount()`, `getControlPoint(int)`, `setControlPoint(Point2D, int)`,
`isControlPointSticky(int)`, `getControlPointNodeName(int)` — the connection points.
- `getInternalLinkName(int, int)` and `getSectionNames(int)` — internal conduction between points,
needed by the netlist and analyzers.
- `IGerberComponentSimple` or `IGerberComponentCustom` if the part should appear in Gerber output.
## Rotation and mirroring
These live in an `IComponentTransformer` under
`diylc-library/src/main/java/org/diylc/components/transform/`, referenced from the `transformer`
attribute of `@ComponentDescriptor` — never inside the component itself. Reuse
`SimpleComponentTransformer` or an existing transformer where the behaviour matches; write a new one
only when the part rotates or mirrors differently from everything that exists.
## Datasheets and variants
If the part has a catalogue of real-world values and sizes, set `enableDatasheet = true` and
`datasheetCreationStepCount`, and add the file under
`diylc-library/src/main/resources/datasheets/`. Follow the format of an existing `.datasheet` file
and add a spot-check to `DatasheetServiceTest`.
## Backward compatibility
Once a component ships, its serialized shape is a contract:
- Never rename, remove, or retype a serialized field, and never rename or move the class.
- To replace a property, keep the old field, mark the old accessors `@Deprecated`, add the new field,
and migrate lazily in the new getter — see `AbstractTransparentComponent.getAlpha()`,
`Resistor.getPowerNew()`, `AbstractFilmCapacitor.getVoltageNew()`.
- Fields added later arrive as `null` from old files; default them in the getter, not the
constructor.
## Verify
```bash
mvn -q -pl diylc-library -am test
```
Then state plainly that the component still needs a visual check in the running app — automated tests
do not cover component appearance. Offer a regression-suite run (see the `regression-test` skill) for
any change to an existing component's drawing or serialization.