Files
Cable/Cable/UnitSystem.swift
Stefan Lange-Hegermann 38183f5282 Calculator: size cables for ampacity and drop budget
recommendedCrossSection now satisfies two criteria and takes whichever
demands more copper: the voltage-drop budget, and an ampacity at least
as high as the fuse protecting the circuit (ABYC E-11: the OCP rating
must not exceed the conductor's ampacity). Ampacity comes from ABYC
E-11 Table 6A for AWG and ISO 13297 Table A1 for mm², both 105 °C.

Before this, a 40 A load over 0.5 m was sized 1.5 mm² / AWG 16 next to
a 50 A fuse: a cable the fuse can never protect. Long runs stay
voltage-drop driven and unchanged.

The drop budget is now stored per system (3 / 5 / 10 %) and editable in
the system editor, with a global default in Settings that the
standalone calculator uses and new systems inherit.

Cross-sections are derived, never entered by the user, so LoadCableSync
re-derives them when a system opens and when its budget changes.
Library loads are computed instead of being stored as a 1.0 mm²
placeholder. LoadConfigurationStatus therefore reports only what the
user can actually fix: missing length or current.

Also removes the dead cable.pro.* strings and folds three copies of the
mm²-to-AWG conversion into ElectricalCalculations.nearestAWG.
2026-08-18 10:59:16 +02:00

82 lines
2.4 KiB
Swift

//
// UnitSystem.swift
// Cable
//
// Created by Stefan Lange-Hegermann on 11.09.25.
//
import Foundation
enum UnitSystem: String, CaseIterable {
case metric = "metric"
case imperial = "imperial"
var displayName: String {
switch self {
case .metric:
return String(localized: "units.metric.display", comment: "Display name for the metric unit system")
case .imperial:
return String(localized: "units.imperial.display", comment: "Display name for the imperial unit system")
}
}
var wireAreaUnit: String {
switch self {
case .metric:
return "mm²"
case .imperial:
return "AWG"
}
}
var lengthUnit: String {
switch self {
case .metric:
return "m"
case .imperial:
return "ft"
}
}
}
enum LocaleDefaults {
private static let lowVoltageRegions: Set<String> = [
"US", "CA", "MX", "JP", "TW", "CO", "VE", "BR"
]
static var mainsVoltage: Double {
let region = Locale.current.region?.identifier.uppercased() ?? ""
return lowVoltageRegions.contains(region) ? 120.0 : 230.0
}
}
@MainActor
class UnitSystemSettings: ObservableObject {
@Published var unitSystem: UnitSystem {
didSet {
UserDefaults.standard.set(unitSystem.rawValue, forKey: "unitSystem")
AnalyticsTracker.log("Unit System Changed", properties: ["system": unitSystem.rawValue])
}
}
/// Applies to the standalone calculator and seeds newly created systems.
@Published var voltageDropTargetPercent: Double {
didSet {
UserDefaults.standard.set(voltageDropTargetPercent, forKey: "voltageDropTargetPercent")
AnalyticsTracker.log("Voltage Drop Target Changed", properties: ["percent": voltageDropTargetPercent])
}
}
init() {
if let saved = UserDefaults.standard.string(forKey: "unitSystem"),
let system = UnitSystem(rawValue: saved) {
self.unitSystem = system
} else {
self.unitSystem = Locale.current.measurementSystem == .us ? .imperial : .metric
}
let savedTarget = UserDefaults.standard.double(forKey: "voltageDropTargetPercent")
self.voltageDropTargetPercent = savedTarget > 0
? savedTarget
: ElectricalCalculations.defaultMaxVoltageDropPercent
}
}