diff --git a/diylc/diylc-library/src/main/java/org/diylc/components/displays/WS2812BStick.java b/diylc/diylc-library/src/main/java/org/diylc/components/displays/WS2812BStick.java
new file mode 100644
index 00000000..22296ab2
--- /dev/null
+++ b/diylc/diylc-library/src/main/java/org/diylc/components/displays/WS2812BStick.java
@@ -0,0 +1,200 @@
+/*
+ *
+ * DIY Layout Creator (DIYLC).
+ * Copyright (c) 2009-2025 held jointly by the individual authors.
+ *
+ * This file is part of DIYLC.
+ *
+ * DIYLC is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DIYLC is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with DIYLC. If not, see .
+ *
+ */
+package org.diylc.components.displays;
+
+import java.awt.Color;
+import java.awt.Composite;
+import java.awt.Graphics2D;
+import java.awt.Shape;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Ellipse2D;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.awt.geom.RoundRectangle2D;
+
+import org.diylc.awt.StringUtils;
+import org.diylc.common.HorizontalAlignment;
+import org.diylc.common.ObjectCache;
+import org.diylc.common.Orientation;
+import org.diylc.common.VerticalAlignment;
+import org.diylc.components.AbstractMakerBoard;
+import org.diylc.core.ComponentState;
+import org.diylc.core.IDIYComponent;
+import org.diylc.core.IDrawingObserver;
+import org.diylc.core.Project;
+import org.diylc.core.annotations.BomPolicy;
+import org.diylc.core.annotations.ComponentDescriptor;
+import org.diylc.core.annotations.KeywordPolicy;
+import org.diylc.core.measures.Size;
+import org.diylc.core.measures.SizeUnit;
+import org.diylc.utils.Constants;
+
+@ComponentDescriptor(name = "NeoPixel Stick (8x WS2812B)", category = "Displays & Outputs",
+ author = "Branislav Stojkovic", description = "8-LED Addressable RGB WS2812B NeoPixel Stick",
+ instanceNamePrefix = "LED", zOrder = IDIYComponent.COMPONENT,
+ bomPolicy = BomPolicy.SHOW_ONLY_TYPE_NAME, keywordPolicy = KeywordPolicy.SHOW_TYPE_NAME,
+ enableCache = true)
+public class WS2812BStick extends AbstractMakerBoard {
+
+ private static final long serialVersionUID = 1L;
+
+ public static Color NEO_BLACK = Color.decode("#111111");
+ public static Color LED_PACKAGE = Color.decode("#FDFEFE");
+ public static Color LED_DIFFUSER = Color.decode("#EAECEE");
+
+ public static Size BOARD_WIDTH = new Size(53.0d, SizeUnit.mm);
+ public static Size BOARD_HEIGHT = new Size(10.2d, SizeUnit.mm);
+
+ public WS2812BStick() {
+ super();
+ this.bodyColor = NEO_BLACK;
+ updateControlPoints();
+ }
+
+ @Override
+ protected void updateControlPoints() {
+ Point2D firstPoint = controlPoints[0];
+ double spacing = PIN_SPACING.convertToPixels();
+ double w = BOARD_WIDTH.convertToPixels();
+
+ // 8 pins total:
+ // Left input header (4 pins): 0 = DIN, 1 = +5V, 2 = GND, 3 = GND
+ // Right output header (4 pins): 4 = DOUT, 5 = +5V, 6 = GND, 7 = GND
+ double[][] relativeOffsets = new double[][] {
+ { 0, 0 },
+ { 0, spacing },
+ { 0, spacing * 2 },
+ { 0, spacing * 3 },
+ { w - 20, 0 },
+ { w - 20, spacing },
+ { w - 20, spacing * 2 },
+ { w - 20, spacing * 3 }
+ };
+
+ rotatePoints(firstPoint, relativeOffsets);
+ }
+
+ @Override
+ public Shape getBodyShape() {
+ Point2D p0 = controlPoints[0];
+ double x = p0.getX();
+ double y = p0.getY();
+ double boardW = BOARD_WIDTH.convertToPixels();
+ double boardH = BOARD_HEIGHT.convertToPixels();
+ return new RoundRectangle2D.Double(x - 10, y - 10, boardW, boardH, 4, 4);
+ }
+
+ @Override
+ public void draw(Graphics2D g2d, ComponentState componentState, boolean outlineMode, Project project,
+ IDrawingObserver drawingObserver) {
+ if (checkPointsClipped(g2d.getClip())) {
+ return;
+ }
+
+ Point2D p0 = controlPoints[0];
+ double x = p0.getX();
+ double y = p0.getY();
+
+ AffineTransform oldTx = g2d.getTransform();
+ if (orientation != Orientation.DEFAULT) {
+ g2d.rotate(orientation.toRadians(), x, y);
+ }
+
+ double boardW = BOARD_WIDTH.convertToPixels();
+ double boardH = BOARD_HEIGHT.convertToPixels();
+ double boardX = x - 10;
+ double boardY = y - 10;
+
+ Shape boardShape = getBodyShape();
+
+ Composite oldComposite = applyAlpha(g2d, componentState);
+
+ drawingObserver.startTracking();
+ g2d.setColor(outlineMode ? Constants.TRANSPARENT_COLOR : bodyColor);
+ g2d.fill(boardShape);
+ drawingObserver.stopTracking();
+
+ g2d.setColor(getFinalBorderColor(componentState, outlineMode));
+ g2d.setStroke(ObjectCache.getInstance().fetchBasicStroke(1.5f));
+ g2d.draw(boardShape);
+
+ if (!outlineMode) {
+ // 8x 5050 SMD WS2812B RGB LEDs
+ double startX = boardX + 45;
+ double ledPitch = (boardW - 90) / 7.0;
+
+ for (int i = 0; i < 8; i++) {
+ double lx = startX + i * ledPitch;
+ double ly = boardY + boardH / 2.0;
+
+ // 5050 White Package
+ g2d.setColor(LED_PACKAGE);
+ g2d.fill(new RoundRectangle2D.Double(lx - 16, ly - 16, 32, 32, 3, 3));
+ g2d.setColor(Color.LIGHT_GRAY);
+ g2d.draw(new RoundRectangle2D.Double(lx - 16, ly - 16, 32, 32, 3, 3));
+
+ // Circular milky phosphor/lens
+ g2d.setColor(LED_DIFFUSER);
+ g2d.fill(new Ellipse2D.Double(lx - 11, ly - 11, 22, 22));
+
+ // Internal tiny IC dot
+ g2d.setColor(Color.decode("#333333"));
+ g2d.fill(new Rectangle2D.Double(lx - 3, ly - 3, 6, 6));
+
+ // Bypass capacitor
+ if (i < 7) {
+ g2d.setColor(Color.decode("#8B6508"));
+ g2d.fill(new Rectangle2D.Double(lx + ledPitch / 2.0 - 4, ly - 14, 8, 4));
+ }
+ }
+
+ // Silk Screen Pin Labels
+ g2d.setColor(Color.WHITE);
+ g2d.setFont(SILK_FONT_SMALL);
+ StringUtils.drawCenteredText(g2d, "DI", boardX + 22, y, HorizontalAlignment.LEFT, VerticalAlignment.CENTER);
+ StringUtils.drawCenteredText(g2d, "5V", boardX + 22, y + PIN_SPACING.convertToPixels(), HorizontalAlignment.LEFT, VerticalAlignment.CENTER);
+ StringUtils.drawCenteredText(g2d, "GND", boardX + 22, y + PIN_SPACING.convertToPixels() * 2, HorizontalAlignment.LEFT, VerticalAlignment.CENTER);
+
+ StringUtils.drawCenteredText(g2d, "DO", boardX + boardW - 22, y, HorizontalAlignment.RIGHT, VerticalAlignment.CENTER);
+ StringUtils.drawCenteredText(g2d, "5V", boardX + boardW - 22, y + PIN_SPACING.convertToPixels(), HorizontalAlignment.RIGHT, VerticalAlignment.CENTER);
+ StringUtils.drawCenteredText(g2d, "GND", boardX + boardW - 22, y + PIN_SPACING.convertToPixels() * 2, HorizontalAlignment.RIGHT, VerticalAlignment.CENTER);
+ }
+
+ g2d.setTransform(oldTx);
+
+ // Draw input and output header pins
+ drawPins(g2d, 0, controlPoints.length, false, outlineMode, drawingObserver);
+
+ g2d.setComposite(oldComposite);
+ }
+
+ @Override
+ public void drawIcon(Graphics2D g2d, int width, int height) {
+ g2d.setColor(NEO_BLACK);
+ g2d.fill(new RoundRectangle2D.Double(2, 10, width - 4, height - 20, 2, 2));
+ Color[] rainbow = new Color[] { Color.RED, Color.YELLOW, Color.GREEN, Color.CYAN, Color.MAGENTA };
+ for (int i = 0; i < 5; i++) {
+ g2d.setColor(rainbow[i]);
+ g2d.fill(new Ellipse2D.Double(4 + i * 6, height / 2 - 2, 4, 4));
+ }
+ }
+}
diff --git a/diylc/diylc-library/src/main/java/org/diylc/components/modules/AnalogJoystickKY023.java b/diylc/diylc-library/src/main/java/org/diylc/components/modules/AnalogJoystickKY023.java
new file mode 100644
index 00000000..d0a2a3e8
--- /dev/null
+++ b/diylc/diylc-library/src/main/java/org/diylc/components/modules/AnalogJoystickKY023.java
@@ -0,0 +1,187 @@
+/*
+ *
+ * DIY Layout Creator (DIYLC).
+ * Copyright (c) 2009-2025 held jointly by the individual authors.
+ *
+ * This file is part of DIYLC.
+ *
+ * DIYLC is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DIYLC is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with DIYLC. If not, see .
+ *
+ */
+package org.diylc.components.modules;
+
+import java.awt.Color;
+import java.awt.Composite;
+import java.awt.Graphics2D;
+import java.awt.Shape;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Ellipse2D;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.awt.geom.RoundRectangle2D;
+
+import org.diylc.awt.StringUtils;
+import org.diylc.common.HorizontalAlignment;
+import org.diylc.common.ObjectCache;
+import org.diylc.common.Orientation;
+import org.diylc.common.VerticalAlignment;
+import org.diylc.components.AbstractMakerBoard;
+import org.diylc.core.ComponentState;
+import org.diylc.core.IDIYComponent;
+import org.diylc.core.IDrawingObserver;
+import org.diylc.core.Project;
+import org.diylc.core.annotations.BomPolicy;
+import org.diylc.core.annotations.ComponentDescriptor;
+import org.diylc.core.annotations.KeywordPolicy;
+import org.diylc.core.measures.Size;
+import org.diylc.core.measures.SizeUnit;
+import org.diylc.utils.Constants;
+
+@ComponentDescriptor(name = "Analog Joystick (KY-023)", category = "Modules & Breakouts",
+ author = "Branislav Stojkovic", description = "KY-023 Dual-Axis PS2 Thumb Joystick Module with Push Switch",
+ instanceNamePrefix = "JOY", zOrder = IDIYComponent.COMPONENT,
+ bomPolicy = BomPolicy.SHOW_ONLY_TYPE_NAME, keywordPolicy = KeywordPolicy.SHOW_TYPE_NAME,
+ enableCache = true)
+public class AnalogJoystickKY023 extends AbstractMakerBoard {
+
+ private static final long serialVersionUID = 1L;
+
+ public static Color JOY_PCB = Color.decode("#1B2631");
+ public static Color POT_GREEN = Color.decode("#27AE60");
+ public static Color THUMB_CAP = Color.decode("#17202A");
+
+ public static Size BOARD_WIDTH = new Size(34.0d, SizeUnit.mm);
+ public static Size BOARD_HEIGHT = new Size(26.0d, SizeUnit.mm);
+
+ public AnalogJoystickKY023() {
+ super();
+ this.bodyColor = JOY_PCB;
+ updateControlPoints();
+ }
+
+ @Override
+ protected void updateControlPoints() {
+ Point2D firstPoint = controlPoints[0];
+ double spacing = PIN_SPACING.convertToPixels();
+
+ // 5-pin 0.1" header: GND, +5V, VRx, VRy, SW
+ double[][] relativeOffsets = new double[5][2];
+ for (int i = 0; i < 5; i++) {
+ relativeOffsets[i][0] = 0;
+ relativeOffsets[i][1] = i * spacing;
+ }
+
+ rotatePoints(firstPoint, relativeOffsets);
+ }
+
+ @Override
+ public Shape getBodyShape() {
+ Point2D p0 = controlPoints[0];
+ double x = p0.getX();
+ double y = p0.getY();
+ double boardW = BOARD_WIDTH.convertToPixels();
+ double boardH = BOARD_HEIGHT.convertToPixels();
+ double boardX = x - boardW + 20;
+ double boardY = y - (boardH - 4 * PIN_SPACING.convertToPixels()) / 2.0;
+ return new RoundRectangle2D.Double(boardX, boardY, boardW, boardH, 6, 6);
+ }
+
+ @Override
+ public void draw(Graphics2D g2d, ComponentState componentState, boolean outlineMode, Project project,
+ IDrawingObserver drawingObserver) {
+ if (checkPointsClipped(g2d.getClip())) {
+ return;
+ }
+
+ Point2D p0 = controlPoints[0];
+ double x = p0.getX();
+ double y = p0.getY();
+
+ AffineTransform oldTx = g2d.getTransform();
+ if (orientation != Orientation.DEFAULT) {
+ g2d.rotate(orientation.toRadians(), x, y);
+ }
+
+ double boardW = BOARD_WIDTH.convertToPixels();
+ double boardH = BOARD_HEIGHT.convertToPixels();
+ double boardX = x - boardW + 20;
+ double boardY = y - (boardH - 4 * PIN_SPACING.convertToPixels()) / 2.0;
+
+ Shape boardShape = getBodyShape();
+
+ Composite oldComposite = applyAlpha(g2d, componentState);
+
+ drawingObserver.startTracking();
+ g2d.setColor(outlineMode ? Constants.TRANSPARENT_COLOR : bodyColor);
+ g2d.fill(boardShape);
+ drawingObserver.stopTracking();
+
+ g2d.setColor(getFinalBorderColor(componentState, outlineMode));
+ g2d.setStroke(ObjectCache.getInstance().fetchBasicStroke(1.5f));
+ g2d.draw(boardShape);
+
+ if (!outlineMode) {
+ // 4 Mounting Holes in corners
+ drawMountingHole(g2d, boardX + 16, boardY + 16, 16);
+ drawMountingHole(g2d, boardX + 16, boardY + boardH - 16, 16);
+ drawMountingHole(g2d, boardX + boardW - 40, boardY + 16, 16);
+ drawMountingHole(g2d, boardX + boardW - 40, boardY + boardH - 16, 16);
+
+ // Metal Gimbal Frame (Center)
+ double cx = boardX + (boardW - 30) / 2.0;
+ double cy = boardY + boardH / 2.0;
+ g2d.setColor(Color.decode("#7F8C8D"));
+ g2d.fill(new RoundRectangle2D.Double(cx - 50, cy - 50, 100, 100, 8, 8));
+
+ // Potentiometer cases (X and Y side blocks)
+ g2d.setColor(POT_GREEN);
+ g2d.fill(new RoundRectangle2D.Double(cx - 56, cy - 30, 12, 60, 3, 3));
+ g2d.fill(new RoundRectangle2D.Double(cx - 30, cy + 44, 60, 12, 3, 3));
+
+ // Circular Rubber Thumbstick Cap
+ g2d.setColor(THUMB_CAP);
+ g2d.fill(new Ellipse2D.Double(cx - 42, cy - 42, 84, 84));
+ g2d.setColor(Color.decode("#2C3E50"));
+ g2d.fill(new Ellipse2D.Double(cx - 30, cy - 30, 60, 60));
+ g2d.setColor(Color.decode("#111111"));
+ g2d.fill(new Ellipse2D.Double(cx - 20, cy - 20, 40, 40));
+
+ // Silk Screen Pin Labels
+ g2d.setColor(Color.WHITE);
+ g2d.setFont(SILK_FONT_SMALL);
+ String[] labels = new String[] { "GND", "+5V", "VRx", "VRy", "SW" };
+ double spacing = PIN_SPACING.convertToPixels();
+ for (int i = 0; i < 5; i++) {
+ StringUtils.drawCenteredText(g2d, labels[i], boardX + boardW - 32, y + i * spacing, HorizontalAlignment.RIGHT, VerticalAlignment.CENTER);
+ }
+ }
+
+ g2d.setTransform(oldTx);
+
+ // Draw 5 header pins
+ drawPins(g2d, 0, controlPoints.length, false, outlineMode, drawingObserver);
+
+ g2d.setComposite(oldComposite);
+ }
+
+ @Override
+ public void drawIcon(Graphics2D g2d, int width, int height) {
+ g2d.setColor(JOY_PCB);
+ g2d.fill(new RoundRectangle2D.Double(2, 2, width - 4, height - 4, 3, 3));
+ g2d.setColor(THUMB_CAP);
+ g2d.fill(new Ellipse2D.Double(width / 2.0 - 10, height / 2.0 - 10, 20, 20));
+ g2d.setColor(Color.BLACK);
+ g2d.fill(new Ellipse2D.Double(width / 2.0 - 5, height / 2.0 - 5, 10, 10));
+ }
+}
diff --git a/diylc/diylc-library/src/main/java/org/diylc/components/modules/HC05Bluetooth.java b/diylc/diylc-library/src/main/java/org/diylc/components/modules/HC05Bluetooth.java
new file mode 100644
index 00000000..be6ea20e
--- /dev/null
+++ b/diylc/diylc-library/src/main/java/org/diylc/components/modules/HC05Bluetooth.java
@@ -0,0 +1,195 @@
+/*
+ *
+ * DIY Layout Creator (DIYLC).
+ * Copyright (c) 2009-2025 held jointly by the individual authors.
+ *
+ * This file is part of DIYLC.
+ *
+ * DIYLC is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DIYLC is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with DIYLC. If not, see .
+ *
+ */
+package org.diylc.components.modules;
+
+import java.awt.Color;
+import java.awt.Composite;
+import java.awt.Graphics2D;
+import java.awt.Shape;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Ellipse2D;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.awt.geom.RoundRectangle2D;
+
+import org.diylc.awt.StringUtils;
+import org.diylc.common.HorizontalAlignment;
+import org.diylc.common.ObjectCache;
+import org.diylc.common.Orientation;
+import org.diylc.common.VerticalAlignment;
+import org.diylc.components.AbstractMakerBoard;
+import org.diylc.core.ComponentState;
+import org.diylc.core.IDIYComponent;
+import org.diylc.core.IDrawingObserver;
+import org.diylc.core.Project;
+import org.diylc.core.annotations.BomPolicy;
+import org.diylc.core.annotations.ComponentDescriptor;
+import org.diylc.core.annotations.KeywordPolicy;
+import org.diylc.core.measures.Size;
+import org.diylc.core.measures.SizeUnit;
+import org.diylc.utils.Constants;
+
+@ComponentDescriptor(name = "Bluetooth Module (HC-05 / HC-06)", category = "Modules & Breakouts",
+ author = "Branislav Stojkovic", description = "HC-05 / HC-06 Serial Bluetooth SPP Module Breakout",
+ instanceNamePrefix = "BT", zOrder = IDIYComponent.COMPONENT,
+ bomPolicy = BomPolicy.SHOW_ONLY_TYPE_NAME, keywordPolicy = KeywordPolicy.SHOW_TYPE_NAME,
+ enableCache = true)
+public class HC05Bluetooth extends AbstractMakerBoard {
+
+ private static final long serialVersionUID = 1L;
+
+ public static Color BT_BLUE = Color.decode("#1B4F72");
+ public static Color DAUGHTER_BOARD = Color.decode("#154360");
+
+ public static Size BOARD_WIDTH = new Size(37.0d, SizeUnit.mm);
+ public static Size BOARD_HEIGHT = new Size(16.0d, SizeUnit.mm);
+
+ public HC05Bluetooth() {
+ super();
+ this.bodyColor = BT_BLUE;
+ updateControlPoints();
+ }
+
+ @Override
+ protected void updateControlPoints() {
+ Point2D firstPoint = controlPoints[0];
+ double spacing = PIN_SPACING.convertToPixels();
+
+ // 6-pin 0.1" header: STATE, RXD, TXD, GND, VCC, EN
+ double[][] relativeOffsets = new double[6][2];
+ for (int i = 0; i < 6; i++) {
+ relativeOffsets[i][0] = 0;
+ relativeOffsets[i][1] = i * spacing;
+ }
+
+ rotatePoints(firstPoint, relativeOffsets);
+ }
+
+ @Override
+ public Shape getBodyShape() {
+ Point2D p0 = controlPoints[0];
+ double x = p0.getX();
+ double y = p0.getY();
+ double boardW = BOARD_WIDTH.convertToPixels();
+ double boardH = BOARD_HEIGHT.convertToPixels();
+ double boardX = x - boardW + 20;
+ double boardY = y - (boardH - 5 * PIN_SPACING.convertToPixels()) / 2.0;
+ return new RoundRectangle2D.Double(boardX, boardY, boardW, boardH, 4, 4);
+ }
+
+ @Override
+ public void draw(Graphics2D g2d, ComponentState componentState, boolean outlineMode, Project project,
+ IDrawingObserver drawingObserver) {
+ if (checkPointsClipped(g2d.getClip())) {
+ return;
+ }
+
+ Point2D p0 = controlPoints[0];
+ double x = p0.getX();
+ double y = p0.getY();
+
+ AffineTransform oldTx = g2d.getTransform();
+ if (orientation != Orientation.DEFAULT) {
+ g2d.rotate(orientation.toRadians(), x, y);
+ }
+
+ double boardW = BOARD_WIDTH.convertToPixels();
+ double boardH = BOARD_HEIGHT.convertToPixels();
+ double boardX = x - boardW + 20;
+ double boardY = y - (boardH - 5 * PIN_SPACING.convertToPixels()) / 2.0;
+
+ Shape boardShape = getBodyShape();
+
+ Composite oldComposite = applyAlpha(g2d, componentState);
+
+ drawingObserver.startTracking();
+ g2d.setColor(outlineMode ? Constants.TRANSPARENT_COLOR : bodyColor);
+ g2d.fill(boardShape);
+ drawingObserver.stopTracking();
+
+ g2d.setColor(getFinalBorderColor(componentState, outlineMode));
+ g2d.setStroke(ObjectCache.getInstance().fetchBasicStroke(1.5f));
+ g2d.draw(boardShape);
+
+ if (!outlineMode) {
+ // Bluetooth Daughterboard (HC-05 Core Module)
+ double dbX = boardX + 15;
+ double dbY = boardY + 12;
+ double dbW = boardW - 55;
+ double dbH = boardH - 24;
+
+ g2d.setColor(DAUGHTER_BOARD);
+ g2d.fill(new RoundRectangle2D.Double(dbX, dbY, dbW, dbH, 3, 3));
+ g2d.setColor(Color.decode("#2E86C1"));
+ g2d.draw(new RoundRectangle2D.Double(dbX, dbY, dbW, dbH, 3, 3));
+
+ // Metal RF Shield on daughterboard
+ g2d.setColor(METAL_SHIELD_COLOR);
+ g2d.fill(new RoundRectangle2D.Double(dbX + 15, dbY + 10, dbW - 40, dbH - 20, 3, 3));
+ g2d.setColor(METAL_SHIELD_BORDER);
+ g2d.draw(new RoundRectangle2D.Double(dbX + 15, dbY + 10, dbW - 40, dbH - 20, 3, 3));
+ g2d.setColor(Color.decode("#555555"));
+ g2d.setFont(SILK_FONT_SMALL);
+ StringUtils.drawCenteredText(g2d, "HC-05", dbX + 15 + (dbW - 40) / 2.0, dbY + dbH / 2.0, HorizontalAlignment.CENTER, VerticalAlignment.CENTER);
+
+ // Gold PCB Antenna on left tip of daughterboard
+ g2d.setColor(Color.decode("#D4AC0D"));
+ g2d.fillRect((int)(dbX + 4), (int)(dbY + 8), 8, (int)(dbH - 16));
+
+ // Tactile Key/EN Push Button (Top Right)
+ g2d.setColor(Color.LIGHT_GRAY);
+ g2d.fill(new RoundRectangle2D.Double(boardX + boardW - 35, boardY + 10, 14, 14, 2, 2));
+ g2d.setColor(Color.DARK_GRAY);
+ g2d.fill(new Ellipse2D.Double(boardX + boardW - 32, boardY + 13, 8, 8));
+
+ // Status LED (Blue, Bottom Right)
+ g2d.setColor(Color.CYAN);
+ g2d.fill(new RoundRectangle2D.Double(boardX + boardW - 34, boardY + boardH - 24, 12, 10, 2, 2));
+
+ // Silk Screen Pin Labels
+ g2d.setColor(Color.WHITE);
+ g2d.setFont(SILK_FONT_SMALL);
+ String[] labels = new String[] { "STATE", "RXD", "TXD", "GND", "VCC", "EN" };
+ double spacing = PIN_SPACING.convertToPixels();
+ for (int i = 0; i < 6; i++) {
+ StringUtils.drawCenteredText(g2d, labels[i], boardX + boardW - 24, y + i * spacing, HorizontalAlignment.RIGHT, VerticalAlignment.CENTER);
+ }
+ }
+
+ g2d.setTransform(oldTx);
+
+ // Draw 6 header pins
+ drawPins(g2d, 0, controlPoints.length, false, outlineMode, drawingObserver);
+
+ g2d.setComposite(oldComposite);
+ }
+
+ @Override
+ public void drawIcon(Graphics2D g2d, int width, int height) {
+ g2d.setColor(BT_BLUE);
+ g2d.fill(new RoundRectangle2D.Double(2, 4, width - 4, height - 8, 3, 3));
+ g2d.setColor(METAL_SHIELD_COLOR);
+ g2d.fillRect(6, height / 2 - 4, 10, 8);
+ g2d.setColor(Color.CYAN);
+ g2d.fill(new Ellipse2D.Double(width - 10, height / 2 - 3, 6, 6));
+ }
+}
diff --git a/diylc/diylc-library/src/main/java/org/diylc/components/modules/L298NMotorDriver.java b/diylc/diylc-library/src/main/java/org/diylc/components/modules/L298NMotorDriver.java
index 82fbd6c4..64d38206 100644
--- a/diylc/diylc-library/src/main/java/org/diylc/components/modules/L298NMotorDriver.java
+++ b/diylc/diylc-library/src/main/java/org/diylc/components/modules/L298NMotorDriver.java
@@ -16,7 +16,7 @@
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
- * along with DIYLC. If not, see .
+ * along with DIYLC. If not, see .
*
*/
package org.diylc.components.modules;
@@ -89,33 +89,30 @@ public class L298NMotorDriver extends AbstractMakerBoard {
@Override
protected void updateControlPoints() {
Point2D firstPoint = controlPoints[0];
- double termSpacing = new Size(5.08d, SizeUnit.mm).convertToPixels(); // ~40px
+ double termSpacing = new Size(5.08d, SizeUnit.mm).convertToPixels(); // 40px
double pinSpacing = PIN_SPACING.convertToPixels(); // 20px
+ double boardSizePx = BOARD_SIZE.convertToPixels();
+ double boardX = -45;
- double[][] relativeOffsets = new double[13][2];
-
- // Power screw terminal (0..2) at bottom-left
- for (int i = 0; i < 3; i++) {
- relativeOffsets[i][0] = i * termSpacing;
- relativeOffsets[i][1] = 0;
- }
- // Motor A terminal (3..4) on left edge
- double leftX = -40;
- double leftY = -120;
- relativeOffsets[3] = new double[] {leftX, leftY};
- relativeOffsets[4] = new double[] {leftX, leftY - termSpacing};
-
- // Motor B terminal (5..6) on right edge
- double rightX = 240;
- relativeOffsets[5] = new double[] {rightX, leftY};
- relativeOffsets[6] = new double[] {rightX, leftY - termSpacing};
-
- // Logic header (7..12) at bottom-right
- double logicX = 120;
- for (int i = 0; i < 6; i++) {
- relativeOffsets[7 + i][0] = logicX + i * pinSpacing;
- relativeOffsets[7 + i][1] = 0;
- }
+ double[][] relativeOffsets = new double[][] {
+ // Power screw terminal (0..2) at bottom-left: 12V, GND, 5V
+ { 0, 0 },
+ { termSpacing, 0 },
+ { termSpacing * 2, 0 },
+ // Motor A terminal (3..4) on left edge: OUT1, OUT2
+ { boardX + 22.5, -140 },
+ { boardX + 22.5, -180 },
+ // Motor B terminal (5..6) on right edge: OUT3, OUT4
+ { boardX + boardSizePx - 22.5, -140 },
+ { boardX + boardSizePx - 22.5, -180 },
+ // Logic header (7..12) at bottom-right: ENA, IN1, IN2, IN3, IN4, ENB
+ { 140, 0 },
+ { 140 + pinSpacing, 0 },
+ { 140 + pinSpacing * 2, 0 },
+ { 140 + pinSpacing * 3, 0 },
+ { 140 + pinSpacing * 4, 0 },
+ { 140 + pinSpacing * 5, 0 }
+ };
rotatePoints(firstPoint, relativeOffsets);
}
@@ -126,8 +123,8 @@ public class L298NMotorDriver extends AbstractMakerBoard {
double x = p0.getX();
double y = p0.getY();
double boardSizePx = BOARD_SIZE.convertToPixels();
- double boardX = x - 55;
- double boardY = y - boardSizePx + 20;
+ double boardX = x - 45;
+ double boardY = y - boardSizePx + 25;
return new RoundRectangle2D.Double(boardX, boardY, boardSizePx, boardSizePx, 10, 10);
}
@@ -148,8 +145,8 @@ public class L298NMotorDriver extends AbstractMakerBoard {
}
double boardSizePx = BOARD_SIZE.convertToPixels();
- double boardX = x - 55;
- double boardY = y - boardSizePx + 20;
+ double boardX = x - 45;
+ double boardY = y - boardSizePx + 25;
Shape boardShape = getBodyShape();
@@ -192,17 +189,27 @@ public class L298NMotorDriver extends AbstractMakerBoard {
// Green screw terminal block bodies
g2d.setColor(SCREW_TERMINAL_COLOR);
- // Power terminal
- g2d.fill(new RoundRectangle2D.Double(x - 15, y - 25, 110, 35, 3, 3));
- // Motor A terminal
- g2d.fill(new RoundRectangle2D.Double(x - 55, y - 175, 35, 75, 3, 3));
- // Motor B terminal
- g2d.fill(new RoundRectangle2D.Double(x + 225, y - 175, 35, 75, 3, 3));
+ // Power terminal (3 pins)
+ g2d.fill(new RoundRectangle2D.Double(x - 15, y - 25, 120, 35, 3, 3));
+ // Motor A terminal (2 pins)
+ g2d.fill(new RoundRectangle2D.Double(boardX + 5, y - 195, 35, 75, 3, 3));
+ // Motor B terminal (2 pins)
+ g2d.fill(new RoundRectangle2D.Double(boardX + boardSizePx - 40, y - 195, 35, 75, 3, 3));
+
+ // Logic header black block
+ g2d.setColor(HEADER_BODY_COLOR);
+ g2d.fill(new RoundRectangle2D.Double(x + 130, y - 10, 128, 20, 2, 2));
// Silkscreen
g2d.setColor(Color.WHITE);
g2d.setFont(SILK_FONT);
StringUtils.drawCenteredText(g2d, "L298N DRIVER", boardX + boardSizePx / 2.0, boardY + 30, HorizontalAlignment.CENTER, VerticalAlignment.CENTER);
+
+ // Terminal Silk labels
+ g2d.setFont(SILK_FONT_SMALL);
+ StringUtils.drawCenteredText(g2d, "12V GND 5V", x + 40, y - 32, HorizontalAlignment.CENTER, VerticalAlignment.CENTER);
+ StringUtils.drawCenteredText(g2d, "OUT1/2", boardX + 50, y - 160, HorizontalAlignment.LEFT, VerticalAlignment.CENTER);
+ StringUtils.drawCenteredText(g2d, "OUT3/4", boardX + boardSizePx - 50, y - 160, HorizontalAlignment.RIGHT, VerticalAlignment.CENTER);
}
g2d.setTransform(oldTx);
diff --git a/diylc/diylc-library/src/main/java/org/diylc/components/modules/LM2596BuckConverter.java b/diylc/diylc-library/src/main/java/org/diylc/components/modules/LM2596BuckConverter.java
new file mode 100644
index 00000000..516a0a47
--- /dev/null
+++ b/diylc/diylc-library/src/main/java/org/diylc/components/modules/LM2596BuckConverter.java
@@ -0,0 +1,204 @@
+/*
+ *
+ * DIY Layout Creator (DIYLC).
+ * Copyright (c) 2009-2025 held jointly by the individual authors.
+ *
+ * This file is part of DIYLC.
+ *
+ * DIYLC is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DIYLC is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with DIYLC. If not, see .
+ *
+ */
+package org.diylc.components.modules;
+
+import java.awt.Color;
+import java.awt.Composite;
+import java.awt.Graphics2D;
+import java.awt.Shape;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Ellipse2D;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.awt.geom.RoundRectangle2D;
+
+import org.diylc.awt.StringUtils;
+import org.diylc.common.HorizontalAlignment;
+import org.diylc.common.ObjectCache;
+import org.diylc.common.Orientation;
+import org.diylc.common.VerticalAlignment;
+import org.diylc.components.AbstractMakerBoard;
+import org.diylc.core.ComponentState;
+import org.diylc.core.IDIYComponent;
+import org.diylc.core.IDrawingObserver;
+import org.diylc.core.Project;
+import org.diylc.core.annotations.BomPolicy;
+import org.diylc.core.annotations.ComponentDescriptor;
+import org.diylc.core.annotations.KeywordPolicy;
+import org.diylc.core.measures.Size;
+import org.diylc.core.measures.SizeUnit;
+import org.diylc.utils.Constants;
+
+@ComponentDescriptor(name = "Buck Converter (LM2596)", category = "Modules & Breakouts",
+ author = "Branislav Stojkovic", description = "LM2596 Adjustable DC-DC Step-Down Buck Converter Module",
+ instanceNamePrefix = "MOD", zOrder = IDIYComponent.COMPONENT,
+ bomPolicy = BomPolicy.SHOW_ONLY_TYPE_NAME, keywordPolicy = KeywordPolicy.SHOW_TYPE_NAME,
+ enableCache = true)
+public class LM2596BuckConverter extends AbstractMakerBoard {
+
+ private static final long serialVersionUID = 1L;
+
+ public static Color BUCK_BLUE = Color.decode("#1F618D");
+ public static Color POT_BLUE = Color.decode("#0055AA");
+ public static Color COPPER_COIL = Color.decode("#D35400");
+
+ public static Size BOARD_WIDTH = new Size(43.0d, SizeUnit.mm);
+ public static Size BOARD_HEIGHT = new Size(21.0d, SizeUnit.mm);
+
+ public LM2596BuckConverter() {
+ super();
+ this.bodyColor = BUCK_BLUE;
+ updateControlPoints();
+ }
+
+ @Override
+ protected void updateControlPoints() {
+ Point2D firstPoint = controlPoints[0];
+ double h = BOARD_HEIGHT.convertToPixels();
+ double w = BOARD_WIDTH.convertToPixels();
+
+ // 4 Solder / Screw terminal pads:
+ // Input (Left): 0 = IN+, 1 = IN-
+ // Output (Right): 2 = OUT+, 3 = OUT-
+ double[][] relativeOffsets = new double[][] {
+ { 0, 0 }, // Pin 0: IN+
+ { 0, h - 20 }, // Pin 1: IN-
+ { w - 20, 0 }, // Pin 2: OUT+
+ { w - 20, h - 20 } // Pin 3: OUT-
+ };
+
+ rotatePoints(firstPoint, relativeOffsets);
+ }
+
+ @Override
+ public Shape getBodyShape() {
+ Point2D p0 = controlPoints[0];
+ double x = p0.getX();
+ double y = p0.getY();
+ double boardW = BOARD_WIDTH.convertToPixels();
+ double boardH = BOARD_HEIGHT.convertToPixels();
+ return new RoundRectangle2D.Double(x - 10, y - 10, boardW, boardH, 6, 6);
+ }
+
+ @Override
+ public void draw(Graphics2D g2d, ComponentState componentState, boolean outlineMode, Project project,
+ IDrawingObserver drawingObserver) {
+ if (checkPointsClipped(g2d.getClip())) {
+ return;
+ }
+
+ Point2D p0 = controlPoints[0];
+ double x = p0.getX();
+ double y = p0.getY();
+
+ AffineTransform oldTx = g2d.getTransform();
+ if (orientation != Orientation.DEFAULT) {
+ g2d.rotate(orientation.toRadians(), x, y);
+ }
+
+ double boardW = BOARD_WIDTH.convertToPixels();
+ double boardH = BOARD_HEIGHT.convertToPixels();
+ double boardX = x - 10;
+ double boardY = y - 10;
+
+ Shape boardShape = getBodyShape();
+
+ Composite oldComposite = applyAlpha(g2d, componentState);
+
+ drawingObserver.startTracking();
+ g2d.setColor(outlineMode ? Constants.TRANSPARENT_COLOR : bodyColor);
+ g2d.fill(boardShape);
+ drawingObserver.stopTracking();
+
+ g2d.setColor(getFinalBorderColor(componentState, outlineMode));
+ g2d.setStroke(ObjectCache.getInstance().fetchBasicStroke(1.5f));
+ g2d.draw(boardShape);
+
+ if (!outlineMode) {
+ // 2 Diagonal Mounting holes
+ drawMountingHole(g2d, boardX + 45, boardY + 16, 16);
+ drawMountingHole(g2d, boardX + boardW - 45, boardY + boardH - 16, 16);
+
+ // Input Capacitor (Left)
+ g2d.setColor(Color.LIGHT_GRAY);
+ g2d.fill(new Ellipse2D.Double(boardX + 22, boardY + boardH / 2.0 - 18, 36, 36));
+ g2d.setColor(Color.DARK_GRAY);
+ g2d.draw(new Ellipse2D.Double(boardX + 22, boardY + boardH / 2.0 - 18, 36, 36));
+
+ // Output Capacitor (Right)
+ g2d.setColor(Color.LIGHT_GRAY);
+ g2d.fill(new Ellipse2D.Double(boardX + boardW - 58, boardY + boardH / 2.0 - 18, 36, 36));
+ g2d.setColor(Color.DARK_GRAY);
+ g2d.draw(new Ellipse2D.Double(boardX + boardW - 58, boardY + boardH / 2.0 - 18, 36, 36));
+
+ // LM2596 IC (Center Left) with tab
+ g2d.setColor(Color.decode("#333333"));
+ g2d.fill(new RoundRectangle2D.Double(boardX + 70, boardY + 15, 38, 48, 3, 3));
+ g2d.setColor(Color.decode("#CCCCCC"));
+ g2d.fill(new RoundRectangle2D.Double(boardX + 70, boardY + 12, 38, 10, 2, 2));
+ g2d.setColor(Color.WHITE);
+ g2d.setFont(SILK_FONT_SMALL);
+ StringUtils.drawCenteredText(g2d, "LM2596", boardX + 89, boardY + 38, HorizontalAlignment.CENTER, VerticalAlignment.CENTER);
+
+ // Toroidal Power Inductor (Center Right)
+ g2d.setColor(Color.decode("#222222"));
+ g2d.fill(new Ellipse2D.Double(boardX + 120, boardY + boardH / 2.0 - 24, 48, 48));
+ g2d.setColor(COPPER_COIL);
+ g2d.setStroke(ObjectCache.getInstance().fetchBasicStroke(3));
+ g2d.draw(new Ellipse2D.Double(boardX + 124, boardY + boardH / 2.0 - 20, 40, 40));
+
+ // Blue 3296W Trimmer Potentiometer (Top Center)
+ g2d.setColor(POT_BLUE);
+ g2d.fill(new RoundRectangle2D.Double(boardX + 185, boardY + 15, 45, 26, 3, 3));
+ // Brass screw head
+ g2d.setColor(Color.decode("#D4AC0D"));
+ g2d.fill(new Ellipse2D.Double(boardX + 190, boardY + 22, 12, 12));
+ g2d.setColor(Color.BLACK);
+ g2d.drawLine((int)(boardX + 192), (int)(boardY + 28), (int)(boardX + 200), (int)(boardY + 28));
+
+ // Silk Screen Text
+ g2d.setColor(Color.WHITE);
+ g2d.setFont(SILK_FONT_SMALL);
+ StringUtils.drawCenteredText(g2d, "IN+", boardX + 14, boardY + 10, HorizontalAlignment.CENTER, VerticalAlignment.CENTER);
+ StringUtils.drawCenteredText(g2d, "IN-", boardX + 14, boardY + boardH - 10, HorizontalAlignment.CENTER, VerticalAlignment.CENTER);
+ StringUtils.drawCenteredText(g2d, "OUT+", boardX + boardW - 14, boardY + 10, HorizontalAlignment.CENTER, VerticalAlignment.CENTER);
+ StringUtils.drawCenteredText(g2d, "OUT-", boardX + boardW - 14, boardY + boardH - 10, HorizontalAlignment.CENTER, VerticalAlignment.CENTER);
+ }
+
+ g2d.setTransform(oldTx);
+
+ // Draw connection pads
+ drawScrewTerminals(g2d, 0, controlPoints.length, 0, outlineMode, drawingObserver);
+
+ g2d.setComposite(oldComposite);
+ }
+
+ @Override
+ public void drawIcon(Graphics2D g2d, int width, int height) {
+ g2d.setColor(BUCK_BLUE);
+ g2d.fill(new RoundRectangle2D.Double(2, 6, width - 4, height - 12, 3, 3));
+ g2d.setColor(COPPER_COIL);
+ g2d.fill(new Ellipse2D.Double(width / 2 - 4, height / 2 - 4, 8, 8));
+ g2d.setColor(POT_BLUE);
+ g2d.fillRect(width - 12, height / 2 - 5, 8, 10);
+ }
+}
diff --git a/diylc/diylc-library/src/main/java/org/diylc/components/modules/MB102PowerSupply.java b/diylc/diylc-library/src/main/java/org/diylc/components/modules/MB102PowerSupply.java
new file mode 100644
index 00000000..cb2f10ad
--- /dev/null
+++ b/diylc/diylc-library/src/main/java/org/diylc/components/modules/MB102PowerSupply.java
@@ -0,0 +1,207 @@
+/*
+ *
+ * DIY Layout Creator (DIYLC).
+ * Copyright (c) 2009-2025 held jointly by the individual authors.
+ *
+ * This file is part of DIYLC.
+ *
+ * DIYLC is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DIYLC is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with DIYLC. If not, see .
+ *
+ */
+package org.diylc.components.modules;
+
+import java.awt.Color;
+import java.awt.Composite;
+import java.awt.Graphics2D;
+import java.awt.Shape;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Ellipse2D;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.awt.geom.RoundRectangle2D;
+
+import org.diylc.awt.StringUtils;
+import org.diylc.common.HorizontalAlignment;
+import org.diylc.common.ObjectCache;
+import org.diylc.common.Orientation;
+import org.diylc.common.VerticalAlignment;
+import org.diylc.components.AbstractMakerBoard;
+import org.diylc.core.ComponentState;
+import org.diylc.core.IDIYComponent;
+import org.diylc.core.IDrawingObserver;
+import org.diylc.core.Project;
+import org.diylc.core.annotations.BomPolicy;
+import org.diylc.core.annotations.ComponentDescriptor;
+import org.diylc.core.annotations.KeywordPolicy;
+import org.diylc.core.measures.Size;
+import org.diylc.core.measures.SizeUnit;
+import org.diylc.utils.Constants;
+
+@ComponentDescriptor(name = "Breadboard Power Supply (MB102)", category = "Modules & Breakouts",
+ author = "Branislav Stojkovic", description = "MB102 Dual 3.3V/5V Breadboard Power Supply Module",
+ instanceNamePrefix = "PWR", zOrder = IDIYComponent.BOARD,
+ bomPolicy = BomPolicy.SHOW_ONLY_TYPE_NAME, keywordPolicy = KeywordPolicy.SHOW_TYPE_NAME,
+ enableCache = true)
+public class MB102PowerSupply extends AbstractMakerBoard {
+
+ private static final long serialVersionUID = 1L;
+
+ public static Color MB102_PCB = Color.decode("#E8E4D9");
+ public static Color JUMPER_YELLOW = Color.decode("#F1C40F");
+ public static Color SWITCH_RED = Color.decode("#E74C3C");
+
+ public static Size BOARD_WIDTH = new Size(32.0d, SizeUnit.mm);
+ public static Size BOARD_HEIGHT = new Size(54.0d, SizeUnit.mm);
+
+ public MB102PowerSupply() {
+ super();
+ this.bodyColor = MB102_PCB;
+ updateControlPoints();
+ }
+
+ @Override
+ protected void updateControlPoints() {
+ Point2D firstPoint = controlPoints[0];
+ double spacing = PIN_SPACING.convertToPixels();
+
+ // 8 output pins to plug directly into breadboard power rails:
+ // Top Power Rail (4 pins: 0..3): +, -, +, -
+ // Bottom Power Rail (4 pins: 4..7): +, -, +, - spaced 18 grid units apart
+ double railSpan = 18 * spacing;
+ double[][] relativeOffsets = new double[][] {
+ { 0, 0 },
+ { spacing, 0 },
+ { 0, spacing },
+ { spacing, spacing },
+ { 0, railSpan },
+ { spacing, railSpan },
+ { 0, railSpan + spacing },
+ { spacing, railSpan + spacing }
+ };
+
+ rotatePoints(firstPoint, relativeOffsets);
+ }
+
+ @Override
+ public Shape getBodyShape() {
+ Point2D p0 = controlPoints[0];
+ double x = p0.getX();
+ double y = p0.getY();
+ double boardW = BOARD_WIDTH.convertToPixels();
+ double boardH = BOARD_HEIGHT.convertToPixels();
+ double boardX = x - boardW + PIN_SPACING.convertToPixels() + 25;
+ double boardY = y - 20;
+ return new RoundRectangle2D.Double(boardX, boardY, boardW, boardH, 8, 8);
+ }
+
+ @Override
+ public void draw(Graphics2D g2d, ComponentState componentState, boolean outlineMode, Project project,
+ IDrawingObserver drawingObserver) {
+ if (checkPointsClipped(g2d.getClip())) {
+ return;
+ }
+
+ Point2D p0 = controlPoints[0];
+ double x = p0.getX();
+ double y = p0.getY();
+
+ AffineTransform oldTx = g2d.getTransform();
+ if (orientation != Orientation.DEFAULT) {
+ g2d.rotate(orientation.toRadians(), x, y);
+ }
+
+ double boardW = BOARD_WIDTH.convertToPixels();
+ double boardH = BOARD_HEIGHT.convertToPixels();
+ double boardX = x - boardW + PIN_SPACING.convertToPixels() + 25;
+ double boardY = y - 20;
+
+ Shape boardShape = getBodyShape();
+
+ Composite oldComposite = applyAlpha(g2d, componentState);
+
+ drawingObserver.startTracking();
+ g2d.setColor(outlineMode ? Constants.TRANSPARENT_COLOR : bodyColor);
+ g2d.fill(boardShape);
+ drawingObserver.stopTracking();
+
+ g2d.setColor(getFinalBorderColor(componentState, outlineMode));
+ g2d.setStroke(ObjectCache.getInstance().fetchBasicStroke(1.5f));
+ g2d.draw(boardShape);
+
+ if (!outlineMode) {
+ // DC Barrel Jack (Left Edge Center)
+ g2d.setColor(Color.decode("#111111"));
+ g2d.fill(new RoundRectangle2D.Double(boardX - 8, boardY + boardH / 2.0 - 25, 45, 50, 4, 4));
+ g2d.setColor(Color.decode("#333333"));
+ g2d.draw(new RoundRectangle2D.Double(boardX - 8, boardY + boardH / 2.0 - 25, 45, 50, 4, 4));
+ g2d.setColor(Color.LIGHT_GRAY);
+ g2d.fill(new Ellipse2D.Double(boardX - 4, boardY + boardH / 2.0 - 10, 20, 20));
+ g2d.setColor(Color.BLACK);
+ g2d.fill(new Ellipse2D.Double(boardX + 1, boardY + boardH / 2.0 - 5, 10, 10));
+
+ // USB-A Connector (Center)
+ drawMetalConnector(g2d, boardX + 45, boardY + boardH / 2.0 - 22, 48, 44, "USB");
+
+ // Push Button Latch Switch (Left-ish)
+ g2d.setColor(Color.LIGHT_GRAY);
+ g2d.fill(new RoundRectangle2D.Double(boardX + 48, boardY + 45, 28, 28, 4, 4));
+ g2d.setColor(SWITCH_RED);
+ g2d.fill(new Ellipse2D.Double(boardX + 53, boardY + 50, 18, 18));
+
+ // Power Green LED
+ g2d.setColor(Color.decode("#2ECC71"));
+ g2d.fill(new Ellipse2D.Double(boardX + 85, boardY + 54, 10, 10));
+
+ // AMS1117 Voltage Regulators (3.3V and 5.0V)
+ drawChip(g2d, boardX + 110, boardY + 70, 28, 22, "3.3V");
+ drawChip(g2d, boardX + 110, boardY + boardH - 92, 28, 22, "5.0V");
+
+ // Voltage Selection Jumpers (Top & Bottom rail selectors)
+ g2d.setColor(JUMPER_YELLOW);
+ g2d.fill(new RoundRectangle2D.Double(boardX + boardW - 75, boardY + 20, 26, 18, 3, 3));
+ g2d.fill(new RoundRectangle2D.Double(boardX + boardW - 75, boardY + boardH - 38, 26, 18, 3, 3));
+
+ // Silk Screen Text
+ g2d.setColor(Color.DARK_GRAY);
+ g2d.setFont(SILK_FONT_SMALL);
+ StringUtils.drawCenteredText(g2d, "MB102", boardX + boardW / 2.0, boardY + boardH - 60, HorizontalAlignment.CENTER, VerticalAlignment.CENTER);
+ StringUtils.drawCenteredText(g2d, "3.3V / 5V", boardX + boardW / 2.0, boardY + boardH - 48, HorizontalAlignment.CENTER, VerticalAlignment.CENTER);
+
+ // Rail markings (+ / -)
+ g2d.setColor(Color.RED);
+ g2d.drawString("+", (int)(x - 12), (int)(y + 8));
+ g2d.drawString("+", (int)(x - 12), (int)(y + 18 * PIN_SPACING.convertToPixels() + 8));
+ g2d.setColor(Color.BLUE);
+ g2d.drawString("-", (int)(x + PIN_SPACING.convertToPixels() + 6), (int)(y + 8));
+ g2d.drawString("-", (int)(x + PIN_SPACING.convertToPixels() + 6), (int)(y + 18 * PIN_SPACING.convertToPixels() + 8));
+ }
+
+ g2d.setTransform(oldTx);
+
+ // Draw header pins connecting to breadboard rails
+ drawPins(g2d, 0, controlPoints.length, false, outlineMode, drawingObserver);
+
+ g2d.setComposite(oldComposite);
+ }
+
+ @Override
+ public void drawIcon(Graphics2D g2d, int width, int height) {
+ g2d.setColor(MB102_PCB);
+ g2d.fill(new RoundRectangle2D.Double(4, 2, width - 8, height - 4, 3, 3));
+ g2d.setColor(SWITCH_RED);
+ g2d.fill(new Ellipse2D.Double(width / 2.0 - 4, height / 2.0 - 4, 8, 8));
+ g2d.setColor(Color.DARK_GRAY);
+ g2d.draw(new RoundRectangle2D.Double(4, 2, width - 8, height - 4, 3, 3));
+ }
+}
diff --git a/diylc/diylc-library/src/main/java/org/diylc/components/modules/MOSFETSwitchModule.java b/diylc/diylc-library/src/main/java/org/diylc/components/modules/MOSFETSwitchModule.java
new file mode 100644
index 00000000..2d33223e
--- /dev/null
+++ b/diylc/diylc-library/src/main/java/org/diylc/components/modules/MOSFETSwitchModule.java
@@ -0,0 +1,205 @@
+/*
+ *
+ * DIY Layout Creator (DIYLC).
+ * Copyright (c) 2009-2025 held jointly by the individual authors.
+ *
+ * This file is part of DIYLC.
+ *
+ * DIYLC is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DIYLC is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with DIYLC. If not, see .
+ *
+ */
+package org.diylc.components.modules;
+
+import java.awt.Color;
+import java.awt.Composite;
+import java.awt.Graphics2D;
+import java.awt.Shape;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Ellipse2D;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.awt.geom.RoundRectangle2D;
+
+import org.diylc.awt.StringUtils;
+import org.diylc.common.HorizontalAlignment;
+import org.diylc.common.ObjectCache;
+import org.diylc.common.Orientation;
+import org.diylc.common.VerticalAlignment;
+import org.diylc.components.AbstractMakerBoard;
+import org.diylc.core.ComponentState;
+import org.diylc.core.IDIYComponent;
+import org.diylc.core.IDrawingObserver;
+import org.diylc.core.Project;
+import org.diylc.core.annotations.BomPolicy;
+import org.diylc.core.annotations.ComponentDescriptor;
+import org.diylc.core.annotations.KeywordPolicy;
+import org.diylc.core.measures.Size;
+import org.diylc.core.measures.SizeUnit;
+import org.diylc.utils.Constants;
+
+@ComponentDescriptor(name = "MOSFET Driver Module (IRF520)", category = "Modules & Breakouts",
+ author = "Branislav Stojkovic", description = "IRF520 High-Current DC MOSFET Power Switch Driver Module",
+ instanceNamePrefix = "MOD", zOrder = IDIYComponent.COMPONENT,
+ bomPolicy = BomPolicy.SHOW_ONLY_TYPE_NAME, keywordPolicy = KeywordPolicy.SHOW_TYPE_NAME,
+ enableCache = true)
+public class MOSFETSwitchModule extends AbstractMakerBoard {
+
+ private static final long serialVersionUID = 1L;
+
+ public static Color MOSFET_RED = Color.decode("#922B21");
+
+ public static Size BOARD_WIDTH = new Size(33.0d, SizeUnit.mm);
+ public static Size BOARD_HEIGHT = new Size(24.0d, SizeUnit.mm);
+
+ public MOSFETSwitchModule() {
+ super();
+ this.bodyColor = MOSFET_RED;
+ updateControlPoints();
+ }
+
+ @Override
+ protected void updateControlPoints() {
+ Point2D firstPoint = controlPoints[0];
+ double spacing = PIN_SPACING.convertToPixels();
+ double w = BOARD_WIDTH.convertToPixels();
+ double h = BOARD_HEIGHT.convertToPixels();
+
+ // 7 pins total:
+ // Pin 0..2: Control header (SIG, VCC, GND on left)
+ // Pin 3, 4: VIN, GND (Power in screw terminal, top right)
+ // Pin 5, 6: V+, V- (Load out screw terminal, bottom right)
+ double[][] relativeOffsets = new double[][] {
+ { 0, 0 },
+ { 0, spacing },
+ { 0, spacing * 2 },
+ { w - 30, -spacing * 1.5 },
+ { w - 30, -spacing * 0.5 },
+ { w - 30, spacing * 1.5 },
+ { w - 30, spacing * 2.5 }
+ };
+
+ rotatePoints(firstPoint, relativeOffsets);
+ }
+
+ @Override
+ public Shape getBodyShape() {
+ Point2D p0 = controlPoints[0];
+ double x = p0.getX();
+ double y = p0.getY();
+ double boardW = BOARD_WIDTH.convertToPixels();
+ double boardH = BOARD_HEIGHT.convertToPixels();
+ double boardX = x - 15;
+ double boardY = y - (boardH - 2 * PIN_SPACING.convertToPixels()) / 2.0;
+ return new RoundRectangle2D.Double(boardX, boardY, boardW, boardH, 6, 6);
+ }
+
+ @Override
+ public void draw(Graphics2D g2d, ComponentState componentState, boolean outlineMode, Project project,
+ IDrawingObserver drawingObserver) {
+ if (checkPointsClipped(g2d.getClip())) {
+ return;
+ }
+
+ Point2D p0 = controlPoints[0];
+ double x = p0.getX();
+ double y = p0.getY();
+
+ AffineTransform oldTx = g2d.getTransform();
+ if (orientation != Orientation.DEFAULT) {
+ g2d.rotate(orientation.toRadians(), x, y);
+ }
+
+ double boardW = BOARD_WIDTH.convertToPixels();
+ double boardH = BOARD_HEIGHT.convertToPixels();
+ double boardX = x - 15;
+ double boardY = y - (boardH - 2 * PIN_SPACING.convertToPixels()) / 2.0;
+
+ Shape boardShape = getBodyShape();
+
+ Composite oldComposite = applyAlpha(g2d, componentState);
+
+ drawingObserver.startTracking();
+ g2d.setColor(outlineMode ? Constants.TRANSPARENT_COLOR : bodyColor);
+ g2d.fill(boardShape);
+ drawingObserver.stopTracking();
+
+ g2d.setColor(getFinalBorderColor(componentState, outlineMode));
+ g2d.setStroke(ObjectCache.getInstance().fetchBasicStroke(1.5f));
+ g2d.draw(boardShape);
+
+ if (!outlineMode) {
+ // 2 Mounting Holes
+ drawMountingHole(g2d, boardX + 14, boardY + 14, 14);
+ drawMountingHole(g2d, boardX + 14, boardY + boardH - 14, 14);
+
+ // TO-220 Power MOSFET (Center)
+ double mosX = boardX + 50;
+ double mosY = boardY + boardH / 2.0 - 24;
+ g2d.setColor(Color.decode("#222222"));
+ g2d.fill(new RoundRectangle2D.Double(mosX, mosY, 36, 48, 3, 3));
+ // Heatsink tab
+ g2d.setColor(Color.decode("#BDC3C7"));
+ g2d.fill(new RoundRectangle2D.Double(mosX, mosY - 10, 36, 12, 2, 2));
+ g2d.setColor(Color.WHITE);
+ g2d.setFont(SILK_FONT_SMALL);
+ StringUtils.drawCenteredText(g2d, "IRF520", mosX + 18, mosY + 24, HorizontalAlignment.CENTER, VerticalAlignment.CENTER);
+
+ // Dual Screw Terminal Blocks (Right side)
+ double stX = boardX + boardW - 48;
+ g2d.setColor(SCREW_TERMINAL_COLOR);
+ g2d.fill(new RoundRectangle2D.Double(stX, boardY + 8, 42, 35, 3, 3));
+ g2d.fill(new RoundRectangle2D.Double(stX, boardY + boardH - 43, 42, 35, 3, 3));
+
+ // Screw heads
+ g2d.setColor(Color.LIGHT_GRAY);
+ g2d.fill(new Ellipse2D.Double(stX + 8, boardY + 18, 12, 12));
+ g2d.fill(new Ellipse2D.Double(stX + 24, boardY + 18, 12, 12));
+ g2d.fill(new Ellipse2D.Double(stX + 8, boardY + boardH - 32, 12, 12));
+ g2d.fill(new Ellipse2D.Double(stX + 24, boardY + boardH - 32, 12, 12));
+
+ // Gate Trigger Status LED (Red)
+ g2d.setColor(Color.RED);
+ g2d.fill(new RoundRectangle2D.Double(boardX + 32, boardY + boardH / 2.0 - 5, 10, 10, 2, 2));
+
+ // Silk Screen Pin Labels
+ g2d.setColor(Color.WHITE);
+ g2d.setFont(SILK_FONT_SMALL);
+ String[] labels = new String[] { "SIG", "VCC", "GND" };
+ double spacing = PIN_SPACING.convertToPixels();
+ for (int i = 0; i < 3; i++) {
+ StringUtils.drawCenteredText(g2d, labels[i], x + 16, y + i * spacing, HorizontalAlignment.LEFT, VerticalAlignment.CENTER);
+ }
+ StringUtils.drawCenteredText(g2d, "VIN", stX - 10, boardY + 24, HorizontalAlignment.RIGHT, VerticalAlignment.CENTER);
+ StringUtils.drawCenteredText(g2d, "OUT", stX - 10, boardY + boardH - 26, HorizontalAlignment.RIGHT, VerticalAlignment.CENTER);
+ }
+
+ g2d.setTransform(oldTx);
+
+ // Draw control header pins and terminal contacts
+ drawPins(g2d, 0, 3, false, outlineMode, drawingObserver);
+ drawScrewTerminals(g2d, 3, 4, 0, outlineMode, drawingObserver);
+
+ g2d.setComposite(oldComposite);
+ }
+
+ @Override
+ public void drawIcon(Graphics2D g2d, int width, int height) {
+ g2d.setColor(MOSFET_RED);
+ g2d.fill(new RoundRectangle2D.Double(2, 4, width - 4, height - 8, 3, 3));
+ g2d.setColor(Color.decode("#222222"));
+ g2d.fillRect(width / 2 - 4, height / 2 - 6, 8, 12);
+ g2d.setColor(SCREW_TERMINAL_COLOR);
+ g2d.fillRect(width - 10, height / 2 - 6, 8, 12);
+ }
+}
diff --git a/diylc/diylc-library/src/main/java/org/diylc/components/modules/MT3608BoostConverter.java b/diylc/diylc-library/src/main/java/org/diylc/components/modules/MT3608BoostConverter.java
new file mode 100644
index 00000000..8354b0f9
--- /dev/null
+++ b/diylc/diylc-library/src/main/java/org/diylc/components/modules/MT3608BoostConverter.java
@@ -0,0 +1,181 @@
+/*
+ *
+ * DIY Layout Creator (DIYLC).
+ * Copyright (c) 2009-2025 held jointly by the individual authors.
+ *
+ * This file is part of DIYLC.
+ *
+ * DIYLC is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DIYLC is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with DIYLC. If not, see .
+ *
+ */
+package org.diylc.components.modules;
+
+import java.awt.Color;
+import java.awt.Composite;
+import java.awt.Graphics2D;
+import java.awt.Shape;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Ellipse2D;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.awt.geom.RoundRectangle2D;
+
+import org.diylc.awt.StringUtils;
+import org.diylc.common.HorizontalAlignment;
+import org.diylc.common.ObjectCache;
+import org.diylc.common.Orientation;
+import org.diylc.common.VerticalAlignment;
+import org.diylc.components.AbstractMakerBoard;
+import org.diylc.core.ComponentState;
+import org.diylc.core.IDIYComponent;
+import org.diylc.core.IDrawingObserver;
+import org.diylc.core.Project;
+import org.diylc.core.annotations.BomPolicy;
+import org.diylc.core.annotations.ComponentDescriptor;
+import org.diylc.core.annotations.KeywordPolicy;
+import org.diylc.core.measures.Size;
+import org.diylc.core.measures.SizeUnit;
+import org.diylc.utils.Constants;
+
+@ComponentDescriptor(name = "Boost Converter (MT3608)", category = "Modules & Breakouts",
+ author = "Branislav Stojkovic", description = "MT3608 2A Adjustable DC-DC Step-Up Boost Converter Module",
+ instanceNamePrefix = "MOD", zOrder = IDIYComponent.COMPONENT,
+ bomPolicy = BomPolicy.SHOW_ONLY_TYPE_NAME, keywordPolicy = KeywordPolicy.SHOW_TYPE_NAME,
+ enableCache = true)
+public class MT3608BoostConverter extends AbstractMakerBoard {
+
+ private static final long serialVersionUID = 1L;
+
+ public static Color BOOST_BLUE = Color.decode("#1B4F72");
+ public static Color POT_BLUE = Color.decode("#0055AA");
+
+ public static Size BOARD_WIDTH = new Size(36.0d, SizeUnit.mm);
+ public static Size BOARD_HEIGHT = new Size(17.0d, SizeUnit.mm);
+
+ public MT3608BoostConverter() {
+ super();
+ this.bodyColor = BOOST_BLUE;
+ updateControlPoints();
+ }
+
+ @Override
+ protected void updateControlPoints() {
+ Point2D firstPoint = controlPoints[0];
+ double h = BOARD_HEIGHT.convertToPixels();
+ double w = BOARD_WIDTH.convertToPixels();
+
+ // 4 Solder pads:
+ // Input (Left): 0 = VIN+, 1 = VIN-
+ // Output (Right): 2 = VOUT+, 3 = VOUT-
+ double[][] relativeOffsets = new double[][] {
+ { 0, 0 }, // Pin 0: VIN+
+ { 0, h - 20 }, // Pin 1: VIN-
+ { w - 20, 0 }, // Pin 2: VOUT+
+ { w - 20, h - 20 } // Pin 3: VOUT-
+ };
+
+ rotatePoints(firstPoint, relativeOffsets);
+ }
+
+ @Override
+ public Shape getBodyShape() {
+ Point2D p0 = controlPoints[0];
+ double x = p0.getX();
+ double y = p0.getY();
+ double boardW = BOARD_WIDTH.convertToPixels();
+ double boardH = BOARD_HEIGHT.convertToPixels();
+ return new RoundRectangle2D.Double(x - 10, y - 10, boardW, boardH, 6, 6);
+ }
+
+ @Override
+ public void draw(Graphics2D g2d, ComponentState componentState, boolean outlineMode, Project project,
+ IDrawingObserver drawingObserver) {
+ if (checkPointsClipped(g2d.getClip())) {
+ return;
+ }
+
+ Point2D p0 = controlPoints[0];
+ double x = p0.getX();
+ double y = p0.getY();
+
+ AffineTransform oldTx = g2d.getTransform();
+ if (orientation != Orientation.DEFAULT) {
+ g2d.rotate(orientation.toRadians(), x, y);
+ }
+
+ double boardW = BOARD_WIDTH.convertToPixels();
+ double boardH = BOARD_HEIGHT.convertToPixels();
+ double boardX = x - 10;
+ double boardY = y - 10;
+
+ Shape boardShape = getBodyShape();
+
+ Composite oldComposite = applyAlpha(g2d, componentState);
+
+ drawingObserver.startTracking();
+ g2d.setColor(outlineMode ? Constants.TRANSPARENT_COLOR : bodyColor);
+ g2d.fill(boardShape);
+ drawingObserver.stopTracking();
+
+ g2d.setColor(getFinalBorderColor(componentState, outlineMode));
+ g2d.setStroke(ObjectCache.getInstance().fetchBasicStroke(1.5f));
+ g2d.draw(boardShape);
+
+ if (!outlineMode) {
+ // Power Inductor (Square SMD, Center Left)
+ g2d.setColor(Color.decode("#2C3E50"));
+ g2d.fill(new RoundRectangle2D.Double(boardX + 35, boardY + boardH / 2.0 - 20, 40, 40, 4, 4));
+ g2d.setColor(Color.LIGHT_GRAY);
+ g2d.setFont(SILK_FONT_SMALL);
+ StringUtils.drawCenteredText(g2d, "220", boardX + 55, boardY + boardH / 2.0, HorizontalAlignment.CENTER, VerticalAlignment.CENTER);
+
+ // MT3608 SOT23-6 Boost Controller IC
+ drawChip(g2d, boardX + 85, boardY + boardH / 2.0 - 10, 20, 20, "3608");
+
+ // Blue 3296W Trimmer Potentiometer (Right Center)
+ g2d.setColor(POT_BLUE);
+ g2d.fill(new RoundRectangle2D.Double(boardX + 115, boardY + 12, 45, 26, 3, 3));
+ // Brass screw head
+ g2d.setColor(Color.decode("#D4AC0D"));
+ g2d.fill(new Ellipse2D.Double(boardX + 120, boardY + 19, 12, 12));
+ g2d.setColor(Color.BLACK);
+ g2d.drawLine((int)(boardX + 122), (int)(boardY + 25), (int)(boardX + 130), (int)(boardY + 25));
+
+ // Silk Screen Text
+ g2d.setColor(Color.WHITE);
+ g2d.setFont(SILK_FONT_SMALL);
+ StringUtils.drawCenteredText(g2d, "VIN+", boardX + 15, boardY + 10, HorizontalAlignment.CENTER, VerticalAlignment.CENTER);
+ StringUtils.drawCenteredText(g2d, "VIN-", boardX + 15, boardY + boardH - 10, HorizontalAlignment.CENTER, VerticalAlignment.CENTER);
+ StringUtils.drawCenteredText(g2d, "VOUT+", boardX + boardW - 18, boardY + 10, HorizontalAlignment.CENTER, VerticalAlignment.CENTER);
+ StringUtils.drawCenteredText(g2d, "VOUT-", boardX + boardW - 18, boardY + boardH - 10, HorizontalAlignment.CENTER, VerticalAlignment.CENTER);
+ }
+
+ g2d.setTransform(oldTx);
+
+ // Draw connection pads
+ drawScrewTerminals(g2d, 0, controlPoints.length, 0, outlineMode, drawingObserver);
+
+ g2d.setComposite(oldComposite);
+ }
+
+ @Override
+ public void drawIcon(Graphics2D g2d, int width, int height) {
+ g2d.setColor(BOOST_BLUE);
+ g2d.fill(new RoundRectangle2D.Double(2, 6, width - 4, height - 12, 3, 3));
+ g2d.setColor(Color.decode("#2C3E50"));
+ g2d.fillRect(6, height / 2 - 4, 8, 8);
+ g2d.setColor(POT_BLUE);
+ g2d.fillRect(width - 12, height / 2 - 4, 8, 8);
+ }
+}
diff --git a/diylc/diylc-library/src/main/java/org/diylc/components/modules/NRF24L01Transceiver.java b/diylc/diylc-library/src/main/java/org/diylc/components/modules/NRF24L01Transceiver.java
new file mode 100644
index 00000000..f01a1ce2
--- /dev/null
+++ b/diylc/diylc-library/src/main/java/org/diylc/components/modules/NRF24L01Transceiver.java
@@ -0,0 +1,201 @@
+/*
+ *
+ * DIY Layout Creator (DIYLC).
+ * Copyright (c) 2009-2025 held jointly by the individual authors.
+ *
+ * This file is part of DIYLC.
+ *
+ * DIYLC is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DIYLC is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with DIYLC. If not, see .
+ *
+ */
+package org.diylc.components.modules;
+
+import java.awt.Color;
+import java.awt.Composite;
+import java.awt.Graphics2D;
+import java.awt.Shape;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Ellipse2D;
+import java.awt.geom.Path2D;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.awt.geom.RoundRectangle2D;
+
+import org.diylc.awt.StringUtils;
+import org.diylc.common.HorizontalAlignment;
+import org.diylc.common.ObjectCache;
+import org.diylc.common.Orientation;
+import org.diylc.common.VerticalAlignment;
+import org.diylc.components.AbstractMakerBoard;
+import org.diylc.core.ComponentState;
+import org.diylc.core.IDIYComponent;
+import org.diylc.core.IDrawingObserver;
+import org.diylc.core.Project;
+import org.diylc.core.annotations.BomPolicy;
+import org.diylc.core.annotations.ComponentDescriptor;
+import org.diylc.core.annotations.KeywordPolicy;
+import org.diylc.core.measures.Size;
+import org.diylc.core.measures.SizeUnit;
+import org.diylc.utils.Constants;
+
+@ComponentDescriptor(name = "2.4GHz RF Transceiver (NRF24L01+)", category = "Modules & Breakouts",
+ author = "Branislav Stojkovic", description = "NRF24L01+ 2.4GHz Wireless SPI Transceiver Module",
+ instanceNamePrefix = "RF", zOrder = IDIYComponent.COMPONENT,
+ bomPolicy = BomPolicy.SHOW_ONLY_TYPE_NAME, keywordPolicy = KeywordPolicy.SHOW_TYPE_NAME,
+ enableCache = true)
+public class NRF24L01Transceiver extends AbstractMakerBoard {
+
+ private static final long serialVersionUID = 1L;
+
+ public static Color NRF_PCB = Color.decode("#111111");
+ public static Color ANTENNA_GOLD = Color.decode("#D4AC0D");
+
+ public static Size BOARD_WIDTH = new Size(29.0d, SizeUnit.mm);
+ public static Size BOARD_HEIGHT = new Size(15.2d, SizeUnit.mm);
+
+ public NRF24L01Transceiver() {
+ super();
+ this.bodyColor = NRF_PCB;
+ updateControlPoints();
+ }
+
+ @Override
+ protected void updateControlPoints() {
+ Point2D firstPoint = controlPoints[0];
+ double spacing = PIN_SPACING.convertToPixels();
+
+ // 2x4 Header (8 pins):
+ // Row 1 (Top): 0 = GND, 1 = CE, 2 = SCK, 3 = MISO
+ // Row 2 (Bottom): 4 = VCC (3.3V), 5 = CSN, 6 = MOSI, 7 = IRQ
+ double[][] relativeOffsets = new double[][] {
+ { 0, 0 },
+ { spacing, 0 },
+ { spacing * 2, 0 },
+ { spacing * 3, 0 },
+ { 0, spacing },
+ { spacing, spacing },
+ { spacing * 2, spacing },
+ { spacing * 3, spacing }
+ };
+
+ rotatePoints(firstPoint, relativeOffsets);
+ }
+
+ @Override
+ public Shape getBodyShape() {
+ Point2D p0 = controlPoints[0];
+ double x = p0.getX();
+ double y = p0.getY();
+ double boardW = BOARD_WIDTH.convertToPixels();
+ double boardH = BOARD_HEIGHT.convertToPixels();
+ double boardX = x - boardW + 4 * PIN_SPACING.convertToPixels() + 10;
+ double boardY = y - (boardH - PIN_SPACING.convertToPixels()) / 2.0;
+ return new RoundRectangle2D.Double(boardX, boardY, boardW, boardH, 4, 4);
+ }
+
+ @Override
+ public void draw(Graphics2D g2d, ComponentState componentState, boolean outlineMode, Project project,
+ IDrawingObserver drawingObserver) {
+ if (checkPointsClipped(g2d.getClip())) {
+ return;
+ }
+
+ Point2D p0 = controlPoints[0];
+ double x = p0.getX();
+ double y = p0.getY();
+
+ AffineTransform oldTx = g2d.getTransform();
+ if (orientation != Orientation.DEFAULT) {
+ g2d.rotate(orientation.toRadians(), x, y);
+ }
+
+ double boardW = BOARD_WIDTH.convertToPixels();
+ double boardH = BOARD_HEIGHT.convertToPixels();
+ double boardX = x - boardW + 4 * PIN_SPACING.convertToPixels() + 10;
+ double boardY = y - (boardH - PIN_SPACING.convertToPixels()) / 2.0;
+
+ Shape boardShape = getBodyShape();
+
+ Composite oldComposite = applyAlpha(g2d, componentState);
+
+ drawingObserver.startTracking();
+ g2d.setColor(outlineMode ? Constants.TRANSPARENT_COLOR : bodyColor);
+ g2d.fill(boardShape);
+ drawingObserver.stopTracking();
+
+ g2d.setColor(getFinalBorderColor(componentState, outlineMode));
+ g2d.setStroke(ObjectCache.getInstance().fetchBasicStroke(1.5f));
+ g2d.draw(boardShape);
+
+ if (!outlineMode) {
+ // Serpentine PCB Trace Antenna (Left edge)
+ g2d.setColor(ANTENNA_GOLD);
+ g2d.setStroke(ObjectCache.getInstance().fetchBasicStroke(2.0f));
+ Path2D antenna = new Path2D.Double();
+ double ax = boardX + 8;
+ double ay = boardY + 12;
+ antenna.moveTo(ax, ay);
+ antenna.lineTo(ax + 35, ay);
+ antenna.lineTo(ax + 35, ay + 16);
+ antenna.lineTo(ax + 8, ay + 16);
+ antenna.lineTo(ax + 8, ay + 32);
+ antenna.lineTo(ax + 35, ay + 32);
+ antenna.lineTo(ax + 35, ay + 48);
+ antenna.lineTo(ax + 8, ay + 48);
+ antenna.lineTo(ax + 8, ay + 64);
+ antenna.lineTo(ax + 35, ay + 64);
+ g2d.draw(antenna);
+
+ // NRF24L01+ QFN IC (Center)
+ drawChip(g2d, boardX + 60, boardY + boardH / 2.0 - 15, 30, 30, "NRF");
+
+ // 16.000 MHz Crystal (Silver can)
+ g2d.setColor(Color.decode("#BDC3C7"));
+ g2d.fill(new RoundRectangle2D.Double(boardX + 98, boardY + boardH / 2.0 - 10, 24, 38, 4, 4));
+ g2d.setColor(Color.decode("#7F8C8D"));
+ g2d.draw(new RoundRectangle2D.Double(boardX + 98, boardY + boardH / 2.0 - 10, 24, 38, 4, 4));
+
+ // 2x4 Header Body (Black Shroud)
+ g2d.setColor(Color.decode("#1C1C1C"));
+ double spacing = PIN_SPACING.convertToPixels();
+ g2d.fill(new RoundRectangle2D.Double(x - 6, y - 6, spacing * 3 + 12, spacing + 12, 3, 3));
+ g2d.setColor(Color.decode("#333333"));
+ g2d.draw(new RoundRectangle2D.Double(x - 6, y - 6, spacing * 3 + 12, spacing + 12, 3, 3));
+
+ // Silk Screen Pin Labels
+ g2d.setColor(Color.WHITE);
+ g2d.setFont(SILK_FONT_SMALL);
+ StringUtils.drawCenteredText(g2d, "NRF24L01+", boardX + boardW / 2.0, boardY + 12, HorizontalAlignment.CENTER, VerticalAlignment.CENTER);
+ }
+
+ g2d.setTransform(oldTx);
+
+ // Draw 8 header pins
+ drawPins(g2d, 0, controlPoints.length, false, outlineMode, drawingObserver);
+
+ g2d.setComposite(oldComposite);
+ }
+
+ @Override
+ public void drawIcon(Graphics2D g2d, int width, int height) {
+ g2d.setColor(NRF_PCB);
+ g2d.fill(new RoundRectangle2D.Double(2, 4, width - 4, height - 8, 3, 3));
+ g2d.setColor(ANTENNA_GOLD);
+ g2d.drawLine(4, 8, 12, 8);
+ g2d.drawLine(12, 8, 12, 14);
+ g2d.drawLine(12, 14, 4, 14);
+ g2d.setColor(Color.GRAY);
+ g2d.fillRect(width - 12, height / 2 - 4, 8, 8);
+ }
+}
diff --git a/diylc/diylc-library/src/main/java/org/diylc/components/modules/PCA9685ServoDriver.java b/diylc/diylc-library/src/main/java/org/diylc/components/modules/PCA9685ServoDriver.java
new file mode 100644
index 00000000..bee85946
--- /dev/null
+++ b/diylc/diylc-library/src/main/java/org/diylc/components/modules/PCA9685ServoDriver.java
@@ -0,0 +1,216 @@
+/*
+ *
+ * DIY Layout Creator (DIYLC).
+ * Copyright (c) 2009-2025 held jointly by the individual authors.
+ *
+ * This file is part of DIYLC.
+ *
+ * DIYLC is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DIYLC is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with DIYLC. If not, see .
+ *
+ */
+package org.diylc.components.modules;
+
+import java.awt.Color;
+import java.awt.Composite;
+import java.awt.Graphics2D;
+import java.awt.Shape;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Ellipse2D;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.awt.geom.RoundRectangle2D;
+
+import org.diylc.awt.StringUtils;
+import org.diylc.common.HorizontalAlignment;
+import org.diylc.common.ObjectCache;
+import org.diylc.common.Orientation;
+import org.diylc.common.VerticalAlignment;
+import org.diylc.components.AbstractMakerBoard;
+import org.diylc.core.ComponentState;
+import org.diylc.core.IDIYComponent;
+import org.diylc.core.IDrawingObserver;
+import org.diylc.core.Project;
+import org.diylc.core.annotations.BomPolicy;
+import org.diylc.core.annotations.ComponentDescriptor;
+import org.diylc.core.annotations.KeywordPolicy;
+import org.diylc.core.measures.Size;
+import org.diylc.core.measures.SizeUnit;
+import org.diylc.utils.Constants;
+
+@ComponentDescriptor(name = "16-Channel PWM/Servo Driver (PCA9685)", category = "Modules & Breakouts",
+ author = "Branislav Stojkovic", description = "PCA9685 16-Channel 12-Bit PWM I2C Servo Controller Board",
+ instanceNamePrefix = "MOD", zOrder = IDIYComponent.BOARD,
+ bomPolicy = BomPolicy.SHOW_ONLY_TYPE_NAME, keywordPolicy = KeywordPolicy.SHOW_TYPE_NAME,
+ enableCache = true)
+public class PCA9685ServoDriver extends AbstractMakerBoard {
+
+ private static final long serialVersionUID = 1L;
+
+ public static Color PCA_BLUE = Color.decode("#1B4F72");
+ public static Color PWM_YELLOW = Color.decode("#F1C40F");
+ public static Color V_RED = Color.decode("#E74C3C");
+ public static Color GND_BLACK = Color.decode("#1C1C1C");
+
+ public static Size BOARD_WIDTH = new Size(62.5d, SizeUnit.mm);
+ public static Size BOARD_HEIGHT = new Size(25.4d, SizeUnit.mm);
+
+ public PCA9685ServoDriver() {
+ super();
+ this.bodyColor = PCA_BLUE;
+ updateControlPoints();
+ }
+
+ @Override
+ protected void updateControlPoints() {
+ Point2D firstPoint = controlPoints[0];
+ double spacing = PIN_SPACING.convertToPixels();
+ double w = BOARD_WIDTH.convertToPixels();
+
+ // 60 pins total:
+ // Left I2C header (6 pins): 0 = GND, 1 = OE, 2 = SCL, 3 = SDA, 4 = VCC, 5 = V+
+ // Right I2C cascade (6 pins): 6..11
+ // 16 Servo Channels x 3 rows (GND, V+, PWM) = 48 pins (12..59)
+ double[][] relativeOffsets = new double[60][2];
+
+ // Left I2C
+ for (int i = 0; i < 6; i++) {
+ relativeOffsets[i][0] = 0;
+ relativeOffsets[i][1] = i * spacing;
+ }
+
+ // Right I2C
+ for (int i = 0; i < 6; i++) {
+ relativeOffsets[6 + i][0] = w - 20;
+ relativeOffsets[6 + i][1] = i * spacing;
+ }
+
+ // 16-Channel 3-row Servo header (Center area)
+ double startX = spacing * 2.5;
+ int idx = 12;
+ for (int col = 0; col < 16; col++) {
+ for (int row = 0; row < 3; row++) {
+ relativeOffsets[idx][0] = startX + col * spacing;
+ relativeOffsets[idx][1] = row * spacing + spacing * 1.5;
+ idx++;
+ }
+ }
+
+ rotatePoints(firstPoint, relativeOffsets);
+ }
+
+ @Override
+ public Shape getBodyShape() {
+ Point2D p0 = controlPoints[0];
+ double x = p0.getX();
+ double y = p0.getY();
+ double boardW = BOARD_WIDTH.convertToPixels();
+ double boardH = BOARD_HEIGHT.convertToPixels();
+ double boardX = x - 10;
+ double boardY = y - (boardH - 5 * PIN_SPACING.convertToPixels()) / 2.0;
+ return new RoundRectangle2D.Double(boardX, boardY, boardW, boardH, 6, 6);
+ }
+
+ @Override
+ public void draw(Graphics2D g2d, ComponentState componentState, boolean outlineMode, Project project,
+ IDrawingObserver drawingObserver) {
+ if (checkPointsClipped(g2d.getClip())) {
+ return;
+ }
+
+ Point2D p0 = controlPoints[0];
+ double x = p0.getX();
+ double y = p0.getY();
+
+ AffineTransform oldTx = g2d.getTransform();
+ if (orientation != Orientation.DEFAULT) {
+ g2d.rotate(orientation.toRadians(), x, y);
+ }
+
+ double boardW = BOARD_WIDTH.convertToPixels();
+ double boardH = BOARD_HEIGHT.convertToPixels();
+ double boardX = x - 10;
+ double boardY = y - (boardH - 5 * PIN_SPACING.convertToPixels()) / 2.0;
+
+ Shape boardShape = getBodyShape();
+
+ Composite oldComposite = applyAlpha(g2d, componentState);
+
+ drawingObserver.startTracking();
+ g2d.setColor(outlineMode ? Constants.TRANSPARENT_COLOR : bodyColor);
+ g2d.fill(boardShape);
+ drawingObserver.stopTracking();
+
+ g2d.setColor(getFinalBorderColor(componentState, outlineMode));
+ g2d.setStroke(ObjectCache.getInstance().fetchBasicStroke(1.5f));
+ g2d.draw(boardShape);
+
+ if (!outlineMode) {
+ // 4 Mounting holes
+ drawMountingHole(g2d, boardX + 16, boardY + 16, 16);
+ drawMountingHole(g2d, boardX + 16, boardY + boardH - 16, 16);
+ drawMountingHole(g2d, boardX + boardW - 16, boardY + 16, 16);
+ drawMountingHole(g2d, boardX + boardW - 16, boardY + boardH - 16, 16);
+
+ // 2-Pin Screw Terminal Block (External Servo Power V+, GND on Top Left)
+ g2d.setColor(SCREW_TERMINAL_COLOR);
+ g2d.fill(new RoundRectangle2D.Double(boardX + 45, boardY + 8, 40, 30, 3, 3));
+ g2d.setColor(Color.LIGHT_GRAY);
+ g2d.fill(new Ellipse2D.Double(boardX + 50, boardY + 15, 12, 12));
+ g2d.fill(new Ellipse2D.Double(boardX + 68, boardY + 15, 12, 12));
+
+ // Large Filter Electrolytic Capacitor (Top)
+ g2d.setColor(Color.LIGHT_GRAY);
+ g2d.fill(new Ellipse2D.Double(boardX + 100, boardY + 6, 32, 32));
+ g2d.setColor(Color.DARK_GRAY);
+ g2d.draw(new Ellipse2D.Double(boardX + 100, boardY + 6, 32, 32));
+
+ // PCA9685 TSSOP-28 IC (Center)
+ drawChip(g2d, boardX + boardW / 2.0 - 25, boardY + 10, 50, 24, "PCA9685");
+
+ // Color-coded headers background strips for 16 channels
+ double spacing = PIN_SPACING.convertToPixels();
+ double startX = x + spacing * 2.5;
+ double chanW = spacing * 16;
+
+ g2d.setColor(GND_BLACK);
+ g2d.fill(new Rectangle2D.Double(startX - 8, y + spacing * 1.5 - 6, chanW + 6, 12));
+ g2d.setColor(V_RED);
+ g2d.fill(new Rectangle2D.Double(startX - 8, y + spacing * 2.5 - 6, chanW + 6, 12));
+ g2d.setColor(PWM_YELLOW);
+ g2d.fill(new Rectangle2D.Double(startX - 8, y + spacing * 3.5 - 6, chanW + 6, 12));
+
+ // Silk Screen Text
+ g2d.setColor(Color.WHITE);
+ g2d.setFont(SILK_FONT_SMALL);
+ StringUtils.drawCenteredText(g2d, "PCA9685 16-CH SERVO DRIVER", boardX + boardW / 2.0, boardY + boardH - 10, HorizontalAlignment.CENTER, VerticalAlignment.CENTER);
+ }
+
+ g2d.setTransform(oldTx);
+
+ // Draw all 60 pins
+ drawPins(g2d, 0, controlPoints.length, false, outlineMode, drawingObserver);
+
+ g2d.setComposite(oldComposite);
+ }
+
+ @Override
+ public void drawIcon(Graphics2D g2d, int width, int height) {
+ g2d.setColor(PCA_BLUE);
+ g2d.fill(new RoundRectangle2D.Double(2, 4, width - 4, height - 8, 3, 3));
+ g2d.setColor(PWM_YELLOW);
+ g2d.fillRect(6, height / 2 - 2, width - 12, 4);
+ g2d.setColor(V_RED);
+ g2d.fillRect(6, height / 2 + 2, width - 12, 4);
+ }
+}
diff --git a/diylc/diylc-library/src/main/java/org/diylc/components/modules/RFIDRC522.java b/diylc/diylc-library/src/main/java/org/diylc/components/modules/RFIDRC522.java
new file mode 100644
index 00000000..8cec79d7
--- /dev/null
+++ b/diylc/diylc-library/src/main/java/org/diylc/components/modules/RFIDRC522.java
@@ -0,0 +1,195 @@
+/*
+ *
+ * DIY Layout Creator (DIYLC).
+ * Copyright (c) 2009-2025 held jointly by the individual authors.
+ *
+ * This file is part of DIYLC.
+ *
+ * DIYLC is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DIYLC is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with DIYLC. If not, see .
+ *
+ */
+package org.diylc.components.modules;
+
+import java.awt.Color;
+import java.awt.Composite;
+import java.awt.Graphics2D;
+import java.awt.Shape;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Ellipse2D;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.awt.geom.RoundRectangle2D;
+
+import org.diylc.awt.StringUtils;
+import org.diylc.common.HorizontalAlignment;
+import org.diylc.common.ObjectCache;
+import org.diylc.common.Orientation;
+import org.diylc.common.VerticalAlignment;
+import org.diylc.components.AbstractMakerBoard;
+import org.diylc.core.ComponentState;
+import org.diylc.core.IDIYComponent;
+import org.diylc.core.IDrawingObserver;
+import org.diylc.core.Project;
+import org.diylc.core.annotations.BomPolicy;
+import org.diylc.core.annotations.ComponentDescriptor;
+import org.diylc.core.annotations.KeywordPolicy;
+import org.diylc.core.measures.Size;
+import org.diylc.core.measures.SizeUnit;
+import org.diylc.utils.Constants;
+
+@ComponentDescriptor(name = "RFID Reader (RC522)", category = "Modules & Breakouts",
+ author = "Branislav Stojkovic", description = "MFRC522 13.56MHz RFID Reader/Writer SPI Module",
+ instanceNamePrefix = "RFID", zOrder = IDIYComponent.BOARD,
+ bomPolicy = BomPolicy.SHOW_ONLY_TYPE_NAME, keywordPolicy = KeywordPolicy.SHOW_TYPE_NAME,
+ enableCache = true)
+public class RFIDRC522 extends AbstractMakerBoard {
+
+ private static final long serialVersionUID = 1L;
+
+ public static Color RFID_BLUE = Color.decode("#1B4F72");
+ public static Color ANTENNA_GOLD = Color.decode("#D4AC0D");
+
+ public static Size BOARD_WIDTH = new Size(60.0d, SizeUnit.mm);
+ public static Size BOARD_HEIGHT = new Size(40.0d, SizeUnit.mm);
+
+ public RFIDRC522() {
+ super();
+ this.bodyColor = RFID_BLUE;
+ updateControlPoints();
+ }
+
+ @Override
+ protected void updateControlPoints() {
+ Point2D firstPoint = controlPoints[0];
+ double spacing = PIN_SPACING.convertToPixels();
+
+ // 8-pin 0.1" header: SDA, SCK, MOSI, MISO, IRQ, GND, RST, 3.3V
+ double[][] relativeOffsets = new double[8][2];
+ for (int i = 0; i < 8; i++) {
+ relativeOffsets[i][0] = 0;
+ relativeOffsets[i][1] = i * spacing;
+ }
+
+ rotatePoints(firstPoint, relativeOffsets);
+ }
+
+ @Override
+ public Shape getBodyShape() {
+ Point2D p0 = controlPoints[0];
+ double x = p0.getX();
+ double y = p0.getY();
+ double boardW = BOARD_WIDTH.convertToPixels();
+ double boardH = BOARD_HEIGHT.convertToPixels();
+ double boardX = x - boardW + 20;
+ double boardY = y - (boardH - 7 * PIN_SPACING.convertToPixels()) / 2.0;
+ return new RoundRectangle2D.Double(boardX, boardY, boardW, boardH, 8, 8);
+ }
+
+ @Override
+ public void draw(Graphics2D g2d, ComponentState componentState, boolean outlineMode, Project project,
+ IDrawingObserver drawingObserver) {
+ if (checkPointsClipped(g2d.getClip())) {
+ return;
+ }
+
+ Point2D p0 = controlPoints[0];
+ double x = p0.getX();
+ double y = p0.getY();
+
+ AffineTransform oldTx = g2d.getTransform();
+ if (orientation != Orientation.DEFAULT) {
+ g2d.rotate(orientation.toRadians(), x, y);
+ }
+
+ double boardW = BOARD_WIDTH.convertToPixels();
+ double boardH = BOARD_HEIGHT.convertToPixels();
+ double boardX = x - boardW + 20;
+ double boardY = y - (boardH - 7 * PIN_SPACING.convertToPixels()) / 2.0;
+
+ Shape boardShape = getBodyShape();
+
+ Composite oldComposite = applyAlpha(g2d, componentState);
+
+ drawingObserver.startTracking();
+ g2d.setColor(outlineMode ? Constants.TRANSPARENT_COLOR : bodyColor);
+ g2d.fill(boardShape);
+ drawingObserver.stopTracking();
+
+ g2d.setColor(getFinalBorderColor(componentState, outlineMode));
+ g2d.setStroke(ObjectCache.getInstance().fetchBasicStroke(1.5f));
+ g2d.draw(boardShape);
+
+ if (!outlineMode) {
+ // 4 Mounting Holes
+ drawMountingHole(g2d, boardX + 16, boardY + 16, 16);
+ drawMountingHole(g2d, boardX + 16, boardY + boardH - 16, 16);
+ drawMountingHole(g2d, boardX + boardW - 40, boardY + 16, 16);
+ drawMountingHole(g2d, boardX + boardW - 40, boardY + boardH - 16, 16);
+
+ // Large PCB Loop Antenna (Left side)
+ double antX = boardX + 25;
+ double antY = boardY + 20;
+ double antW = boardW * 0.45;
+ double antH = boardH - 40;
+
+ g2d.setColor(ANTENNA_GOLD);
+ g2d.setStroke(ObjectCache.getInstance().fetchBasicStroke(1.5f));
+ for (int i = 0; i < 4; i++) {
+ g2d.draw(new RoundRectangle2D.Double(antX + i * 4, antY + i * 4, antW - i * 8, antH - i * 8, 4, 4));
+ }
+
+ // MFRC522 QFN IC (Center Right)
+ double icX = boardX + boardW * 0.62;
+ double icY = boardY + boardH / 2.0 - 18;
+ drawChip(g2d, icX, icY, 36, 36, "RC522");
+
+ // 27.120 MHz Crystal
+ g2d.setColor(Color.decode("#BDC3C7"));
+ g2d.fill(new RoundRectangle2D.Double(icX - 25, icY + 6, 18, 24, 3, 3));
+ g2d.setColor(Color.decode("#7F8C8D"));
+ g2d.draw(new RoundRectangle2D.Double(icX - 25, icY + 6, 18, 24, 3, 3));
+
+ // Silk Screen Text
+ g2d.setColor(Color.WHITE);
+ g2d.setFont(SILK_FONT);
+ StringUtils.drawCenteredText(g2d, "RFID-RC522", antX + antW / 2.0, antY + antH / 2.0, HorizontalAlignment.CENTER, VerticalAlignment.CENTER);
+
+ // Silk Screen Pin Labels
+ g2d.setFont(SILK_FONT_SMALL);
+ String[] labels = new String[] { "SDA", "SCK", "MOSI", "MISO", "IRQ", "GND", "RST", "3.3V" };
+ double spacing = PIN_SPACING.convertToPixels();
+ for (int i = 0; i < 8; i++) {
+ StringUtils.drawCenteredText(g2d, labels[i], boardX + boardW - 24, y + i * spacing, HorizontalAlignment.RIGHT, VerticalAlignment.CENTER);
+ }
+ }
+
+ g2d.setTransform(oldTx);
+
+ // Draw 8 header pins
+ drawPins(g2d, 0, controlPoints.length, false, outlineMode, drawingObserver);
+
+ g2d.setComposite(oldComposite);
+ }
+
+ @Override
+ public void drawIcon(Graphics2D g2d, int width, int height) {
+ g2d.setColor(RFID_BLUE);
+ g2d.fill(new RoundRectangle2D.Double(2, 2, width - 4, height - 4, 3, 3));
+ g2d.setColor(ANTENNA_GOLD);
+ g2d.drawRect(6, 6, width / 2 - 4, height - 12);
+ g2d.drawRect(8, 8, width / 2 - 8, height - 16);
+ g2d.setColor(Color.BLACK);
+ g2d.fillRect(width - 12, height / 2 - 4, 8, 8);
+ }
+}
diff --git a/diylc/diylc-library/src/main/java/org/diylc/components/modules/RotaryEncoderKY040.java b/diylc/diylc-library/src/main/java/org/diylc/components/modules/RotaryEncoderKY040.java
new file mode 100644
index 00000000..a8f8a863
--- /dev/null
+++ b/diylc/diylc-library/src/main/java/org/diylc/components/modules/RotaryEncoderKY040.java
@@ -0,0 +1,188 @@
+/*
+ *
+ * DIY Layout Creator (DIYLC).
+ * Copyright (c) 2009-2025 held jointly by the individual authors.
+ *
+ * This file is part of DIYLC.
+ *
+ * DIYLC is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DIYLC is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with DIYLC. If not, see .
+ *
+ */
+package org.diylc.components.modules;
+
+import java.awt.Color;
+import java.awt.Composite;
+import java.awt.Graphics2D;
+import java.awt.Shape;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Ellipse2D;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.awt.geom.RoundRectangle2D;
+
+import org.diylc.awt.StringUtils;
+import org.diylc.common.HorizontalAlignment;
+import org.diylc.common.ObjectCache;
+import org.diylc.common.Orientation;
+import org.diylc.common.VerticalAlignment;
+import org.diylc.components.AbstractMakerBoard;
+import org.diylc.core.ComponentState;
+import org.diylc.core.IDIYComponent;
+import org.diylc.core.IDrawingObserver;
+import org.diylc.core.Project;
+import org.diylc.core.annotations.BomPolicy;
+import org.diylc.core.annotations.ComponentDescriptor;
+import org.diylc.core.annotations.KeywordPolicy;
+import org.diylc.core.measures.Size;
+import org.diylc.core.measures.SizeUnit;
+import org.diylc.utils.Constants;
+
+@ComponentDescriptor(name = "Rotary Encoder (KY-040)", category = "Modules & Breakouts",
+ author = "Branislav Stojkovic", description = "KY-040 Incremental Rotary Encoder Module with Push Button",
+ instanceNamePrefix = "ENC", zOrder = IDIYComponent.COMPONENT,
+ bomPolicy = BomPolicy.SHOW_ONLY_TYPE_NAME, keywordPolicy = KeywordPolicy.SHOW_TYPE_NAME,
+ enableCache = true)
+public class RotaryEncoderKY040 extends AbstractMakerBoard {
+
+ private static final long serialVersionUID = 1L;
+
+ public static Color ENCODER_PCB = Color.decode("#1B2631");
+ public static Color METAL_SHAFT = Color.decode("#BDC3C7");
+
+ public static Size BOARD_WIDTH = new Size(32.0d, SizeUnit.mm);
+ public static Size BOARD_HEIGHT = new Size(19.0d, SizeUnit.mm);
+
+ public RotaryEncoderKY040() {
+ super();
+ this.bodyColor = ENCODER_PCB;
+ updateControlPoints();
+ }
+
+ @Override
+ protected void updateControlPoints() {
+ Point2D firstPoint = controlPoints[0];
+ double spacing = PIN_SPACING.convertToPixels();
+
+ // 5-pin 0.1" header: CLK, DT, SW, +, GND
+ double[][] relativeOffsets = new double[5][2];
+ for (int i = 0; i < 5; i++) {
+ relativeOffsets[i][0] = 0;
+ relativeOffsets[i][1] = i * spacing;
+ }
+
+ rotatePoints(firstPoint, relativeOffsets);
+ }
+
+ @Override
+ public Shape getBodyShape() {
+ Point2D p0 = controlPoints[0];
+ double x = p0.getX();
+ double y = p0.getY();
+ double boardW = BOARD_WIDTH.convertToPixels();
+ double boardH = BOARD_HEIGHT.convertToPixels();
+ double boardX = x - boardW + 20;
+ double boardY = y - (boardH - 4 * PIN_SPACING.convertToPixels()) / 2.0;
+ return new RoundRectangle2D.Double(boardX, boardY, boardW, boardH, 6, 6);
+ }
+
+ @Override
+ public void draw(Graphics2D g2d, ComponentState componentState, boolean outlineMode, Project project,
+ IDrawingObserver drawingObserver) {
+ if (checkPointsClipped(g2d.getClip())) {
+ return;
+ }
+
+ Point2D p0 = controlPoints[0];
+ double x = p0.getX();
+ double y = p0.getY();
+
+ AffineTransform oldTx = g2d.getTransform();
+ if (orientation != Orientation.DEFAULT) {
+ g2d.rotate(orientation.toRadians(), x, y);
+ }
+
+ double boardW = BOARD_WIDTH.convertToPixels();
+ double boardH = BOARD_HEIGHT.convertToPixels();
+ double boardX = x - boardW + 20;
+ double boardY = y - (boardH - 4 * PIN_SPACING.convertToPixels()) / 2.0;
+
+ Shape boardShape = getBodyShape();
+
+ Composite oldComposite = applyAlpha(g2d, componentState);
+
+ drawingObserver.startTracking();
+ g2d.setColor(outlineMode ? Constants.TRANSPARENT_COLOR : bodyColor);
+ g2d.fill(boardShape);
+ drawingObserver.stopTracking();
+
+ g2d.setColor(getFinalBorderColor(componentState, outlineMode));
+ g2d.setStroke(ObjectCache.getInstance().fetchBasicStroke(1.5f));
+ g2d.draw(boardShape);
+
+ if (!outlineMode) {
+ // 2 Mounting Holes
+ drawMountingHole(g2d, boardX + 16, boardY + 16, 16);
+ drawMountingHole(g2d, boardX + 16, boardY + boardH - 16, 16);
+
+ // Rotary Encoder Metal Body (Center Left)
+ double cx = boardX + 90;
+ double cy = boardY + boardH / 2.0;
+
+ g2d.setColor(Color.decode("#7F8C8D"));
+ g2d.fill(new RoundRectangle2D.Double(cx - 36, cy - 36, 72, 72, 6, 6));
+
+ // Threaded Collar / Bushing
+ g2d.setColor(METAL_SHAFT);
+ g2d.fill(new Ellipse2D.Double(cx - 24, cy - 24, 48, 48));
+ g2d.setColor(Color.decode("#95A5A6"));
+ g2d.draw(new Ellipse2D.Double(cx - 24, cy - 24, 48, 48));
+
+ // D-Shaft (Flatted center shaft)
+ g2d.setColor(Color.decode("#34495E"));
+ g2d.fill(new Ellipse2D.Double(cx - 15, cy - 15, 30, 30));
+ g2d.setColor(METAL_SHAFT);
+ g2d.fillRect((int)(cx + 4), (int)(cy - 14), 10, 28);
+
+ // SMD Pull-up Resistors (10k)
+ drawChip(g2d, boardX + 145, boardY + 25, 12, 18, "");
+ drawChip(g2d, boardX + 145, boardY + boardH - 43, 12, 18, "");
+
+ // Silk Screen Pin Labels
+ g2d.setColor(Color.WHITE);
+ g2d.setFont(SILK_FONT_SMALL);
+ String[] labels = new String[] { "CLK", "DT", "SW", "+", "GND" };
+ double spacing = PIN_SPACING.convertToPixels();
+ for (int i = 0; i < 5; i++) {
+ StringUtils.drawCenteredText(g2d, labels[i], boardX + boardW - 32, y + i * spacing, HorizontalAlignment.RIGHT, VerticalAlignment.CENTER);
+ }
+ }
+
+ g2d.setTransform(oldTx);
+
+ // Draw 5 header pins
+ drawPins(g2d, 0, controlPoints.length, false, outlineMode, drawingObserver);
+
+ g2d.setComposite(oldComposite);
+ }
+
+ @Override
+ public void drawIcon(Graphics2D g2d, int width, int height) {
+ g2d.setColor(ENCODER_PCB);
+ g2d.fill(new RoundRectangle2D.Double(2, 4, width - 4, height - 8, 3, 3));
+ g2d.setColor(METAL_SHAFT);
+ g2d.fill(new Ellipse2D.Double(width / 2.0 - 8, height / 2.0 - 8, 16, 16));
+ g2d.setColor(Color.decode("#34495E"));
+ g2d.fill(new Ellipse2D.Double(width / 2.0 - 4, height / 2.0 - 4, 8, 8));
+ }
+}
diff --git a/diylc/diylc-library/src/main/java/org/diylc/components/modules/TP4056Charger.java b/diylc/diylc-library/src/main/java/org/diylc/components/modules/TP4056Charger.java
new file mode 100644
index 00000000..1b1932e6
--- /dev/null
+++ b/diylc/diylc-library/src/main/java/org/diylc/components/modules/TP4056Charger.java
@@ -0,0 +1,183 @@
+/*
+ *
+ * DIY Layout Creator (DIYLC).
+ * Copyright (c) 2009-2025 held jointly by the individual authors.
+ *
+ * This file is part of DIYLC.
+ *
+ * DIYLC is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DIYLC is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with DIYLC. If not, see .
+ *
+ */
+package org.diylc.components.modules;
+
+import java.awt.Color;
+import java.awt.Composite;
+import java.awt.Graphics2D;
+import java.awt.Shape;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Ellipse2D;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.awt.geom.RoundRectangle2D;
+
+import org.diylc.awt.StringUtils;
+import org.diylc.common.HorizontalAlignment;
+import org.diylc.common.ObjectCache;
+import org.diylc.common.Orientation;
+import org.diylc.common.VerticalAlignment;
+import org.diylc.components.AbstractMakerBoard;
+import org.diylc.core.ComponentState;
+import org.diylc.core.IDIYComponent;
+import org.diylc.core.IDrawingObserver;
+import org.diylc.core.Project;
+import org.diylc.core.annotations.BomPolicy;
+import org.diylc.core.annotations.ComponentDescriptor;
+import org.diylc.core.annotations.KeywordPolicy;
+import org.diylc.core.measures.Size;
+import org.diylc.core.measures.SizeUnit;
+import org.diylc.utils.Constants;
+
+@ComponentDescriptor(name = "LiPo Charger (TP4056)", category = "Modules & Breakouts",
+ author = "Branislav Stojkovic", description = "TP4056 1A Li-ion Battery Charging and Protection Module",
+ instanceNamePrefix = "MOD", zOrder = IDIYComponent.COMPONENT,
+ bomPolicy = BomPolicy.SHOW_ONLY_TYPE_NAME, keywordPolicy = KeywordPolicy.SHOW_TYPE_NAME,
+ enableCache = true)
+public class TP4056Charger extends AbstractMakerBoard {
+
+ private static final long serialVersionUID = 1L;
+
+ public static Color TP4056_BLUE = Color.decode("#1B4F72");
+ public static Size BOARD_WIDTH = new Size(28.0d, SizeUnit.mm);
+ public static Size BOARD_HEIGHT = new Size(17.5d, SizeUnit.mm);
+
+ public TP4056Charger() {
+ super();
+ this.bodyColor = TP4056_BLUE;
+ updateControlPoints();
+ }
+
+ @Override
+ protected void updateControlPoints() {
+ Point2D firstPoint = controlPoints[0];
+ double spacing = PIN_SPACING.convertToPixels();
+ double h = BOARD_HEIGHT.convertToPixels();
+ double w = BOARD_WIDTH.convertToPixels();
+
+ // 6 Solder pads:
+ // Input (Left): 0 = IN+, 1 = IN-
+ // Output (Right): 2 = OUT+, 3 = B+, 4 = B-, 5 = OUT-
+ double[][] relativeOffsets = new double[][] {
+ { 0, 0 }, // Pin 0: IN+ (Top-Left)
+ { 0, h - 20 }, // Pin 1: IN- (Bottom-Left)
+ { w - 20, 0 }, // Pin 2: OUT+ (Top-Right)
+ { w - 20, spacing }, // Pin 3: B+
+ { w - 20, h - 20 - spacing }, // Pin 4: B-
+ { w - 20, h - 20 } // Pin 5: OUT- (Bottom-Right)
+ };
+
+ rotatePoints(firstPoint, relativeOffsets);
+ }
+
+ @Override
+ public Shape getBodyShape() {
+ Point2D p0 = controlPoints[0];
+ double x = p0.getX();
+ double y = p0.getY();
+ double boardW = BOARD_WIDTH.convertToPixels();
+ double boardH = BOARD_HEIGHT.convertToPixels();
+ return new RoundRectangle2D.Double(x - 10, y - 10, boardW, boardH, 6, 6);
+ }
+
+ @Override
+ public void draw(Graphics2D g2d, ComponentState componentState, boolean outlineMode, Project project,
+ IDrawingObserver drawingObserver) {
+ if (checkPointsClipped(g2d.getClip())) {
+ return;
+ }
+
+ Point2D p0 = controlPoints[0];
+ double x = p0.getX();
+ double y = p0.getY();
+
+ AffineTransform oldTx = g2d.getTransform();
+ if (orientation != Orientation.DEFAULT) {
+ g2d.rotate(orientation.toRadians(), x, y);
+ }
+
+ double boardW = BOARD_WIDTH.convertToPixels();
+ double boardH = BOARD_HEIGHT.convertToPixels();
+ double boardX = x - 10;
+ double boardY = y - 10;
+
+ Shape boardShape = getBodyShape();
+
+ Composite oldComposite = applyAlpha(g2d, componentState);
+
+ drawingObserver.startTracking();
+ g2d.setColor(outlineMode ? Constants.TRANSPARENT_COLOR : bodyColor);
+ g2d.fill(boardShape);
+ drawingObserver.stopTracking();
+
+ g2d.setColor(getFinalBorderColor(componentState, outlineMode));
+ g2d.setStroke(ObjectCache.getInstance().fetchBasicStroke(1.5f));
+ g2d.draw(boardShape);
+
+ if (!outlineMode) {
+ // USB Type-C Connector (Left Edge)
+ drawMetalConnector(g2d, boardX - 6, boardY + boardH / 2.0 - 18, 32, 36, "Type-C");
+
+ // TP4056 Linear Charger IC (SOP-8)
+ drawChip(g2d, boardX + 60, boardY + 20, 26, 32, "4056");
+
+ // DW01A Battery Protection IC & Dual MOSFET
+ drawChip(g2d, boardX + 110, boardY + 22, 18, 20, "DW01");
+ drawChip(g2d, boardX + 110, boardY + boardH - 44, 20, 24, "8205");
+
+ // Dual Status LEDs (Red = CHRG, Blue = STDBY)
+ g2d.setColor(Color.RED);
+ g2d.fill(new RoundRectangle2D.Double(boardX + 45, boardY + 16, 8, 12, 2, 2));
+ g2d.setColor(Color.CYAN);
+ g2d.fill(new RoundRectangle2D.Double(boardX + 45, boardY + boardH - 28, 8, 12, 2, 2));
+
+ // Silk Screen Labels
+ g2d.setColor(Color.WHITE);
+ g2d.setFont(SILK_FONT_SMALL);
+ StringUtils.drawCenteredText(g2d, "IN+", boardX + 18, boardY + 12, HorizontalAlignment.CENTER, VerticalAlignment.CENTER);
+ StringUtils.drawCenteredText(g2d, "IN-", boardX + 18, boardY + boardH - 12, HorizontalAlignment.CENTER, VerticalAlignment.CENTER);
+ StringUtils.drawCenteredText(g2d, "OUT+", boardX + boardW - 22, boardY + 12, HorizontalAlignment.CENTER, VerticalAlignment.CENTER);
+ StringUtils.drawCenteredText(g2d, "B+", boardX + boardW - 22, boardY + 32, HorizontalAlignment.CENTER, VerticalAlignment.CENTER);
+ StringUtils.drawCenteredText(g2d, "B-", boardX + boardW - 22, boardY + boardH - 32, HorizontalAlignment.CENTER, VerticalAlignment.CENTER);
+ StringUtils.drawCenteredText(g2d, "OUT-", boardX + boardW - 22, boardY + boardH - 12, HorizontalAlignment.CENTER, VerticalAlignment.CENTER);
+ }
+
+ g2d.setTransform(oldTx);
+
+ // Draw solder pads
+ drawScrewTerminals(g2d, 0, controlPoints.length, 0, outlineMode, drawingObserver);
+
+ g2d.setComposite(oldComposite);
+ }
+
+ @Override
+ public void drawIcon(Graphics2D g2d, int width, int height) {
+ g2d.setColor(TP4056_BLUE);
+ g2d.fill(new RoundRectangle2D.Double(2, 6, width - 4, height - 12, 3, 3));
+ g2d.setColor(Color.LIGHT_GRAY);
+ g2d.fillRect(2, height / 2 - 4, 8, 8);
+ g2d.setColor(Color.RED);
+ g2d.fill(new Ellipse2D.Double(14, height / 2 - 3, 6, 6));
+ g2d.setColor(Color.CYAN);
+ g2d.fill(new Ellipse2D.Double(22, height / 2 - 3, 6, 6));
+ }
+}
diff --git a/diylc/diylc-library/src/main/java/org/diylc/components/modules/ULN2003Driver.java b/diylc/diylc-library/src/main/java/org/diylc/components/modules/ULN2003Driver.java
new file mode 100644
index 00000000..4bfb23af
--- /dev/null
+++ b/diylc/diylc-library/src/main/java/org/diylc/components/modules/ULN2003Driver.java
@@ -0,0 +1,201 @@
+/*
+ *
+ * DIY Layout Creator (DIYLC).
+ * Copyright (c) 2009-2025 held jointly by the individual authors.
+ *
+ * This file is part of DIYLC.
+ *
+ * DIYLC is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DIYLC is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with DIYLC. If not, see .
+ *
+ */
+package org.diylc.components.modules;
+
+import java.awt.Color;
+import java.awt.Composite;
+import java.awt.Graphics2D;
+import java.awt.Shape;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Ellipse2D;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.awt.geom.RoundRectangle2D;
+
+import org.diylc.awt.StringUtils;
+import org.diylc.common.HorizontalAlignment;
+import org.diylc.common.ObjectCache;
+import org.diylc.common.Orientation;
+import org.diylc.common.VerticalAlignment;
+import org.diylc.components.AbstractMakerBoard;
+import org.diylc.core.ComponentState;
+import org.diylc.core.IDIYComponent;
+import org.diylc.core.IDrawingObserver;
+import org.diylc.core.Project;
+import org.diylc.core.annotations.BomPolicy;
+import org.diylc.core.annotations.ComponentDescriptor;
+import org.diylc.core.annotations.KeywordPolicy;
+import org.diylc.core.measures.Size;
+import org.diylc.core.measures.SizeUnit;
+import org.diylc.utils.Constants;
+
+@ComponentDescriptor(name = "Stepper Driver Board (ULN2003)", category = "Modules & Breakouts",
+ author = "Branislav Stojkovic", description = "ULN2003 Stepper Motor Driver Board with 4 Status LEDs",
+ instanceNamePrefix = "MOD", zOrder = IDIYComponent.BOARD,
+ bomPolicy = BomPolicy.SHOW_ONLY_TYPE_NAME, keywordPolicy = KeywordPolicy.SHOW_TYPE_NAME,
+ enableCache = true)
+public class ULN2003Driver extends AbstractMakerBoard {
+
+ private static final long serialVersionUID = 1L;
+
+ public static Color ULN_BLUE = Color.decode("#1F618D");
+ public static Color JST_WHITE = Color.decode("#EAEDED");
+ public static Color LED_YELLOW = Color.decode("#F1C40F");
+
+ public static Size BOARD_WIDTH = new Size(35.0d, SizeUnit.mm);
+ public static Size BOARD_HEIGHT = new Size(31.5d, SizeUnit.mm);
+
+ public ULN2003Driver() {
+ super();
+ this.bodyColor = ULN_BLUE;
+ updateControlPoints();
+ }
+
+ @Override
+ protected void updateControlPoints() {
+ Point2D firstPoint = controlPoints[0];
+ double spacing = PIN_SPACING.convertToPixels();
+
+ // 11 pins total:
+ // Pin 0..3: IN1, IN2, IN3, IN4 (Bottom left control header)
+ // Pin 4, 5: +5-12V, GND (Power input header)
+ // Pin 6..10: 5-pin JST motor socket (Top right)
+ double[][] relativeOffsets = new double[][] {
+ { 0, 0 },
+ { spacing, 0 },
+ { spacing * 2, 0 },
+ { spacing * 3, 0 },
+ { spacing * 5, 0 },
+ { spacing * 6, 0 },
+ // JST motor connector pins
+ { spacing * 4, -spacing * 7 },
+ { spacing * 5, -spacing * 7 },
+ { spacing * 6, -spacing * 7 },
+ { spacing * 7, -spacing * 7 },
+ { spacing * 8, -spacing * 7 }
+ };
+
+ rotatePoints(firstPoint, relativeOffsets);
+ }
+
+ @Override
+ public Shape getBodyShape() {
+ Point2D p0 = controlPoints[0];
+ double x = p0.getX();
+ double y = p0.getY();
+ double boardW = BOARD_WIDTH.convertToPixels();
+ double boardH = BOARD_HEIGHT.convertToPixels();
+ double boardX = x - 20;
+ double boardY = y - boardH + 20;
+ return new RoundRectangle2D.Double(boardX, boardY, boardW, boardH, 6, 6);
+ }
+
+ @Override
+ public void draw(Graphics2D g2d, ComponentState componentState, boolean outlineMode, Project project,
+ IDrawingObserver drawingObserver) {
+ if (checkPointsClipped(g2d.getClip())) {
+ return;
+ }
+
+ Point2D p0 = controlPoints[0];
+ double x = p0.getX();
+ double y = p0.getY();
+
+ AffineTransform oldTx = g2d.getTransform();
+ if (orientation != Orientation.DEFAULT) {
+ g2d.rotate(orientation.toRadians(), x, y);
+ }
+
+ double boardW = BOARD_WIDTH.convertToPixels();
+ double boardH = BOARD_HEIGHT.convertToPixels();
+ double boardX = x - 20;
+ double boardY = y - boardH + 20;
+
+ Shape boardShape = getBodyShape();
+
+ Composite oldComposite = applyAlpha(g2d, componentState);
+
+ drawingObserver.startTracking();
+ g2d.setColor(outlineMode ? Constants.TRANSPARENT_COLOR : bodyColor);
+ g2d.fill(boardShape);
+ drawingObserver.stopTracking();
+
+ g2d.setColor(getFinalBorderColor(componentState, outlineMode));
+ g2d.setStroke(ObjectCache.getInstance().fetchBasicStroke(1.5f));
+ g2d.draw(boardShape);
+
+ if (!outlineMode) {
+ // 4 Mounting Holes in corners
+ drawMountingHole(g2d, boardX + 16, boardY + 16, 16);
+ drawMountingHole(g2d, boardX + 16, boardY + boardH - 16, 16);
+ drawMountingHole(g2d, boardX + boardW - 16, boardY + 16, 16);
+ drawMountingHole(g2d, boardX + boardW - 16, boardY + boardH - 16, 16);
+
+ // ULN2003A DIP-16 IC Chip (Center)
+ drawChip(g2d, boardX + 35, boardY + 45, 95, 42, "ULN2003A");
+
+ // 4 Status LEDs (A, B, C, D)
+ for (int i = 0; i < 4; i++) {
+ double lx = boardX + 42 + i * 20;
+ double ly = boardY + 25;
+ g2d.setColor(LED_YELLOW);
+ g2d.fill(new RoundRectangle2D.Double(lx - 5, ly - 5, 10, 10, 2, 2));
+ }
+
+ // JST-XH 5-Pin Shrouded Motor Header (Top Right)
+ double spacing = PIN_SPACING.convertToPixels();
+ g2d.setColor(JST_WHITE);
+ g2d.fill(new RoundRectangle2D.Double(x + spacing * 3.5, y - spacing * 7.8, spacing * 5 + 10, spacing + 14, 3, 3));
+ g2d.setColor(Color.LIGHT_GRAY);
+ g2d.draw(new RoundRectangle2D.Double(x + spacing * 3.5, y - spacing * 7.8, spacing * 5 + 10, spacing + 14, 3, 3));
+
+ // Silk Screen Text
+ g2d.setColor(Color.WHITE);
+ g2d.setFont(SILK_FONT_SMALL);
+ StringUtils.drawCenteredText(g2d, "IN1", x, y - 12, HorizontalAlignment.CENTER, VerticalAlignment.CENTER);
+ StringUtils.drawCenteredText(g2d, "IN2", x + spacing, y - 12, HorizontalAlignment.CENTER, VerticalAlignment.CENTER);
+ StringUtils.drawCenteredText(g2d, "IN3", x + spacing * 2, y - 12, HorizontalAlignment.CENTER, VerticalAlignment.CENTER);
+ StringUtils.drawCenteredText(g2d, "IN4", x + spacing * 3, y - 12, HorizontalAlignment.CENTER, VerticalAlignment.CENTER);
+ StringUtils.drawCenteredText(g2d, "5-12V", x + spacing * 5, y - 12, HorizontalAlignment.CENTER, VerticalAlignment.CENTER);
+ StringUtils.drawCenteredText(g2d, "GND", x + spacing * 6, y - 12, HorizontalAlignment.CENTER, VerticalAlignment.CENTER);
+ }
+
+ g2d.setTransform(oldTx);
+
+ // Draw header pins and JST contacts
+ drawPins(g2d, 0, controlPoints.length, false, outlineMode, drawingObserver);
+
+ g2d.setComposite(oldComposite);
+ }
+
+ @Override
+ public void drawIcon(Graphics2D g2d, int width, int height) {
+ g2d.setColor(ULN_BLUE);
+ g2d.fill(new RoundRectangle2D.Double(2, 2, width - 4, height - 4, 3, 3));
+ g2d.setColor(Color.DARK_GRAY);
+ g2d.fillRect(6, height / 2 - 4, width - 12, 8);
+ for (int i = 0; i < 4; i++) {
+ g2d.setColor(LED_YELLOW);
+ g2d.fill(new Ellipse2D.Double(6 + i * 5, 4, 3, 3));
+ }
+ }
+}
diff --git a/diylc/diylc-library/src/main/java/org/diylc/components/sensors/BME280Sensor.java b/diylc/diylc-library/src/main/java/org/diylc/components/sensors/BME280Sensor.java
new file mode 100644
index 00000000..b312402e
--- /dev/null
+++ b/diylc/diylc-library/src/main/java/org/diylc/components/sensors/BME280Sensor.java
@@ -0,0 +1,174 @@
+/*
+ *
+ * DIY Layout Creator (DIYLC).
+ * Copyright (c) 2009-2025 held jointly by the individual authors.
+ *
+ * This file is part of DIYLC.
+ *
+ * DIYLC is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DIYLC is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with DIYLC. If not, see .
+ *
+ */
+package org.diylc.components.sensors;
+
+import java.awt.Color;
+import java.awt.Composite;
+import java.awt.Graphics2D;
+import java.awt.Shape;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Ellipse2D;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.awt.geom.RoundRectangle2D;
+
+import org.diylc.awt.StringUtils;
+import org.diylc.common.HorizontalAlignment;
+import org.diylc.common.ObjectCache;
+import org.diylc.common.Orientation;
+import org.diylc.common.VerticalAlignment;
+import org.diylc.components.AbstractMakerBoard;
+import org.diylc.core.ComponentState;
+import org.diylc.core.IDIYComponent;
+import org.diylc.core.IDrawingObserver;
+import org.diylc.core.Project;
+import org.diylc.core.annotations.BomPolicy;
+import org.diylc.core.annotations.ComponentDescriptor;
+import org.diylc.core.annotations.KeywordPolicy;
+import org.diylc.core.measures.Size;
+import org.diylc.core.measures.SizeUnit;
+import org.diylc.utils.Constants;
+
+@ComponentDescriptor(name = "Temp/Pressure/Humidity Sensor (BME280)", category = "Sensors",
+ author = "Branislav Stojkovic", description = "BME280 / BMP280 Temperature, Humidity, and Pressure Sensor (I2C/SPI)",
+ instanceNamePrefix = "SEN", zOrder = IDIYComponent.COMPONENT,
+ bomPolicy = BomPolicy.SHOW_ONLY_TYPE_NAME, keywordPolicy = KeywordPolicy.SHOW_TYPE_NAME,
+ enableCache = true)
+public class BME280Sensor extends AbstractMakerBoard {
+
+ private static final long serialVersionUID = 1L;
+
+ public static Color BME_PURPLE = Color.decode("#5B2C6F");
+ public static Color SENSOR_METAL = Color.decode("#C0C0C0");
+
+ public static Size BOARD_WIDTH = new Size(15.2d, SizeUnit.mm);
+ public static Size BOARD_HEIGHT = new Size(12.0d, SizeUnit.mm);
+
+ public BME280Sensor() {
+ super();
+ this.bodyColor = BME_PURPLE;
+ updateControlPoints();
+ }
+
+ @Override
+ protected void updateControlPoints() {
+ Point2D firstPoint = controlPoints[0];
+ double spacing = PIN_SPACING.convertToPixels();
+
+ // 6-pin 0.1" header: VCC, GND, SCL, SDA, CSB, SDO
+ double[][] relativeOffsets = new double[6][2];
+ for (int i = 0; i < 6; i++) {
+ relativeOffsets[i][0] = i * spacing;
+ relativeOffsets[i][1] = 0;
+ }
+
+ rotatePoints(firstPoint, relativeOffsets);
+ }
+
+ @Override
+ public Shape getBodyShape() {
+ Point2D p0 = controlPoints[0];
+ double x = p0.getX();
+ double y = p0.getY();
+ double boardW = BOARD_WIDTH.convertToPixels();
+ double boardH = BOARD_HEIGHT.convertToPixels();
+ double boardX = x - (boardW - 5 * PIN_SPACING.convertToPixels()) / 2.0;
+ double boardY = y - 16;
+ return new RoundRectangle2D.Double(boardX, boardY, boardW, boardH, 4, 4);
+ }
+
+ @Override
+ public void draw(Graphics2D g2d, ComponentState componentState, boolean outlineMode, Project project,
+ IDrawingObserver drawingObserver) {
+ if (checkPointsClipped(g2d.getClip())) {
+ return;
+ }
+
+ Point2D p0 = controlPoints[0];
+ double x = p0.getX();
+ double y = p0.getY();
+
+ AffineTransform oldTx = g2d.getTransform();
+ if (orientation != Orientation.DEFAULT) {
+ g2d.rotate(orientation.toRadians(), x, y);
+ }
+
+ double boardW = BOARD_WIDTH.convertToPixels();
+ double boardH = BOARD_HEIGHT.convertToPixels();
+ double boardX = x - (boardW - 5 * PIN_SPACING.convertToPixels()) / 2.0;
+ double boardY = y - 16;
+
+ Shape boardShape = getBodyShape();
+
+ Composite oldComposite = applyAlpha(g2d, componentState);
+
+ drawingObserver.startTracking();
+ g2d.setColor(outlineMode ? Constants.TRANSPARENT_COLOR : bodyColor);
+ g2d.fill(boardShape);
+ drawingObserver.stopTracking();
+
+ g2d.setColor(getFinalBorderColor(componentState, outlineMode));
+ g2d.setStroke(ObjectCache.getInstance().fetchBasicStroke(1.5f));
+ g2d.draw(boardShape);
+
+ if (!outlineMode) {
+ // 1 Mounting Hole (Right side)
+ drawMountingHole(g2d, boardX + boardW - 16, boardY + boardH - 20, 14);
+
+ // BME280 Metal Sensor Can with vent hole (Center)
+ double sx = boardX + 35;
+ double sy = boardY + boardH - 35;
+ g2d.setColor(SENSOR_METAL);
+ g2d.fill(new RoundRectangle2D.Double(sx, sy, 24, 20, 2, 2));
+ g2d.setColor(Color.DARK_GRAY);
+ g2d.draw(new RoundRectangle2D.Double(sx, sy, 24, 20, 2, 2));
+ // Vent hole
+ g2d.setColor(Color.BLACK);
+ g2d.fill(new Ellipse2D.Double(sx + 5, sy + 5, 4, 4));
+
+ // LDO Voltage Regulator (Left)
+ drawChip(g2d, boardX + 12, boardY + boardH - 32, 16, 16, "");
+
+ // Silk Screen Text
+ g2d.setColor(Color.WHITE);
+ g2d.setFont(SILK_FONT_SMALL);
+ StringUtils.drawCenteredText(g2d, "BME280", boardX + boardW / 2.0, boardY + 18, HorizontalAlignment.CENTER, VerticalAlignment.CENTER);
+ }
+
+ g2d.setTransform(oldTx);
+
+ // Draw 6 header pins
+ drawPins(g2d, 0, controlPoints.length, false, outlineMode, drawingObserver);
+
+ g2d.setComposite(oldComposite);
+ }
+
+ @Override
+ public void drawIcon(Graphics2D g2d, int width, int height) {
+ g2d.setColor(BME_PURPLE);
+ g2d.fill(new RoundRectangle2D.Double(2, 4, width - 4, height - 8, 3, 3));
+ g2d.setColor(SENSOR_METAL);
+ g2d.fill(new RoundRectangle2D.Double(width / 2 - 5, height / 2 - 4, 10, 8, 2, 2));
+ g2d.setColor(Color.BLACK);
+ g2d.fill(new Ellipse2D.Double(width / 2 - 3, height / 2 - 2, 2, 2));
+ }
+}
diff --git a/diylc/diylc-library/src/main/java/org/diylc/components/sensors/SoilMoistureSensor.java b/diylc/diylc-library/src/main/java/org/diylc/components/sensors/SoilMoistureSensor.java
new file mode 100644
index 00000000..ed62773b
--- /dev/null
+++ b/diylc/diylc-library/src/main/java/org/diylc/components/sensors/SoilMoistureSensor.java
@@ -0,0 +1,196 @@
+/*
+ *
+ * DIY Layout Creator (DIYLC).
+ * Copyright (c) 2009-2025 held jointly by the individual authors.
+ *
+ * This file is part of DIYLC.
+ *
+ * DIYLC is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DIYLC is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with DIYLC. If not, see .
+ *
+ */
+package org.diylc.components.sensors;
+
+import java.awt.Color;
+import java.awt.Composite;
+import java.awt.Graphics2D;
+import java.awt.Shape;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Path2D;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.awt.geom.RoundRectangle2D;
+
+import org.diylc.awt.StringUtils;
+import org.diylc.common.HorizontalAlignment;
+import org.diylc.common.ObjectCache;
+import org.diylc.common.Orientation;
+import org.diylc.common.VerticalAlignment;
+import org.diylc.components.AbstractMakerBoard;
+import org.diylc.core.ComponentState;
+import org.diylc.core.IDIYComponent;
+import org.diylc.core.IDrawingObserver;
+import org.diylc.core.Project;
+import org.diylc.core.annotations.BomPolicy;
+import org.diylc.core.annotations.ComponentDescriptor;
+import org.diylc.core.annotations.KeywordPolicy;
+import org.diylc.core.measures.Size;
+import org.diylc.core.measures.SizeUnit;
+import org.diylc.utils.Constants;
+
+@ComponentDescriptor(name = "Soil Moisture Sensor (Capacitive)", category = "Sensors",
+ author = "Branislav Stojkovic", description = "Capacitive Corrosion-Resistant Soil Moisture Sensor Module",
+ instanceNamePrefix = "SEN", zOrder = IDIYComponent.COMPONENT,
+ bomPolicy = BomPolicy.SHOW_ONLY_TYPE_NAME, keywordPolicy = KeywordPolicy.SHOW_TYPE_NAME,
+ enableCache = true)
+public class SoilMoistureSensor extends AbstractMakerBoard {
+
+ private static final long serialVersionUID = 1L;
+
+ public static Color SOIL_PCB = Color.decode("#17202A");
+ public static Color PROBE_TRACE = Color.decode("#273746");
+
+ public static Size BOARD_WIDTH = new Size(60.0d, SizeUnit.mm);
+ public static Size BOARD_HEIGHT = new Size(20.0d, SizeUnit.mm);
+
+ public SoilMoistureSensor() {
+ super();
+ this.bodyColor = SOIL_PCB;
+ updateControlPoints();
+ }
+
+ @Override
+ protected void updateControlPoints() {
+ Point2D firstPoint = controlPoints[0];
+ double spacing = PIN_SPACING.convertToPixels();
+
+ // 3-pin 0.1" header on handle: VCC, GND, AOUT
+ double[][] relativeOffsets = new double[][] {
+ { 0, 0 },
+ { 0, spacing },
+ { 0, spacing * 2 }
+ };
+
+ rotatePoints(firstPoint, relativeOffsets);
+ }
+
+ @Override
+ public Shape getBodyShape() {
+ Point2D p0 = controlPoints[0];
+ double x = p0.getX();
+ double y = p0.getY();
+ double boardW = BOARD_WIDTH.convertToPixels();
+ double boardH = BOARD_HEIGHT.convertToPixels();
+ double boardX = x - 15;
+ double boardY = y - (boardH - 2 * PIN_SPACING.convertToPixels()) / 2.0;
+
+ Path2D path = new Path2D.Double();
+ path.moveTo(boardX, boardY);
+ path.lineTo(boardX + boardW * 0.35, boardY);
+ path.lineTo(boardX + boardW - 15, boardY + 4);
+ path.lineTo(boardX + boardW, boardY + boardH / 2.0);
+ path.lineTo(boardX + boardW - 15, boardY + boardH - 4);
+ path.lineTo(boardX + boardW * 0.35, boardY + boardH);
+ path.lineTo(boardX, boardY + boardH);
+ path.closePath();
+ return path;
+ }
+
+ @Override
+ public void draw(Graphics2D g2d, ComponentState componentState, boolean outlineMode, Project project,
+ IDrawingObserver drawingObserver) {
+ if (checkPointsClipped(g2d.getClip())) {
+ return;
+ }
+
+ Point2D p0 = controlPoints[0];
+ double x = p0.getX();
+ double y = p0.getY();
+
+ AffineTransform oldTx = g2d.getTransform();
+ if (orientation != Orientation.DEFAULT) {
+ g2d.rotate(orientation.toRadians(), x, y);
+ }
+
+ double boardW = BOARD_WIDTH.convertToPixels();
+ double boardH = BOARD_HEIGHT.convertToPixels();
+ double boardX = x - 15;
+ double boardY = y - (boardH - 2 * PIN_SPACING.convertToPixels()) / 2.0;
+
+ Shape boardShape = getBodyShape();
+
+ Composite oldComposite = applyAlpha(g2d, componentState);
+
+ drawingObserver.startTracking();
+ g2d.setColor(outlineMode ? Constants.TRANSPARENT_COLOR : bodyColor);
+ g2d.fill(boardShape);
+ drawingObserver.stopTracking();
+
+ g2d.setColor(getFinalBorderColor(componentState, outlineMode));
+ g2d.setStroke(ObjectCache.getInstance().fetchBasicStroke(1.5f));
+ g2d.draw(boardShape);
+
+ if (!outlineMode) {
+ // Capacitive Sensing Copper Area (Right blade section)
+ double bladeX = boardX + boardW * 0.38;
+ double bladeW = boardW * 0.55;
+ g2d.setColor(PROBE_TRACE);
+ g2d.fill(new RoundRectangle2D.Double(bladeX, boardY + 12, bladeW, boardH - 24, 6, 6));
+
+ // White silk dividing line & warning
+ g2d.setColor(Color.WHITE);
+ g2d.setStroke(ObjectCache.getInstance().fetchBasicStroke(1));
+ g2d.drawLine((int)(boardX + boardW * 0.35), (int)boardY + 4, (int)(boardX + boardW * 0.35), (int)(boardY + boardH - 4));
+
+ // NE555 / TLC555 Oscillator Timer IC (Handle area)
+ drawChip(g2d, boardX + 45, boardY + boardH / 2.0 - 14, 28, 28, "555");
+
+ // 3.3V Voltage Regulator
+ drawChip(g2d, boardX + 85, boardY + boardH / 2.0 - 10, 18, 20, "");
+
+ // Silk Screen Text
+ g2d.setColor(Color.WHITE);
+ g2d.setFont(SILK_FONT_SMALL);
+ StringUtils.drawCenteredText(g2d, "Capacitive Soil Sensor v1.2", bladeX + bladeW / 2.0, boardY + boardH / 2.0, HorizontalAlignment.CENTER, VerticalAlignment.CENTER);
+
+ // Pin labels
+ String[] labels = new String[] { "VCC", "GND", "AOUT" };
+ double spacing = PIN_SPACING.convertToPixels();
+ for (int i = 0; i < 3; i++) {
+ StringUtils.drawCenteredText(g2d, labels[i], x + 16, y + i * spacing, HorizontalAlignment.LEFT, VerticalAlignment.CENTER);
+ }
+ }
+
+ g2d.setTransform(oldTx);
+
+ // Draw 3 header pins
+ drawPins(g2d, 0, controlPoints.length, false, outlineMode, drawingObserver);
+
+ g2d.setComposite(oldComposite);
+ }
+
+ @Override
+ public void drawIcon(Graphics2D g2d, int width, int height) {
+ g2d.setColor(SOIL_PCB);
+ Path2D p = new Path2D.Double();
+ p.moveTo(2, 6);
+ p.lineTo(width - 10, 6);
+ p.lineTo(width - 2, height / 2.0);
+ p.lineTo(width - 10, height - 6);
+ p.lineTo(2, height - 6);
+ p.closePath();
+ g2d.fill(p);
+ g2d.setColor(PROBE_TRACE);
+ g2d.fillRect(width / 2, 8, width / 2 - 8, height - 16);
+ }
+}
diff --git a/diylc/diylc-library/src/main/java/org/diylc/components/sensors/TCRT5000Sensor.java b/diylc/diylc-library/src/main/java/org/diylc/components/sensors/TCRT5000Sensor.java
new file mode 100644
index 00000000..72bc2123
--- /dev/null
+++ b/diylc/diylc-library/src/main/java/org/diylc/components/sensors/TCRT5000Sensor.java
@@ -0,0 +1,188 @@
+/*
+ *
+ * DIY Layout Creator (DIYLC).
+ * Copyright (c) 2009-2025 held jointly by the individual authors.
+ *
+ * This file is part of DIYLC.
+ *
+ * DIYLC is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DIYLC is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with DIYLC. If not, see .
+ *
+ */
+package org.diylc.components.sensors;
+
+import java.awt.Color;
+import java.awt.Composite;
+import java.awt.Graphics2D;
+import java.awt.Shape;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Ellipse2D;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.awt.geom.RoundRectangle2D;
+
+import org.diylc.awt.StringUtils;
+import org.diylc.common.HorizontalAlignment;
+import org.diylc.common.ObjectCache;
+import org.diylc.common.Orientation;
+import org.diylc.common.VerticalAlignment;
+import org.diylc.components.AbstractMakerBoard;
+import org.diylc.core.ComponentState;
+import org.diylc.core.IDIYComponent;
+import org.diylc.core.IDrawingObserver;
+import org.diylc.core.Project;
+import org.diylc.core.annotations.BomPolicy;
+import org.diylc.core.annotations.ComponentDescriptor;
+import org.diylc.core.annotations.KeywordPolicy;
+import org.diylc.core.measures.Size;
+import org.diylc.core.measures.SizeUnit;
+import org.diylc.utils.Constants;
+
+@ComponentDescriptor(name = "Line / Obstacle Sensor (TCRT5000)", category = "Sensors",
+ author = "Branislav Stojkovic", description = "TCRT5000 Infrared Line Tracking and Optical Obstacle Avoidance Sensor",
+ instanceNamePrefix = "SEN", zOrder = IDIYComponent.COMPONENT,
+ bomPolicy = BomPolicy.SHOW_ONLY_TYPE_NAME, keywordPolicy = KeywordPolicy.SHOW_TYPE_NAME,
+ enableCache = true)
+public class TCRT5000Sensor extends AbstractMakerBoard {
+
+ private static final long serialVersionUID = 1L;
+
+ public static Color SENSOR_BLUE = Color.decode("#1B4F72");
+ public static Color POT_BLUE = Color.decode("#0055AA");
+
+ public static Size BOARD_WIDTH = new Size(32.0d, SizeUnit.mm);
+ public static Size BOARD_HEIGHT = new Size(14.0d, SizeUnit.mm);
+
+ public TCRT5000Sensor() {
+ super();
+ this.bodyColor = SENSOR_BLUE;
+ updateControlPoints();
+ }
+
+ @Override
+ protected void updateControlPoints() {
+ Point2D firstPoint = controlPoints[0];
+ double spacing = PIN_SPACING.convertToPixels();
+
+ // 4-pin 0.1" header: VCC, GND, DO, AO
+ double[][] relativeOffsets = new double[4][2];
+ for (int i = 0; i < 4; i++) {
+ relativeOffsets[i][0] = 0;
+ relativeOffsets[i][1] = i * spacing;
+ }
+
+ rotatePoints(firstPoint, relativeOffsets);
+ }
+
+ @Override
+ public Shape getBodyShape() {
+ Point2D p0 = controlPoints[0];
+ double x = p0.getX();
+ double y = p0.getY();
+ double boardW = BOARD_WIDTH.convertToPixels();
+ double boardH = BOARD_HEIGHT.convertToPixels();
+ double boardX = x - boardW + 20;
+ double boardY = y - (boardH - 3 * PIN_SPACING.convertToPixels()) / 2.0;
+ return new RoundRectangle2D.Double(boardX, boardY, boardW, boardH, 4, 4);
+ }
+
+ @Override
+ public void draw(Graphics2D g2d, ComponentState componentState, boolean outlineMode, Project project,
+ IDrawingObserver drawingObserver) {
+ if (checkPointsClipped(g2d.getClip())) {
+ return;
+ }
+
+ Point2D p0 = controlPoints[0];
+ double x = p0.getX();
+ double y = p0.getY();
+
+ AffineTransform oldTx = g2d.getTransform();
+ if (orientation != Orientation.DEFAULT) {
+ g2d.rotate(orientation.toRadians(), x, y);
+ }
+
+ double boardW = BOARD_WIDTH.convertToPixels();
+ double boardH = BOARD_HEIGHT.convertToPixels();
+ double boardX = x - boardW + 20;
+ double boardY = y - (boardH - 3 * PIN_SPACING.convertToPixels()) / 2.0;
+
+ Shape boardShape = getBodyShape();
+
+ Composite oldComposite = applyAlpha(g2d, componentState);
+
+ drawingObserver.startTracking();
+ g2d.setColor(outlineMode ? Constants.TRANSPARENT_COLOR : bodyColor);
+ g2d.fill(boardShape);
+ drawingObserver.stopTracking();
+
+ g2d.setColor(getFinalBorderColor(componentState, outlineMode));
+ g2d.setStroke(ObjectCache.getInstance().fetchBasicStroke(1.5f));
+ g2d.draw(boardShape);
+
+ if (!outlineMode) {
+ // TCRT5000 Optical Package (Left Edge)
+ g2d.setColor(Color.decode("#2C3E50"));
+ g2d.fill(new RoundRectangle2D.Double(boardX - 10, boardY + boardH / 2.0 - 20, 36, 40, 4, 4));
+ // Blue IR LED
+ g2d.setColor(Color.decode("#3498DB"));
+ g2d.fill(new Ellipse2D.Double(boardX - 6, boardY + boardH / 2.0 - 15, 14, 14));
+ // Black Phototransistor
+ g2d.setColor(Color.BLACK);
+ g2d.fill(new Ellipse2D.Double(boardX - 6, boardY + boardH / 2.0 + 1, 14, 14));
+
+ // LM393 Comparator IC (Center)
+ drawChip(g2d, boardX + 45, boardY + boardH / 2.0 - 14, 30, 28, "393");
+
+ // Sensitivity Trimmer Potentiometer (Right Center)
+ g2d.setColor(POT_BLUE);
+ g2d.fill(new RoundRectangle2D.Double(boardX + 90, boardY + boardH / 2.0 - 16, 32, 32, 3, 3));
+ g2d.setColor(Color.decode("#BDC3C7"));
+ g2d.fill(new Ellipse2D.Double(boardX + 96, boardY + boardH / 2.0 - 10, 20, 20));
+ g2d.setColor(Color.BLACK);
+ g2d.drawLine((int)(boardX + 99), (int)(boardY + boardH / 2.0), (int)(boardX + 113), (int)(boardY + boardH / 2.0));
+
+ // Status LEDs (Power & Signal)
+ g2d.setColor(Color.RED);
+ g2d.fill(new RoundRectangle2D.Double(boardX + 135, boardY + 12, 10, 8, 2, 2));
+ g2d.setColor(Color.GREEN);
+ g2d.fill(new RoundRectangle2D.Double(boardX + 135, boardY + boardH - 20, 10, 8, 2, 2));
+
+ // Silk Screen Pin Labels
+ g2d.setColor(Color.WHITE);
+ g2d.setFont(SILK_FONT_SMALL);
+ String[] labels = new String[] { "VCC", "GND", "DO", "AO" };
+ double spacing = PIN_SPACING.convertToPixels();
+ for (int i = 0; i < 4; i++) {
+ StringUtils.drawCenteredText(g2d, labels[i], boardX + boardW - 24, y + i * spacing, HorizontalAlignment.RIGHT, VerticalAlignment.CENTER);
+ }
+ }
+
+ g2d.setTransform(oldTx);
+
+ // Draw 4 header pins
+ drawPins(g2d, 0, controlPoints.length, false, outlineMode, drawingObserver);
+
+ g2d.setComposite(oldComposite);
+ }
+
+ @Override
+ public void drawIcon(Graphics2D g2d, int width, int height) {
+ g2d.setColor(SENSOR_BLUE);
+ g2d.fill(new RoundRectangle2D.Double(2, 4, width - 4, height - 8, 3, 3));
+ g2d.setColor(Color.CYAN);
+ g2d.fill(new Ellipse2D.Double(4, height / 2 - 6, 6, 6));
+ g2d.setColor(Color.BLACK);
+ g2d.fill(new Ellipse2D.Double(4, height / 2, 6, 6));
+ }
+}
diff --git a/diylc/diylc-library/src/test/java/org/diylc/components/maker/MakerComponentsTest.java b/diylc/diylc-library/src/test/java/org/diylc/components/maker/MakerComponentsTest.java
index fa21cb5c..ffd91539 100644
--- a/diylc/diylc-library/src/test/java/org/diylc/components/maker/MakerComponentsTest.java
+++ b/diylc/diylc-library/src/test/java/org/diylc/components/maker/MakerComponentsTest.java
@@ -75,6 +75,9 @@ public class MakerComponentsTest {
DHTSensor.class,
MPU6050.class,
LDRSensorModule.class,
+ BME280Sensor.class,
+ SoilMoistureSensor.class,
+ TCRT5000Sensor.class,
// Displays & Outputs
CharacterLCD.class,
@@ -82,6 +85,7 @@ public class MakerComponentsTest {
SevenSegmentDisplay.class,
LEDMatrix8x8.class,
TFTDisplay.class,
+ WS2812BStick.class,
// Modules & Breakouts
LogicLevelConverter.class,
@@ -90,6 +94,18 @@ public class MakerComponentsTest {
RelayModule.class,
SDCardModule.class,
RTCModule.class,
+ MB102PowerSupply.class,
+ TP4056Charger.class,
+ LM2596BuckConverter.class,
+ MT3608BoostConverter.class,
+ AnalogJoystickKY023.class,
+ RotaryEncoderKY040.class,
+ NRF24L01Transceiver.class,
+ HC05Bluetooth.class,
+ RFIDRC522.class,
+ ULN2003Driver.class,
+ PCA9685ServoDriver.class,
+ MOSFETSwitchModule.class,
// Robotics
MicroServoSG90.class,