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.
140 lines
5.2 KiB
Swift
140 lines
5.2 KiB
Swift
//
|
|
// SystemEditorView.swift
|
|
// Cable
|
|
//
|
|
// Created by Stefan Lange-Hegermann on 16.09.25.
|
|
//
|
|
|
|
import SwiftUI
|
|
|
|
struct SystemEditorView: View {
|
|
@Binding var systemName: String
|
|
@Binding var location: String
|
|
@Binding var iconName: String
|
|
@Binding var colorName: String
|
|
@Binding var maxVoltageDropPercent: Double
|
|
|
|
@State private var tempLocation: String
|
|
|
|
private let systemIcons = [
|
|
"building.2", "house", "building", "tent", "sailboat",
|
|
"airplane", "ferry", "bus", "truck.box",
|
|
"server.rack", "externaldrive", "cpu", "gear", "wrench.adjustable", "hammer",
|
|
"lightbulb", "bolt", "powerplug", "battery.100","sun.max",
|
|
"engine.combustion", "fuelpump", "drop", "flame", "snowflake", "thermometer"
|
|
]
|
|
|
|
/// Targets offered as presets: ABYC E-11 uses 3 % for critical circuits and 10 % for
|
|
/// non-critical ones, 5 % is the default middle ground.
|
|
private let voltageDropTargets: [Double] = [3, 5, 10]
|
|
|
|
init(
|
|
systemName: Binding<String>,
|
|
location: Binding<String>,
|
|
iconName: Binding<String>,
|
|
colorName: Binding<String>,
|
|
maxVoltageDropPercent: Binding<Double>
|
|
) {
|
|
self._systemName = systemName
|
|
self._location = location
|
|
self._iconName = iconName
|
|
self._colorName = colorName
|
|
self._maxVoltageDropPercent = maxVoltageDropPercent
|
|
self._tempLocation = State(initialValue: location.wrappedValue)
|
|
}
|
|
|
|
var body: some View {
|
|
let editorTitle = String(localized: "editor.system.title", comment: "Title for the system editor")
|
|
let namePlaceholder = String(localized: "editor.system.name_field", comment: "Label for the system name text field")
|
|
let locationPlaceholder = String(localized: "editor.system.location.optional", comment: "Placeholder text shown when no location is specified")
|
|
|
|
ItemEditorView(
|
|
title: editorTitle,
|
|
nameFieldLabel: namePlaceholder,
|
|
previewSubtitle: tempLocation.isEmpty ? locationPlaceholder : tempLocation,
|
|
icons: systemIcons,
|
|
name: $systemName,
|
|
iconName: $iconName,
|
|
colorName: $colorName,
|
|
additionalFields: {
|
|
AnyView(
|
|
VStack(alignment: .leading, spacing: 12) {
|
|
TextField(locationPlaceholder, text: $tempLocation)
|
|
.autocapitalization(.words)
|
|
.onChange(of: tempLocation) { _, newValue in
|
|
location = newValue
|
|
}
|
|
|
|
VStack(alignment: .leading, spacing: 6) {
|
|
Text(
|
|
String(
|
|
localized: "editor.system.voltage_drop.label",
|
|
defaultValue: "Voltage drop budget"
|
|
)
|
|
)
|
|
.font(.subheadline.weight(.semibold))
|
|
|
|
Picker(
|
|
String(
|
|
localized: "editor.system.voltage_drop.label",
|
|
defaultValue: "Voltage drop budget"
|
|
),
|
|
selection: $maxVoltageDropPercent
|
|
) {
|
|
ForEach(voltageDropTargets, id: \.self) { target in
|
|
Text(String(format: "%.0f %%", target)).tag(target)
|
|
}
|
|
}
|
|
.pickerStyle(.segmented)
|
|
.accessibilityIdentifier("system-voltage-drop-picker")
|
|
|
|
Text(voltageDropHint)
|
|
.font(.caption)
|
|
.foregroundStyle(.secondary)
|
|
}
|
|
}
|
|
)
|
|
}
|
|
)
|
|
.onAppear {
|
|
tempLocation = location
|
|
}
|
|
}
|
|
|
|
private var voltageDropHint: String {
|
|
switch maxVoltageDropPercent {
|
|
case ..<4:
|
|
return String(
|
|
localized: "editor.system.voltage_drop.hint.critical",
|
|
defaultValue: "3 % — for critical circuits like navigation, bilge pumps and electronics."
|
|
)
|
|
case ..<6:
|
|
return String(
|
|
localized: "editor.system.voltage_drop.hint.standard",
|
|
defaultValue: "5 % — balanced default for mixed installations."
|
|
)
|
|
default:
|
|
return String(
|
|
localized: "editor.system.voltage_drop.hint.noncritical",
|
|
defaultValue: "10 % — for non-critical loads such as cabin lighting."
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
#Preview {
|
|
@Previewable @State var name = "My System"
|
|
@Previewable @State var location = "Main Building"
|
|
@Previewable @State var icon = "building.2"
|
|
@Previewable @State var color = "blue"
|
|
@Previewable @State var dropTarget = 5.0
|
|
|
|
return SystemEditorView(
|
|
systemName: $name,
|
|
location: $location,
|
|
iconName: $icon,
|
|
colorName: $color,
|
|
maxVoltageDropPercent: $dropTarget
|
|
)
|
|
}
|