add inductance and voltage

This commit is contained in:
Bane Stojkovic
2026-11-10 10:40:34 +01:00
parent b2f99c7022
commit 025fcab11b
20 changed files with 318 additions and 202 deletions
@@ -102,8 +102,8 @@ public interface IPlugInPort extends ISelectionProcessor, IMouseProcessor, IKeyP
public static final String SNAP_TO_COMPONENTS = "Components"; public static final String SNAP_TO_COMPONENTS = "Components";
public static final String SNAP_TO_DEFAULT = SNAP_TO_GRID; public static final String SNAP_TO_DEFAULT = SNAP_TO_GRID;
public static final String VALUE_NOTATION_DECIMAL = "Decimal (4.7K, 0.22uF)"; public static final String VALUE_NOTATION_DECIMAL = "Decimal (4.7K, 0.22uF, 6.3V)";
public static final String VALUE_NOTATION_RKM = "RKM (4K7, 0u22)"; public static final String VALUE_NOTATION_RKM = "RKM (4K7, 0u22, 6V3)";
public static final String VALUE_NOTATION_DEFAULT = VALUE_NOTATION_DECIMAL; public static final String VALUE_NOTATION_DEFAULT = VALUE_NOTATION_DECIMAL;
public static final String SHOW_NODE_NAME_TOOLTIPS_KEY = "showNodeNameTooltips"; public static final String SHOW_NODE_NAME_TOOLTIPS_KEY = "showNodeNameTooltips";
@@ -41,13 +41,24 @@ public class Inductance extends AbstractMeasure<InductanceUnit> {
} }
public static Inductance parseCapacitance(String value) { public static Inductance parseCapacitance(String value) {
value = value.replace("*", ""); ValueNotation.ParsedMeasure<InductanceUnit> parsed;
for (InductanceUnit unit : InductanceUnit.values()) { try {
if (value.toLowerCase().endsWith(unit.toString().toLowerCase())) { parsed = ValueNotation.parse(value, InductanceUnit::fromSymbol);
value = value.substring(0, value.length() - unit.toString().length()).trim(); } catch (IllegalArgumentException e) {
return new Inductance(parse(value), unit); throw new IllegalArgumentException("Could not parse inductance: " + value, e);
}
} }
throw new IllegalArgumentException("Could not parse inductance: " + value); if (parsed.unit() == null) {
throw new IllegalArgumentException("Could not parse inductance: " + value);
}
return new Inductance(parsed.value(), parsed.unit());
}
@Override
public String toString() {
if (ValueNotation.getCurrent() == ValueNotation.RKM && value != null && unit != null
&& unit.getRkmSymbol() != null) {
return ValueNotation.formatRkm(value, unit.getRkmSymbol());
}
return super.toString();
} }
} }
@@ -23,14 +23,16 @@ package org.diylc.core.measures;
public enum InductanceUnit implements Unit { public enum InductanceUnit implements Unit {
pH(1, "pH"), nH(1e3, "nH"), uH(1e6, "uH"), mH(1e9, "mH"), H(1e12, "H"); pH(1, "pH", "p"), nH(1e3, "nH", "n"), uH(1e6, "uH", "u"), mH(1e9, "mH", "m"), H(1e12, "H", "H");
double factor; double factor;
String display; String display;
String rkmSymbol;
private InductanceUnit(double factor, String display) { private InductanceUnit(double factor, String display, String rkmSymbol) {
this.factor = factor; this.factor = factor;
this.display = display; this.display = display;
this.rkmSymbol = rkmSymbol;
} }
@Override @Override
@@ -38,8 +40,50 @@ public enum InductanceUnit implements Unit {
return factor; return factor;
} }
public String getRkmSymbol() {
return rkmSymbol;
}
@Override @Override
public String toString() { public String toString() {
return display; return display;
} }
/**
* Resolves the unit written after an inductance value, e.g. "uH", "u", "\u00B5H" or "H".
* Case does not matter when the trailing "H" is present. Without it, "M" is rejected so that it
* is never mistaken for mega. "R" is not accepted: inductor makers print 4R7 for 4.7uH, while
* in RKM it would mean 4.7H.
*
* @param symbol unit text with no surrounding whitespace
* @return the unit, or null when the text is not an inductance unit
*/
public static InductanceUnit fromSymbol(String symbol) {
if (symbol.equalsIgnoreCase("H")) {
return H;
}
String prefix = symbol;
if (symbol.length() == 2 && (symbol.endsWith("H") || symbol.endsWith("h"))) {
prefix = symbol.substring(0, 1).toLowerCase();
} else if (symbol.equals("M")) {
return null;
}
switch (prefix) {
case "p":
case "P":
return pH;
case "n":
case "N":
return nH;
case "u":
case "U":
case "\u00B5":
case "\u03BC":
return uH;
case "m":
return mH;
default:
return null;
}
}
} }
@@ -29,9 +29,10 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
/** /**
* Notation used to write resistance and capacitance values: plain decimal ("4.7K", "0.22uF") or * Notation used to write resistance, capacitance, inductance and voltage values: plain decimal
* RKM as defined by IEC 60062, where the unit symbol takes the place of the decimal point ("4K7", * ("4.7K", "0.22uF", "6.3V") or RKM, where the unit symbol takes the place of the decimal point
* "0u22"). Parsing accepts both regardless of the selected notation. * ("4K7", "0u22", "6V3"). IEC 60062 defines it for resistance and capacitance; inductance and
* voltage follow common practice. Parsing accepts both regardless of the selected notation.
* *
* @author Branislav Stojkovic * @author Branislav Stojkovic
*/ */
@@ -41,14 +41,16 @@ public class Voltage extends AbstractMeasure<VoltageUnit> {
} }
public static Voltage parseVoltage(String value) { public static Voltage parseVoltage(String value) {
value = value.replace("*", ""); ValueNotation.ParsedMeasure<VoltageUnit> parsed;
for (VoltageUnit unit : VoltageUnit.values()) { try {
if (value.toLowerCase().endsWith(unit.toString().toLowerCase())) { parsed = ValueNotation.parse(value, VoltageUnit::fromSymbol);
value = value.substring(0, value.length() - unit.toString().length()).trim(); } catch (IllegalArgumentException e) {
return new Voltage(parse(value), unit); throw new IllegalArgumentException("Could not parse voltage: " + value, e);
}
} }
throw new IllegalArgumentException("Could not parse voltage: " + value); if (parsed.unit() == null) {
throw new IllegalArgumentException("Could not parse voltage: " + value);
}
return new Voltage(parsed.value(), parsed.unit());
} }
public Double getNormalizedValue() { public Double getNormalizedValue() {
@@ -56,4 +58,13 @@ public class Voltage extends AbstractMeasure<VoltageUnit> {
return null; return null;
return value * unit.getFactor(); return value * unit.getFactor();
} }
@Override
public String toString() {
if (ValueNotation.getCurrent() == ValueNotation.RKM && value != null && unit != null
&& unit.getRkmSymbol() != null) {
return ValueNotation.formatRkm(value, unit.getRkmSymbol());
}
return super.toString();
}
} }
@@ -23,14 +23,17 @@ package org.diylc.core.measures;
public enum VoltageUnit implements Unit { public enum VoltageUnit implements Unit {
MV(1e-3, "mV"), V(1, "V"), KV(1e3, "KV"); // only volts have an RKM form (6V3, 3V3); 1m5 or 1k5 would not say that the value is a voltage
MV(1e-3, "mV", null), V(1, "V", "V"), KV(1e3, "KV", null);
double factor; double factor;
String display; String display;
String rkmSymbol;
private VoltageUnit(double factor, String display) { private VoltageUnit(double factor, String display, String rkmSymbol) {
this.factor = factor; this.factor = factor;
this.display = display; this.display = display;
this.rkmSymbol = rkmSymbol;
} }
@Override @Override
@@ -38,8 +41,31 @@ public enum VoltageUnit implements Unit {
return factor; return factor;
} }
public String getRkmSymbol() {
return rkmSymbol;
}
@Override @Override
public String toString() { public String toString() {
return display; return display;
} }
/**
* Resolves the unit written after a voltage value: "V", "mV" or "kV", case-insensitive.
*
* @param symbol unit text with no surrounding whitespace
* @return the unit, or null when the text is not a voltage unit
*/
public static VoltageUnit fromSymbol(String symbol) {
switch (symbol.toLowerCase()) {
case "v":
return V;
case "mv":
return MV;
case "kv":
return KV;
default:
return null;
}
}
} }
@@ -631,7 +631,7 @@
<changes> <changes>
<org.diylc.appframework.update.Change> <org.diylc.appframework.update.Change>
<changeType>NEW_FEATURE</changeType> <changeType>NEW_FEATURE</changeType>
<description>Added 'Value Notation' to the Config menu to show resistance and capacitance values in RKM notation, e.g. 4K7, 0R22, 2n2 or 0u22, instead of decimal; values can be typed in either notation, and typing the unit together with the value, e.g. 4K7 or 2.2nF, sets the unit at the same time</description> <description>Added 'Value Notation' to the Config menu to show resistance, capacitance, inductance and voltage values in RKM notation, e.g. 4K7, 0R22, 2n2, 4u7 or 6V3, instead of decimal; values can be typed in either notation, and typing the unit together with the value, e.g. 4K7 or 2.2nF, sets the unit at the same time</description>
</org.diylc.appframework.update.Change> </org.diylc.appframework.update.Change>
<org.diylc.appframework.update.Change> <org.diylc.appframework.update.Change>
<changeType>BUG_FIX</changeType> <changeType>BUG_FIX</changeType>
@@ -138,6 +138,62 @@ public class ValueNotationTest {
assertCapacitance(4.7, CapacitanceUnit.pF, "4p7"); assertCapacitance(4.7, CapacitanceUnit.pF, "4p7");
} }
@Test
public void rkmInductance() {
ValueNotation.setCurrent(ValueNotation.RKM);
assertEquals("4u7", new Inductance(4.7, InductanceUnit.uH).toString());
assertEquals("2n2", new Inductance(2.2, InductanceUnit.nH).toString());
assertEquals("10m", new Inductance(10d, InductanceUnit.mH).toString());
assertEquals("1H", new Inductance(1d, InductanceUnit.H).toString());
}
@Test
public void rkmVoltage() {
ValueNotation.setCurrent(ValueNotation.RKM);
assertEquals("6V3", new Voltage(6.3, VoltageUnit.V).toString());
assertEquals("3V3", new Voltage(3.3, VoltageUnit.V).toString());
assertEquals("400V", new Voltage(400d, VoltageUnit.V).toString());
assertEquals("500mV", new Voltage(500d, VoltageUnit.MV).toString());
assertEquals("1.5KV", new Voltage(1.5, VoltageUnit.KV).toString());
}
@Test
public void parsesInductance() {
assertInductance(4.7, InductanceUnit.uH, "4.7uH");
assertInductance(4.7, InductanceUnit.uH, "4u7");
assertInductance(4.7, InductanceUnit.uH, "4u7H");
assertInductance(4.7, InductanceUnit.uH, "4\u00B57");
assertInductance(2.2, InductanceUnit.nH, "2n2");
assertInductance(10, InductanceUnit.mH, "10mH");
assertInductance(10, InductanceUnit.mH, "10MH");
assertInductance(10, InductanceUnit.mH, "10m");
assertInductance(1, InductanceUnit.H, "1H");
assertInductance(100, InductanceUnit.pH, "100pH");
}
@Test
public void parsesVoltage() {
assertVoltage(6.3, VoltageUnit.V, "6.3V");
assertVoltage(6.3, VoltageUnit.V, "6V3");
assertVoltage(3.3, VoltageUnit.V, "3v3");
assertVoltage(0.8, VoltageUnit.V, "0V8");
assertVoltage(400, VoltageUnit.V, "400 V");
assertVoltage(500, VoltageUnit.MV, "500mV");
assertVoltage(1.5, VoltageUnit.KV, "1.5KV");
assertVoltage(1.5, VoltageUnit.KV, "1.5kV");
assertVoltage(1.5, VoltageUnit.KV, "1k5V");
}
@Test
public void rejectsInvalidInductanceAndVoltage() {
assertInductanceRejected("4R7");
assertInductanceRejected("4M7");
assertInductanceRejected("10");
assertVoltageRejected("4K7");
assertVoltageRejected("1k5");
assertVoltageRejected("16");
}
@Test @Test
public void bareNumberHasNoUnit() { public void bareNumberHasNoUnit() {
ValueNotation.ParsedMeasure<ResistanceUnit> parsed = ValueNotation.ParsedMeasure<ResistanceUnit> parsed =
@@ -172,6 +228,14 @@ public class ValueNotationTest {
Capacitance capacitance = new Capacitance(value, unit); Capacitance capacitance = new Capacitance(value, unit);
assertEquals(capacitance, Capacitance.parseCapacitance(capacitance.toString())); assertEquals(capacitance, Capacitance.parseCapacitance(capacitance.toString()));
} }
for (InductanceUnit unit : InductanceUnit.values()) {
Inductance inductance = new Inductance(value, unit);
assertEquals(inductance, Inductance.parseCapacitance(inductance.toString()));
}
for (VoltageUnit unit : VoltageUnit.values()) {
Voltage voltage = new Voltage(value, unit);
assertEquals(voltage, Voltage.parseVoltage(voltage.toString()));
}
} }
} }
} }
@@ -188,6 +252,36 @@ public class ValueNotationTest {
assertEquals(text, unit, parsed.getUnit()); assertEquals(text, unit, parsed.getUnit());
} }
private static void assertInductance(double value, InductanceUnit unit, String text) {
Inductance parsed = Inductance.parseCapacitance(text);
assertEquals(text, value, parsed.getValue(), DELTA);
assertEquals(text, unit, parsed.getUnit());
}
private static void assertVoltage(double value, VoltageUnit unit, String text) {
Voltage parsed = Voltage.parseVoltage(text);
assertEquals(text, value, parsed.getValue(), DELTA);
assertEquals(text, unit, parsed.getUnit());
}
private static void assertInductanceRejected(String text) {
try {
Inductance.parseCapacitance(text);
fail("Expected " + text + " to be rejected");
} catch (IllegalArgumentException e) {
// expected
}
}
private static void assertVoltageRejected(String text) {
try {
Voltage.parseVoltage(text);
fail("Expected " + text + " to be rejected");
} catch (IllegalArgumentException e) {
// expected
}
}
private static void assertRejected(String text, Class<?> unitType) { private static void assertRejected(String text, Class<?> unitType) {
try { try {
if (unitType == ResistanceUnit.class) { if (unitType == ResistanceUnit.class) {
@@ -45,12 +45,16 @@ import org.diylc.appframework.miscutils.ConfigurationManager;
import org.diylc.common.IPlugInPort; import org.diylc.common.IPlugInPort;
import org.diylc.core.measures.Capacitance; import org.diylc.core.measures.Capacitance;
import org.diylc.core.measures.CapacitanceUnit; import org.diylc.core.measures.CapacitanceUnit;
import org.diylc.core.measures.Inductance;
import org.diylc.core.measures.InductanceUnit;
import org.diylc.core.measures.Resistance; import org.diylc.core.measures.Resistance;
import org.diylc.core.measures.ResistanceUnit; import org.diylc.core.measures.ResistanceUnit;
import org.diylc.core.measures.Size; import org.diylc.core.measures.Size;
import org.diylc.core.measures.SizeUnit; import org.diylc.core.measures.SizeUnit;
import org.diylc.core.measures.ValueNotation; import org.diylc.core.measures.ValueNotation;
import org.diylc.core.measures.ValueNotation.ParsedMeasure; import org.diylc.core.measures.ValueNotation.ParsedMeasure;
import org.diylc.core.measures.Voltage;
import org.diylc.core.measures.VoltageUnit;
import org.diylc.swingframework.DoubleTextField; import org.diylc.swingframework.DoubleTextField;
import org.diylc.common.PropertyWrapper; import org.diylc.common.PropertyWrapper;
@@ -184,6 +188,12 @@ public class MeasureEditor extends Container {
if (Capacitance.class.equals(measureType)) { if (Capacitance.class.equals(measureType)) {
return CapacitanceUnit::fromSymbol; return CapacitanceUnit::fromSymbol;
} }
if (Inductance.class.equals(measureType)) {
return InductanceUnit::fromSymbol;
}
if (Voltage.class.equals(measureType)) {
return VoltageUnit::fromSymbol;
}
return null; return null;
} }
@@ -730,5 +730,5 @@ Delete|Löschen
Set default model|Als Standardmodell festlegen Set default model|Als Standardmodell festlegen
Remove default model|Standardmodell entfernen Remove default model|Standardmodell entfernen
Value Notation|Wertnotation Value Notation|Wertnotation
Decimal (4.7K, 0.22uF)|Dezimal (4.7K, 0.22uF) Decimal (4.7K, 0.22uF, 6.3V)|Dezimal (4.7K, 0.22uF, 6.3V)
RKM (4K7, 0u22)|RKM (4K7, 0u22) RKM (4K7, 0u22, 6V3)|RKM (4K7, 0u22, 6V3)
@@ -723,5 +723,5 @@ Delete
Set default model Set default model
Remove default model Remove default model
Value Notation Value Notation
Decimal (4.7K, 0.22uF) Decimal (4.7K, 0.22uF, 6.3V)
RKM (4K7, 0u22) RKM (4K7, 0u22, 6V3)
@@ -726,5 +726,5 @@ Delete|Eliminar
Set default model|Establecer como modelo predeterminado Set default model|Establecer como modelo predeterminado
Remove default model|Quitar modelo predeterminado Remove default model|Quitar modelo predeterminado
Value Notation|Notación de valores Value Notation|Notación de valores
Decimal (4.7K, 0.22uF)|Decimal (4.7K, 0.22uF) Decimal (4.7K, 0.22uF, 6.3V)|Decimal (4.7K, 0.22uF, 6.3V)
RKM (4K7, 0u22)|RKM (4K7, 0u22) RKM (4K7, 0u22, 6V3)|RKM (4K7, 0u22, 6V3)
@@ -725,5 +725,5 @@ Delete|Supprimer
Set default model|Définir comme modèle par défaut Set default model|Définir comme modèle par défaut
Remove default model|Retirer le modèle par défaut Remove default model|Retirer le modèle par défaut
Value Notation|Notation des valeurs Value Notation|Notation des valeurs
Decimal (4.7K, 0.22uF)|Décimale (4.7K, 0.22uF) Decimal (4.7K, 0.22uF, 6.3V)|Décimale (4.7K, 0.22uF, 6.3V)
RKM (4K7, 0u22)|RKM (4K7, 0u22) RKM (4K7, 0u22, 6V3)|RKM (4K7, 0u22, 6V3)
@@ -727,5 +727,5 @@ Delete|Elimina
Set default model|Imposta come modello predefinito Set default model|Imposta come modello predefinito
Remove default model|Rimuovi modello predefinito Remove default model|Rimuovi modello predefinito
Value Notation|Notazione dei valori Value Notation|Notazione dei valori
Decimal (4.7K, 0.22uF)|Decimale (4.7K, 0.22uF) Decimal (4.7K, 0.22uF, 6.3V)|Decimale (4.7K, 0.22uF, 6.3V)
RKM (4K7, 0u22)|RKM (4K7, 0u22) RKM (4K7, 0u22, 6V3)|RKM (4K7, 0u22, 6V3)
@@ -731,5 +731,5 @@ Delete|Verwijderen
Set default model|Instellen als standaardmodel Set default model|Instellen als standaardmodel
Remove default model|Standaardmodel verwijderen Remove default model|Standaardmodel verwijderen
Value Notation|Waardenotatie Value Notation|Waardenotatie
Decimal (4.7K, 0.22uF)|Decimaal (4.7K, 0.22uF) Decimal (4.7K, 0.22uF, 6.3V)|Decimaal (4.7K, 0.22uF, 6.3V)
RKM (4K7, 0u22)|RKM (4K7, 0u22) RKM (4K7, 0u22, 6V3)|RKM (4K7, 0u22, 6V3)
@@ -726,5 +726,5 @@ Delete|Excluir
Set default model|Definir como modelo padrão Set default model|Definir como modelo padrão
Remove default model|Remover modelo padrão Remove default model|Remover modelo padrão
Value Notation|Notação de valores Value Notation|Notação de valores
Decimal (4.7K, 0.22uF)|Decimal (4.7K, 0.22uF) Decimal (4.7K, 0.22uF, 6.3V)|Decimal (4.7K, 0.22uF, 6.3V)
RKM (4K7, 0u22)|RKM (4K7, 0u22) RKM (4K7, 0u22, 6V3)|RKM (4K7, 0u22, 6V3)
@@ -722,5 +722,5 @@ Delete|Obriši
Set default model|Postavi kao podrazumevani model Set default model|Postavi kao podrazumevani model
Remove default model|Ukloni podrazumevani model Remove default model|Ukloni podrazumevani model
Value Notation|Zapis vrednosti Value Notation|Zapis vrednosti
Decimal (4.7K, 0.22uF)|Decimalni (4.7K, 0.22uF) Decimal (4.7K, 0.22uF, 6.3V)|Decimalni (4.7K, 0.22uF, 6.3V)
RKM (4K7, 0u22)|RKM (4K7, 0u22) RKM (4K7, 0u22, 6V3)|RKM (4K7, 0u22, 6V3)
@@ -724,5 +724,5 @@ Delete|हटाएँ
Set default model|डिफ़ॉल्ट मॉडल सेट करें Set default model|डिफ़ॉल्ट मॉडल सेट करें
Remove default model|डिफ़ॉल्ट मॉडल हटाएँ Remove default model|डिफ़ॉल्ट मॉडल हटाएँ
Value Notation|मान संकेतन Value Notation|मान संकेतन
Decimal (4.7K, 0.22uF)|दशमलव (4.7K, 0.22uF) Decimal (4.7K, 0.22uF, 6.3V)|दशमलव (4.7K, 0.22uF, 6.3V)
RKM (4K7, 0u22)|RKM (4K7, 0u22) RKM (4K7, 0u22, 6V3)|RKM (4K7, 0u22, 6V3)
@@ -724,5 +724,5 @@ Delete|删除
Set default model|设为默认型号 Set default model|设为默认型号
Remove default model|取消默认型号 Remove default model|取消默认型号
Value Notation|数值表示法 Value Notation|数值表示法
Decimal (4.7K, 0.22uF)|小数 (4.7K, 0.22uF) Decimal (4.7K, 0.22uF, 6.3V)|小数 (4.7K, 0.22uF, 6.3V)
RKM (4K7, 0u22)|RKM (4K7, 0u22) RKM (4K7, 0u22, 6V3)|RKM (4K7, 0u22, 6V3)
+77 -158
View File
@@ -1,185 +1,104 @@
# RKM Value Notation for Resistance and Capacitance # RKM Value Notation
Let users read and type resistor and capacitor values in RKM (IEC 60062) notation, where the unit Resistance, capacitance, inductance and voltage values can be shown in RKM notation, where the unit
letter replaces the decimal point (`4K7`, `0R22`, `2n2`, `0u22`), as well as in today's decimal letter replaces the decimal point (`4K7`, `0R22`, `2n2`, `4u7`, `6V3`), instead of decimal (`4.7K`,
form (`4.7K`, `0.22uF`). Typing a value with a unit letter in it, in either notation, also sets the `0.22uF`, `6.3V`). Either notation can always be typed, and typing the unit together with the
unit, so `4K7` typed into a field whose unit is `Ω` becomes 4.7 K. value (`4K7`, `2.2nF`) also sets the unit.
IEC 60062 defines RKM for resistance and capacitance only. Inductance (`4u7`, `2n2`) and voltage
(`3V3`, `6V3`, zener `5V1`) follow common practice. Power, current and frequency are left out:
`0W25`, `1A6` or `4M43` are rare in practice.
## 1. User-facing behaviour ## 1. User-facing behaviour
### Config menu ### Config menu
One radio submenu in `ConfigPlugin` covering both resistance and capacitance, built the same way
as `Snap To` and `Ruler Inch Subdivision...` (`ToggleAction`, string config key, title stored as
the value):
``` ```
Config Config
... ...
Value Notation > (after Sticky Points, alphabetical like the rest) Value Notation > (after Sticky Points; icon IconLoader.Decimal)
(o) Decimal (4.7K, 0.22uF) (o) Decimal (4.7K, 0.22uF, 6.3V)
( ) RKM (4K7, 0u22) ( ) RKM (4K7, 0u22, 6V3)
...
``` ```
- Key `IPlugInPort.VALUE_NOTATION_KEY = "valueNotation"`, values `VALUE_NOTATION_DECIMAL` / One setting for all four measures: key `IPlugInPort.VALUE_NOTATION_KEY = "valueNotation"`, values
`VALUE_NOTATION_RKM`, default decimal. `VALUE_NOTATION_DECIMAL` / `VALUE_NOTATION_RKM` (the English menu titles, since `ToggleAction`
- Switching notation repaints the canvas immediately, so labels on the board change at once. stores its title), default decimal. Switching clears the drawing cache and repaints.
### What the notation controls (display) ### Display
The notation governs `Resistance.toString()` and `Capacitance.toString()` and therefore everything The notation governs `toString()` of `Resistance`, `Capacitance`, `Inductance` and `Voltage`, and
built on them: labels drawn on the canvas, `getValueForDisplay()` (search, BOM, netlist text, so canvas labels, `getValueForDisplay()`, search, BOM, netlist text and AI assistant context.
project explorer, AI assistant context). The edit dialog's value field is the exception: it always The edit dialog's value field is the exception: it always shows the bare decimal number, because
shows the bare decimal number, because the unit box beside it already carries the unit and an RKM the unit box beside it already shows the unit.
code there would repeat it.
| Value | Decimal (unchanged) | RKM | | Value | Decimal | RKM |
| --- | --- | --- | | --- | --- | --- |
| 4.7 Ω | `4.7Ω` | `4R7` | | 4.7 Ω / 0.22 Ω / 100 Ω | `4.7Ω` `0.22Ω` `100Ω` | `4R7` `0R22` `100R` |
| 0.22 Ω | `0.22Ω` | `0R22` | | 4.7 K / 1 M | `4.7K` `1M` | `4K7` `1M` |
| 100 Ω | `100Ω` | `100R` | | 0.22 uF / 2.2 nF / 100 nF | `0.22uF` `2.2nF` `100nF` | `0u22` `2n2` `100n` |
| 4.7 K | `4.7K` | `4K7` | | 4.7 uH / 10 mH | `4.7uH` `10mH` | `4u7` `10m` |
| 47 K | `47K` | `47K` | | 6.3 V / 400 V | `6.3V` `400V` | `6V3` `400V` |
| 1 M | `1M` | `1M` | | 500 mV / 1.5 KV | `500mV` `1.5KV` | unchanged: `500mV` `1.5KV` |
| 0.22 uF | `0.22uF` | `0u22` |
| 2.2 nF | `2.2nF` | `2n2` |
| 100 nF | `100nF` | `100n` |
| 4.7 pF | `4.7pF` | `4p7` |
| 1000 uF | `1000uF` | `1000u` |
Rules: RKM keeps the stored unit (it never renormalises 4700 Ω to 4K7), uses `R` for ohms, drops Rules: the value stays in its stored unit (4700 Ω is `4700R`, never `4K7`); `R` stands for ohms;
the trailing `F` on capacitance, puts the unit letter where the decimal point was, and writes it capacitance drops `F` and inductance drops `H`; whole numbers take the letter as a suffix; digits
as a suffix for whole numbers. The digits after the letter are rounded the same way as today's are rounded like the `0.####` decimal format. Only volts have an RKM form, since `1m5` or `1k5`
`0.####` decimal format. would not say the value is a voltage.
### What the user can type (input) ### Input
The value field accepts, in **both** notations: Accepted in either notation: a bare number (keeps the current unit), a decimal with a unit
(`4.7k`, `220 nF`, `4.7ohm`, `1.5kV`), or an RKM code (`4K7`, `R22`, `2n2F`, `4µ7`, `6V3`, `1k5V`).
Unit text per measure:
- a bare number (`4.7`, `0,22`) — the unit is unchanged, as today; - Resistance: `R`, `Ω`, `ohm(s)` → Ω; `K` → K; `M`, `Meg`, and lower-case `m` → M (there is no
- a decimal number with a unit (`4.7k`, `0.22uF`, `220 nF`, `4.7ohm`, `4.7Ω`); milliohm unit).
- an RKM code (`4K7`, `4k7`, `0R22`, `R22`, `2n2`, `0u22`, `4µ7`, `1M`, `100R`). - Capacitance / inductance: `p`, `n`, `u`/`µ`, `m`, optionally followed by `F` / `H`; `F` / `H`
alone. Case-insensitive with the trailing letter; without it, `M` is rejected so it is never
read as mega.
- Inductance does **not** accept `R`: maker codes print `4R7` for 4.7 µH, which RKM would read as
4.7 H.
- Voltage: `V`, `mV`, `kV`, case-insensitive.
When the text carries a unit, the unit combo switches to it and the property gets the new value The property updates on every keystroke (Enter in the dialog commits at once); on focus loss the
and unit together. Accepted unit letters, case-insensitive except where noted: text is rewritten as the bare decimal number. Unparseable text shows the warning icon.
- Resistance: `R`, `Ω`, `ohm`/`ohms` → Ω; `K`/`k` → K; `M`/`m`/`Meg` → M (there is no milliohm
unit, so a lower-case `m` on a resistor means mega, as it does on many parts lists).
- Capacitance: `p` → pF; `n` → nF; `u`/`µ` → uF; `m` → mF (**case-sensitive**: `M` is not
accepted, to avoid reading "mega"); `F` alone → F. A trailing `F` after any of these is
optional (`4u7F`, `2.2nF`).
A unit letter that does not belong to the measure (`4u7` on a resistor) shows the existing warning
icon, as any unparseable text does today.
**Commit behaviour.** The property is updated on every keystroke, as today — the edit dialog's
Enter key commits straight away, so it cannot wait for focus loss. When the field loses focus,
the text is rewritten as the bare decimal number (`4K7` typed → `4.7` with the combo showing `K`),
whichever notation is selected. The text is not reformatted while the user is typing, so the
caret never jumps.
## 2. Design ## 2. Design
### Core (`diylc-core`, `org.diylc.core.measures`) - **`org.diylc.core.measures.ValueNotation`** — enum `DECIMAL, RKM` with the global
`getCurrent()` / `setCurrent()` (a volatile static, read by `toString()` on every repaint),
`formatRkm(value, symbol)` and `parse(text, unitResolver)` returning a `ParsedMeasure(value,
unit)` record (unit `null` for a bare number).
- **Unit enums** — `ResistanceUnit`, `CapacitanceUnit`, `InductanceUnit` and `VoltageUnit` carry an
`rkmSymbol` (`null` for mV and KV) and a static `fromSymbol(String)` resolver. Constant names and
`toString()` are unchanged, so `.diy` files are unaffected.
- **Measures** — `toString()` returns RKM when selected and the unit has a symbol, else
`super.toString()`. `parseResistance`, `parseCapacitance`, `Inductance.parseCapacitance`
(historical name) and `parseVoltage` delegate to `ValueNotation.parse`, so the AI assistant,
`V1FileParser`, `TangoNetlistParser` and `CapacitorDatasheetService` read RKM too.
`parseVoltage` used to reject `1KV`; it now parses it.
- **`org.diylc.presenter.ValueNotationBinder`** — applies the config value and listens for changes.
Bound from `ConfigPlugin.connect()` rather than the `Presenter` constructor, because the
secondary presenters (previews, thumbnails, batch converter, regression runner) run on in-memory
configurations and would reset the global notation to decimal.
- **Swing** — `MeasureTextField` (in DIYLC, not `bancika-gui-framework`, whose
`lib/swing-framework-1.1.jar` would need re-vendoring) parses without rewriting the text while
typing. `MeasureEditor.getUnitResolver` picks it for the four measures; `DoubleTextField` serves
the rest. A typed unit switches the unit box behind a guard flag. `MeasureArrayEditor`
(`MultiSectionCapacitor`) parses `/`-separated sections, each with an optional unit; the box
shows the first section's unit and a section in another unit is written with it (`47 / 100nF`).
- **Localization** — `Value Notation`, `Decimal (4.7K, 0.22uF, 6.3V)` and `RKM (4K7, 0u22, 6V3)`
in every file in `lang/`.
- **`ValueNotation`** — new enum `DECIMAL, RKM` holding the formatting and parsing logic, so the ## 3. Status and next steps
two measure classes stay thin:
- `String format(double value, Unit unit, String rkmSymbol)` — decimal falls through to the
existing `0.####` + `unit.toString()`; RKM implements the rules in §1.
- `static ParsedMeasure parse(String text, ...)` — returns value + unit (unit `null` for a bare
number), or throws `IllegalArgumentException`. One small record `ParsedMeasure<U>` at the
bottom of the file.
- **Current notation.** One `private static volatile ValueNotation current` on `ValueNotation`
with static `getCurrent()` / `setCurrent()`, shared by `Resistance` and `Capacitance`.
`toString()` reads the field, never
`ConfigurationManager`: it runs from `draw()` on every repaint. Default `DECIMAL`, so headless
rendering, the regression suite and unit tests are unchanged unless they opt in.
- **Unit display symbol for RKM.** `ResistanceUnit` and `CapacitanceUnit` get an `rkmSymbol`
constructor argument (`R`/`K`/`M`, `p`/`n`/`u`/`m`/`F`). Enum constant names and `toString()`
are untouched — XStream stores the constant name, so files are unaffected.
- **Parsing.** `Resistance.parseResistance` / `Capacitance.parseCapacitance` delegate to
`ValueNotation.parse`, so RKM is accepted everywhere a string is turned into a value: the
editor, `V1FileParser`, `TangoNetlistParser`, `CapacitorDatasheetService` and — important once
`toString()` can emit `4K7` — the AI assistant, which receives values as text and writes them
back through these parsers. Existing accepted inputs must keep parsing identically.
- `Inductance` and `Power` are out of scope; their names are left as they are.
### Wiring the setting Implemented; `mvn -q test` passes, including `ValueNotationTest` (formatting, parsing, rejection
and round trips for all four measures). With the default decimal notation every string the app
produces is unchanged.
`org.diylc.presenter.ValueNotationBinder.bind(configManager, refresh)` reads the key once, 1. Try it in the running app: the edit dialog (single value and `MultiSectionCapacitor`),
pushes it into `ValueNotation.setCurrent()`, and on change updates the static, calls inductor and capacitor voltage properties, canvas labels after switching, BOM export.
`DrawingCache.Instance.clear()` (cached component images contain the old labels) and refreshes
the canvas. It is called from `ConfigPlugin.connect()`, not from the `Presenter` constructor:
the notation is global, and the many secondary presenters (project preview, cloud thumbnails,
batch converter, regression runner) are built on in-memory configurations and would reset it to
decimal.
### Swing (`diylc-swing`)
- **`MeasureTextField`** — new field in `org.diylc.swing.gui.editor`, beside `MeasureEditor`.
Same look as `DoubleTextField` (warning icon on the right) but it parses through
`ValueNotation.parse` for the measure type and fires a `MEASURE_PROPERTY` change carrying
value + optional unit; on `focusLost` it reformats its text. It is generic over the unit enum
so it can serve `Inductance` later without changes.
Recommendation: keep it in DIYLC rather than extending `DoubleTextField` in
`bancika-gui-framework`. The framework is consumed as the vendored
`lib/swing-framework-1.1.jar`, so a change there means rebuilding and re-vendoring the jar for a
widget whose parsing rules are DIYLC domain knowledge. `DoubleTextField` keeps serving
`PercentageEditor`, `LoadlineEditorFrame` and every other plain-number measure.
One `DoubleTextField` bug is worth fixing in the framework separately, and the new field must
not copy it: `DecimalFormat.parseObject(String)` accepts a valid prefix, so `4R7` silently
parses as `4` today, with no warning.
- **`MeasureEditor`** — uses `MeasureTextField` when the property type is `Resistance` or
`Capacitance`, `DoubleTextField` otherwise. On a value + unit event it sets the combo's
selected unit behind a guard flag, so the combo's own `ActionListener` does not fire a second
`setValue`. Read-only properties keep today's behaviour (disabled field, combo converts).
- **`MeasureArrayEditor`** (`MultiSectionCapacitor`) — each section value is parsed with the same
rules. Units travel per section in the text (`47u / 22u / 10u`) and a section without a unit
takes the combo's unit, as today. The combo shows the first section's unit; in decimal notation a
section in a different unit is written with its unit (`47 / 100nF`). Changing the combo applies
its unit to every section, as before.
- **`ConfigPlugin`** — the `Value Notation` submenu (`IconLoader.Decimal`), and the call to
`ValueNotationBinder.bind`.
### Localization
New keys in `English.txt` and every other file in `lang/`: `Value Notation`,
`Decimal (4.7K, 0.22uF)`, `RKM (4K7, 0u22)`.
Because `ToggleAction` stores its title as the config value, the stored values are the English
titles and `MainFrame` translates only what is shown.
## 3. Compatibility
- `.diy` files store `value` and the unit enum name; neither changes. No serialization impact.
- With the default (decimal) every string the app produces is byte-identical to today, so
regression references do not move. A regression run is still worth doing after implementation,
since `parseResistance` / `parseCapacitance` change.
- A user who switches to RKM gets RKM in BOM and netlist exports as well. That is the intended
"everywhere" behaviour; anything that re-imports those files goes through the RKM-aware
parsers.
## 4. Tests (`diylc-core`, JUnit 4)
`ValueNotationTest`:
- formatting: every row of the §1 table, both notations; zero, null, values ≥ 1000 in their unit,
rounding at the fourth decimal;
- parsing: every input form in §1, per measure; case rules (`m` vs `M` for capacitance); comma as
decimal separator; rejects `4u7` on a resistor, `4K7K`, `K`, empty text;
- round trip: `parse(format(x))` returns `x` for both notations;
- regression guard: every string currently accepted by `parseResistance` / `parseCapacitance`
(the inputs in `V1FileParser`, `TangoNetlistParser` and `CapacitorDatasheetService` tests and
`AiEditScriptEditorTest`) still parses to the same value.
## 5. Status and next steps
Implemented as described; `mvn -q test` passes, including `ValueNotationTest`.
1. Try it in the running app: the edit dialog (single value and `MultiSectionCapacitor`), canvas
labels after switching notation, BOM export.
2. Run the regression suite to confirm decimal output is unchanged. 2. Run the regression suite to confirm decimal output is unchanged.
3. Optional, in `bancika-gui-framework`: stop `DoubleTextField` / `DoubleArrayTextField` 3. Optional, in `bancika-gui-framework`: stop `DoubleTextField` / `DoubleArrayTextField` accepting
accepting a valid prefix (`4R7` read as `4`). a valid prefix (`4R7` read as `4`).