8.3 KiB
name, description
| name | description |
|---|---|
| add-component | 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
- Find the closest existing component and read it end to end. A new axial part follows
passive/Resistor.java; a new board followsboards/VeroBoard.java; a new schematic symbol followsAbstractSchematicLeadedSymbolsubclasses; a new jack followselectromechanical/. The new class should read like a sibling of that file. - 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. - Pick the right package —
passive,semiconductors,electromechanical,boards,guitar,tube,chassis,shapes,smd,connectivity,misc,autocreate. The package should match thecategoryin 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
/* ... 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;. @ComponentDescriptorneedsname,description,category,author,instanceNamePrefixandzOrderat minimum. SetenableCache = truefor anything whose drawing is expensive and does not depend on neighbouring components.- Appearance constants are
public staticand deliberately not final, so they can be overridden at runtime. Follow that.
Editable properties
- Annotate the getter with
@EditableProperty; a matchingsetXYZmust exist. The property name comes from the getter unless you passname. - Use
sortOrderwhen the order in the editor matters,defaultable = falsefor properties that must not be remembered as a user default, andvalidatorClass(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. (TheadditionalOptionsexample inEditableProperty'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)orfetchStroke(w, dash, phase, cap). Do not allocatenew BasicStroke(...)indraw(). - Honour
outlineModeand everyComponentState(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 intodraw().
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)andgetSectionNames(int)— internal conduction between points, needed by the netlist and analyzers.IGerberComponentSimpleorIGerberComponentCustomif 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 — seeAbstractTransparentComponent.getAlpha(),Resistor.getPowerNew(),AbstractFilmCapacitor.getVoltageNew(). - Fields added later arrive as
nullfrom old files; default them in the getter, not the constructor.
Verify
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.