Files
diy-layout-creator/diylc/.claude/skills/add-component/SKILL.md
T
Bane Stojkovic 6f51c3ca42 agents setup
2026-09-04 15:31:00 +02:00

171 lines
8.3 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) { ... }
}
```
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.
## 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.