├── Assets
├── .DS_Store
├── Others
│ ├── Front.PNG
│ └── Banner.PNG
├── FocusEntity
│ ├── .DS_Store
│ ├── Sources
│ │ ├── .DS_Store
│ │ └── FocusEntity
│ │ │ └── .DS_Store
│ └── LICENSE
├── Markup Images
│ ├── KCL.PNG
│ ├── KVL.PNG
│ ├── Series.PNG
│ ├── OhmsLaw.PNG
│ ├── Parallel.PNG
│ ├── Capacitance.PNG
│ └── Inductance.PNG
├── USDZ Models
│ ├── Battery.usdz
│ ├── Ground.usdz
│ ├── Source.usdz
│ ├── ArrowSign.usdz
│ ├── Capacitor.usdz
│ ├── Inductor.usdz
│ ├── MinusSign.usdz
│ ├── PlusSign.usdz
│ ├── Resistor.usdz
│ ├── WireCross.usdz
│ ├── WireCorner.usdz
│ ├── WireStraight.usdz
│ └── WireTshape.usdz
└── Circuit Grid Images
│ ├── Arrow.PNG
│ ├── Diode.PNG
│ ├── Battery.PNG
│ ├── Ground1.PNG
│ ├── Ground2.PNG
│ ├── Ground3.PNG
│ ├── Source.PNG
│ ├── Inductor.PNG
│ ├── LightBulb.PNG
│ ├── Resistor.PNG
│ ├── WireColor.PNG
│ ├── WireCross.PNG
│ ├── NegativeSign.PNG
│ ├── PositiveSign.PNG
│ ├── WireCornerDL.PNG
│ ├── WireCornerDR.PNG
│ ├── WireCornerUL.PNG
│ ├── WireCornerUR.PNG
│ ├── WireGround1.PNG
│ ├── WireGround2.PNG
│ ├── WireStraight.PNG
│ ├── CapacitorHead.PNG
│ ├── CapacitorShape.PNG
│ ├── InductorShape.PNG
│ ├── WireCornerShape.PNG
│ ├── WireCrossShape.PNG
│ ├── WireGroundShape.PNG
│ ├── WireTshapeDLR.PNG
│ ├── WireTshapeDUL.PNG
│ ├── WireTshapeDUR.PNG
│ ├── WireTshapeShape.PNG
│ ├── WireTshapeULR.PNG
│ ├── WireStraightShape.PNG
│ └── WireCondensedShape.PNG
├── Screenshots
├── Header.PNG
├── Screenshot-1.PNG
├── Screenshot-2.PNG
├── Screenshot-3.PNG
├── Screenshot-4.PNG
└── Screenshot-5.PNG
├── CircuitPlay.playgroundbook
└── Contents
│ ├── UserModules
│ ├── CircuitKit.playgroundmodule
│ │ └── Sources
│ │ │ ├── Location.swift
│ │ │ ├── Circuit+Errors.swift
│ │ │ ├── Circuit+ComponentActions.swift
│ │ │ ├── ComponentProtocols.swift
│ │ │ ├── ComponentEnums.swift
│ │ │ ├── CircuitGrid.swift
│ │ │ ├── Circuit.swift
│ │ │ ├── Circuit+Debug.swift
│ │ │ ├── UnitEnums.swift
│ │ │ ├── Presets.swift
│ │ │ ├── Circuit+Navigation.swift
│ │ │ ├── LocationEnums.swift
│ │ │ ├── Contacts.swift
│ │ │ └── Units.swift
│ ├── CircuitUI.playgroundmodule
│ │ └── Sources
│ │ │ ├── ImageView.swift
│ │ │ ├── BlurView.swift
│ │ │ ├── NameView.swift
│ │ │ ├── AddButon.swift
│ │ │ ├── ViewEnums.swift
│ │ │ ├── GridLines.swift
│ │ │ ├── ComponentView.swift
│ │ │ ├── NextStepView.swift
│ │ │ ├── CurrentSignView.swift
│ │ │ ├── InterfacePicker.swift
│ │ │ ├── InductorView.swift
│ │ │ ├── ChevronButton.swift
│ │ │ ├── CapacitorView.swift
│ │ │ ├── SignsView.swift
│ │ │ ├── MeasurementView.swift
│ │ │ ├── StatePickerView.swift
│ │ │ ├── SideButtonsView.swift
│ │ │ ├── MainView.swift
│ │ │ ├── ArrowsView.swift
│ │ │ ├── WiredComponentView.swift
│ │ │ ├── ARContainerView.swift
│ │ │ ├── Environment.swift
│ │ │ ├── DockView.swift
│ │ │ ├── GroundView.swift
│ │ │ ├── InfoView.swift
│ │ │ ├── WireView.swift
│ │ │ ├── GridView.swift
│ │ │ ├── SlotView.swift
│ │ │ └── MeasurementEditView.swift
│ ├── CircuitNetwork.playgroundmodule
│ │ └── Sources
│ │ │ ├── Model.swift
│ │ │ ├── Node.swift
│ │ │ ├── Branch.swift
│ │ │ ├── ResistorModel.swift
│ │ │ ├── CurrentSourceModel.swift
│ │ │ ├── InductorModel.swift
│ │ │ ├── CapacitorModel.swift
│ │ │ ├── VoltageSourceModel.swift
│ │ │ ├── Network.swift
│ │ │ └── Network+Solver.swift
│ └── FocusEntity.playgroundmodule
│ │ └── Sources
│ │ ├── float4x4+Extension.swift
│ │ ├── FocusEntity+Colored.swift
│ │ ├── FocusEntityComponent.swift
│ │ ├── FocusEntity+Classic.swift
│ │ ├── FocusEntity+Segment.swift
│ │ └── FocusEntity+Alignment.swift
│ ├── PublicResources
│ ├── Arrow.PNG
│ ├── Diode.PNG
│ ├── Front.PNG
│ ├── KCL.PNG
│ ├── KVL.PNG
│ ├── Banner.PNG
│ ├── Battery.PNG
│ ├── Ground.usdz
│ ├── Ground1.PNG
│ ├── Ground2.PNG
│ ├── Ground3.PNG
│ ├── OhmsLaw.PNG
│ ├── Series.PNG
│ ├── Source.PNG
│ ├── Source.usdz
│ ├── ArrowSign.usdz
│ ├── Battery.usdz
│ ├── Capacitor.usdz
│ ├── Inductance.PNG
│ ├── Inductor.PNG
│ ├── Inductor.usdz
│ ├── LightBulb.PNG
│ ├── MinusSign.usdz
│ ├── Parallel.PNG
│ ├── PlusSign.usdz
│ ├── Resistor.PNG
│ ├── Resistor.usdz
│ ├── WireColor.PNG
│ ├── WireCross.PNG
│ ├── WireCross.usdz
│ ├── Capacitance.PNG
│ ├── NegativeSign.PNG
│ ├── PositiveSign.PNG
│ ├── WireCorner.usdz
│ ├── WireCornerDL.PNG
│ ├── WireCornerDR.PNG
│ ├── WireCornerUL.PNG
│ ├── WireCornerUR.PNG
│ ├── WireGround1.PNG
│ ├── WireGround2.PNG
│ ├── WireStraight.PNG
│ ├── WireTshape.usdz
│ ├── CapacitorHead.PNG
│ ├── CapacitorShape.PNG
│ ├── InductorShape.PNG
│ ├── WireCornerShape.PNG
│ ├── WireCrossShape.PNG
│ ├── WireGroundShape.PNG
│ ├── WireStraight.usdz
│ ├── WireTshapeDLR.PNG
│ ├── WireTshapeDUL.PNG
│ ├── WireTshapeDUR.PNG
│ ├── WireTshapeShape.PNG
│ ├── WireTshapeULR.PNG
│ ├── WireStraightShape.PNG
│ └── WireCondensedShape.PNG
│ ├── Chapters
│ └── CircuitPlay.playgroundchapter
│ │ ├── Pages
│ │ ├── Sandbox.playgroundpage
│ │ │ ├── Manifest.plist
│ │ │ └── main.swift
│ │ ├── Inductance.playgroundpage
│ │ │ ├── Manifest.plist
│ │ │ └── main.swift
│ │ ├── Capacitance.playgroundpage
│ │ │ ├── Manifest.plist
│ │ │ └── main.swift
│ │ ├── Introduction.playgroundpage
│ │ │ ├── Manifest.plist
│ │ │ └── main.swift
│ │ ├── Ohm's Law.playgroundpage
│ │ │ ├── Manifest.plist
│ │ │ └── main.swift
│ │ ├── Series Circuits.playgroundpage
│ │ │ ├── Manifest.plist
│ │ │ └── main.swift
│ │ └── Parallel Circuits.playgroundpage
│ │ │ ├── Manifest.plist
│ │ │ └── main.swift
│ │ └── Manifest.plist
│ └── Manifest.plist
└── README.md
/Assets/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/Assets/.DS_Store
--------------------------------------------------------------------------------
/Assets/Others/Front.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/Assets/Others/Front.PNG
--------------------------------------------------------------------------------
/Screenshots/Header.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/Screenshots/Header.PNG
--------------------------------------------------------------------------------
/Assets/Others/Banner.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/Assets/Others/Banner.PNG
--------------------------------------------------------------------------------
/Assets/FocusEntity/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/Assets/FocusEntity/.DS_Store
--------------------------------------------------------------------------------
/Assets/Markup Images/KCL.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/Assets/Markup Images/KCL.PNG
--------------------------------------------------------------------------------
/Assets/Markup Images/KVL.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/Assets/Markup Images/KVL.PNG
--------------------------------------------------------------------------------
/Screenshots/Screenshot-1.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/Screenshots/Screenshot-1.PNG
--------------------------------------------------------------------------------
/Screenshots/Screenshot-2.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/Screenshots/Screenshot-2.PNG
--------------------------------------------------------------------------------
/Screenshots/Screenshot-3.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/Screenshots/Screenshot-3.PNG
--------------------------------------------------------------------------------
/Screenshots/Screenshot-4.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/Screenshots/Screenshot-4.PNG
--------------------------------------------------------------------------------
/Screenshots/Screenshot-5.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/Screenshots/Screenshot-5.PNG
--------------------------------------------------------------------------------
/Assets/Markup Images/Series.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/Assets/Markup Images/Series.PNG
--------------------------------------------------------------------------------
/Assets/USDZ Models/Battery.usdz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/Assets/USDZ Models/Battery.usdz
--------------------------------------------------------------------------------
/Assets/USDZ Models/Ground.usdz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/Assets/USDZ Models/Ground.usdz
--------------------------------------------------------------------------------
/Assets/USDZ Models/Source.usdz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/Assets/USDZ Models/Source.usdz
--------------------------------------------------------------------------------
/Assets/Markup Images/OhmsLaw.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/Assets/Markup Images/OhmsLaw.PNG
--------------------------------------------------------------------------------
/Assets/Markup Images/Parallel.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/Assets/Markup Images/Parallel.PNG
--------------------------------------------------------------------------------
/Assets/USDZ Models/ArrowSign.usdz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/Assets/USDZ Models/ArrowSign.usdz
--------------------------------------------------------------------------------
/Assets/USDZ Models/Capacitor.usdz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/Assets/USDZ Models/Capacitor.usdz
--------------------------------------------------------------------------------
/Assets/USDZ Models/Inductor.usdz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/Assets/USDZ Models/Inductor.usdz
--------------------------------------------------------------------------------
/Assets/USDZ Models/MinusSign.usdz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/Assets/USDZ Models/MinusSign.usdz
--------------------------------------------------------------------------------
/Assets/USDZ Models/PlusSign.usdz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/Assets/USDZ Models/PlusSign.usdz
--------------------------------------------------------------------------------
/Assets/USDZ Models/Resistor.usdz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/Assets/USDZ Models/Resistor.usdz
--------------------------------------------------------------------------------
/Assets/USDZ Models/WireCross.usdz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/Assets/USDZ Models/WireCross.usdz
--------------------------------------------------------------------------------
/Assets/Circuit Grid Images/Arrow.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/Assets/Circuit Grid Images/Arrow.PNG
--------------------------------------------------------------------------------
/Assets/Circuit Grid Images/Diode.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/Assets/Circuit Grid Images/Diode.PNG
--------------------------------------------------------------------------------
/Assets/FocusEntity/Sources/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/Assets/FocusEntity/Sources/.DS_Store
--------------------------------------------------------------------------------
/Assets/Markup Images/Capacitance.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/Assets/Markup Images/Capacitance.PNG
--------------------------------------------------------------------------------
/Assets/Markup Images/Inductance.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/Assets/Markup Images/Inductance.PNG
--------------------------------------------------------------------------------
/Assets/USDZ Models/WireCorner.usdz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/Assets/USDZ Models/WireCorner.usdz
--------------------------------------------------------------------------------
/Assets/USDZ Models/WireStraight.usdz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/Assets/USDZ Models/WireStraight.usdz
--------------------------------------------------------------------------------
/Assets/USDZ Models/WireTshape.usdz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/Assets/USDZ Models/WireTshape.usdz
--------------------------------------------------------------------------------
/Assets/Circuit Grid Images/Battery.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/Assets/Circuit Grid Images/Battery.PNG
--------------------------------------------------------------------------------
/Assets/Circuit Grid Images/Ground1.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/Assets/Circuit Grid Images/Ground1.PNG
--------------------------------------------------------------------------------
/Assets/Circuit Grid Images/Ground2.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/Assets/Circuit Grid Images/Ground2.PNG
--------------------------------------------------------------------------------
/Assets/Circuit Grid Images/Ground3.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/Assets/Circuit Grid Images/Ground3.PNG
--------------------------------------------------------------------------------
/Assets/Circuit Grid Images/Source.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/Assets/Circuit Grid Images/Source.PNG
--------------------------------------------------------------------------------
/Assets/Circuit Grid Images/Inductor.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/Assets/Circuit Grid Images/Inductor.PNG
--------------------------------------------------------------------------------
/Assets/Circuit Grid Images/LightBulb.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/Assets/Circuit Grid Images/LightBulb.PNG
--------------------------------------------------------------------------------
/Assets/Circuit Grid Images/Resistor.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/Assets/Circuit Grid Images/Resistor.PNG
--------------------------------------------------------------------------------
/Assets/Circuit Grid Images/WireColor.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/Assets/Circuit Grid Images/WireColor.PNG
--------------------------------------------------------------------------------
/Assets/Circuit Grid Images/WireCross.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/Assets/Circuit Grid Images/WireCross.PNG
--------------------------------------------------------------------------------
/Assets/Circuit Grid Images/NegativeSign.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/Assets/Circuit Grid Images/NegativeSign.PNG
--------------------------------------------------------------------------------
/Assets/Circuit Grid Images/PositiveSign.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/Assets/Circuit Grid Images/PositiveSign.PNG
--------------------------------------------------------------------------------
/Assets/Circuit Grid Images/WireCornerDL.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/Assets/Circuit Grid Images/WireCornerDL.PNG
--------------------------------------------------------------------------------
/Assets/Circuit Grid Images/WireCornerDR.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/Assets/Circuit Grid Images/WireCornerDR.PNG
--------------------------------------------------------------------------------
/Assets/Circuit Grid Images/WireCornerUL.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/Assets/Circuit Grid Images/WireCornerUL.PNG
--------------------------------------------------------------------------------
/Assets/Circuit Grid Images/WireCornerUR.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/Assets/Circuit Grid Images/WireCornerUR.PNG
--------------------------------------------------------------------------------
/Assets/Circuit Grid Images/WireGround1.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/Assets/Circuit Grid Images/WireGround1.PNG
--------------------------------------------------------------------------------
/Assets/Circuit Grid Images/WireGround2.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/Assets/Circuit Grid Images/WireGround2.PNG
--------------------------------------------------------------------------------
/Assets/Circuit Grid Images/WireStraight.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/Assets/Circuit Grid Images/WireStraight.PNG
--------------------------------------------------------------------------------
/Assets/Circuit Grid Images/CapacitorHead.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/Assets/Circuit Grid Images/CapacitorHead.PNG
--------------------------------------------------------------------------------
/Assets/Circuit Grid Images/CapacitorShape.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/Assets/Circuit Grid Images/CapacitorShape.PNG
--------------------------------------------------------------------------------
/Assets/Circuit Grid Images/InductorShape.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/Assets/Circuit Grid Images/InductorShape.PNG
--------------------------------------------------------------------------------
/Assets/Circuit Grid Images/WireCornerShape.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/Assets/Circuit Grid Images/WireCornerShape.PNG
--------------------------------------------------------------------------------
/Assets/Circuit Grid Images/WireCrossShape.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/Assets/Circuit Grid Images/WireCrossShape.PNG
--------------------------------------------------------------------------------
/Assets/Circuit Grid Images/WireGroundShape.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/Assets/Circuit Grid Images/WireGroundShape.PNG
--------------------------------------------------------------------------------
/Assets/Circuit Grid Images/WireTshapeDLR.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/Assets/Circuit Grid Images/WireTshapeDLR.PNG
--------------------------------------------------------------------------------
/Assets/Circuit Grid Images/WireTshapeDUL.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/Assets/Circuit Grid Images/WireTshapeDUL.PNG
--------------------------------------------------------------------------------
/Assets/Circuit Grid Images/WireTshapeDUR.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/Assets/Circuit Grid Images/WireTshapeDUR.PNG
--------------------------------------------------------------------------------
/Assets/Circuit Grid Images/WireTshapeShape.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/Assets/Circuit Grid Images/WireTshapeShape.PNG
--------------------------------------------------------------------------------
/Assets/Circuit Grid Images/WireTshapeULR.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/Assets/Circuit Grid Images/WireTshapeULR.PNG
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/UserModules/CircuitKit.playgroundmodule/Sources/Location.swift:
--------------------------------------------------------------------------------
1 | public typealias Location = (x: Int, y: Int)
2 |
3 |
--------------------------------------------------------------------------------
/Assets/Circuit Grid Images/WireStraightShape.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/Assets/Circuit Grid Images/WireStraightShape.PNG
--------------------------------------------------------------------------------
/Assets/FocusEntity/Sources/FocusEntity/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/Assets/FocusEntity/Sources/FocusEntity/.DS_Store
--------------------------------------------------------------------------------
/Assets/Circuit Grid Images/WireCondensedShape.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/Assets/Circuit Grid Images/WireCondensedShape.PNG
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/PublicResources/Arrow.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/CircuitPlay.playgroundbook/Contents/PublicResources/Arrow.PNG
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/PublicResources/Diode.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/CircuitPlay.playgroundbook/Contents/PublicResources/Diode.PNG
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/PublicResources/Front.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/CircuitPlay.playgroundbook/Contents/PublicResources/Front.PNG
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/PublicResources/KCL.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/CircuitPlay.playgroundbook/Contents/PublicResources/KCL.PNG
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/PublicResources/KVL.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/CircuitPlay.playgroundbook/Contents/PublicResources/KVL.PNG
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/PublicResources/Banner.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/CircuitPlay.playgroundbook/Contents/PublicResources/Banner.PNG
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/PublicResources/Battery.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/CircuitPlay.playgroundbook/Contents/PublicResources/Battery.PNG
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/PublicResources/Ground.usdz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/CircuitPlay.playgroundbook/Contents/PublicResources/Ground.usdz
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/PublicResources/Ground1.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/CircuitPlay.playgroundbook/Contents/PublicResources/Ground1.PNG
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/PublicResources/Ground2.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/CircuitPlay.playgroundbook/Contents/PublicResources/Ground2.PNG
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/PublicResources/Ground3.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/CircuitPlay.playgroundbook/Contents/PublicResources/Ground3.PNG
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/PublicResources/OhmsLaw.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/CircuitPlay.playgroundbook/Contents/PublicResources/OhmsLaw.PNG
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/PublicResources/Series.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/CircuitPlay.playgroundbook/Contents/PublicResources/Series.PNG
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/PublicResources/Source.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/CircuitPlay.playgroundbook/Contents/PublicResources/Source.PNG
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/PublicResources/Source.usdz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/CircuitPlay.playgroundbook/Contents/PublicResources/Source.usdz
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/PublicResources/ArrowSign.usdz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/CircuitPlay.playgroundbook/Contents/PublicResources/ArrowSign.usdz
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/PublicResources/Battery.usdz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/CircuitPlay.playgroundbook/Contents/PublicResources/Battery.usdz
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/PublicResources/Capacitor.usdz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/CircuitPlay.playgroundbook/Contents/PublicResources/Capacitor.usdz
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/PublicResources/Inductance.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/CircuitPlay.playgroundbook/Contents/PublicResources/Inductance.PNG
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/PublicResources/Inductor.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/CircuitPlay.playgroundbook/Contents/PublicResources/Inductor.PNG
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/PublicResources/Inductor.usdz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/CircuitPlay.playgroundbook/Contents/PublicResources/Inductor.usdz
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/PublicResources/LightBulb.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/CircuitPlay.playgroundbook/Contents/PublicResources/LightBulb.PNG
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/PublicResources/MinusSign.usdz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/CircuitPlay.playgroundbook/Contents/PublicResources/MinusSign.usdz
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/PublicResources/Parallel.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/CircuitPlay.playgroundbook/Contents/PublicResources/Parallel.PNG
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/PublicResources/PlusSign.usdz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/CircuitPlay.playgroundbook/Contents/PublicResources/PlusSign.usdz
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/PublicResources/Resistor.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/CircuitPlay.playgroundbook/Contents/PublicResources/Resistor.PNG
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/PublicResources/Resistor.usdz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/CircuitPlay.playgroundbook/Contents/PublicResources/Resistor.usdz
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/PublicResources/WireColor.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/CircuitPlay.playgroundbook/Contents/PublicResources/WireColor.PNG
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/PublicResources/WireCross.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/CircuitPlay.playgroundbook/Contents/PublicResources/WireCross.PNG
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/PublicResources/WireCross.usdz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/CircuitPlay.playgroundbook/Contents/PublicResources/WireCross.usdz
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/PublicResources/Capacitance.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/CircuitPlay.playgroundbook/Contents/PublicResources/Capacitance.PNG
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/PublicResources/NegativeSign.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/CircuitPlay.playgroundbook/Contents/PublicResources/NegativeSign.PNG
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/PublicResources/PositiveSign.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/CircuitPlay.playgroundbook/Contents/PublicResources/PositiveSign.PNG
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/PublicResources/WireCorner.usdz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/CircuitPlay.playgroundbook/Contents/PublicResources/WireCorner.usdz
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/PublicResources/WireCornerDL.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/CircuitPlay.playgroundbook/Contents/PublicResources/WireCornerDL.PNG
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/PublicResources/WireCornerDR.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/CircuitPlay.playgroundbook/Contents/PublicResources/WireCornerDR.PNG
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/PublicResources/WireCornerUL.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/CircuitPlay.playgroundbook/Contents/PublicResources/WireCornerUL.PNG
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/PublicResources/WireCornerUR.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/CircuitPlay.playgroundbook/Contents/PublicResources/WireCornerUR.PNG
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/PublicResources/WireGround1.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/CircuitPlay.playgroundbook/Contents/PublicResources/WireGround1.PNG
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/PublicResources/WireGround2.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/CircuitPlay.playgroundbook/Contents/PublicResources/WireGround2.PNG
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/PublicResources/WireStraight.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/CircuitPlay.playgroundbook/Contents/PublicResources/WireStraight.PNG
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/PublicResources/WireTshape.usdz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/CircuitPlay.playgroundbook/Contents/PublicResources/WireTshape.usdz
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/PublicResources/CapacitorHead.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/CircuitPlay.playgroundbook/Contents/PublicResources/CapacitorHead.PNG
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/PublicResources/CapacitorShape.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/CircuitPlay.playgroundbook/Contents/PublicResources/CapacitorShape.PNG
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/PublicResources/InductorShape.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/CircuitPlay.playgroundbook/Contents/PublicResources/InductorShape.PNG
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/PublicResources/WireCornerShape.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/CircuitPlay.playgroundbook/Contents/PublicResources/WireCornerShape.PNG
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/PublicResources/WireCrossShape.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/CircuitPlay.playgroundbook/Contents/PublicResources/WireCrossShape.PNG
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/PublicResources/WireGroundShape.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/CircuitPlay.playgroundbook/Contents/PublicResources/WireGroundShape.PNG
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/PublicResources/WireStraight.usdz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/CircuitPlay.playgroundbook/Contents/PublicResources/WireStraight.usdz
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/PublicResources/WireTshapeDLR.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/CircuitPlay.playgroundbook/Contents/PublicResources/WireTshapeDLR.PNG
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/PublicResources/WireTshapeDUL.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/CircuitPlay.playgroundbook/Contents/PublicResources/WireTshapeDUL.PNG
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/PublicResources/WireTshapeDUR.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/CircuitPlay.playgroundbook/Contents/PublicResources/WireTshapeDUR.PNG
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/PublicResources/WireTshapeShape.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/CircuitPlay.playgroundbook/Contents/PublicResources/WireTshapeShape.PNG
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/PublicResources/WireTshapeULR.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/CircuitPlay.playgroundbook/Contents/PublicResources/WireTshapeULR.PNG
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/PublicResources/WireStraightShape.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/CircuitPlay.playgroundbook/Contents/PublicResources/WireStraightShape.PNG
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/PublicResources/WireCondensedShape.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devjoseadolfo/CircuitPlay/HEAD/CircuitPlay.playgroundbook/Contents/PublicResources/WireCondensedShape.PNG
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/UserModules/CircuitKit.playgroundmodule/Sources/Circuit+Errors.swift:
--------------------------------------------------------------------------------
1 | public enum CircuitError: Error {
2 | case LocationOutOfBoundsError
3 | case MeasurementNotFound
4 | }
5 |
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/UserModules/CircuitUI.playgroundmodule/Sources/ImageView.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 |
3 | public struct SquareImageView: View {
4 | private var imageName: String
5 |
6 | public init(_ imageName: String) {
7 | self.imageName = imageName
8 | }
9 |
10 | public var body: some View {
11 | Image(uiImage: UIImage(named: imageName + ".PNG")!)
12 | .resizable()
13 | .aspectRatio(1, contentMode: .fit)
14 | }
15 | }
16 |
17 |
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/UserModules/CircuitUI.playgroundmodule/Sources/BlurView.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 |
3 | public struct BlurView: UIViewRepresentable {
4 | public init(){}
5 |
6 | public func makeUIView(context: Context) -> UIView {
7 | let view = UIVisualEffectView(effect: UIBlurEffect(style: .regular))
8 | DispatchQueue.main.async {
9 | view.superview?.superview?.backgroundColor = .clear
10 | }
11 | return view
12 | }
13 |
14 | public func updateUIView(_ uiView: UIView, context: Context) {}
15 | }
16 |
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/Chapters/CircuitPlay.playgroundchapter/Pages/Sandbox.playgroundpage/Manifest.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Name
6 | Sandbox
7 | LiveViewEdgeToEdge
8 |
9 | LiveViewMode
10 | VisibleByDefault
11 | PosterReference
12 | Front.PNG
13 | PlaygroundLoggingMode
14 | Off
15 |
16 |
17 |
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/Chapters/CircuitPlay.playgroundchapter/Pages/Inductance.playgroundpage/Manifest.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Name
6 | Inductance
7 | LiveViewEdgeToEdge
8 |
9 | LiveViewMode
10 | VisibleByDefault
11 | PosterReference
12 | Front.PNG
13 | PlaygroundLoggingMode
14 | Off
15 |
16 |
17 |
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/Chapters/CircuitPlay.playgroundchapter/Pages/Capacitance.playgroundpage/Manifest.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Name
6 | Capacitance
7 | LiveViewEdgeToEdge
8 |
9 | LiveViewMode
10 | VisibleByDefault
11 | PosterReference
12 | Front.PNG
13 | PlaygroundLoggingMode
14 | Off
15 |
16 |
17 |
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/Chapters/CircuitPlay.playgroundchapter/Pages/Introduction.playgroundpage/Manifest.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Name
6 | Introduction
7 | LiveViewEdgeToEdge
8 |
9 | LiveViewMode
10 | VisibleByDefault
11 | PlaygroundLoggingMode
12 | Off
13 | PosterReference
14 | Front.PNG
15 |
16 |
17 |
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/Chapters/CircuitPlay.playgroundchapter/Pages/Ohm's Law.playgroundpage/Manifest.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Name
6 | Ohm's Law
7 | LiveViewEdgeToEdge
8 |
9 | LiveViewMode
10 | VisibleByDefault
11 | PosterReference
12 | Front.PNG
13 | PlaygroundLoggingMode
14 | Off
15 |
16 |
17 |
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/Chapters/CircuitPlay.playgroundchapter/Pages/Series Circuits.playgroundpage/Manifest.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Name
6 | Series Circuits
7 | LiveViewEdgeToEdge
8 |
9 | LiveViewMode
10 | VisibleByDefault
11 | PosterReference
12 | Front.PNG
13 | PlaygroundLoggingMode
14 | Off
15 |
16 |
17 |
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/Chapters/CircuitPlay.playgroundchapter/Pages/Parallel Circuits.playgroundpage/Manifest.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Name
6 | Parallel Circuits
7 | LiveViewEdgeToEdge
8 |
9 | LiveViewMode
10 | VisibleByDefault
11 | PosterReference
12 | Front.PNG
13 | PlaygroundLoggingMode
14 | Off
15 |
16 |
17 |
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/UserModules/CircuitNetwork.playgroundmodule/Sources/Model.swift:
--------------------------------------------------------------------------------
1 | import CircuitKit
2 |
3 | public protocol Model: CustomStringConvertible {
4 | var number: Int { get }
5 | var component: Connectable { get }
6 | var location: Location { get }
7 |
8 | var connectedNodes: [Direction : Int] { get set}
9 |
10 | var voltage: Double { get set }
11 | var current: Double { get set }
12 |
13 | var currentGoing: Direction? { get set }
14 |
15 | var power: Double { get }
16 | }
17 |
18 | public protocol TerminalDependentModel: Model {
19 | var positiveTerminalNode: Int? { get set }
20 | var negativeTerminalNode: Int? { get set }
21 | }
22 |
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/Chapters/CircuitPlay.playgroundchapter/Manifest.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Name
6 | CircuitPlay
7 | Pages
8 |
9 | Introduction.playgroundpage
10 | Ohm's Law.playgroundpage
11 | Series Circuits.playgroundpage
12 | Parallel Circuits.playgroundpage
13 | Capacitance.playgroundpage
14 | Inductance.playgroundpage
15 | Sandbox.playgroundpage
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/UserModules/CircuitKit.playgroundmodule/Sources/Circuit+ComponentActions.swift:
--------------------------------------------------------------------------------
1 | extension Circuit {
2 | public mutating func removeComponent(from location: Location) {
3 | circuitGrid[location] = Slot(component: BlankSpace())
4 | }
5 |
6 | public mutating func moveComponent(from origin: Location, to destination: Location) {
7 | let selectedComp = circuitGrid[origin].component
8 | removeComponent(from: origin)
9 | replaceComponent(with: selectedComp, at: destination)
10 | }
11 |
12 | public mutating func replaceComponent(with comp: CircuitComponent, at destination: Location) {
13 | circuitGrid[destination] = Slot(component: comp)
14 | }
15 | }
16 |
17 |
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/UserModules/CircuitNetwork.playgroundmodule/Sources/Node.swift:
--------------------------------------------------------------------------------
1 | import CircuitKit
2 | import Foundation
3 |
4 | public struct Node: CustomStringConvertible, Identifiable {
5 | public var number: Int
6 | public var description: String { "N" + String(number) }
7 |
8 | public var connectedNodes: [String : Int] = [:]
9 | public var connectedBranches: [Int] = []
10 | public var connectedModels: [String] = []
11 |
12 | public var openings: [(Location, Direction)] = []
13 | public var locations: [Location] = []
14 |
15 | public var voltage: Double = 0
16 |
17 | public var id = UUID()
18 |
19 | public init(_ number: Int) {
20 | self.number = number
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/UserModules/CircuitNetwork.playgroundmodule/Sources/Branch.swift:
--------------------------------------------------------------------------------
1 | import CircuitKit
2 |
3 | public enum End {
4 | case first, last
5 | }
6 |
7 | public struct Branch: CustomStringConvertible {
8 | public var number: Int
9 | public var description: String { "B" + String(number) }
10 | public var path: [Location]
11 | public var connectedBranches: [Int: End] = [:]
12 |
13 | public var dependence: String?
14 | public var dependenceFirst: Location?
15 | public var dependenceLast: Location?
16 |
17 | public var isReversed: Bool = false
18 | public var current: Double = 0
19 | public init(_ count: Int, path: [Location]) {
20 | self.number = count
21 | self.path = path
22 | }
23 | }
24 |
25 |
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/UserModules/CircuitUI.playgroundmodule/Sources/NameView.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 | import CircuitKit
3 |
4 | public struct NameView: View {
5 | private var name: String
6 | public init(name: String) {
7 | self.name = name
8 | }
9 |
10 | public var body: some View {
11 | Text(name)
12 | .font(.system(size: 13.5, weight: .regular, design: .monospaced))
13 | .foregroundColor(Color.primary)
14 | .frame(height: 20, alignment: .center)
15 | .padding(.horizontal, 4)
16 | .background(Color.gray.opacity(0.25))
17 | .background(BlurView())
18 | .background(Color.white.opacity(0))
19 | .cornerRadius(4)
20 | }
21 |
22 | }
23 |
24 |
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/UserModules/CircuitNetwork.playgroundmodule/Sources/ResistorModel.swift:
--------------------------------------------------------------------------------
1 | import CircuitKit
2 |
3 | public struct ResistorModel: Model {
4 | public var number: Int
5 | public var description: String { "R" + String(number + 1)}
6 | public var component: Connectable
7 | public var location: Location
8 |
9 | public var connectedNodes: [Direction : Int] = [:]
10 |
11 | public var resistance: Double = 0
12 | public var voltage: Double = 0
13 | public var current: Double = 0
14 | public var power: Double { voltage * current }
15 |
16 | public var currentGoing: Direction?
17 |
18 | public init(for resistor: Resistor, count: Int, location: Location) {
19 | component = resistor
20 | number = count
21 | self.location = location
22 | resistance = resistor.measurements[0].value
23 | }
24 | }
25 |
26 |
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/UserModules/CircuitUI.playgroundmodule/Sources/AddButon.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 | import CircuitKit
3 |
4 | public struct AddButton: View {
5 | @EnvironmentObject var envi: CircuitEnvironment
6 | private var direction: Direction
7 |
8 | public init(to direction: Direction) {
9 | self.direction = direction
10 | }
11 |
12 | public var body: some View {
13 | Button {
14 | envi.selectedSlot = nil
15 | envi.circuit.shiftGrid(going: direction.opposite)
16 | } label: {
17 | Label("Add",
18 | systemImage: "plus")
19 | .labelStyle(IconOnlyLabelStyle())
20 | .imageScale(.large)
21 | .font(Font.body.weight(.bold))
22 | }
23 | .foregroundColor(Color.secondary)
24 | .buttonStyle(ChevronButtonStyle())
25 | }
26 | }
27 |
28 |
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/UserModules/CircuitUI.playgroundmodule/Sources/ViewEnums.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 |
3 | public enum ViewState: CaseIterable, Identifiable {
4 | case design
5 | case setup
6 | case play
7 |
8 | public var id: Int {
9 | self.hashValue
10 | }
11 | }
12 |
13 | public enum DragState {
14 | case unknown
15 | case valid
16 | }
17 |
18 | public enum ViewColor {
19 | case slotBackground
20 |
21 | var color: Color {
22 | switch self {
23 | case .slotBackground:
24 | return Color(UIColor.secondaryLabel)
25 | }
26 | }
27 | }
28 |
29 | extension Color {
30 | public func getBackground(for colorScheme: ColorScheme) -> Color {
31 | switch colorScheme {
32 | case .light:
33 | return Color(white: 0.5)
34 | case .dark:
35 | return Color(white: 0.65)
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/UserModules/FocusEntity.playgroundmodule/Sources/float4x4+Extension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // float4x4+Extension.swift
3 | // FocusEntity
4 | //
5 | // Created by Max Cobb on 8/26/19.
6 | // Copyright © 2019 Max Cobb. All rights reserved.
7 | //
8 |
9 | import simd
10 |
11 | internal extension float4x4 {
12 | /**
13 | Treats matrix as a (right-hand column-major convention) transform matrix
14 | and factors out the translation component of the transform.
15 | */
16 | var translation: SIMD3 {
17 | get {
18 | let translation = columns.3
19 | return SIMD3(translation.x, translation.y, translation.z)
20 | }
21 | set(newValue) {
22 | columns.3 = SIMD4(newValue.x, newValue.y, newValue.z, columns.3.w)
23 | }
24 | }
25 |
26 | /**
27 | Factors out the orientation component of the transform.
28 | */
29 | var orientation: simd_quatf {
30 | return simd_quaternion(self)
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/UserModules/CircuitKit.playgroundmodule/Sources/ComponentProtocols.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | public protocol CircuitComponent: CustomStringConvertible {
4 | var componentType: ComponentType { get }
5 | var connectionType: ConnectionType { get }
6 | var measurements: [CircuitUnit] { get set}
7 | mutating func rotate(_ rotation: Rotation)
8 | }
9 |
10 | public protocol Connectable: CircuitComponent {
11 | var contacts: Contacts { get }
12 | var rotationCount: Double { get }
13 | }
14 |
15 | public protocol Conductor: Connectable {}
16 |
17 | public protocol TerminalDependent {
18 | var terminals: (positive: Direction, negative: Direction) { get }
19 | }
20 |
21 | extension Connectable {
22 | func setMeasurement(of unit: CircuitUnit, to value: Double) throws {
23 | guard let index = (measurements.firstIndex{$0.prefixValue == unit.prefixValue}) else {
24 | throw CircuitError.MeasurementNotFound }
25 | measurements[index]
26 | }
27 | }
28 |
29 |
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/Manifest.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Chapters
6 |
7 | CircuitPlay.playgroundchapter
8 |
9 | ContentIdentifier
10 | com.apple.playgrounds.blank
11 | ContentVersion
12 | 1.0
13 | DeploymentTarget
14 | ios-current
15 | DevelopmentRegion
16 | en
17 | EnableConsole
18 |
19 | SwiftVersion
20 | 5.3
21 | Name
22 | CircuitPlay
23 | Version
24 | 8.0
25 | UserAutoImportedAuxiliaryModules
26 |
27 | UserModuleMode
28 | Full
29 | HasMeaningfulResetPoint
30 |
31 | ImageReference
32 | Banner.PNG
33 |
34 |
35 |
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/UserModules/CircuitUI.playgroundmodule/Sources/GridLines.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 |
3 | public struct GridLines: Shape {
4 | private var columns: Int
5 | private var rows: Int
6 |
7 | public init(columns: Int, rows: Int){
8 | self.columns = columns
9 | self.rows = rows
10 | }
11 |
12 | public func path(in rect: CGRect) -> Path {
13 | var xRange: ClosedRange = 0...(columns * 2)
14 | var yRange: ClosedRange = 0...(rows * 2)
15 | let offset: CGFloat = 0.5
16 | return Path { path in
17 | xRange.forEach { x in
18 | path.move(to: CGPoint(x: CGFloat(x * 64), y: -offset))
19 | path.addLine(to: CGPoint(x: CGFloat(x * 64), y: rect.height + offset))
20 | }
21 | yRange.forEach { y in
22 | path.move(to: CGPoint(x: -offset, y: CGFloat(y * 64)))
23 | path.addLine(to: CGPoint(x: rect.width + offset, y: CGFloat(y * 64)))
24 | }
25 | }
26 | }
27 | }
28 |
29 |
--------------------------------------------------------------------------------
/Assets/FocusEntity/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Max Fraser Cobb
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/Chapters/CircuitPlay.playgroundchapter/Pages/Sandbox.playgroundpage/main.swift:
--------------------------------------------------------------------------------
1 | /*:
2 | # Sandbox
3 | In this page, you will be free to create your own circuit!
4 |
5 | Here are some rules to remember when making a circuit:
6 | - Make sure there are no loose ends. Each terminal of a component should be connected to another terminal of a different component.
7 | - Each loop in a circuit should at least have one resistor. Otherwise, you would be createing an invalid circuit with a short.
8 | - Don't forget to add the ground. The ground serves a reference nodes so the simulator can determine the voltages of each node.
9 | - When using a capacitor or an inductor, always include a resistor in the loop as well. Without a resistor, the current or voltage across a capacitor or inductor will be an impulse, causing the values to be discontinuous.
10 |
11 | Have fun!
12 | */
13 | import SwiftUI
14 | import PlaygroundSupport
15 |
16 | let preset: CircuitPreset = .blank
17 | PlaygroundPage.current.wantsFullScreenLiveView = true
18 | PlaygroundPage.current.needsIndefiniteExecution = true
19 | PlaygroundPage.current.setLiveView(
20 | MainView()
21 | .environmentObject(CircuitEnvironment(circuit: preset.circuit))
22 | .ignoresSafeArea()
23 | )
24 |
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/UserModules/CircuitNetwork.playgroundmodule/Sources/CurrentSourceModel.swift:
--------------------------------------------------------------------------------
1 | import CircuitKit
2 |
3 | public struct CurrentSourceModel: TerminalDependentModel {
4 | public var number: Int
5 | public var description: String { "Is" + String(number + 1)}
6 | public var component: Connectable
7 | public var location: Location
8 |
9 | public var positiveTerminalNode: Int?
10 | public var negativeTerminalNode: Int?
11 |
12 | public var connectedNodes: [Direction : Int] = [:]
13 |
14 | public var voltage: Double = 0
15 | public var current: Double
16 | public var power: Double { voltage * current }
17 |
18 | public var currentGoing: Direction?
19 |
20 | public init(for currentSource: CurrentSource, count: Int, location: Location) {
21 | component = currentSource
22 | number = count
23 | self.location = location
24 | current = currentSource.measurements[0].value
25 | if current == 0 {
26 | currentGoing = nil
27 | } else if current > 0 {
28 | currentGoing = currentSource.terminals.positive
29 | } else {
30 | currentGoing = currentSource.terminals.negative
31 | }
32 | }
33 | }
34 |
35 |
36 |
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/UserModules/CircuitNetwork.playgroundmodule/Sources/InductorModel.swift:
--------------------------------------------------------------------------------
1 | import CircuitKit
2 |
3 | public struct InductorModel: TerminalDependentModel {
4 | public var number: Int
5 | public var description: String { "L" + String(number + 1)}
6 | public var component: Connectable
7 | public var location: Location
8 |
9 | public var positiveTerminalNode: Int?
10 | public var negativeTerminalNode: Int?
11 |
12 | public var timeStep: Double
13 |
14 | public var connectedNodes: [Direction : Int] = [:]
15 |
16 | public var inductance: Double
17 | public var equivalentResistance: Double { inductance / Double(timeStep)}
18 | public var equivalentVoltage: Double { -current * inductance / timeStep }
19 |
20 | public var voltage: Double = 0
21 | public var current: Double = 0.0000001
22 | public var power: Double { voltage * current }
23 |
24 | public var currentGoing: Direction?
25 |
26 | public init(for inductor: Inductor, count: Int, timeStep: Double, location: Location) {
27 | component = inductor
28 | number = count
29 | inductance = inductor.measurements[0].value
30 | self.location = location
31 | self.timeStep = timeStep
32 | }
33 | }
34 |
35 |
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/UserModules/CircuitNetwork.playgroundmodule/Sources/CapacitorModel.swift:
--------------------------------------------------------------------------------
1 | import CircuitKit
2 |
3 | public struct CapacitorModel: TerminalDependentModel {
4 | public var number: Int
5 | public var description: String { "C" + String(number + 1)}
6 | public var component: Connectable
7 | public var location: Location
8 |
9 | public var positiveTerminalNode: Int?
10 | public var negativeTerminalNode: Int?
11 |
12 | public var timeStep: Double
13 |
14 | public var connectedNodes: [Direction : Int] = [:]
15 |
16 | public var capacitance: Double
17 | public var equivalentResistance: Double { Double(timeStep) / capacitance }
18 | public var equivalentCurrent: Double { voltage * capacitance / timeStep}
19 |
20 | public var voltage: Double = 0.0000001
21 | public var current: Double = 0
22 | public var power: Double { voltage * current }
23 |
24 | public var currentGoing: Direction?
25 |
26 | public init(for capacitor: Capacitor, count: Int, timeStep: Double, location: Location) {
27 | component = capacitor
28 | number = count
29 | capacitance = capacitor.measurements[0].value
30 | self.location = location
31 | self.timeStep = timeStep
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/UserModules/FocusEntity.playgroundmodule/Sources/FocusEntity+Colored.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FocusEntity+Colored.swift
3 | // FocusEntity
4 | //
5 | // Created by Max Cobb on 8/26/19.
6 | // Copyright © 2019 Max Cobb. All rights reserved.
7 | //
8 |
9 | #if canImport(ARKit)
10 | import RealityKit
11 |
12 | /// An extension of FocusEntity holding the methods for the "colored" style.
13 | public extension FocusEntity {
14 |
15 | internal func coloredStateChanged() {
16 | guard let coloredStyle = self.focus.coloredStyle else {
17 | return
18 | }
19 | var endColor: MaterialColorParameter
20 | if self.state == .initializing {
21 | endColor = coloredStyle.nonTrackingColor
22 | } else {
23 | endColor = self.onPlane ? coloredStyle.onColor : coloredStyle.offColor
24 | }
25 | if self.fillPlane?.model?.materials.count == 0 {
26 | self.fillPlane?.model?.materials = [SimpleMaterial()]
27 | }
28 | // Necessary for transparency.
29 | var modelMaterial = UnlitMaterial(color: .clear)
30 | modelMaterial.baseColor = endColor
31 | // Necessary for transparency.
32 | modelMaterial.tintColor = Material.Color.white.withAlphaComponent(0.995)
33 | self.fillPlane?.model?.materials[0] = modelMaterial
34 | }
35 | }
36 | #endif
37 |
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/UserModules/CircuitUI.playgroundmodule/Sources/ComponentView.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 | import CircuitKit
3 |
4 | public struct ComponentView: View {
5 | private var component: CircuitComponent
6 |
7 | public init(component: CircuitComponent) {
8 | self.component = component
9 | }
10 |
11 | public var body: some View {
12 | switch component.componentType {
13 | case .wire:
14 | WireView(wire: component as! Wire)
15 | case .battery:
16 | WiredComponentView(component: component as! Connectable)
17 | case .resistor:
18 | WiredComponentView(component: component as! Connectable)
19 | case .currentSource:
20 | WiredComponentView(component: component as! Connectable)
21 | case .ground:
22 | GroundView(ground: component as! Ground)
23 | case .capacitor:
24 | CapacitorView(capacitor: component as! Capacitor)
25 | case .inductor:
26 | InductorView(inductor: component as! Inductor)
27 | default:
28 | Rectangle()
29 | .fill(Color.white.opacity(0.0001))
30 | .aspectRatio(1.0, contentMode: .fit)
31 | .frame(width: .infinity, height: .infinity)
32 | }
33 | }
34 | }
35 |
36 |
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/UserModules/CircuitUI.playgroundmodule/Sources/NextStepView.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 |
3 | public struct NextStepView: View {
4 | @EnvironmentObject var envi: CircuitEnvironment
5 | public init() {}
6 | public var body: some View {
7 | GeometryReader { geo in
8 | Button(action: {
9 | do {
10 | try envi.network?.solveNetwork()
11 | } catch {
12 |
13 | }
14 | }){
15 | Label("Next Time Step", systemImage: "forward.fill")
16 | .foregroundColor(Color.primary)
17 | }
18 | .buttonStyle(StepButtonStyle())
19 | .position(x: geo.frame(in: .global).midX, y: geo.frame(in: .global).maxY - 135)
20 | }
21 | }
22 | }
23 |
24 | private struct StepButtonStyle: ButtonStyle {
25 | init(){}
26 | func makeBody(configuration: Configuration) -> some View {
27 | configuration.label
28 | .scaleEffect(configuration.isPressed ? 1.05 : 1.0)
29 | .padding(8)
30 | .background(BlurView())
31 | .background(configuration.isPressed ? Color.blue.opacity(0.5) : Color.gray.opacity(0.25))
32 | .shadow(radius: configuration.isPressed ? 7.5 : 0)
33 | .cornerRadius(6)
34 | .animation(.linear, value: configuration.isPressed)
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/UserModules/CircuitUI.playgroundmodule/Sources/CurrentSignView.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 | import CircuitKit
3 |
4 | public struct CurrentSignView: View {
5 | private var direction: Direction
6 |
7 | public init(direction: Direction) {
8 | self.direction = direction
9 | }
10 |
11 | public var body: some View {
12 | GeometryReader{ geometry in
13 | SquareImageView("Arrow")
14 | .rotation3DEffect(.degrees(direction == .up || direction == .down ? 180 : 0), axis: (x: 1, y: 0, z: 0))
15 | .frame(width: geometry.size.width / 8, height: geometry.size.height / 8)
16 | .position(CGPoint(x: 7 * geometry.size.width / 8, y: 5 * geometry.size.height / 8) )
17 | SquareImageView("Arrow")
18 | .rotation3DEffect(.degrees(direction == .up || direction == .down ? 180 : 0), axis: (x: 1, y: 0, z: 0))
19 | .frame(width: geometry.size.width / 8, height: geometry.size.height / 8)
20 | .position(CGPoint(x: 1 * geometry.size.width / 8, y: 5 * geometry.size.height / 8))
21 | }.rotation3DEffect(.degrees(direction == .left ? 180 : 0), axis: (x: 0, y: 1, z: 0))
22 | .rotation3DEffect(.degrees(direction == .up || direction == .down ? -90 : 0), axis: (x: 0, y: 0, z: 1))
23 | .rotation3DEffect(.degrees(direction == .down ? 180 : 0), axis: (x: 1, y: 0, z: 0))
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/UserModules/CircuitUI.playgroundmodule/Sources/InterfacePicker.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 |
3 | public struct InterfacePicker: View {
4 | @EnvironmentObject var envi: CircuitEnvironment
5 |
6 | public var body: some View {
7 | GeometryReader { geo in
8 | ZStack{
9 | RoundedRectangle(cornerRadius: 8)
10 | .fill(Color.blue.opacity(0.5))
11 | .frame(width: 36, height: 36, alignment: .center)
12 | .opacity(envi.arMode ? 1 : 0)
13 | Toggle("", isOn: $envi.arMode)
14 | .toggleStyle(CheckMarkToggleStyle())
15 | }
16 | .frame(width: 52, height: 52, alignment: .center)
17 | .background(BlurView())
18 | .background(Color.gray.opacity(0.25))
19 | .cornerRadius(12)
20 | .position(x: geo.frame(in: .global).midX, y: geo.frame(in: .global).minY + 192)
21 | }.animation(.spring(), value: envi.currentState)
22 | }
23 | }
24 |
25 | struct CheckMarkToggleStyle: ToggleStyle {
26 | func makeBody(configuration: Self.Configuration) -> some View {
27 | HStack {
28 | Button(action: { configuration.isOn.toggle() } )
29 | {
30 | Image(systemName: "arkit")
31 | .foregroundColor(Color.primary)
32 | }
33 | }
34 | .font(.title)
35 | .padding(.horizontal)
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/Chapters/CircuitPlay.playgroundchapter/Pages/Inductance.playgroundpage/main.swift:
--------------------------------------------------------------------------------
1 | /*:
2 | # Inductance
3 | This page features a circuit with a resistor-inductor circuit. A capacitor is passive circuit component and it has the ability to store electric charge and oppose the current through it using the magnetic field generated by its coils. The voltage across a inductor is equal to the inductance of the inductor and the current change per unit time.
4 |
5 | 
6 |
7 | **Task:** Induce My Current
8 | 1. Tap `Run My Code` button on the right.
9 | 2. Click on the `Solve` button. What is the voltage across the inductor?
10 | 3. Tap on the `Next Time Step` button. What happens to voltage and current? Does it increase or decrease?
11 | 4. Change the value of the inductance? How does it affect the rate of change of the voltage and current?
12 |
13 | In a direct current circuit, the inductor would have stored its full capacity after five time constants, thus, the inductor will cause the circuit act as an open circuit.
14 |
15 | Go to the [Next Page](@next) where it will be your time to shine.
16 | */
17 | import SwiftUI
18 | import PlaygroundSupport
19 |
20 | let preset: CircuitPreset = .resistorInductor
21 | PlaygroundPage.current.wantsFullScreenLiveView = true
22 | PlaygroundPage.current.needsIndefiniteExecution = true
23 | PlaygroundPage.current.setLiveView(
24 | MainView()
25 | .environmentObject(CircuitEnvironment(circuit: preset.circuit))
26 | .ignoresSafeArea()
27 | )
28 |
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/Chapters/CircuitPlay.playgroundchapter/Pages/Capacitance.playgroundpage/main.swift:
--------------------------------------------------------------------------------
1 | /*:
2 | # Capacitance
3 | This page features a circuit with a resistor-capacitor circuit. A capacitor is passive circuit component and it has the ability to store electric charge using an electric field. The current across a capacitor is equal to the capacitance of the capacitor and the voltage change per unit time.
4 |
5 | 
6 |
7 | **Task:** Capacitate My Current
8 | 1. Tap `Run My Code` button on the right.
9 | 2. Click on the `Solve` button. What is the voltage across the capacitor?
10 | 3. Tap on the `Next Time Step` button. This should solve the circuit values after a time step of 10 millisecond. What happens to voltage and current? Does it increase or decrease?
11 | 4. Change the value of the capacitance? How does it affect the rate of change of the voltage and current?
12 |
13 | In a direct current circuit, the capacitor would have stored its full capacity after five time constants, thus, the capacitor will cause the circuit to short.
14 |
15 | Go to the [Next Page](@next) where you will learn about inductors.
16 | */
17 | import SwiftUI
18 | import PlaygroundSupport
19 |
20 | let preset: CircuitPreset = .resistorCapacitor
21 | PlaygroundPage.current.wantsFullScreenLiveView = true
22 | PlaygroundPage.current.needsIndefiniteExecution = true
23 | PlaygroundPage.current.setLiveView(
24 | MainView()
25 | .environmentObject(CircuitEnvironment(circuit: preset.circuit))
26 | .ignoresSafeArea()
27 | )
28 |
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/UserModules/CircuitUI.playgroundmodule/Sources/InductorView.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 | import CircuitKit
3 |
4 | public struct InductorView: View {
5 | private var inductor: Inductor
6 |
7 | private var rotationCount: Double { inductor.rotationCount }
8 |
9 | private var isVertical: Bool { inductor.contacts.orientation == .vertical}
10 | private var isFlippedY: Bool {
11 | return [-3.0,-2.0,1.0,2.0].contains(rotationCount.remainder(dividingBy: 4))
12 | }
13 |
14 | private var isFlippedX: Bool {
15 | return [-2.0,-1.0,2.0,3.0].contains(rotationCount.remainder(dividingBy: 4))
16 | }
17 |
18 | public init(inductor: Inductor) {
19 | self.inductor = inductor
20 | }
21 |
22 | public var body: some View {
23 | WireShape {
24 | SquareImageView("InductorShape")
25 | .rotation3DEffect( Angle(degrees: rotationCount * 90),
26 | axis: (x: 0, y: 0, z: 1))
27 | .scaleEffect(1.5)
28 | } overlay: {
29 | SquareImageView("Inductor")
30 | .rotation3DEffect( Angle(degrees: isFlippedY ? 180 : 0), axis: (x: 0, y: 1, z: 0))
31 | .rotation3DEffect( Angle(degrees: isFlippedX ? 180 : 0), axis: (x: 1, y: 0, z: 0))
32 | .rotation3DEffect( Angle(degrees: isVertical ? 180 : 0), axis: (x: 0, y: 1, z: 0))
33 | .rotation3DEffect( Angle(degrees: rotationCount * 90), axis: (x: 0, y: 0, z: 1))
34 | }
35 |
36 | }
37 | }
38 |
39 |
40 |
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/Chapters/CircuitPlay.playgroundchapter/Pages/Introduction.playgroundpage/main.swift:
--------------------------------------------------------------------------------
1 | /*:
2 | # CircuitPlay
3 | WWDC21 Swift Student Challenge Submission by Jose Adolfo Talactac
4 |
5 | ## Overview
6 | Hello, welcome to CircuitPlay! CircuitPlay is an interactive sandbox experience that allows you to design, setup, and simulate circuit. Each page in this playground can be experienced for 30 seconds or less. However, since this is a sandbox environment, feel free to do what ever you wish!
7 |
8 | ## Credits
9 | - [Barlow font](https://github.com/jpt/barlow) designed by Jeremy Tribby found in the Playground thumbnail and the images in each PlaygroundPage is used under the [Open Font License](https://github.com/jpt/barlow/blob/main/OFL.txt).
10 | - San Francisco font found in the Playground’s interface is accessed as part of iPadOS’ system fonts and used fairly within the license set by Apple Inc. for the development of software for Apple platforms.
11 | - [Focus Entity](https://github.com/maxxfrazer/FocusEntity) Swift Package by Max Cobb is used in the augmented reality component of the Playground for placing the model entity in the view. The package is used under the [MIT license](https://github.com/maxxfrazer/FocusEntity/blob/main/LICENSE).
12 |
13 | - Important:
14 | This Playground has an augmented reality component tested on an iPad Pro 12.9-inch (3rd Generation) model. It is recommended to run this Playground in a full screen Live View, without any other app running in Split View and Slide Over and without any videos running in Picture-in-Picture.
15 |
16 | Go to the [Next Page](@next) once you're ready.
17 | */
18 |
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/UserModules/CircuitNetwork.playgroundmodule/Sources/VoltageSourceModel.swift:
--------------------------------------------------------------------------------
1 | import CircuitKit
2 |
3 | public struct VoltageSourceModel: TerminalDependentModel {
4 | public var number: Int
5 | public var description: String { "Vs" + String(number + 1)}
6 | public var component: Connectable
7 | public var location: Location
8 |
9 | public var positiveTerminalNode: Int?
10 | public var negativeTerminalNode: Int?
11 |
12 | public var connectedNodes: [Direction : Int] = [:]
13 |
14 | public var voltage: Double
15 | public var current: Double = 0
16 | public var power: Double { voltage * current }
17 |
18 | public var currentGoing: Direction?
19 |
20 | public var signal: SignalType = .direct
21 | public var amplitude: Double = 0
22 | public var frequency: Double = 0
23 |
24 | public init(for voltageSource: Connectable, count: Int, location: Location) {
25 | component = voltageSource
26 | number = count
27 | voltage = voltageSource.measurements[0].value
28 | self.location = location
29 |
30 | let terminalDependent = voltageSource as! TerminalDependent
31 |
32 | if voltage == 0 {
33 | currentGoing = nil
34 | } else if voltage > 0 {
35 | currentGoing = terminalDependent.terminals.positive
36 | } else {
37 | currentGoing = terminalDependent.terminals.negative
38 | }
39 |
40 | if let source = voltageSource as? VoltageSource {
41 | amplitude = source.measurements[0].value
42 | frequency = source.measurements[1].value
43 | signal = source.signal
44 | }
45 | }
46 | }
47 |
48 |
49 |
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/Chapters/CircuitPlay.playgroundchapter/Pages/Parallel Circuits.playgroundpage/main.swift:
--------------------------------------------------------------------------------
1 | /*:
2 | # Parallel Circuits
3 | This page features a parallel circuit with a voltage source and two resistors. In a parallel circuit, the equivalent resistance of the circuit is equal to the inverse of the sum of the inverse of the resistance of each resistor in parallel.
4 |
5 | 
6 |
7 | **Task:** Parallel Parking
8 | 1. Tap `Run My Code` button on the right.
9 | 2. Tap on the `Solve` button. What are the voltages across each resistor? Do they have the same values?
10 | 3. Try changing the value of each resistor. What happens to the voltages and current?
11 | 4. Add another resistor to the circuits in parallel. Observe how it affects the current.
12 | 5. What happens when we swap the voltage source for a current source?
13 |
14 | The current across each resistor in parallel is different because according to Kirchoff’s Current Law states that the sum of the current going into a node is equal to the sum of the current going out a node. In the original example, the voltage source pushes `1.00A` of current while the resistor pulls `500.00mA` of current each.
15 |
16 | 
17 |
18 | Go to the [Next Page](@next) where you will learn about capacitors.
19 | */
20 | import SwiftUI
21 | import PlaygroundSupport
22 |
23 | let preset: CircuitPreset = .parallel
24 | PlaygroundPage.current.wantsFullScreenLiveView = true
25 | PlaygroundPage.current.needsIndefiniteExecution = true
26 | PlaygroundPage.current.setLiveView(
27 | MainView()
28 | .environmentObject(CircuitEnvironment(circuit: preset.circuit))
29 | .ignoresSafeArea()
30 | )
31 |
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/UserModules/CircuitUI.playgroundmodule/Sources/ChevronButton.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 | import CircuitKit
3 |
4 | public struct ChevronButton: View {
5 | @Binding private var show: Bool
6 | private var showDirection: Direction
7 | private var hideDirection: Direction
8 |
9 | public init(_ show: Binding, direction: Direction) {
10 | self._show = show
11 | self.showDirection = direction
12 | self.hideDirection = direction.opposite
13 | }
14 |
15 | public var body: some View {
16 | Button {
17 | show.toggle()
18 | } label: {
19 | Label(show ? "Hide Components" : "Show Components",
20 | systemImage: hideDirection.chevron)
21 | .labelStyle(IconOnlyLabelStyle())
22 | .imageScale(.large)
23 | .font(Font.body.weight(.bold))
24 | .rotationEffect(show ? .degrees(180) : .zero)
25 | }
26 | .foregroundColor(Color.secondary)
27 | .buttonStyle(ChevronButtonStyle())
28 | }
29 | }
30 |
31 | struct ChevronButtonStyle: ButtonStyle {
32 | func makeBody(configuration: Configuration) -> some View {
33 | configuration.label
34 | .frame(width: 32, height: 32, alignment: .center)
35 | .aspectRatio(1.0, contentMode: .fit)
36 | .padding(4)
37 | .background(configuration.isPressed ? Color.blue.opacity(0.5) : Color.gray.opacity(0.25))
38 | .background(BlurView())
39 | .clipShape(Circle())
40 | .scaleEffect(configuration.isPressed ? 1.5 : 1.0)
41 | .shadow(radius: configuration.isPressed ? 7.5 : 0)
42 | .animation(.linear, value: configuration.isPressed)
43 | }
44 | }
45 |
46 |
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/Chapters/CircuitPlay.playgroundchapter/Pages/Series Circuits.playgroundpage/main.swift:
--------------------------------------------------------------------------------
1 | /*:
2 | # Series Circuits
3 | This page features a simple series circuit with a voltage source and two resistors. In a series circuit, the equivalent resistance of the circuit is equal to the sum of the resistance of each resistor in series.
4 |
5 | 
6 |
7 | **Task:** Choo! Choo!
8 | 1. Tap `Run My Code` button on the right.
9 | 2. Tap on the `Solve` button. What are the voltages across each resistor? Do they have the same values?
10 | 3. Try changing the value of each resistor. What happens to the voltages and current?
11 | 4. Add another resistor to the circuits in series. Observe how it affects the voltage.
12 | 5. Swap the voltage source for a current source. Does current differ across each resistor?
13 |
14 |
15 | The voltage across each resistor in series is different because according to Kirchoff’s Voltage Law, the sum of voltage rise is equal to the sum of voltage drop. In our original example, The voltage source causes a voltage rise of `5.00V` and each resistor each drops the voltage by `2.50V`. The voltage rise is equal to the sum of the two voltage drops.
16 |
17 | 
18 |
19 | Go to the [Next Page](@next) where you will learn about resistors in parallel.
20 | */
21 | import SwiftUI
22 | import PlaygroundSupport
23 |
24 | let preset: CircuitPreset = .series
25 | PlaygroundPage.current.wantsFullScreenLiveView = true
26 | PlaygroundPage.current.needsIndefiniteExecution = true
27 | PlaygroundPage.current.setLiveView(
28 | MainView()
29 | .environmentObject(CircuitEnvironment(circuit: preset.circuit))
30 | .ignoresSafeArea()
31 | )
32 |
33 |
34 |
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/UserModules/CircuitUI.playgroundmodule/Sources/CapacitorView.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 | import CircuitKit
3 |
4 | public struct CapacitorView: View {
5 | private var capacitor: Capacitor
6 |
7 | private var rotationCount: Double { capacitor.rotationCount }
8 |
9 | private var isVertical: Bool { capacitor.contacts.orientation == .vertical}
10 | private var isFlippedY: Bool {
11 | return [-3.0,-2.0,1.0,2.0].contains(rotationCount.remainder(dividingBy: 4))
12 | }
13 |
14 | private var isFlippedX: Bool {
15 | return [-2.0,-1.0,2.0,3.0].contains(rotationCount.remainder(dividingBy: 4))
16 | }
17 |
18 | public init(capacitor: Capacitor) {
19 | self.capacitor = capacitor
20 | }
21 |
22 | public var body: some View {
23 | ZStack {
24 | WireShape {
25 | SquareImageView("CapacitorShape")
26 | .rotation3DEffect( Angle(degrees: rotationCount * 90),
27 | axis: (x: 0, y: 0, z: 1))
28 | .scaleEffect(1.5)
29 | } overlay: {
30 | SquareImageView("WireStraight")
31 | .rotation3DEffect(isVertical ?
32 | Angle(degrees: 90) :
33 | Angle(degrees: 0), axis: (x: 0, y: 0, z: 1))
34 | }
35 | SquareImageView("CapacitorHead")
36 | .rotation3DEffect( Angle(degrees: isFlippedY ? 180 : 0), axis: (x: 0, y: 1, z: 0))
37 | .rotation3DEffect( Angle(degrees: isFlippedX ? 180 : 0), axis: (x: 1, y: 0, z: 0))
38 | .rotation3DEffect( Angle(degrees: rotationCount * 90), axis: (x: 0, y: 0, z: 1))
39 | }
40 | }
41 | }
42 |
43 |
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/UserModules/CircuitKit.playgroundmodule/Sources/ComponentEnums.swift:
--------------------------------------------------------------------------------
1 | public enum ComponentType {
2 | case wire
3 | case spacer
4 | case ground
5 | case battery, voltageSource
6 | case currentSource
7 | case resistor
8 | case inductor, capacitor
9 | case diode
10 |
11 | public var name: String {
12 | switch self {
13 | case .wire:
14 | return "Wire"
15 | case .resistor:
16 | return "Resistor"
17 | case .battery:
18 | return "Voltage \n Source"
19 | case .ground:
20 | return "Ground"
21 | case .diode:
22 | return "Diode"
23 | case .inductor:
24 | return "Inductor"
25 | case .capacitor:
26 | return "Capacitor"
27 | case .currentSource:
28 | return "Current \n Source"
29 | case .voltageSource:
30 | return "Voltage \n Source"
31 | default:
32 | return "Unnamed"
33 | }
34 | }
35 |
36 | public var imageName: String {
37 | switch self {
38 | case .battery:
39 | return "Battery"
40 | case .currentSource, .voltageSource:
41 | return "Source"
42 | case .resistor:
43 | return "Resistor"
44 | case .diode:
45 | return "Diode"
46 | case .inductor:
47 | return "Inductor"
48 | case .capacitor:
49 | return "Capacitor"
50 | default:
51 | return ""
52 | }
53 | }
54 | }
55 |
56 | public enum SignalType: String, CaseIterable {
57 | case direct
58 | case pulse
59 | case sine
60 |
61 | public var rawValue: String {
62 | self.rawValue.capitalized
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/UserModules/CircuitUI.playgroundmodule/Sources/SignsView.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 | import CircuitKit
3 |
4 | public struct SignsView: View {
5 | private var positiveDirection: Direction
6 | private var negativeDirection: Direction { positiveDirection.opposite }
7 |
8 | public init(positiveDirection: Direction) {
9 | self.positiveDirection = positiveDirection
10 | }
11 |
12 | public var body: some View {
13 | GeometryReader{ geometry in
14 | SquareImageView("PositiveSign")
15 | .frame(width: geometry.size.width / 8, height: geometry.size.height / 8)
16 | .position(SignOffset(geometry: geometry, direction: positiveDirection).offset)
17 | SquareImageView("NegativeSign")
18 | .frame(width: geometry.size.width / 8, height: geometry.size.height / 8)
19 | .position(SignOffset(geometry: geometry, direction: negativeDirection).offset)
20 | }
21 | }
22 | }
23 |
24 | private struct SignOffset {
25 | private var geometry: GeometryProxy
26 | private var direction: Direction
27 | public var offset: CGPoint {
28 | switch direction {
29 | case .down:
30 | return CGPoint(x: 3 * geometry.size.height / 8, y: 7 * geometry.size.width / 8)
31 | case .up:
32 | return CGPoint(x: 3 * geometry.size.height / 8, y: geometry.size.width / 8)
33 | case .left:
34 | return CGPoint(x: 1 * geometry.size.width / 8, y: 3 * geometry.size.height / 8)
35 | case .right:
36 | return CGPoint(x: 7 * geometry.size.width / 8, y: 3 * geometry.size.height / 8)
37 | }
38 | }
39 |
40 | public init(geometry: GeometryProxy, direction: Direction) {
41 | self.geometry = geometry
42 | self.direction = direction
43 | }
44 | }
45 |
46 |
47 |
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/UserModules/CircuitKit.playgroundmodule/Sources/CircuitGrid.swift:
--------------------------------------------------------------------------------
1 | import Combine
2 | import Foundation
3 | import CoreGraphics
4 |
5 | public struct Slot: CustomStringConvertible, Identifiable {
6 | public var component: CircuitComponent
7 | public let id = UUID()
8 |
9 | public var zIndex: Int = 0
10 | public var gridIndex: Location = (x: 0, y: 0)
11 | public var selected: Bool = false
12 |
13 | public var assocNode: Int?
14 | public var assocModel: String?
15 | public var assocBranch: Int?
16 |
17 | public var description: String { component.description }
18 |
19 | public init(component: CircuitComponent) {
20 | self.component = component
21 | }
22 |
23 | public mutating func changeComponent(to newComponent: CircuitComponent){
24 | self.component = newComponent
25 | }
26 | }
27 |
28 | public struct Row: Identifiable {
29 | public let id = UUID()
30 | public var slots: [Slot]
31 |
32 | public init(_ slots: Slot...) {
33 | self.slots = slots
34 | }
35 |
36 | public init(_ slots: [Slot]) {
37 | self.slots = slots
38 | }
39 |
40 | public mutating func append(_ slot: Slot) {
41 | slots.append(slot)
42 | }
43 | }
44 |
45 | public struct Grid {
46 | public var rows: [Row]
47 |
48 | public init(_ rows: Row...) {
49 | self.rows = rows
50 | }
51 |
52 | public init(_ rows: [Row]) {
53 | self.rows = rows
54 | }
55 |
56 | public mutating func append(_ row: Row) {
57 | rows.append(row)
58 | }
59 |
60 | public subscript(index: Location) -> Slot {
61 | get {
62 | return rows[index.y].slots[index.x]
63 | }
64 | set {
65 | rows[index.y].slots[index.x] = newValue
66 | }
67 | }
68 | }
69 |
70 |
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/UserModules/CircuitUI.playgroundmodule/Sources/MeasurementView.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 | import CircuitKit
3 |
4 | public struct MeasurementView: View {
5 | @EnvironmentObject var envi: CircuitEnvironment
6 | @Binding private var measurement: CircuitUnit
7 | @State private var selected: Bool = false
8 |
9 | public init(measurement: Binding) {
10 | self._measurement = measurement
11 | }
12 |
13 | @State var showPopover = false
14 |
15 | public var body: some View {
16 | Button {
17 | showPopover = true
18 | }
19 | label: {
20 | HStack(spacing: 4){
21 | Text(measurement.description)
22 | .font(.system(size: 13.5, weight: .regular, design: .monospaced))
23 | if envi.currentState == .setup {
24 | button
25 | }
26 | }
27 | }
28 | .foregroundColor(Color.primary)
29 | .frame(height: 20, alignment: .center)
30 | .padding(.horizontal, 4)
31 | .background(Color.gray.opacity(selected ? 0 : 0.25))
32 | .background(BlurView())
33 | .background(Color.white.opacity(selected ? 0.5 : 0))
34 | .cornerRadius(4)
35 | .animation(.spring(), value: selected)
36 | .popover(isPresented: $showPopover , arrowEdge: .top) { MeasurementEditView(measurement: $measurement, show: $showPopover) }
37 | .allowsHitTesting(envi.currentState == .setup)
38 | }
39 |
40 | var button: some View {
41 | Label("EDIT", systemImage: "pencil")
42 | .font(.system(size: 15, weight: .bold))
43 | .labelStyle(IconOnlyLabelStyle())
44 | .imageScale(.small)
45 | .aspectRatio(1.0, contentMode: .fit)
46 | .cornerRadius(4)
47 | }
48 | }
49 |
50 |
51 |
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/UserModules/CircuitKit.playgroundmodule/Sources/Circuit.swift:
--------------------------------------------------------------------------------
1 | import Combine
2 |
3 | public struct Circuit {
4 | public var rows: Int {
5 | didSet {
6 | setZIndex()
7 | setGridIndex()
8 | }
9 | }
10 | public var columns: Int {
11 | didSet {
12 | setZIndex()
13 | setGridIndex()
14 | }
15 | }
16 |
17 | let minRows: Int
18 | let minColumns: Int
19 |
20 | public var xRange: Range { 0.. { 0.. , yR: Range) -> Grid {
36 | var emptyGrid: Grid = Grid()
37 | for _ in yR {
38 | var emptyRow: Row = Row()
39 | for _ in xR {
40 | emptyRow.append(Slot(component: BlankSpace()))
41 | }
42 | emptyGrid.append(emptyRow)
43 | }
44 | return emptyGrid
45 | }
46 |
47 | mutating func setZIndex() {
48 | let xCount = columns
49 |
50 | for y in yRange{
51 | for x in xRange {
52 | circuitGrid.rows[y].slots[x].zIndex = y * xCount + (xCount - x)
53 | }
54 | }
55 | }
56 |
57 | mutating func setGridIndex() {
58 | var zArray = [Int]()
59 | let xCount = columns
60 |
61 | for y in yRange{
62 | for x in xRange {
63 | circuitGrid.rows[y].slots[x].gridIndex = (x: x, y: y)
64 | }
65 | }
66 | }
67 | }
68 |
69 |
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/Chapters/CircuitPlay.playgroundchapter/Pages/Ohm's Law.playgroundpage/main.swift:
--------------------------------------------------------------------------------
1 | /*:
2 | # Ohm's Law
3 | This page features a simple circuit with two components: a voltage source and a resistor, which will show how the relationship of the voltage to the resistance and current. This relationship is described by the Ohm’s Law, which states that the voltage equal to the product of the resistance and current.
4 |
5 | 
6 |
7 | **Task:** Ohm My Law!
8 | 1. Tap `Run My Code` button on the right.
9 | 2. Click on the `Solve` button. Feel free to tap on any of the components to check the values they hold. Components like the voltage source and resistor will show their voltage, current, and power values while wires will show their voltage in respect to the ground. The arrows indicate the current direction while the positive and negative signs indicate where the higher and lower voltages are.
10 | 3. With a `5.00V` voltage source and `10.00Ω` resistor, the current across the voltage source is 500mA. You may change values of each components with the Setup button and tapping on the value you want to change.
11 | 4. What happens when we increase the resistance value of the resistor? What happens to the current? What happens when we increase the value of the voltage?
12 | 5. Click on the `Design` button and replace the voltage source with a current source and solve for the circuit? What is the current across the resistor? What about voltages of the components?
13 | 6. On the `Setup` button, you may also view your circuits in augmented reality. In this mode, you may also tap on each model to view their values.
14 |
15 |
16 | Go to the [Next Page](@next) where you will learn about resistors in series.
17 | */
18 | import SwiftUI
19 | import PlaygroundSupport
20 |
21 | let preset: CircuitPreset = .ohmsLaw
22 | PlaygroundPage.current.wantsFullScreenLiveView = true
23 | PlaygroundPage.current.needsIndefiniteExecution = true
24 | PlaygroundPage.current.setLiveView(
25 | MainView()
26 | .environmentObject(CircuitEnvironment(circuit: preset.circuit))
27 | .ignoresSafeArea()
28 | )
29 |
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/UserModules/CircuitUI.playgroundmodule/Sources/StatePickerView.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 |
3 | public struct StatePicker: View {
4 | @EnvironmentObject var envi: CircuitEnvironment
5 |
6 | public var body: some View {
7 | GeometryReader { geo in
8 | ZStack{
9 | RoundedRectangle(cornerRadius: 8)
10 | .fill(Color.blue.opacity(0.5))
11 | .offset(x: envi.currentState == .design ? -88 : 0)
12 | .offset(x: envi.currentState == .play ? 88 : 0)
13 | .frame(width: 80, height: 40, alignment: .center)
14 | HStack{
15 | Button(action: {envi.currentState = .design} ) {
16 | Text("DESIGN")
17 | .fontWeight(.heavy)
18 | }.buttonStyle(PickerButtonStyle())
19 | Button(action: {envi.currentState = .setup} ) {
20 | Text("SETUP")
21 | .fontWeight(.heavy)
22 | }.buttonStyle(PickerButtonStyle())
23 | Button(action: {envi.solveCircuit()} ) {
24 | Text("SOLVE")
25 | .fontWeight(.heavy)
26 | }.buttonStyle(PickerButtonStyle())
27 | }.padding(8)
28 | }.background(BlurView())
29 | .background(Color.gray.opacity(0.25))
30 | .cornerRadius(12)
31 | .position(x: geo.frame(in: .global).midX, y: geo.frame(in: .global).minY + 120)
32 | }.animation(.spring(), value: envi.currentState)
33 | }
34 | }
35 |
36 | struct PickerButtonStyle: ButtonStyle {
37 | func makeBody(configuration: Configuration) -> some View {
38 | configuration.label
39 | .frame(width: 72, height: 32, alignment: .center)
40 | .aspectRatio(1.0, contentMode: .fit)
41 | .scaleEffect(configuration.isPressed ? 1.0 : 1.0)
42 | .shadow(radius: configuration.isPressed ? 7.5 : 0)
43 | .animation(.linear, value: configuration.isPressed)
44 | .padding(4)
45 | .foregroundColor(Color.primary)
46 | }
47 | }
48 |
49 |
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/UserModules/CircuitUI.playgroundmodule/Sources/SideButtonsView.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 | import CircuitKit
3 |
4 | public struct DesignSideButtonsView: View {
5 | @EnvironmentObject var envi: CircuitEnvironment
6 |
7 | public var body: some View {
8 | VStack(spacing: 0) {
9 | Button(action: {
10 | guard let selectedSlot = envi.selectedSlot else { return }
11 | envi.circuit.circuitGrid[selectedSlot].component.rotate(.clockwise)
12 | }){
13 | Label("Rotate Right", systemImage: "rotate.right")
14 | .labelStyle(IconOnlyLabelStyle())
15 | }.buttonStyle(SideButtonStyle())
16 | Button(action: {
17 | guard let selectedSlot = envi.selectedSlot else { return }
18 | envi.circuit.circuitGrid[selectedSlot].component.rotate(.counterclockwise)
19 | }){
20 | Label("Rotate Left", systemImage: "rotate.left")
21 | .labelStyle(IconOnlyLabelStyle())
22 | }.buttonStyle(SideButtonStyle())
23 | Button(action: {
24 | guard let selectedSlot = envi.selectedSlot else { return }
25 | envi.circuit.circuitGrid[selectedSlot].component = BlankSpace()
26 | envi.selectedSlot = nil
27 | }){
28 | Label("Trash", systemImage: "trash")
29 | .labelStyle(IconOnlyLabelStyle())
30 | }.buttonStyle(SideButtonStyle())
31 | }.background(BlurView())
32 | .cornerRadius(8)
33 | }
34 | }
35 |
36 | public struct SideButtonStyle: ButtonStyle {
37 | public init(){}
38 | public func makeBody(configuration: Configuration) -> some View {
39 | configuration.label
40 | .scaleEffect(configuration.isPressed ? 1.25 : 1.0)
41 | .frame(width: 24, height: 24, alignment: .center)
42 | .aspectRatio(1.0, contentMode: .fit)
43 | .padding(4)
44 | .background(configuration.isPressed ? Color.blue.opacity(0.5) : Color.gray.opacity(0.25))
45 | .shadow(radius: configuration.isPressed ? 7.5 : 0)
46 | .animation(.linear, value: configuration.isPressed)
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/UserModules/CircuitKit.playgroundmodule/Sources/Circuit+Debug.swift:
--------------------------------------------------------------------------------
1 | extension Circuit {
2 | public func debugLayout() {
3 | for row in circuitGrid.rows{
4 | var rowStr: String = ""
5 | for comp in row.slots {
6 | rowStr.append(comp.description)
7 | rowStr.append(" ")
8 | }
9 | print(rowStr)
10 | }
11 | }
12 |
13 | public mutating func stringGridTranslation(_ grid: [[String]]) {
14 | let translationGrid = grid.map({$0.map({$0.translateToComponent()})})
15 | for y in yRange {
16 | for x in xRange {
17 | circuitGrid.rows[y].slots[x].changeComponent(to: translationGrid[y][x])
18 | }
19 | }
20 | }
21 | }
22 |
23 | extension String {
24 | public func translateToComponent() -> CircuitComponent{
25 | switch self {
26 | case "│":
27 | return Wire(.up, .down)
28 | case "─":
29 | return Wire(.right, .left)
30 | case "┘":
31 | return Wire(.up, .left)
32 | case "└":
33 | return Wire(.up, .right)
34 | case "┐":
35 | return Wire(.left, .down)
36 | case "┌":
37 | return Wire(.right, .down)
38 | case "┴":
39 | return Wire(.up, .left, .right)
40 | case "┬":
41 | return Wire(.down, .right, .left)
42 | case "┤":
43 | return Wire(.up, .down, .left)
44 | case "├":
45 | return Wire(.up, .down, .right)
46 | case "┼":
47 | return Wire(.up, .down, .left, .right)
48 | case "R":
49 | return Resistor(.left, .right)
50 | case "V":
51 | return Battery(positive: .right, negative: .left)
52 | case "G":
53 | return Ground(.right)
54 | case "D":
55 | return Diode(positive: .right, negative: .left)
56 | case "L":
57 | return Inductor(.right, .left)
58 | case "C":
59 | return Capacitor(.right, .left)
60 | case "I":
61 | return CurrentSource(positive: .right, negative: .left)
62 | case "S":
63 | return VoltageSource(positive: .right, negative: .left)
64 | default:
65 | return BlankSpace()
66 | }
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/UserModules/CircuitKit.playgroundmodule/Sources/UnitEnums.swift:
--------------------------------------------------------------------------------
1 |
2 |
3 | public enum Dimension: String, Identifiable {
4 | case voltage
5 | case resistance
6 | case current
7 | case power
8 | case frequency
9 | case inductance
10 | case capacitance
11 |
12 | public var id: String {
13 | self.rawValue.capitalized
14 | }
15 | }
16 |
17 | public enum UnitPrefix: Int {
18 | case tera = 12
19 | case giga = 9
20 | case mega = 6
21 | case kilo = 3
22 | case base = 0
23 | case milli = -3
24 | case micro = -6
25 | case nano = -9
26 | case pico = -12
27 |
28 | public var symbol: String {
29 | switch self {
30 | case .tera:
31 | return "T"
32 | case .giga:
33 | return "G"
34 | case .mega:
35 | return "M"
36 | case .kilo:
37 | return "k"
38 | case .base:
39 | return ""
40 | case .milli:
41 | return "m"
42 | case .micro:
43 | return "μ"
44 | case .nano:
45 | return "n"
46 | case .pico:
47 | return "p"
48 | }
49 | }
50 | }
51 |
52 | public enum BaseUnit {
53 | case volt
54 | case ohm
55 | case ampere
56 | case watt
57 | case hertz
58 | case henry
59 | case farad
60 |
61 | public var symbol: String {
62 | switch self {
63 | case .volt:
64 | return "V"
65 | case .ampere:
66 | return "A"
67 | case .ohm:
68 | return "Ω"
69 | case .watt:
70 | return "W"
71 | case .hertz:
72 | return "Hz"
73 | case .henry:
74 | return "H"
75 | case .farad:
76 | return "F"
77 | }
78 | }
79 |
80 | public func createUnit(from value: Double) -> CircuitUnit {
81 | switch self {
82 | case .volt:
83 | return value.volts()
84 | case .ampere:
85 | return value.amperes()
86 | case .ohm:
87 | return value.ohms()
88 | case .watt:
89 | return value.watts()
90 | case .hertz:
91 | return value.hertz()
92 | case .henry:
93 | return value.henries()
94 | case .farad:
95 | return value.farads()
96 | }
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/UserModules/CircuitKit.playgroundmodule/Sources/Presets.swift:
--------------------------------------------------------------------------------
1 | public enum CircuitPreset {
2 | case blank
3 | case ohmsLaw
4 | case series
5 | case parallel
6 | case resistorCapacitor
7 | case resistorInductor
8 |
9 | public var circuit: Circuit {
10 | switch self {
11 | case .blank:
12 | var blankCircuit = Circuit(3, 3)
13 | return blankCircuit
14 | case .ohmsLaw:
15 | let circuit: [[String]] = [["G","R","┐"],
16 | [" "," ","│",],
17 | ["G","V","┘"]]
18 |
19 | var translatedCircuit = Circuit(3, 3)
20 | translatedCircuit.stringGridTranslation(circuit)
21 | return translatedCircuit
22 | case .series:
23 | let circuit: [[String]] = [["G","R","┐"],
24 | [" "," ","R",],
25 | ["G","V","┘"]]
26 |
27 | var translatedCircuit = Circuit(3, 3)
28 | translatedCircuit.stringGridTranslation(circuit)
29 | translatedCircuit.circuitGrid[(2,1)].component.rotate(.clockwise)
30 | return translatedCircuit
31 | case .parallel:
32 | let circuit: [[String]] = [["G","R","┐"],
33 | ["G","R","┤"],
34 | ["G","V","┘"]]
35 |
36 | var translatedCircuit = Circuit(3, 3)
37 | translatedCircuit.stringGridTranslation(circuit)
38 | return translatedCircuit
39 | case .resistorCapacitor:
40 | let circuit: [[String]] = [["G","C","┐"],
41 | [" "," ","R",],
42 | ["G","V","┘"]]
43 |
44 | var translatedCircuit = Circuit(3, 3)
45 | translatedCircuit.stringGridTranslation(circuit)
46 | translatedCircuit.circuitGrid[(2,1)].component.rotate(.clockwise)
47 | return translatedCircuit
48 | case .resistorInductor:
49 | let circuit: [[String]] = [["G","L","┐"],
50 | [" "," ","R",],
51 | ["G","V","┘"]]
52 |
53 | var translatedCircuit = Circuit(3, 3)
54 | translatedCircuit.stringGridTranslation(circuit)
55 | translatedCircuit.circuitGrid[(2,1)].component.rotate(.clockwise)
56 | return translatedCircuit
57 | }
58 | }
59 | }
60 |
61 |
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/UserModules/CircuitKit.playgroundmodule/Sources/Circuit+Navigation.swift:
--------------------------------------------------------------------------------
1 | extension Circuit {
2 | public mutating func shiftGrid(going direction: Direction){
3 | func createRepeatingBlankSlots(_ count: Int) -> [Slot] {
4 | var blankSlots = [Slot]()
5 | for _ in 0.. Location {
53 | var newLocation: Location {
54 | switch direction {
55 | case .up:
56 | return (location.x, location.y - 1)
57 | case .down:
58 | return (location.x, location.y + 1)
59 | case .left:
60 | return (location.x - 1, location.y)
61 | case .right:
62 | return (location.x + 1, location.y)
63 | }
64 | }
65 |
66 | guard newLocation.x >= 0,
67 | newLocation.y >= 0,
68 | newLocation.x < self.columns,
69 | newLocation.y < self.rows else {
70 | throw CircuitError.LocationOutOfBoundsError
71 | }
72 |
73 | return newLocation
74 | }
75 | }
76 |
77 |
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/UserModules/CircuitUI.playgroundmodule/Sources/MainView.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 | import CircuitKit
3 | import CircuitReality
4 |
5 | public struct MainView: View {
6 | @EnvironmentObject var envi: CircuitEnvironment
7 |
8 | public init(){}
9 |
10 | public var body: some View {
11 | ZStack {
12 | if envi.currentState == .play && envi.arMode {
13 | ARViewContainer()
14 | } else {
15 | GridView()
16 | }
17 | StatePicker()
18 | if envi.currentState == .play {
19 | InterfacePicker()
20 | }
21 | DockView()
22 | .opacity(envi.currentState == .design ? 1.0 : 0.0)
23 | .allowsHitTesting(envi.currentState == .design)
24 | .offset(y: envi.currentState == .design ? 0.0 : 120)
25 | .animation(.spring(), value: envi.currentState)
26 | if let position = envi.dragPosition {
27 | HoverView()
28 | .position(position)
29 | }
30 | if envi.currentState == .play {
31 | InfoView()
32 | NextStepView()
33 | }
34 | }.animation(.linear, value: envi.currentState)
35 | }
36 | }
37 |
38 | public struct HoverView: View {
39 | @EnvironmentObject var envi: CircuitEnvironment
40 |
41 | public init(){}
42 |
43 | public var body: some View {
44 | if envi.firstDragIndex == nil, let draggedDock = envi.draggedDock {
45 | ComponentView(component: draggedDock)
46 | .frame(width: 72, height: 72, alignment: .center)
47 | .background(BlurView())
48 | .cornerRadius(8)
49 | .overlay(
50 | RoundedRectangle(cornerRadius: 8)
51 | .stroke(envi.dragState == .valid ? Color.green : Color.red, lineWidth: 4)
52 | )
53 | .shadow(radius: 8)
54 | .animation(.linear(duration: 0.1875), value: envi.dragState)
55 | }
56 | if let firstDragIndex = envi.firstDragIndex {
57 | ComponentView(component: envi.circuit.circuitGrid[firstDragIndex].component)
58 | .frame(width: 72, height: 72, alignment: .center)
59 | .background(BlurView())
60 | .cornerRadius(8)
61 | .overlay(
62 | RoundedRectangle(cornerRadius: 8)
63 | .stroke(envi.dragState == .valid ? Color.green : Color.red, lineWidth: 4)
64 | )
65 | .shadow(radius: 8)
66 | .animation(.linear(duration: 0.1875), value: envi.dragState)
67 | }
68 | }
69 | }
70 |
71 |
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/UserModules/CircuitUI.playgroundmodule/Sources/ArrowsView.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 | import CircuitKit
3 |
4 | public struct ArrowsView: View {
5 | private var direction: Direction
6 | private var rotationCount: Double
7 | private var isNegative: Bool
8 | private var isFlipped: Bool { direction == .right || direction == .down}
9 | public init(direction: Direction, rotationCount: Double, isNegative: Bool) {
10 | self.direction = direction
11 | self.rotationCount = rotationCount
12 | self.isNegative = isNegative
13 | }
14 |
15 | public var body: some View {
16 | GeometryReader{ geometry in
17 | SquareImageView("Arrow")
18 | .rotation3DEffect(Angle(degrees: isNegative ? 180 : 0), axis: (x: 0, y: 1, z: 0))
19 | .rotation3DEffect(isFlipped ?
20 | Angle(degrees: 180) :
21 | Angle(degrees: 0), axis: (x: 1, y: 0, z: 0))
22 | .rotation3DEffect(Angle(degrees: rotationCount * 90), axis: (x: 0, y: 0, z: 1))
23 | .frame(width: geometry.size.width / 8, height: geometry.size.height / 8)
24 | .position(ArrowOffset(geometry: geometry, direction: direction).offset)
25 | SquareImageView("Arrow")
26 | .rotation3DEffect(Angle(degrees: isNegative ? 180 : 0), axis: (x: 0, y: 1, z: 0))
27 | .rotation3DEffect(isFlipped ?
28 | Angle(degrees: 180) :
29 | Angle(degrees: 0), axis: (x: 1, y: 0, z: 0))
30 | .rotation3DEffect(Angle(degrees: rotationCount * 90), axis: (x: 0, y: 0, z: 1))
31 | .frame(width: geometry.size.width / 8, height: geometry.size.height / 8)
32 | .position(ArrowOffset(geometry: geometry, direction: direction.opposite).offset)
33 | }
34 | }
35 | }
36 |
37 | private struct ArrowOffset {
38 | private var geometry: GeometryProxy
39 | private var direction: Direction
40 | public var offset: CGPoint {
41 | switch direction {
42 | case .down:
43 | return CGPoint(x: 5 * geometry.size.height / 8, y: 7 * geometry.size.width / 8)
44 | case .up:
45 | return CGPoint(x: 5 * geometry.size.height / 8, y: geometry.size.width / 8)
46 | case .left:
47 | return CGPoint(x: 1 * geometry.size.width / 8, y: 5 * geometry.size.height / 8)
48 | case .right:
49 | return CGPoint(x: 7 * geometry.size.width / 8, y: 5 * geometry.size.height / 8)
50 | }
51 | }
52 |
53 | public init(geometry: GeometryProxy, direction: Direction) {
54 | self.geometry = geometry
55 | self.direction = direction
56 | }
57 | }
58 |
59 |
60 |
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/UserModules/FocusEntity.playgroundmodule/Sources/FocusEntityComponent.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FocusEntity.swift
3 | // FocusEntity
4 | //
5 | // Created by Max Cobb on 8/26/19.
6 | // Copyright © 2019 Max Cobb. All rights reserved.
7 | //
8 |
9 | #if canImport(ARKit)
10 | import RealityKit
11 | import ARKit
12 |
13 | internal struct ClassicStyle {
14 | var color: Material.Color
15 | }
16 |
17 | /// When using colored style, first material of a mesh will be replaced with the chosen color
18 | internal struct ColoredStyle {
19 | /// Color when tracking the surface of a known plane
20 | var onColor: MaterialColorParameter
21 | /// Color when tracking an estimated plane
22 | var offColor: MaterialColorParameter
23 | /// Color when no surface tracking is achieved
24 | var nonTrackingColor: MaterialColorParameter
25 | var mesh: MeshResource
26 | }
27 |
28 | public struct FocusEntityComponent: Component {
29 | public enum Style {
30 | case classic(color: Material.Color)
31 | case colored(
32 | onColor: MaterialColorParameter,
33 | offColor: MaterialColorParameter,
34 | nonTrackingColor: MaterialColorParameter,
35 | mesh: MeshResource = MeshResource.generatePlane(width: 0.1, depth: 0.1)
36 | )
37 | }
38 |
39 | let style: Style
40 | var classicStyle: ClassicStyle? {
41 | switch self.style {
42 | case .classic(let color):
43 | return ClassicStyle(color: color)
44 | default:
45 | return nil
46 | }
47 | }
48 |
49 | var coloredStyle: ColoredStyle? {
50 | switch self.style {
51 | case .colored(let onColor, let offColor, let nonTrackingColor, let mesh):
52 | return ColoredStyle(
53 | onColor: onColor, offColor: offColor,
54 | nonTrackingColor: nonTrackingColor, mesh: mesh
55 | )
56 | default:
57 | return nil
58 | }
59 | }
60 |
61 | /// Convenient presets
62 | public static let classic = FocusEntityComponent(style: .classic(color: #colorLiteral(red: 1, green: 0.8, blue: 0, alpha: 1)))
63 | public static let plane = FocusEntityComponent(
64 | style: .colored(
65 | onColor: .color(.green),
66 | offColor: .color(.orange),
67 | nonTrackingColor: .color(Material.Color.red.withAlphaComponent(0.2)),
68 | mesh: FocusEntityComponent.defaultPlane
69 | )
70 | )
71 | internal var isOpen = true
72 | internal var segments: [FocusEntity.Segment] = []
73 | public var allowedRaycast: ARRaycastQuery.Target = .estimatedPlane
74 |
75 | static var defaultPlane = MeshResource.generatePlane(
76 | width: 0.1, depth: 0.1
77 | )
78 |
79 | public init(style: Style) {
80 | self.style = style
81 | // If the device has LiDAR, then default behaviour is to only allow
82 | // existing detected planes
83 | if #available(iOS 13.4, *),
84 | ARWorldTrackingConfiguration.supportsSceneReconstruction(.mesh) {
85 | self.allowedRaycast = .existingPlaneGeometry
86 | }
87 | }
88 | }
89 | #endif
90 |
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/UserModules/CircuitKit.playgroundmodule/Sources/LocationEnums.swift:
--------------------------------------------------------------------------------
1 | public enum Direction: String, Identifiable, Hashable {
2 | case right, left, up, down
3 |
4 | public var id: String {
5 | self.rawValue.capitalized
6 | }
7 |
8 | public var int: Int {
9 | switch self {
10 | case .right:
11 | return 0
12 | case .left:
13 | return 2
14 | case .up:
15 | return 3
16 | case .down:
17 | return 1
18 | }
19 | }
20 |
21 | public var symbol: String {
22 | switch self {
23 | case .right:
24 | return "arrow.right"
25 | case .left:
26 | return "arrow.left"
27 | case .up:
28 | return "arrow.up"
29 | case .down:
30 | return "arrow.down"
31 | }
32 | }
33 |
34 | public var chevron: String {
35 | switch self {
36 | case .right:
37 | return "chevron.right"
38 | case .left:
39 | return "chevron.left"
40 | case .up:
41 | return "chevron.up"
42 | case .down:
43 | return "chevron.down"
44 | }
45 | }
46 |
47 | public var rotationClockwise: Direction {
48 | switch self {
49 | case .right:
50 | return .down
51 | case .left:
52 | return .up
53 | case .up:
54 | return .right
55 | case .down:
56 | return .left
57 | }
58 | }
59 |
60 | public var rotationCounterclockwise: Direction {
61 | switch self {
62 | case .right:
63 | return .up
64 | case .left:
65 | return .down
66 | case .up:
67 | return .left
68 | case .down:
69 | return .right
70 | }
71 | }
72 |
73 | public var flipHorizontal: Direction {
74 | switch self {
75 | case .right:
76 | return .left
77 | case .left:
78 | return .right
79 | case .up:
80 | return .up
81 | case .down:
82 | return .down
83 | }
84 | }
85 |
86 | public var flipVertical: Direction {
87 | switch self {
88 | case .right:
89 | return .right
90 | case .left:
91 | return .left
92 | case .up:
93 | return .down
94 | case .down:
95 | return .up
96 | }
97 | }
98 |
99 | public var opposite: Direction {
100 | switch self {
101 | case .right:
102 | return .left
103 | case .left:
104 | return .right
105 | case .up:
106 | return .down
107 | case .down:
108 | return .up
109 | }
110 | }
111 | }
112 |
113 | public enum Rotation {
114 | case clockwise, counterclockwise
115 | }
116 |
117 | public enum Orientation {
118 | case vertical, horizontal
119 | }
120 |
121 |
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/UserModules/CircuitUI.playgroundmodule/Sources/WiredComponentView.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 | import CircuitKit
3 |
4 | public struct WiredComponentView: View {
5 | @Environment(\.accessibilityReduceMotion) var reduceMotion
6 | @Environment (\.colorScheme) var colorScheme: ColorScheme
7 |
8 | private var component: Connectable
9 |
10 | private var rotationCount: Double { component.rotationCount }
11 |
12 | private var isFlipped: Bool {
13 | if let terminalComponent = component as? TerminalDependent {
14 | return terminalComponent.terminals.positive == .down || terminalComponent.terminals.positive == .right
15 | }
16 | return [-1.0,-2.0,2.0,3.0].contains(rotationCount.remainder(dividingBy: 4))
17 | }
18 | private var flippable: Bool { component.componentType.imageName != "Source" }
19 |
20 | public init(component: Connectable){
21 | self.component = component
22 | }
23 |
24 | public var body: some View {
25 | ZStack {
26 | CondensedWireShape(rotationCount: rotationCount)
27 | SquareImageView(component.componentType.imageName)
28 | .rotation3DEffect(isFlipped && flippable ?
29 | Angle(degrees: 180) :
30 | Angle(degrees: 0), axis: (x: 1, y: 0, z: 0))
31 | .rotation3DEffect(Angle(degrees: flippable ? rotationCount * 90 : 0), axis: (x: 0, y: 0, z: 1))
32 | if let currentSource = component as? CurrentSource {
33 | var current = currentSource.measurements[0].value
34 | if current != 0 {
35 | ArrowsView(direction: currentSource.terminals.positive, rotationCount: currentSource.rotationCount, isNegative: current > 0)
36 | }
37 | }
38 | if let terminalComponent = component as? TerminalDependent {
39 | SignsView(positiveDirection: terminalComponent.terminals.positive)
40 | }
41 | }
42 | .aspectRatio(1, contentMode: .fit)
43 | }
44 | }
45 |
46 | struct CondensedWireShape: View {
47 | private var rotationCount: Double
48 | private var rotatingRemainder: Double { rotationCount.truncatingRemainder(dividingBy: 2.0) }
49 |
50 | init(rotationCount: Double) {
51 | self.rotationCount = rotationCount
52 | }
53 | var body: some View {
54 | ZStack {
55 | mainShape
56 | .overlay(
57 | overlay
58 | .mask(
59 | mainShape
60 | )
61 | )
62 | .mask(Rectangle())
63 | }
64 | }
65 |
66 | private var mainShape: some View {
67 | SquareImageView("WireCondensedShape")
68 | .rotation3DEffect( Angle(degrees: rotationCount * 90),
69 | axis: (x: 0, y: 0, z: 1))
70 | .scaleEffect(1.5)
71 | }
72 |
73 | private var overlay: some View {
74 | Group {
75 | switch rotatingRemainder {
76 | case 0:
77 | SquareImageView("WireStraight")
78 | default:
79 | SquareImageView("WireStraight")
80 | .rotation3DEffect( Angle(degrees: 90),
81 | axis: (x: 0, y: 0, z: 1))
82 | }
83 | }
84 | }
85 | }
86 |
87 |
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/UserModules/CircuitUI.playgroundmodule/Sources/ARContainerView.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 | import RealityKit
3 | import ARKit
4 | import CircuitKit
5 | import CircuitNetwork
6 | import FocusEntity
7 | import CircuitReality
8 |
9 | protocol CircuitDelegate {
10 | func slotTapped(location: Location)
11 | }
12 |
13 | public struct ARViewContainer: UIViewRepresentable, CircuitDelegate {
14 | @EnvironmentObject var envi: CircuitEnvironment
15 | public init(){}
16 | public func makeUIView(context: Context) -> CircuitARView {
17 | let arView = CircuitARView(frame: .zero, delegate: self, network: envi.network!)
18 | arView.focusEntity?.isEnabled = true
19 | arView.contentScaleFactor = 1
20 | return arView
21 | }
22 | public func updateUIView(_ uiView: CircuitARView, context: Context) {
23 | }
24 | func slotTapped(location: Location) {
25 | envi.selectedSlot = location
26 | }
27 | }
28 |
29 | public class CircuitARView: ARView {
30 | var focusEntity: FocusEntity?
31 | var delegate: CircuitDelegate?
32 | var network: CircuitNetwork?
33 | var objectPlaced = false
34 |
35 | required init(frame frameRect: CGRect, delegate: CircuitDelegate, network: CircuitNetwork) {
36 | super.init(frame: frameRect)
37 | focusEntity = FocusEntity(on: self, focus: .classic)
38 | self.network = network
39 | self.cameraMode = .ar
40 | self.delegate = delegate
41 | self.renderOptions = [.disableFaceOcclusions, .disablePersonOcclusion, .disableHDR]
42 | configure()
43 | enablePlacement()
44 | }
45 |
46 | @objc override required dynamic init(frame frameRect: CGRect) {
47 | fatalError("init(frame:) has not been implemented")
48 | }
49 |
50 | @objc required dynamic init?(coder decoder: NSCoder) {
51 | fatalError("init(coder:) has not been implemented")
52 | }
53 |
54 | private func configure() {
55 | let config = ARWorldTrackingConfiguration()
56 | config.planeDetection = .horizontal
57 | config.environmentTexturing = .automatic
58 | config.wantsHDREnvironmentTextures = false
59 | session.run(config)
60 | }
61 | func enablePlacement() {
62 | let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTap(recognizer:)))
63 | self.addGestureRecognizer(tapGestureRecognizer)
64 | }
65 | @objc func handleTap(recognizer: UITapGestureRecognizer) {
66 | if !objectPlaced {
67 | do {
68 | let reality = CircuitReality(network: network!)
69 | let circuit = reality.makeAnchorEntityForNetwork(position: focusEntity?.position ?? .zero)
70 | self.scene.addAnchor(circuit)
71 | objectPlaced = true
72 | focusEntity?.isEnabled = false
73 | } catch {
74 | print("No model")
75 | }
76 | } else {
77 | let location = recognizer.location(in: self)
78 | let hitTests = hitTest(location, query: .nearest)
79 | for hit in hitTests {
80 | if let locStr = hit.entity.parent?.name.components(separatedBy: ","), let x = Int(locStr[0]), let y = Int(locStr[1]) {
81 | delegate?.slotTapped(location: (x, y))
82 | }
83 | if let locStr = hit.entity.parent?.parent?.name.components(separatedBy: ","), let x = Int(locStr[0]), let y = Int(locStr[1]) {
84 | delegate?.slotTapped(location: (x, y))
85 | }
86 | if let locStr = hit.entity.parent?.parent?.parent?.name.components(separatedBy: ","), let x = Int(locStr[0]), let y = Int(locStr[1]) {
87 | delegate?.slotTapped(location: (x, y))
88 | }
89 | if let locStr = hit.entity.parent?.parent?.parent?.parent?.name.components(separatedBy: ","), let x = Int(locStr[0]), let y = Int(locStr[1]) {
90 | delegate?.slotTapped(location: (x, y))
91 | }
92 | }
93 | }
94 | }
95 | }
96 |
97 |
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/UserModules/FocusEntity.playgroundmodule/Sources/FocusEntity+Classic.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FocusEntity+Classic.swift
3 | // FocusEntity
4 | //
5 | // Created by Max Cobb on 8/28/19.
6 | // Copyright © 2019 Max Cobb. All rights reserved.
7 | //
8 |
9 | #if canImport(ARKit)
10 | import RealityKit
11 |
12 | /// An extension of FocusEntity holding the methods for the "classic" style.
13 | internal extension FocusEntity {
14 |
15 | // MARK: - Configuration Properties
16 |
17 | /// Original size of the focus square in meters. Not currently customizable
18 | static let size: Float = 0.17
19 |
20 | /// Thickness of the focus square lines in meters. Not currently customizable
21 | static let thickness: Float = 0.018
22 |
23 | /// Scale factor for the focus square when it is closed, w.r.t. the original size.
24 | static let scaleForClosedSquare: Float = 0.97
25 |
26 | /// Side length of the focus square segments when it is open (w.r.t. to a 1x1 square).
27 | // static let sideLengthForOpenSegments: CGFloat = 0.2
28 |
29 | /// Duration of the open/close animation. Not currently used.
30 | static let animationDuration = 0.7
31 |
32 | /// Color of the focus square fill. Not currently used.
33 | // static var fillColor = #colorLiteral(red: 1, green: 0.9254901961, blue: 0.4117647059, alpha: 1)
34 |
35 | /// Indicates whether the segments of the focus square are disconnected.
36 | // private var isOpen = true
37 |
38 | /// List of the segments in the focus square.
39 |
40 | // MARK: - Initialization
41 |
42 | func setupClassic(_ classicStyle: ClassicStyle) {
43 | // opacity = 0.0
44 | /*
45 | The focus square consists of eight segments as follows, which can be individually animated.
46 |
47 | s0 s1
48 | _ _
49 | s2 | | s3
50 |
51 | s4 | | s5
52 | - -
53 | s6 s7
54 | */
55 |
56 | let segCorners: [(Corner, Alignment)] = [
57 | (.topLeft, .horizontal), (.topRight, .horizontal),
58 | (.topLeft, .vertical), (.topRight, .vertical),
59 | (.bottomLeft, .vertical), (.bottomRight, .vertical),
60 | (.bottomLeft, .horizontal), (.bottomRight, .horizontal)
61 | ]
62 | self.segments = segCorners.enumerated().map { (index, cornerAlign) -> Segment in
63 | Segment(
64 | name: "s\(index)",
65 | corner: cornerAlign.0,
66 | alignment: cornerAlign.1,
67 | color: classicStyle.color
68 | )
69 | }
70 |
71 | let sl: Float = 0.5 // segment length
72 | let c: Float = FocusEntity.thickness / 2 // correction to align lines perfectly
73 | segments[0].position += [-(sl / 2 - c), 0, -(sl - c)]
74 | segments[1].position += [sl / 2 - c, 0, -(sl - c)]
75 | segments[2].position += [-sl, 0, -sl / 2]
76 | segments[3].position += [sl, 0, -sl / 2]
77 | segments[4].position += [-sl, 0, sl / 2]
78 | segments[5].position += [sl, 0, sl / 2]
79 | segments[6].position += [-(sl / 2 - c), 0, sl - c]
80 | segments[7].position += [sl / 2 - c, 0, sl - c]
81 |
82 | for segment in segments {
83 | self.positioningEntity.addChild(segment)
84 | segment.open()
85 | }
86 |
87 | // self.positioningEntity.addChild(fillPlane)
88 | self.positioningEntity.scale = SIMD3(repeating: FocusEntity.size * FocusEntity.scaleForClosedSquare)
89 |
90 | // Always render focus square on top of other content.
91 | // self.displayNodeHierarchyOnTop(true)
92 | }
93 |
94 | // MARK: Animations
95 |
96 | func offPlaneAniation() {
97 | // Open animation
98 | guard !isOpen else {
99 | return
100 | }
101 | isOpen = true
102 |
103 | for segment in segments {
104 | segment.open()
105 | }
106 | positioningEntity.scale = .init(repeating: FocusEntity.size)
107 | }
108 |
109 | func onPlaneAnimation(newPlane: Bool = false) {
110 | guard isOpen else {
111 | return
112 | }
113 | self.isOpen = false
114 |
115 | // Close animation
116 | for segment in self.segments {
117 | segment.close()
118 | }
119 |
120 | if newPlane {
121 | // New plane animation not implemented
122 | }
123 | }
124 |
125 | }
126 | #endif
127 |
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/UserModules/CircuitUI.playgroundmodule/Sources/Environment.swift:
--------------------------------------------------------------------------------
1 | import Combine
2 | import SwiftUI
3 | import CircuitKit
4 | import CircuitNetwork
5 | import RealityKit
6 | import ARKit
7 |
8 | public class CircuitEnvironment: ObservableObject {
9 | @Published public var circuit: Circuit
10 |
11 | @Published public var network: CircuitNetwork?
12 |
13 | @Published public var arMode: Bool = false
14 |
15 | public init(circuit: Circuit){
16 | self.circuit = circuit
17 | let detector = CurrentValueSubject(.zero)
18 | self.publisher = detector
19 | .debounce(for: .seconds(1/60), scheduler: DispatchQueue.main)
20 | .dropFirst()
21 | .eraseToAnyPublisher()
22 | self.detector = detector
23 | }
24 |
25 | public func solveCircuit() {
26 | network = CircuitNetwork(circuit: circuit)
27 | network?.solveNetwork()
28 | currentState = .play
29 | }
30 |
31 | @Published var currentState: ViewState = .design {
32 | didSet {
33 | selectedSlot = nil
34 | }
35 | }
36 |
37 | let timer: Publishers.Autoconnect = Timer.publish(every: 1 / 60, on: .main, in: .common).autoconnect()
38 |
39 | let detector: CurrentValueSubject
40 | let publisher: AnyPublisher
41 |
42 | @Published public var selectedSlot: Location? {
43 | willSet {
44 | guard let selectedSlot = selectedSlot else { return }
45 | circuit.circuitGrid[(selectedSlot)].selected = false
46 | }
47 | }
48 |
49 | @Published var popoverShown: Bool = false
50 |
51 | @Published var dragPosition: CGPoint?
52 |
53 | @Published var firstDragIndex: Location?
54 |
55 | var dragState: DragState? {
56 | switch (dragPosition, dragGridIndex) {
57 | case (nil, _):
58 | return nil
59 | case (_, nil):
60 | return .unknown
61 | default:
62 | return .valid
63 | }
64 | }
65 |
66 | @Published var gridOrigin: CGPoint = CGPoint.zero {
67 | didSet {
68 | DispatchQueue.main.async { [self] in
69 | dragPosition = nil
70 | }
71 | }
72 | }
73 |
74 |
75 | var gridRect: CGRect {
76 | let gridSize = CGSize(width: circuit.columns * 128, height: circuit.rows * 128)
77 | return CGRect(origin: gridOrigin, size: gridSize)
78 | }
79 |
80 | var dragGridIndex: Location? {
81 | guard let dragPoint = dragPosition,
82 | gridRect.contains(dragPoint) else { return nil }
83 | return pointToGridIndex(point: dragPoint, in: gridRect)
84 | }
85 |
86 | public func pointToGridIndex(point: CGPoint, in rect: CGRect) -> Location? {
87 | let xDiff = point.x - rect.origin.x
88 | let yDiff = point.y - rect.origin.y
89 |
90 | let x: Int = Int(floor(xDiff / 128))
91 | let y: Int = Int(floor(yDiff / 128))
92 |
93 | guard x >= 0, y >= 0 else { return nil }
94 |
95 | guard x < circuit.columns, y < circuit.rows else { return nil }
96 |
97 | return (x, y)
98 | }
99 |
100 | @Published var draggedDock: CircuitComponent?
101 |
102 | @Published var dockOrigin: CGFloat = CGFloat.zero {
103 | didSet {
104 | DispatchQueue.main.async { [self] in
105 | dragPosition = nil
106 | }
107 | }
108 | }
109 |
110 | func getModelIndexAt(location: Location) -> Int? {
111 | guard let network = network else { return nil }
112 | let modelSlot = network.circuit.circuitGrid[location]
113 | guard let model = modelSlot.assocModel else { return nil }
114 | return network.models.firstIndex { $0.description == model}
115 | }
116 |
117 | func getNodeIndexAt(location: Location) -> Int? {
118 | guard let network = network else { return nil }
119 | let nodeSlot = network.circuit.circuitGrid[location]
120 | guard let node = nodeSlot.assocNode else { return nil }
121 | return node
122 | }
123 | }
124 |
125 |
126 |
127 |
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/UserModules/CircuitUI.playgroundmodule/Sources/DockView.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 | import CircuitKit
3 |
4 | public struct DockView: View {
5 | @EnvironmentObject var envi: CircuitEnvironment
6 | @State private var dockOrigin: CGFloat = 0
7 | @State private var dockSlots: [Slot] = [Slot(component: Wire(.left, .right)),
8 | Slot(component: Wire(.up, .right)),
9 | Slot(component: Wire(.up, .right, .left)),
10 | Slot(component: Wire(.up, .right, .left, .down)),
11 | Slot(component: Battery(positive: .right, negative: .left)),
12 | Slot(component: Resistor(.left, .right)),
13 | Slot(component: Ground(.up)),
14 | Slot(component: CurrentSource(positive: .right, negative: .left)),
15 | Slot(component: Capacitor(.left, .right)),
16 | Slot(component: Inductor(.left, .right))]
17 | @State var showDock: Bool = true
18 |
19 | public init(){}
20 | public var body: some View {
21 | GeometryReader { geo in
22 | ChevronButton($showDock, direction: .up)
23 | .position(x: showDock ? geo.frame(in: .global).midX - 260 : geo.frame(in: .global).midX, y: showDock ? geo.frame(in: .global).maxY - 270 : geo.frame(in: .global).maxY - 115
24 | )
25 | ScrollView(.horizontal, showsIndicators: false) {
26 | HStack(alignment: .top,spacing: 16) {
27 | ForEach(dockSlots) { slot in
28 | CardView(slot: slot)
29 | .simultaneousGesture(DragGesture(coordinateSpace: .global)
30 | .onChanged { value in
31 | envi.dragPosition = envi.popoverShown ? nil : value.location
32 | envi.selectedSlot = nil
33 | envi.draggedDock = slot.component
34 | }
35 | .onEnded{ finalValue in
36 | envi.dragPosition = nil
37 | let finalDragIndex = envi.pointToGridIndex(point: finalValue.location, in: envi.gridRect)
38 | guard let finalIndex = finalDragIndex else { return }
39 | envi.circuit.circuitGrid[finalIndex].changeComponent(to: slot.component)
40 | }
41 | )
42 | .allowsHitTesting(envi.dragPosition == nil)
43 | }
44 | }
45 | .overlay(
46 | GeometryReader { geo in
47 | Color.clear
48 | .onChange(of: geo.frame(in: .global).origin, perform: {value in
49 | DispatchQueue.main.async {
50 | envi.dockOrigin = geo.frame(in: .global).origin.x
51 | }
52 | })
53 | }
54 | )
55 | .padding(16)
56 | }
57 | .background(BlurView())
58 | .background(Color.gray.opacity(0.25))
59 | .cornerRadius(16)
60 | .padding(16)
61 | .frame(maxWidth: 600, alignment: .center)
62 | .position(x: geo.frame(in: .global).midX, y: showDock ? geo.frame(in: .global).maxY - 170 : geo.frame(in: .global).maxY + 200)
63 | }.animation(.spring(), value: showDock)
64 | }
65 | }
66 |
67 | public struct CardView: View {
68 | @Environment (\.colorScheme) var colorScheme: ColorScheme
69 | @EnvironmentObject var envi: CircuitEnvironment
70 | var slot: Slot
71 |
72 | public var body: some View {
73 | VStack {
74 | ComponentView(component: slot.component)
75 | .clipShape(RoundedRectangle(cornerRadius: 8))
76 | .overlay(RoundedRectangle(cornerRadius: 8)
77 | .stroke(Color.clear.getBackground(for: colorScheme), lineWidth: 4))
78 | .transition(.opacity)
79 | .frame(width: 72, height: 72, alignment: .center)
80 | Text(slot.component.componentType.name)
81 | .font(.system(size: 15))
82 | }
83 | }
84 | }
85 |
86 |
87 |
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/UserModules/CircuitKit.playgroundmodule/Sources/Contacts.swift:
--------------------------------------------------------------------------------
1 | public enum ConnectionType {
2 | case single, straight, corner, tshape, cross, none
3 | }
4 |
5 | public struct Contacts: CustomStringConvertible, Equatable {
6 | public var contactPoints: Set
7 |
8 | public var orientation: Orientation {
9 | if contactPoints == Set([.up, .down]) {
10 | return .vertical
11 | } else {
12 | return .horizontal
13 | }
14 | }
15 |
16 | var connectionType: ConnectionType {
17 | switch contactPoints.count {
18 | case 1:
19 | return .single
20 | case 2:
21 | switch contactPoints {
22 | case Set([.right, .left]), Set([.up, .down]):
23 | return .straight
24 | default:
25 | return .corner
26 | }
27 | case 3:
28 | return .tshape
29 | case 4:
30 | return .cross
31 | default:
32 | return .none
33 | }
34 | }
35 | public var description: String {
36 | switch contactPoints {
37 | case Set([.up]):
38 | return "╵"
39 | case Set([.down]):
40 | return "╷"
41 | case Set([.right]):
42 | return "╴"
43 | case Set([.left]):
44 | return "╶"
45 | case Set([.up, .down]):
46 | return "│"
47 | case Set([.right, .left]):
48 | return "─"
49 | case Set([.up, .left]):
50 | return "┘"
51 | case Set([.up, .right]):
52 | return "└"
53 | case Set([.left, .down]):
54 | return "┐"
55 | case Set([.right, .down]):
56 | return "┌"
57 | case Set([.up, .left, .right]):
58 | return "┴"
59 | case Set([.down, .right, .left]):
60 | return "┬"
61 | case Set([.up, .down, .left]):
62 | return "┤"
63 | case Set([.up, .down, .right]):
64 | return "├"
65 | case Set([.up, .down, .left, .right]):
66 | return "┼"
67 | default:
68 | return " "
69 | }
70 | }
71 |
72 | public init(_ contactPoints: [Direction]) {
73 | self.contactPoints = Set(contactPoints)
74 | }
75 |
76 | public mutating func rotate(_ rotation: Rotation) {
77 | switch rotation {
78 | case .clockwise:
79 | self.contactPoints = Set(contactPoints.map({$0.rotationClockwise}))
80 | case .counterclockwise:
81 | self.contactPoints = Set(contactPoints.map({$0.rotationCounterclockwise}))
82 | }
83 | }
84 |
85 | public mutating func flip(_ orientation: Orientation) {
86 | switch orientation {
87 | case .horizontal:
88 | self.contactPoints = Set(contactPoints.map({$0.flipHorizontal}))
89 | case .vertical:
90 | self.contactPoints = Set(contactPoints.map({$0.flipVertical}))
91 | }
92 | }
93 |
94 | public static func == (lhs: Contacts, rhs: Contacts) -> Bool {
95 | return
96 | lhs.contactPoints == rhs.contactPoints
97 | }
98 |
99 | public func getRotationCount(count: inout Double) {
100 | switch connectionType {
101 | case .single:
102 | switch contactPoints {
103 | case Set([.up]):
104 | count = 3
105 | case Set([.right]):
106 | count = 0
107 | case Set([.left]):
108 | count = 1
109 | default:
110 | count = 2
111 | }
112 | case .straight:
113 | switch contactPoints {
114 | case Set([.up, .down]):
115 | count = 1
116 | default:
117 | count = 0
118 | }
119 | case .corner:
120 | switch contactPoints {
121 | case Set([.up, .right]):
122 | count = 1
123 | case Set([.down, .right]):
124 | count = 2
125 | case Set([.down, .left]):
126 | count = 3
127 | default:
128 | count = 0
129 | }
130 | case .tshape:
131 | switch contactPoints {
132 | case Set([.up, .left, .right]):
133 | count = 1
134 | case Set([.down, .up, .right]):
135 | count = 2
136 | case Set([.down, .left, .right]):
137 | count = 3
138 | default:
139 | count = 0
140 | }
141 | default:
142 | count = 0
143 | }
144 | }
145 | }
146 |
147 |
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/UserModules/CircuitUI.playgroundmodule/Sources/GroundView.swift:
--------------------------------------------------------------------------------
1 | import CircuitKit
2 | import SwiftUI
3 |
4 | public struct GroundView: View {
5 | private var ground: Ground
6 |
7 | private var rotationCount: Double { ground.rotationCount }
8 | private var rotatingRemainder: Double { rotationCount.truncatingRemainder(dividingBy: 4.0) }
9 | private var isFlipped: Bool {
10 | [-1.0,-2.0,2.0,3.0].contains(rotationCount.remainder(dividingBy: 4))
11 | }
12 |
13 | public init(ground: Ground) {
14 | self.ground = ground
15 | }
16 |
17 | public var body: some View {
18 | ZStack {
19 | wireGround
20 | groundNode
21 | }
22 | }
23 |
24 | var wireGround: some View {
25 | WireShape(mask: {
26 | SquareImageView("WireGroundShape")
27 | .rotation3DEffect( Angle(degrees: rotationCount * 90),
28 | axis: (x: 0, y: 0, z: 1))
29 | .scaleEffect(1.5)
30 | }, overlay: {
31 | switch rotatingRemainder {
32 | case 1, -3:
33 | SquareImageView("WireGround2")
34 | .rotation3DEffect( Angle(degrees: 90),
35 | axis: (x: 0, y: 0, z: 1))
36 | .rotation3DEffect( Angle(degrees: 180),
37 | axis: (x: 0, y: 1, z: 0))
38 | case 2, -2:
39 | SquareImageView("WireGround2")
40 | .rotation3DEffect( Angle(degrees: 180),
41 | axis: (x: 0, y: 0, z: 1))
42 | case 3, -1:
43 | SquareImageView("WireGround1")
44 | .rotation3DEffect( Angle(degrees: -90),
45 | axis: (x: 0, y: 0, z: 1))
46 | .rotation3DEffect( Angle(degrees: 180),
47 | axis: (x: 0, y: 1, z: 0))
48 | default:
49 | SquareImageView("WireGround1")
50 | }
51 | })
52 | }
53 |
54 | var groundNode: some View {
55 | GeometryReader { geo in
56 | ZStack {
57 | SquareImageView("Ground3")
58 | .rotation3DEffect(Angle(degrees: rotationCount * 90), axis: (x: 0, y: 0, z: 1))
59 | .rotation3DEffect([1, -3, 2, -2].contains(rotatingRemainder) ? .degrees(180) : .zero, axis: (x: 1, y: 0, z: 0))
60 | .rotation3DEffect([2, -2, 3, -1].contains(rotatingRemainder) ? .degrees(180) : .zero, axis: (x: 0, y: 1, z: 0))
61 | .offset(getOffsetGround3(geo.size))
62 | SquareImageView("Ground2")
63 | .rotation3DEffect(Angle(degrees: rotationCount * 90), axis: (x: 0, y: 0, z: 1))
64 | .rotation3DEffect([1, -3, 2, -2].contains(rotatingRemainder) ? .degrees(180) : .zero, axis: (x: 1, y: 0, z: 0))
65 | .rotation3DEffect([2, -2, 3, -1].contains(rotatingRemainder) ? .degrees(180) : .zero, axis: (x: 0, y: 1, z: 0))
66 | SquareImageView("Ground1")
67 | .rotation3DEffect(Angle(degrees: rotationCount * 90), axis: (x: 0, y: 0, z: 1))
68 | .rotation3DEffect([1, -3, 2, -2].contains(rotatingRemainder) ? .degrees(180) : .zero, axis: (x: 1, y: 0, z: 0))
69 | .rotation3DEffect([2, -2, 3, -1].contains(rotatingRemainder) ? .degrees(180) : .zero, axis: (x: 0, y: 1, z: 0))
70 | .offset(getOffsetGround1(geo.size))
71 | }.position(x: geo.frame(in: .local).midX, y: geo.frame(in: .local).midY)
72 | }.aspectRatio(1,contentMode: .fit )
73 | }
74 |
75 | func getOffsetGround1(_ size: CGSize) -> CGSize {
76 | switch rotatingRemainder {
77 | case 1, -3:
78 | return CGSize(width: 0, height: -3 * size.height / 32)
79 | case 2, -2:
80 | return CGSize(width: 3 * size.width / 32, height: 0)
81 | case 3, -1:
82 | return CGSize(width: 0, height: 3 * size.height / 32)
83 | default:
84 | return CGSize(width: -3 * size.width / 32, height: 0)
85 | }
86 | }
87 |
88 | func getOffsetGround3(_ size: CGSize) -> CGSize {
89 | switch rotatingRemainder {
90 | case 1, -3:
91 | return CGSize(width: 0, height: 3 * size.height / 32)
92 | case 2, -2:
93 | return CGSize(width: -3 * size.width / 32, height: 0)
94 | case 3, -1:
95 | return CGSize(width: 0, height: -3 * size.height / 32)
96 | default:
97 | return CGSize(width: 3 * size.width / 32, height: 0)
98 | }
99 | }
100 | }
101 |
102 |
103 |
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/UserModules/FocusEntity.playgroundmodule/Sources/FocusEntity+Segment.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FocusEntity+Segment.swift
3 | // FocusEntity
4 | //
5 | // Created by Max Cobb on 8/28/19.
6 | // Copyright © 2019 Max Cobb. All rights reserved.
7 | //
8 |
9 | #if canImport(ARKit)
10 | import RealityKit
11 |
12 | internal extension FocusEntity {
13 | /*
14 | The focus square consists of eight segments as follows, which can be individually animated.
15 |
16 | s0 s1
17 | _ _
18 | s2 | | s3
19 |
20 | s4 | | s5
21 | - -
22 | s6 s7
23 | */
24 | enum Corner {
25 | case topLeft // s0, s2
26 | case topRight // s1, s3
27 | case bottomRight // s5, s7
28 | case bottomLeft // s4, s6
29 | }
30 |
31 | enum Alignment {
32 | case horizontal // s0, s1, s6, s7
33 | case vertical // s2, s3, s4, s5
34 | }
35 |
36 | enum Direction {
37 | case up, down, left, right
38 |
39 | var reversed: Direction {
40 | switch self {
41 | case .up: return .down
42 | case .down: return .up
43 | case .left: return .right
44 | case .right: return .left
45 | }
46 | }
47 | }
48 |
49 | class Segment: Entity, HasModel {
50 |
51 | // MARK: - Configuration & Initialization
52 |
53 | /// Thickness of the focus square lines in m.
54 | static let thickness: Float = 0.018
55 |
56 | /// Length of the focus square lines in m.
57 | static let length: Float = 0.5 // segment length
58 |
59 | /// Side length of the focus square segments when it is open (w.r.t. to a 1x1 square).
60 | static let openLength: Float = 0.2
61 |
62 | let corner: Corner
63 | let alignment: Alignment
64 | let plane: ModelComponent
65 |
66 | init(name: String, corner: Corner, alignment: Alignment, color: Material.Color) {
67 | self.corner = corner
68 | self.alignment = alignment
69 |
70 | switch alignment {
71 | case .vertical:
72 | plane = ModelComponent(
73 | mesh: .generatePlane(width: 1, depth: 1),
74 | materials: [UnlitMaterial(color: color)]
75 | )
76 | case .horizontal:
77 | plane = ModelComponent(
78 | mesh: .generatePlane(width: 1, depth: 1),
79 | materials: [UnlitMaterial(color: color)]
80 | )
81 | }
82 | super.init()
83 |
84 | switch alignment {
85 | case .vertical:
86 | self.scale = [Segment.thickness, 1, Segment.length]
87 | case .horizontal:
88 | self.scale = [Segment.length, 1, Segment.thickness]
89 | }
90 | // self.orientation = .init(angle: .pi / 2, axis: [1, 0, 0])
91 | self.name = name
92 |
93 | // let material = plane.firstMaterial!
94 | // material.diffuse.contents = FocusSquare.primaryColor
95 | // material.isDoubleSided = true
96 | // material.ambient.contents = UIColor.black
97 | // material.lightingModel = .constant
98 | // material.emission.contents = FocusSquare.primaryColor
99 | model = plane
100 | }
101 |
102 | required init() {
103 | fatalError("init() has not been implemented")
104 | }
105 |
106 | // MARK: - Animating Open/Closed
107 |
108 | var openDirection: Direction {
109 | switch (corner, alignment) {
110 | case (.topLeft, .horizontal): return .left
111 | case (.topLeft, .vertical): return .up
112 | case (.topRight, .horizontal): return .right
113 | case (.topRight, .vertical): return .up
114 | case (.bottomLeft, .horizontal): return .left
115 | case (.bottomLeft, .vertical): return .down
116 | case (.bottomRight, .horizontal): return .right
117 | case (.bottomRight, .vertical): return .down
118 | }
119 | }
120 |
121 | func open() {
122 | if alignment == .horizontal {
123 | self.scale[0] = Segment.openLength
124 | } else {
125 | self.scale[2] = Segment.openLength
126 | }
127 |
128 | let offset = Segment.length / 2 - Segment.openLength / 2
129 | updatePosition(withOffset: Float(offset), for: openDirection)
130 | }
131 |
132 | func close() {
133 | let oldLength: Float
134 | if alignment == .horizontal {
135 | oldLength = self.scale[0]
136 | self.scale[0] = Segment.length
137 | } else {
138 | oldLength = self.scale[2]
139 | self.scale[2] = Segment.length
140 | }
141 |
142 | let offset = Segment.length / 2 - oldLength / 2
143 | updatePosition(withOffset: offset, for: openDirection.reversed)
144 | }
145 |
146 | private func updatePosition(withOffset offset: Float, for direction: Direction) {
147 | switch direction {
148 | case .left: position.x -= offset
149 | case .right: position.x += offset
150 | case .up: position.z -= offset
151 | case .down: position.z += offset
152 | }
153 | }
154 |
155 | }
156 | }
157 | #endif
158 |
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/UserModules/CircuitUI.playgroundmodule/Sources/InfoView.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 | import CircuitKit
3 | import CircuitNetwork
4 |
5 | public struct InfoView: View {
6 | @EnvironmentObject var envi: CircuitEnvironment
7 | private var model: Model? {
8 | guard let network = envi.network, let loc = envi.selectedSlot else { return nil }
9 | guard let index = envi.getModelIndexAt(location: loc) else { return nil }
10 | return network.models[index]
11 | }
12 |
13 | private var node: Node? {
14 | guard let network = envi.network, let loc = envi.selectedSlot else { return nil }
15 | guard let index = envi.getNodeIndexAt(location: loc) else { return nil }
16 | return network.nodes[index]
17 | }
18 |
19 | private var show: Bool { node != nil || model != nil}
20 |
21 | public var body: some View {
22 | Group {
23 | if show {
24 | if let node = node {
25 | NodeInfoView(node: node)
26 | .transition(.opacity)
27 | } else if let model = model {
28 | ModelInfoView(model: model)
29 | .transition(.opacity)
30 | }
31 | }
32 | }.animation(.linear, value: show)
33 | }
34 | }
35 |
36 | private struct NodeInfoView: View {
37 | @EnvironmentObject var envi: CircuitEnvironment
38 | var node: Node
39 | var name: String { node.description }
40 | var voltage: Volts { Volts(node.voltage) }
41 | public init(node: Node) {
42 | self.node = node
43 | }
44 | var body: some View {
45 | GeometryReader { geo in
46 | VStack(alignment: .custom) {
47 | HStack{
48 | Text("Node")
49 | .font(.caption)
50 | Text(name)
51 | .font(.system(size: 32, weight: .heavy, design: .monospaced))
52 | .alignmentGuide(.custom) { $0[.leading] }
53 | }
54 | HStack{
55 | Text("Voltage")
56 | .font(.caption)
57 | Text(voltage.description)
58 | .font(.system(size: 16, weight: .regular, design: .monospaced))
59 | .alignmentGuide(.custom) { $0[.leading] }
60 | }
61 |
62 | }
63 | .padding(16)
64 | .background(BlurView())
65 | .background(Color.gray.opacity(0.25))
66 | .cornerRadius(16)
67 | .position(x: geo.frame(in: .local).maxX - 120, y: geo.frame(in: .local).midY)
68 | }
69 | }
70 | }
71 |
72 | private struct ModelInfoView: View {
73 | var model: Model
74 | var name: String { model.description }
75 | var voltage: Volts { Volts(model.voltage) }
76 | var current: Amperes { Amperes(model.current) }
77 | var power: Watts { Watts(model.power) }
78 |
79 | public init(model: Model) {
80 | self.model = model
81 | }
82 |
83 | var body: some View {
84 | GeometryReader { geo in
85 | VStack(alignment: .custom) {
86 | HStack{
87 | Text("Component")
88 | .font(.caption)
89 | Text(name)
90 | .font(.system(size: 32, weight: .heavy, design: .monospaced))
91 | .alignmentGuide(.custom) { $0[.leading] }
92 | }
93 | HStack{
94 | Text("Voltage")
95 | .font(.caption)
96 | Text(voltage.description)
97 | .font(.system(size: 16, weight: .regular, design: .monospaced))
98 | .alignmentGuide(.custom) { $0[.leading] }
99 | }
100 | HStack{
101 | Text("Current")
102 | .font(.caption)
103 | Text(current.description)
104 | .font(.system(size: 16, weight: .regular, design: .monospaced))
105 | .alignmentGuide(.custom) { $0[.leading] }
106 | }
107 | HStack{
108 | Text("Power")
109 | .font(.caption)
110 | Text(power.description)
111 | .font(.system(size: 16, weight: .regular, design: .monospaced))
112 | .alignmentGuide(.custom) { $0[.leading] }
113 | }
114 | }
115 | .padding(16)
116 | .background(BlurView())
117 | .background(Color.gray.opacity(0.25))
118 | .cornerRadius(16)
119 | .position(x: geo.frame(in: .local).maxX - 120, y: geo.frame(in: .local).midY)
120 | }
121 | }
122 | }
123 |
124 | struct CustomAlignment: AlignmentID {
125 | static func defaultValue(in context: ViewDimensions) -> CGFloat {
126 | return context[.leading]
127 | }
128 | }
129 |
130 | extension HorizontalAlignment {
131 | static let custom: HorizontalAlignment = HorizontalAlignment(CustomAlignment.self)
132 | }
133 |
134 |
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/UserModules/CircuitUI.playgroundmodule/Sources/WireView.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 | import CircuitKit
3 |
4 | public struct WireView: View {
5 | @Environment(\.accessibilityReduceMotion) var reduceMotion
6 |
7 | private var wire: Wire
8 |
9 | private var rotationCount: Double { wire.rotationCount }
10 |
11 | public init(wire: Wire) {
12 | self.wire = wire
13 | }
14 |
15 | public var body: some View {
16 | Group {
17 | switch wire.connectionType {
18 | case .straight:
19 | WireStraightShape(wire)
20 | case .corner:
21 | WireCornerShape(wire)
22 | case .tshape:
23 | WireTshapeShape(wire)
24 | case .cross:
25 | WireCrossShape(wire)
26 | default:
27 | EmptyView()
28 | }
29 | }.mask(LinearGradient(gradient: Gradient(colors: [Color.black, Color.white]), startPoint: .leading, endPoint: .trailing))
30 | }
31 | }
32 |
33 | public struct WireShape: View {
34 | let mask: Mask
35 | let overlay: Overlay
36 |
37 | public init(@ViewBuilder mask: () -> Mask, @ViewBuilder overlay: () -> Overlay) {
38 | self.mask = mask()
39 | self.overlay = overlay()
40 | }
41 |
42 | public var body: some View {
43 | mask
44 | .overlay(
45 | overlay
46 | .mask(
47 | mask
48 | )
49 | )
50 | .clipped()
51 | }
52 | }
53 |
54 | struct WireStraightShape: View {
55 | private var wire: Wire
56 | private var rotationCount: Double { wire.rotationCount }
57 | private var rotatingRemainder: Double { rotationCount.truncatingRemainder(dividingBy: 2.0) }
58 |
59 | init(_ wire: Wire) {
60 | self.wire = wire
61 | }
62 |
63 | var body: some View {
64 | WireShape(mask: {
65 | SquareImageView("WireStraightShape")
66 | .rotation3DEffect( Angle(degrees: rotationCount * 90),
67 | axis: (x: 0, y: 0, z: 1))
68 | .scaleEffect(1.5)
69 | }, overlay: {
70 | switch rotatingRemainder {
71 | case 0:
72 | SquareImageView("WireStraight")
73 | default:
74 | SquareImageView("WireStraight")
75 | .rotation3DEffect( Angle(degrees: 90),
76 | axis: (x: 0, y: 0, z: 1))
77 | }
78 | })
79 | }
80 | }
81 |
82 | struct WireCornerShape: View {
83 | private var wire: Wire
84 | private var rotationCount: Double { wire.rotationCount }
85 | private var rotatingRemainder: Double { rotationCount.truncatingRemainder(dividingBy: 4.0) }
86 |
87 | init(_ wire: Wire) {
88 | self.wire = wire
89 | }
90 |
91 | var body: some View {
92 | WireShape(mask: {
93 | SquareImageView("WireCornerShape")
94 | .rotation3DEffect( Angle(degrees: rotationCount * 90),
95 | axis: (x: 0, y: 0, z: 1))
96 | .scaleEffect(1.5)
97 | }, overlay: {
98 | switch rotatingRemainder {
99 | case 1, -3:
100 | SquareImageView("WireCornerUR")
101 | case 2, -2:
102 | SquareImageView("WireCornerDR")
103 | case 3, -1:
104 | SquareImageView("WireCornerDL")
105 | default:
106 | SquareImageView("WireCornerUL")
107 | }
108 | })
109 | }
110 | }
111 |
112 | struct WireTshapeShape: View {
113 | private var wire: Wire
114 | private var rotationCount: Double { wire.rotationCount }
115 | private var rotatingRemainder: Double { rotationCount.truncatingRemainder(dividingBy: 4.0) }
116 |
117 | init(_ wire: Wire) {
118 | self.wire = wire
119 | }
120 |
121 | var body: some View {
122 | WireShape(mask: {
123 | SquareImageView("WireTshapeShape")
124 | .rotation3DEffect( Angle(degrees: rotationCount * 90),
125 | axis: (x: 0, y: 0, z: 1))
126 | .scaleEffect(1.5)
127 | }, overlay: {
128 | switch rotatingRemainder {
129 | case 1, -3:
130 | SquareImageView("WireTshapeULR")
131 | case 2, -2:
132 | SquareImageView("WireTshapeDUR")
133 | case 3, -1:
134 | SquareImageView("WireTshapeDLR")
135 | default:
136 | SquareImageView("WireTshapeDUL")
137 | }
138 | })
139 | }
140 | }
141 |
142 | struct WireCrossShape: View {
143 | private var wire: Wire
144 |
145 | init(_ wire: Wire) {
146 | self.wire = wire
147 | }
148 |
149 | var body: some View {
150 | WireShape(mask: {
151 | SquareImageView("WireCrossShape")
152 | .scaleEffect(1.5)
153 | .rotationEffect(.degrees(90 * wire.rotationCount))
154 | }, overlay: {
155 | SquareImageView("WireCross")
156 | })
157 | }
158 | }
159 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # CircuitPlay
2 | 
3 | ## Introduction
4 | Hello, welcome to Jose Adolfo Talactac's submission for WWDC21 Swift Student Challenge, CircuitPlay! CircuitPlay is an interactive sandbox experience that allows you to design, setup, and simulate circuit. CircuitPlay is written in Swift and built with SwiftUI, Accelerate, ARKit, and RealityKit. The showcase video can be found [here](https://youtu.be/pm3mlDZJSes).
5 |
6 |
7 | ## User Interface
8 | Existing circuit simulators on the market today have cluttered and outdated user interface. For students who are new to circuit theory, they may find these simulators daunting and overwhelming. CircuitPlay aims to fill the need for a simple and intuitive circuit simulator. CircuitPlay has a user interface with touch input in mind. The fluidity of its UI is thanks to versatility and power that SwiftUI provides.
9 |
10 | CircuitPlay has three stages: Design, Setup, and Solve. In the Design stage, the user is presented with a grid and dock. The user can pick any component from the dock and drop it to a slot in the grid. Tapping a slot will show controls to rotate or delete the component. The Setup stage allows the user to change a component’s value by showing a popover prompt when the measurement is tapped.
11 |
12 | ## Circuit Solving Algorithm
13 | In the Solve stage, CircuitPlay uses its circuit solving algorithm. CircuitPlay first determines the nodes present in the circuit through recursion and matches the appropriate model for each circuit component. These nodes and models are the main parts of the circuit network.
14 |
15 | Afterwards, CircuitPlay uses the modified nodal analysis (MNA) method, where the *A* matrix and *b* vector are formed. The *A* matrix describes the conductance between each node and the connections between voltage sources and each node. The *b* vector includes the known values, which are the current across nodes and the voltage sources values. Solving the *x* vector in `Ax = b` will provide the node voltages and the current across voltage sources.
16 |
17 | The *A* matrix is sparse, meaning, there are zeroes that are present in the matrix. To leverage this, CircuitPlay utilizes Accelerate’s Sparse Solver framework to create a sparse matrix, and to factor and solve it. This is opposed to LAPACK which requires matrices to be dense. CircuitPlay only contains passive and time-domain components, thus, the matrix generated by CircuitPlay is also always symmetric. Memory use is reduced by only storing the upper triangle of the symmetric matrix. The appropriate factorization method for a symmetric and positive-indefinite matrix is the LDLᵀ decomposition. A direct method is utilized, meaning the matrix is only factored and solved once. Since there are no nonlinear components, such as diodes and transistors, it removes the need for an iterative method as there is no need to determine convergence with an I-V curve.
18 |
19 | Using the SparseSolve methods, the node voltages and voltage source currents are solved. Using Current Divider Rule and Ohm’s Law, the voltage and current across different components are determined. For time-domain components like capacitors and inductors, a linearized companion model are utilized and backwards Euler’s method is performed to determine the equivalent values of the companion model at time step *n+1*.
20 |
21 | ## Augmented Reality
22 | CircuitPlay also provides an option to view the circuit in augmented reality. This AR portion is powered by ARKit and RealityKit. RealityKit allowed CircuitPlay to display 3D models with such high performance. It uses Focus Entity, an open-source Swift Package, to easily communicate surface detection to the user. Just like Jose Adolfo's WWDC2020 submission [LearnWithAR](https://github.com/devjoseadolfo/LearnWithAR), CircuitPlay further proves that augmented reality will have a significant role in the STEM education space in the future.
23 |
24 | ## Credits
25 |
26 | - [Barlow font](https://github.com/jpt/barlow) designed by Jeremy Tribby found in the Playground thumbnail and the images in each PlaygroundPage is used under the [Open Font License](https://github.com/jpt/barlow/blob/main/OFL.txt).
27 | - San Francisco font and SF Symbols icons found in the Playground’s interface is accessed as part of iPadOS’ system fonts and used fairly within the license set by Apple Inc. for the development of software for Apple platforms.
28 | - [Focus Entity](https://github.com/maxxfrazer/FocusEntity) Swift Package by Max Cobb is used in the augmented reality component of the Playground for placing the model entity in the view. The package is used under the [MIT license](https://github.com/maxxfrazer/FocusEntity/blob/main/LICENSE).
29 | - All assets that aren’t attributed to another party are made by the creator of this Playground, Jose Adolfo Talactac. These include the instructional text and images, 2D UI icons, and 3D USDZ models.
30 |
31 | ## Screenshots
32 | 
33 | 
34 | 
35 | 
36 | 
37 |
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/UserModules/CircuitUI.playgroundmodule/Sources/GridView.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 | import CircuitKit
3 | import Combine
4 | public struct GridView: View {
5 | @EnvironmentObject var envi: CircuitEnvironment
6 |
7 | private var slots: [Slot] {
8 | envi.circuit.circuitGrid.rows.flatMap({$0.slots})
9 | }
10 |
11 | private var columns: [GridItem] {
12 | Array(repeating: .init(.flexible(), spacing: envi.currentState == .design ? 0 : 0), count: envi.circuit.columns)
13 | }
14 |
15 | public init() {}
16 |
17 | public var body: some View {
18 | ScrollView([.horizontal, .vertical], showsIndicators: false) {
19 | ZStack{
20 | Group {
21 | if envi.circuit.rows <= 10 {
22 | AddButton(to: .down)
23 | .offset(y: CGFloat(64 * envi.circuit.rows) + 24)
24 | AddButton(to: .up)
25 | .offset(y: CGFloat(-64 * envi.circuit.rows) - 24)
26 | }
27 | if envi.circuit.columns <= 10 {
28 | AddButton(to: .right)
29 | .offset(x: CGFloat(64 * envi.circuit.columns) + 24)
30 | AddButton(to: .left)
31 | .offset(x: CGFloat(-64 * envi.circuit.columns) - 24)
32 | }
33 | }
34 | .opacity(envi.currentState == .design ? 1 : 0)
35 | .allowsHitTesting(envi.currentState == .design)
36 | GridLines(columns: envi.circuit.columns, rows: envi.circuit.rows)
37 | .stroke(Color.secondary,lineWidth: 1.5)
38 | .opacity(envi.currentState == .setup ? 1 : 0)
39 | .blur(radius: envi.currentState == .setup ? 0 : 1)
40 | .frame(width: CGFloat(envi.circuit.columns) * 128, height: CGFloat(envi.circuit.rows) * 128)
41 | .background(
42 | GeometryReader { geo in
43 | var point = geo.frame(in: .global).origin
44 | Color.clear.preference(key: GridOriginKey.self,
45 | value: CGPoint(x: round(point.x),y: round(point.y)))
46 | .onAppear{envi.gridOrigin = geo.frame(in: .global).origin}
47 | }
48 | )
49 |
50 | LazyVGrid(columns: columns, spacing: envi.currentState == .design ? 16 : 0) {
51 | ForEach(slots) { slot in
52 | GridSlotView(slot: $envi.circuit.circuitGrid.rows[slot.gridIndex.y].slots[slot.gridIndex.x])
53 | .frame(width: envi.currentState == .design ? 112 : 128, height: envi.currentState == .design ? 112 : 128, alignment: .center)
54 | .simultaneousGesture(envi.currentState == .design ? drag : nil)
55 | .allowsHitTesting(envi.dragPosition == nil)
56 | .zIndex(Double(slot.zIndex))
57 | }
58 | }
59 | }
60 | .animation(.easeOut)
61 | .padding(128)
62 | }
63 | .frame(width: .infinity, height: .infinity, alignment: .center)
64 | .background(Color(UIColor.secondarySystemBackground))
65 | .onPreferenceChange(GridOriginKey.self) {value in
66 | DispatchQueue.main.async {
67 | envi.detector.send(value)
68 | }
69 | }
70 | .onReceive(envi.publisher, perform: { envi.gridOrigin = $0})
71 | }
72 |
73 | var drag: some Gesture {
74 | DragGesture(minimumDistance: 1, coordinateSpace: .global)
75 | .onChanged { value in
76 | envi.firstDragIndex = envi.pointToGridIndex(point: value.startLocation, in: envi.gridRect)
77 | envi.selectedSlot = nil
78 | envi.dragPosition = envi.popoverShown ? nil : value.location
79 |
80 | }
81 | .onEnded { finalValue in
82 | let finalDragIndex = envi.pointToGridIndex(point: finalValue.location, in: envi.gridRect)
83 | envi.dragPosition = nil
84 | guard let firstIndex = envi.firstDragIndex else { return }
85 | envi.firstDragIndex = nil
86 | guard let finalIndex = finalDragIndex else { return }
87 | let firstComp = envi.circuit.circuitGrid[firstIndex].component
88 | envi.circuit.circuitGrid[firstIndex].changeComponent(to: BlankSpace())
89 | envi.circuit.circuitGrid[finalIndex].changeComponent(to: firstComp)
90 | }
91 | }
92 |
93 | struct GridOriginKey: PreferenceKey {
94 | typealias Value = CGPoint
95 | static var defaultValue = CGPoint.zero
96 | static func reduce(value: inout Value, nextValue: () -> Value) {
97 | value.x += nextValue().x
98 | value.y += nextValue().y
99 | }
100 | }
101 | }
102 |
103 | extension View {
104 | func execute(_ closure: () -> Void) -> Self {
105 | closure()
106 | return self
107 | }
108 | }
109 |
110 |
111 |
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/UserModules/FocusEntity.playgroundmodule/Sources/FocusEntity+Alignment.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FocusEntity.swift
3 | // FocusEntity
4 | //
5 | // Created by Max Cobb on 8/26/19.
6 | // Copyright © 2019 Max Cobb. All rights reserved.
7 | //
8 |
9 | #if canImport(ARKit)
10 | import RealityKit
11 | import ARKit
12 | import Combine
13 |
14 | extension FocusEntity {
15 |
16 | // MARK: Helper Methods
17 |
18 | /// Update the position of the focus square.
19 | internal func updatePosition() {
20 | // Average using several most recent positions.
21 | recentFocusEntityPositions = Array(recentFocusEntityPositions.suffix(10))
22 |
23 | // Move to average of recent positions to avoid jitter.
24 | let average = recentFocusEntityPositions.reduce(
25 | SIMD3.zero, { $0 + $1 }
26 | ) / Float(recentFocusEntityPositions.count)
27 | self.position = average
28 | }
29 |
30 | /// Update the transform of the focus square to be aligned with the camera.
31 | internal func updateTransform(raycastResult: ARRaycastResult) {
32 | self.updatePosition()
33 |
34 | if state != .initializing {
35 | updateAlignment(for: raycastResult)
36 | }
37 | }
38 |
39 | internal func updateAlignment(for raycastResult: ARRaycastResult) {
40 |
41 | var targetAlignment = raycastResult.worldTransform.orientation
42 |
43 | // Determine current alignment
44 | var alignment: ARPlaneAnchor.Alignment?
45 | if let planeAnchor = raycastResult.anchor as? ARPlaneAnchor {
46 | alignment = planeAnchor.alignment
47 | // Catching case when looking at ceiling
48 | if targetAlignment.act([0, 1, 0]).y < -0.9 {
49 | targetAlignment *= simd_quatf(angle: .pi, axis: [0, 1, 0])
50 | }
51 | } else if raycastResult.targetAlignment == .horizontal {
52 | alignment = .horizontal
53 | } else if raycastResult.targetAlignment == .vertical {
54 | alignment = .vertical
55 | }
56 |
57 | // add to list of recent alignments
58 | if alignment != nil {
59 | self.recentFocusEntityAlignments.append(alignment!)
60 | }
61 |
62 | // Average using several most recent alignments.
63 | self.recentFocusEntityAlignments = Array(self.recentFocusEntityAlignments.suffix(20))
64 |
65 | let alignCount = self.recentFocusEntityAlignments.count
66 | let horizontalHistory = recentFocusEntityAlignments.filter({ $0 == .horizontal }).count
67 | let verticalHistory = recentFocusEntityAlignments.filter({ $0 == .vertical }).count
68 |
69 | // Alignment is same as most of the history - change it
70 | if alignment == .horizontal && horizontalHistory > alignCount * 3/4 ||
71 | alignment == .vertical && verticalHistory > alignCount / 2 ||
72 | raycastResult.anchor is ARPlaneAnchor {
73 | if alignment != self.currentAlignment ||
74 | (alignment == .vertical && self.shouldContinueAlignAnim(to: targetAlignment)
75 | ) {
76 | isChangingAlignment = true
77 | self.currentAlignment = alignment
78 | }
79 | } else {
80 | // Alignment is different than most of the history - ignore it
81 | return
82 | }
83 |
84 | // Change the focus entity's alignment
85 | if isChangingAlignment {
86 | // Uses interpolation.
87 | // Needs to be called on every frame that the animation is desired, Not just the first frame.
88 | performAlignmentAnimation(to: targetAlignment)
89 | } else {
90 | orientation = targetAlignment
91 | }
92 | }
93 |
94 | internal func normalize(_ angle: Float, forMinimalRotationTo ref: Float) -> Float {
95 | // Normalize angle in steps of 90 degrees such that the rotation to the other angle is minimal
96 | var normalized = angle
97 | while abs(normalized - ref) > .pi / 4 {
98 | if angle > ref {
99 | normalized -= .pi / 2
100 | } else {
101 | normalized += .pi / 2
102 | }
103 | }
104 | return normalized
105 | }
106 |
107 | internal func getCamVector() -> (position: SIMD3, direciton: SIMD3)? {
108 | guard let camTransform = self.arView?.cameraTransform else {
109 | return nil
110 | }
111 | let camDirection = camTransform.matrix.columns.2
112 | return (camTransform.translation, -[camDirection.x, camDirection.y, camDirection.z])
113 | }
114 |
115 | /// - Parameters:
116 | /// - Returns: ARRaycastResult if an existing plane geometry or an estimated plane are found, otherwise nil.
117 | internal func smartRaycast() -> ARRaycastResult? {
118 | // Perform the hit test.
119 | guard let (camPos, camDir) = self.getCamVector() else {
120 | return nil
121 | }
122 | let rcQuery = ARRaycastQuery(
123 | origin: camPos, direction: camDir,
124 | allowing: self.allowedRaycast, alignment: .any
125 | )
126 | let results = self.arView?.session.raycast(rcQuery) ?? []
127 |
128 | // 1. Check for a result on an existing plane using geometry.
129 | if let existingPlaneUsingGeometryResult = results.first(
130 | where: { $0.target == .existingPlaneGeometry }
131 | ) {
132 | return existingPlaneUsingGeometryResult
133 | }
134 |
135 | // 2. As a fallback, check for a result on estimated planes.
136 | return results.first(where: { $0.target == .estimatedPlane })
137 | }
138 |
139 | /// Uses interpolation between orientations to create a smooth `easeOut` orientation adjustment animation.
140 | internal func performAlignmentAnimation(to newOrientation: simd_quatf) {
141 | // Interpolate between current and target orientations.
142 | orientation = simd_slerp(orientation, newOrientation, 0.15)
143 | // This length creates a normalized vector (of length 1) with all 3 components being equal.
144 | self.isChangingAlignment = self.shouldContinueAlignAnim(to: newOrientation)
145 | }
146 |
147 | func shouldContinueAlignAnim(to newOrientation: simd_quatf) -> Bool {
148 | let testVector = simd_float3(repeating: 1 / sqrtf(3))
149 | let point1 = orientation.act(testVector)
150 | let point2 = newOrientation.act(testVector)
151 | let vectorsDot = simd_dot(point1, point2)
152 | // Stop interpolating when the rotations are close enough to each other.
153 | return vectorsDot < 0.999
154 | }
155 |
156 | /**
157 | Reduce visual size change with distance by scaling up when close and down when far away.
158 |
159 | These adjustments result in a scale of 1.0x for a distance of 0.7 m or less
160 | (estimated distance when looking at a table), and a scale of 1.2x
161 | for a distance 1.5 m distance (estimated distance when looking at the floor).
162 | */
163 | internal func scaleBasedOnDistance(camera: ARCamera?) -> Float {
164 | guard let camera = camera else { return 1.0 }
165 |
166 | let distanceFromCamera = simd_length(self.convert(position: .zero, to: nil) - camera.transform.translation)
167 | if distanceFromCamera < 0.7 {
168 | return distanceFromCamera / 0.7
169 | } else {
170 | return 0.25 * distanceFromCamera + 0.825
171 | }
172 | }
173 | }
174 | #endif
175 |
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/UserModules/CircuitUI.playgroundmodule/Sources/SlotView.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 | import CircuitKit
3 |
4 | public struct GridSlotView: View {
5 | @EnvironmentObject var envi: CircuitEnvironment
6 | @Binding private var slot: Slot
7 | @Environment (\.colorScheme) private var colorScheme: ColorScheme
8 | @State private var modelIndex: Int?
9 | @State private var nodeIndex: Int?
10 | private var showDesignSideButton: Bool { slot.selected && envi.currentState == .design }
11 |
12 | public init(slot: Binding) {
13 | self._slot = slot
14 | }
15 |
16 | var isVertical: Bool {
17 | guard slot.component is Connectable else { return false }
18 | let component = slot.component as! Connectable
19 | if slot.component is Capacitor {
20 | return component.contacts.orientation != .vertical
21 | }
22 | return component.contacts.orientation == .vertical
23 | }
24 |
25 | public var body: some View {
26 | ZStack {
27 | Group {
28 | background
29 | main
30 | stroke
31 | }.allowsHitTesting(envi.currentState != .setup)
32 | if envi.currentState == .play {
33 | if let index = modelIndex,
34 | let network = envi.network,
35 | let model = network.models[index],
36 | let direction = model.currentGoing,
37 | model.current != 0 {
38 | Group {
39 | CurrentSignView(direction: direction)
40 | if !(slot.component is Battery || slot.component is CurrentSource) {
41 | SignsView(positiveDirection: direction.opposite)
42 | }
43 | }
44 | .shadow(radius: envi.currentState == .design ? 0 : 2.5,
45 | x: envi.currentState == .design ? 0 : -2.5,
46 | y: envi.currentState == .design ? 0 : 2.5)
47 | GeometryReader { geo in
48 | NameView(name: model.description)
49 | .rotationEffect(isVertical ? .degrees(-90) : .zero)
50 | .position(x: isVertical ? 1 * geo.frame(in: .local).width / 4 : geo.frame(in: .local).midX,
51 | y: isVertical ? geo.frame(in: .local).midY : 1 * geo.frame(in: .local).height / 4)
52 | .offset(x: isVertical && slot.component.componentType.imageName == "Source" ? -geo.frame(in: .local).width / 8 : 0, y: !isVertical && slot.component.componentType.imageName == "Source" ? -geo.frame(in: .local).height / 8 : 0)
53 | }
54 | }
55 | }
56 | if envi.currentState != .design {
57 | measurement
58 | }
59 | }
60 | .frame(alignment: .center)
61 | .overlay(sideOverlay)
62 | .offset(x: slot.selected && envi.currentState == .design ? 8 : 0,
63 | y: slot.selected && envi.currentState == .design ? -8 : 0)
64 | .onTapGesture(count: 1) {
65 | slot.selected = !slot.selected
66 | envi.selectedSlot = slot.selected ? slot.gridIndex : nil
67 | }
68 | .allowsHitTesting(slot.component.componentType != .spacer)
69 | .onChange(of: envi.currentState) {
70 | if $0 == .play {
71 | modelIndex = envi.getModelIndexAt(location: slot.gridIndex)
72 | nodeIndex = envi.getNodeIndexAt(location: slot.gridIndex)
73 | }
74 | }
75 | }
76 |
77 | var main: some View {
78 | ComponentView(component: slot.component)
79 | .transition(.fadeAndScale)
80 | .shadow(radius: envi.currentState == .design ? 0 : 2.5,
81 | x: envi.currentState == .design ? 0 : -2.5,
82 | y: envi.currentState == .design ? 0 : 2.5)
83 | }
84 |
85 | var stroke: some View {
86 | RoundedRectangle(cornerRadius: envi.currentState == .design ? 8 : 0)
87 | .stroke(slot.gridIndex == envi.dragGridIndex ?? Location(-1,-1) ? Color.green : Color.clear.getBackground(for: colorScheme),
88 | lineWidth: envi.currentState == .design ? 4 : 0)
89 |
90 | }
91 |
92 | var background: some View {
93 | GeometryReader { geo in
94 | RoundedRectangle(cornerRadius: envi.currentState == .setup ? 0 : 8)
95 | .fill(slot.selected ? Color(red: 0.353, green: 0.521, blue: 0.652, opacity: 1.000) : Color.clear)
96 | .shadow(
97 | radius: slot.selected && envi.currentState == .design ? 10 : 0,
98 | x: slot.selected && envi.currentState == .design ? -5 : 0,
99 | y: slot.selected && envi.currentState == .design ? 5 : 0)
100 | .frame(width: geo.size.width - 1, height: geo.size.height - 1, alignment: .center)
101 | .offset(x: 0.5, y: 0.5)
102 | }
103 | }
104 | var sideOverlay: some View {
105 | DesignSideButtonsView()
106 | .offset(x: showDesignSideButton ? 88: 60)
107 | .opacity(showDesignSideButton ? 1 : 0)
108 | .allowsHitTesting(showDesignSideButton)
109 | }
110 |
111 | var measurement: some View {
112 | GeometryReader { geo in
113 | if !slot.component.measurements.isEmpty {
114 | MeasurementView(measurement: $slot.component.measurements[0])
115 | .rotationEffect(isVertical ? .degrees(-90) : .zero)
116 | .position(x: isVertical ? 3 * geo.frame(in: .local).width / 4 : geo.frame(in: .local).midX,
117 | y: isVertical ? geo.frame(in: .local).midY : 3 * geo.frame(in: .local).height / 4)
118 | .offset(x: isVertical && slot.component.componentType.imageName == "Source" ? geo.frame(in: .local).width / 8 : 0, y: !isVertical && slot.component.componentType.imageName == "Source" ? geo.frame(in: .local).height / 8 : 0)
119 |
120 | }
121 | }
122 | }
123 |
124 | var name: some View {
125 | GeometryReader { geo in
126 | if !slot.component.measurements.isEmpty {
127 | MeasurementView(measurement: $slot.component.measurements[0])
128 | .rotationEffect(isVertical ? .degrees(-90) : .zero)
129 | .position(x: isVertical ? 3 * geo.frame(in: .local).width / 4 : geo.frame(in: .local).midX,
130 | y: isVertical ? geo.frame(in: .local).midY : 3 * geo.frame(in: .local).height / 4)
131 | .offset(x: isVertical && slot.component.componentType.imageName == "Source" ? geo.frame(in: .local).width / 8 : 0, y: !isVertical && slot.component.componentType.imageName == "Source" ? geo.frame(in: .local).height / 8 : 0)
132 |
133 | }
134 | }
135 | }
136 | }
137 |
138 | extension AnyTransition {
139 | static var fadeAndScale: AnyTransition {
140 | AnyTransition.opacity.combined(with: .scale)
141 | }
142 | }
143 |
144 |
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/UserModules/CircuitNetwork.playgroundmodule/Sources/Network.swift:
--------------------------------------------------------------------------------
1 | import CircuitKit
2 | import Accelerate
3 |
4 | public struct CircuitNetwork {
5 | public var circuit: Circuit
6 |
7 | public var timeStep: Double = 0.01
8 | public var step: Int = 0
9 |
10 | public var nodes: [Node] = [Node(0)]
11 | public var models: [Model] = []
12 |
13 |
14 | public var conductanceMatrix: SparseMatrix_Double?
15 | public var values: [Double] = []
16 | public var invertedConductanceMatrix: SparseMatrix_Double?
17 | public var xVector: [Double] = []
18 | public var bVector: [Double] = []
19 |
20 | public var groundCount: Int = 0
21 |
22 | public init(circuit: Circuit) {
23 | self.circuit = circuit
24 | // print("Making ground node")
25 | makeGroundNode()
26 | // print("Making rest of the network")
27 | makeNodesAndModels()
28 | // print("Assigning connections")
29 | assignConnections()
30 | makeHiddenNodes()
31 | // attachHiddenGroundNodes()
32 | assignInductorNodes()
33 | }
34 |
35 | public mutating func solveNetwork() {
36 | createConductanceMatrix()
37 | createBVector()
38 | createXVector()
39 | solveSparseMatrix()
40 | // print("b", bVector)
41 | // print("x", xVector)
42 | // multiplyInverseMatrixToVector()
43 | updateNodeVoltages()
44 | updateModelVoltage()
45 | updateModelCurrent()
46 | }
47 |
48 | public mutating func updateNodeVoltages() {
49 | for i in 1.. 0 else {
58 | continue
59 | }
60 | let firstNode = modelNodes[0]
61 | let secondNode = modelNodes[1]
62 | let firstVoltage = nodes[firstNode.1].voltage
63 | let secondVoltage = nodes[secondNode.1].voltage
64 | let voltageDifference = firstVoltage - secondVoltage
65 | models[i].voltage = abs(voltageDifference)
66 |
67 | if models[i] is CapacitorModel {
68 | var capacitor = models[i] as! CapacitorModel
69 | if voltageDifference > 0 {
70 | capacitor.positiveTerminalNode = firstNode.1
71 | capacitor.negativeTerminalNode = secondNode.1
72 | capacitor.currentGoing = secondNode.0
73 | } else if voltageDifference < 0 {
74 | capacitor.positiveTerminalNode = secondNode.1
75 | capacitor.negativeTerminalNode = firstNode.1
76 | capacitor.currentGoing = firstNode.0
77 | } else {
78 | capacitor.positiveTerminalNode = nil
79 | capacitor.negativeTerminalNode = nil
80 | capacitor.currentGoing = nil
81 | }
82 | models[i] = capacitor
83 | } else if models[i] is InductorModel {
84 | var inductor = models[i] as! InductorModel
85 | if voltageDifference > 0 {
86 | inductor.positiveTerminalNode = firstNode.1
87 | inductor.negativeTerminalNode = secondNode.1
88 | inductor.currentGoing = secondNode.0
89 | } else if voltageDifference < 0 {
90 | inductor.positiveTerminalNode = secondNode.1
91 | inductor.negativeTerminalNode = firstNode.1
92 | inductor.currentGoing = firstNode.0
93 | }
94 | models[i] = inductor
95 | } else if !(models[i] is VoltageSourceModel || models[i] is CurrentSourceModel) {
96 | if voltageDifference > 0 {
97 | models[i].currentGoing = secondNode.0
98 | } else if voltageDifference < 0 {
99 | models[i].currentGoing = firstNode.0
100 | } else {
101 | models[i].currentGoing = nil
102 | }
103 | }
104 | }
105 | }
106 |
107 | public mutating func updateModelCurrent() {
108 | let voltageSources = models
109 | .filter{$0 is VoltageSourceModel}
110 | .map{$0 as! VoltageSourceModel}
111 |
112 | for source in voltageSources {
113 | let current = xVector[nodeCount - 1 + source.number]
114 | let sourceIndex = models.firstIndex {$0.description == source.description}
115 | models[sourceIndex!].current = abs(current)
116 | if let sourceComp = source.component as? Battery {
117 | if current > 0 {
118 | models[sourceIndex!].currentGoing = sourceComp.terminals.negative
119 | } else if current < 0 {
120 | models[sourceIndex!].currentGoing = sourceComp.terminals.positive
121 | }
122 | }
123 | }
124 |
125 | let inductors = models
126 | .filter{$0 is InductorModel}
127 | .map{$0 as! InductorModel}
128 |
129 | for inductor in inductors {
130 | let current = xVector[nodeCount + voltageSources.count + inductor.number - 1]
131 | let sourceIndex = models.firstIndex {$0.description == inductor.description}
132 | models[sourceIndex!].current = abs(current)
133 | }
134 |
135 | let remainingModels = models.filter{$0 is ResistorModel || $0 is CapacitorModel}
136 |
137 | for model in remainingModels {
138 | let matchedModels = remainingModels
139 | .filter{$0.connectedNodes.map{$0.value}.sorted() == model.connectedNodes.map{$0.value}.sorted()}
140 |
141 | var equivalentResistance: Double = 0
142 | var totalResistance: Double = 0
143 | for model in matchedModels {
144 | if let resistor = model as? ResistorModel {
145 | equivalentResistance += 1 / resistor.resistance
146 | totalResistance += resistor.resistance
147 | }
148 | if let capacitor = model as? CapacitorModel {
149 | equivalentResistance += 1 / capacitor.equivalentResistance
150 | totalResistance += capacitor.equivalentResistance
151 | }
152 | }
153 |
154 | var modelResistance: Double = 0
155 |
156 | if let resistor = model as? ResistorModel {
157 | modelResistance = resistor.resistance
158 | }
159 | if let capacitor = model as? CapacitorModel {
160 | modelResistance = capacitor.equivalentResistance
161 | }
162 | let modelIndex = models.firstIndex{$0.description == model.description}
163 | models[modelIndex!].current = abs(model.voltage * equivalentResistance * ( modelResistance / totalResistance))
164 | }
165 | }
166 | }
167 |
168 |
169 |
170 |
171 |
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/UserModules/CircuitUI.playgroundmodule/Sources/MeasurementEditView.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 | import CircuitKit
3 |
4 | public struct MeasurementEditView: View {
5 | @EnvironmentObject var envi: CircuitEnvironment
6 | @Binding private var measurement: CircuitUnit
7 | @Binding private var show: Bool
8 | @State private var value: Double = 0
9 | @State private var multiplier: Double = 1
10 | @State private var field = ""
11 | @State private var isNegative: Bool = false
12 |
13 | private var unitPrefix: UnitPrefix {
14 | return getPrefix(value: multiplier)
15 | }
16 | private var baseUnit: BaseUnit = .watt
17 |
18 | private var fieldLimit: Int = 3
19 |
20 | public init(measurement: Binding, show: Binding) {
21 | self._measurement = measurement
22 | self._show = show
23 | self.value = measurement.wrappedValue.value
24 | self.baseUnit = measurement.wrappedValue.base
25 | multiplier = pow(10, Double(measurement.wrappedValue.unitPrefix.rawValue))
26 | self.multiplier = multiplier.isZero ? 1 : multiplier
27 | }
28 |
29 | var display: String {
30 | let sign = isNegative ? "-" : "+"
31 | return sign + field + " " + unitPrefix.symbol + baseUnit.symbol
32 | }
33 |
34 | public var body: some View {
35 | HStack{
36 | VStack(spacing: 4) {
37 | Text(display)
38 | .font(.system(size: 13.5, weight: .regular, design: .monospaced))
39 | .multilineTextAlignment(.leading)
40 | .frame(width: 102, height: 32)
41 | .overlay(RoundedRectangle(cornerRadius: 5)
42 | .stroke(Color.gray.opacity(0.25), lineWidth: 2)
43 | .frame(width: 102))
44 | numPad
45 | }
46 | unitPicker
47 | }.padding(10)
48 | }
49 |
50 | private var columns: [GridItem] {
51 | Array(repeating: .init(.fixed(32), spacing: 4), count: 3)
52 | }
53 |
54 | var numPad: some View {
55 | LazyVGrid(columns: columns, spacing: 4) {
56 | ForEach(NumPadButton.allCases, id: \.self) { number in
57 | Button {
58 | switch number {
59 | case .plusminus:
60 | isNegative.toggle()
61 | default:
62 | field.append(number.rawValue)
63 | }
64 | } label: {
65 | Label(number.rawValue, systemImage: number.sfSymbol)
66 | .labelStyle(IconOnlyLabelStyle())
67 | .font(.system(size: 24, weight: .regular))
68 | .imageScale(.small)
69 | .scaleEffect(number == .decimal ? 0.25 : 1)
70 | }
71 | .buttonStyle(PadButtonStyle(color: Color.gray))
72 | .opacity(getBool(for: number, in: field) ? 1 : 0.25)
73 | .allowsHitTesting(getBool(for: number, in: field))
74 | }
75 | }
76 | }
77 |
78 | func getBool(for button: NumPadButton, in field: String) -> Bool{
79 | switch button {
80 | case .plusminus:
81 | return !(measurement is Ohms || measurement is Farads || measurement is Henries || measurement is Hertz)
82 | case .decimal:
83 | return field.count > 0 && field.count <= fieldLimit && !field.contains(".")
84 | default:
85 | return field.count <= fieldLimit && (!field.hasSuffix("0") || field.count > 1)
86 | }
87 | }
88 |
89 |
90 | var unitPicker: some View {
91 | VStack(spacing: 4){
92 | Button {
93 | let sign: Double = isNegative ? -1 : 1
94 | let finalValue = (Double(field) ?? 0) * multiplier * sign
95 | measurement = measurement.base.createUnit(from: finalValue)
96 | show = false
97 | }
98 | label: {
99 | Label("Done", systemImage: "checkmark")
100 | .font(Font.body.bold())
101 | .labelStyle(IconOnlyLabelStyle())
102 | .foregroundColor(Color.green)
103 | }
104 | .buttonStyle(PadButtonStyle(color: Color.green))
105 | .opacity(field.count > 0 && !field.hasSuffix(".") ? 1 : 0.25)
106 | .allowsHitTesting(field.count > 0 && !field.hasSuffix("."))
107 | Button { multiplier *= 1000 }
108 | label: {
109 | Label("Increase", systemImage: "chevron.up")
110 | .font(Font.body.bold())
111 | .labelStyle(IconOnlyLabelStyle())
112 | }
113 | .buttonStyle(PadButtonStyle(color: Color.blue))
114 | .opacity(unitPrefix != .giga ? 1 : 0.25)
115 | .allowsHitTesting(unitPrefix != .giga)
116 | Text(unitPrefix.symbol + baseUnit.symbol)
117 | .font(.system(size: 13.5, weight: .regular, design: .monospaced))
118 | .frame( height: 32)
119 | .overlay(RoundedRectangle(cornerRadius: 5)
120 | .stroke(Color.gray.opacity(0.25), lineWidth: 2)
121 | .frame(width: 31))
122 | Button { multiplier *= 0.001 }
123 | label: {
124 | Label("Decrease", systemImage: "chevron.down")
125 | .font(Font.body.bold())
126 | .labelStyle(IconOnlyLabelStyle())
127 | }
128 | .buttonStyle(PadButtonStyle(color: Color.blue))
129 | .opacity(unitPrefix != .nano ? 1 : 0.25)
130 | .allowsHitTesting(unitPrefix != .nano)
131 | Button { field = "" }
132 | label: {
133 | Label("Clear", systemImage: "c.circle")
134 | .font(Font.body.bold())
135 | .foregroundColor(Color.red)
136 | .labelStyle(IconOnlyLabelStyle())
137 | }
138 | .opacity(!field.isEmpty ? 1 : 0.25)
139 | .allowsHitTesting(!field.isEmpty)
140 | .buttonStyle(PadButtonStyle(color: Color.red))
141 | }
142 | }
143 | }
144 |
145 | enum NumPadButton: String, CaseIterable, Identifiable{
146 | case one = "1", two = "2", three = "3"
147 | case four = "4" , five = "5" , six = "6"
148 | case seven = "7", eight = "8", nine = "9"
149 | case decimal = ".", zero = "0", plusminus
150 |
151 | var sfSymbol: String {
152 | switch self {
153 | case .decimal:
154 | return "circlebadge.fill"
155 | case .plusminus:
156 | return "plusminus"
157 | default:
158 | return self.rawValue + ".circle"
159 | }
160 | }
161 |
162 | var id: String { self.rawValue }
163 | }
164 |
165 | public struct PadButtonStyle: ButtonStyle {
166 | var color: Color
167 | public init(color: Color){
168 | self.color = color
169 | }
170 | public func makeBody(configuration: Configuration) -> some View {
171 | configuration.label
172 | .scaleEffect(configuration.isPressed ? 1.25 : 1.0)
173 | .frame(width: 24, height: 24, alignment: .center)
174 | .aspectRatio(1.0, contentMode: .fit)
175 | .padding(4)
176 | .background(configuration.isPressed ? color.opacity(0.25) : Color.gray.opacity(0.125))
177 | .cornerRadius(4)
178 | .shadow(radius: configuration.isPressed ? 7.5 : 0)
179 | .animation(.linear(duration: 0.25), value: configuration.isPressed)
180 | }
181 | }
182 |
183 |
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/UserModules/CircuitKit.playgroundmodule/Sources/Units.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | public protocol CircuitUnit: CustomStringConvertible {
3 | var dimension: Dimension { get }
4 | var value: Double { get set }
5 | var unitPrefix: UnitPrefix { get }
6 | var base: BaseUnit { get }
7 | var prefixValue: Double { get }
8 | }
9 |
10 | protocol AddAndSub {
11 | static func + (rhs: Self, lhs: Self) -> Self
12 | static func - (rhs: Self, lhs: Self) -> Self
13 | }
14 |
15 | public func getPrefix(value: Double) -> UnitPrefix {
16 | if value.isZero { return .base }
17 | let log: Int = Int(floor(log10(abs(value)) / 3) * 3)
18 | return UnitPrefix(rawValue: log) ?? (log < 0 ? .pico : .tera)
19 | }
20 |
21 | func getDescription(_ unit: CircuitUnit) -> String {
22 | let fmt = NumberFormatter()
23 | fmt.numberStyle = .decimal
24 | fmt.minimumSignificantDigits = 3
25 | fmt.maximumSignificantDigits = 3
26 | fmt.string(for: unit.prefixValue)
27 | let finalValue = fmt.string(for: unit.prefixValue) ?? String(format: "%.2f", unit.prefixValue)
28 | return finalValue + " " + unit.unitPrefix.symbol + unit.base.symbol
29 | }
30 |
31 |
32 | public struct Volts: CircuitUnit, AddAndSub {
33 | public var dimension: Dimension = .voltage
34 | public var value: Double
35 | public var unitPrefix: UnitPrefix { getPrefix(value: value) }
36 | public var base: BaseUnit = .volt
37 | public var prefixValue: Double {
38 | let power = pow(10, Double(unitPrefix.rawValue))
39 | return unitPrefix == .base ? value : value / power
40 | }
41 |
42 | public init(_ value: Double = 0) {
43 | self.value = value
44 | }
45 |
46 | public var description: String { getDescription(self) }
47 |
48 | public static func + (lhs: Self, rhs: Self) -> Self {
49 | return Self(lhs.value + rhs.value)
50 | }
51 |
52 | public static func - (lhs: Self, rhs: Self) -> Self {
53 | return Self(lhs.value - rhs.value)
54 | }
55 | }
56 |
57 | public struct Ohms: CircuitUnit, AddAndSub {
58 | public var dimension: Dimension = .resistance
59 | public var value: Double
60 | public var unitPrefix: UnitPrefix { getPrefix(value: value) }
61 | public var base: BaseUnit = .ohm
62 | public var prefixValue: Double {
63 | let power = pow(10, Double(unitPrefix.rawValue))
64 | return unitPrefix == .base ? value : value / power
65 | }
66 |
67 | public init(_ value: Double = 0) {
68 | self.value = value
69 | }
70 |
71 | public var description: String { getDescription(self) }
72 |
73 | public static func + (lhs: Self, rhs: Self) -> Self {
74 | return Self(lhs.value + rhs.value)
75 | }
76 |
77 | public static func - (lhs: Self, rhs: Self) -> Self {
78 | return Self(lhs.value - rhs.value)
79 | }
80 | }
81 |
82 | public struct Amperes: CircuitUnit, AddAndSub {
83 | public var dimension: Dimension = .current
84 | public var value: Double
85 | public var unitPrefix: UnitPrefix { getPrefix(value: value) }
86 | public var base: BaseUnit = .ampere
87 | public var prefixValue: Double {
88 | let power = pow(10, Double(unitPrefix.rawValue))
89 | return unitPrefix == .base ? value : value / power
90 | }
91 |
92 | public init(_ value: Double = 0) {
93 | self.value = value
94 | }
95 |
96 | public var description: String { getDescription(self) }
97 |
98 | public static func + (lhs: Self, rhs: Self) -> Self {
99 | return Self(lhs.value + rhs.value)
100 | }
101 |
102 | public static func - (lhs: Self, rhs: Self) -> Self {
103 | return Self(lhs.value - rhs.value)
104 | }
105 | }
106 |
107 | public struct Watts: CircuitUnit, AddAndSub {
108 | public var dimension: Dimension = .power
109 | public var value: Double
110 | public var unitPrefix: UnitPrefix { getPrefix(value: value) }
111 | public var base: BaseUnit = .watt
112 | public var prefixValue: Double {
113 | let power = pow(10, Double(unitPrefix.rawValue))
114 | return unitPrefix == .base ? value : value / power
115 | }
116 |
117 | public init(_ value: Double = 0) {
118 | self.value = value
119 | }
120 |
121 | public var description: String { getDescription(self) }
122 |
123 | public static func + (lhs: Self, rhs: Self) -> Self {
124 | return Self(lhs.value + rhs.value)
125 | }
126 |
127 | public static func - (lhs: Self, rhs: Self) -> Self {
128 | return Self(lhs.value - rhs.value)
129 | }
130 | }
131 |
132 |
133 | public struct Hertz: CircuitUnit, AddAndSub {
134 | public var dimension: Dimension = .frequency
135 | public var value: Double
136 | public var unitPrefix: UnitPrefix { getPrefix(value: value) }
137 | public var base: BaseUnit = .hertz
138 | public var prefixValue: Double {
139 | let power = pow(10, Double(unitPrefix.rawValue))
140 | return unitPrefix == .base ? value : value / power
141 | }
142 |
143 | public init(_ value: Double = 0) {
144 | self.value = value
145 | }
146 |
147 | public var description: String { let fmt = NumberFormatter()
148 | fmt.numberStyle = .decimal
149 | fmt.maximumFractionDigits = 0
150 | fmt.string(for: prefixValue)
151 | let finalValue = fmt.string(for: prefixValue) ?? String(format: "%.2f", prefixValue)
152 | return finalValue + " " + unitPrefix.symbol + base.symbol
153 | }
154 |
155 | public static func + (lhs: Self, rhs: Self) -> Self {
156 | return Self(lhs.value + rhs.value)
157 | }
158 |
159 | public static func - (lhs: Self, rhs: Self) -> Self {
160 | return Self(lhs.value - rhs.value)
161 | }
162 | }
163 |
164 | public struct Henries: CircuitUnit, AddAndSub {
165 | public var dimension: Dimension = .inductance
166 | public var value: Double
167 | public var unitPrefix: UnitPrefix { getPrefix(value: value) }
168 | public var base: BaseUnit = .henry
169 | public var prefixValue: Double {
170 | let power = pow(10, Double(unitPrefix.rawValue))
171 | return unitPrefix == .base ? value : value / power
172 | }
173 |
174 | public init(_ value: Double = 0) {
175 | self.value = value
176 | }
177 |
178 | public var description: String { getDescription(self) }
179 |
180 | public static func + (lhs: Self, rhs: Self) -> Self {
181 | return Self(lhs.value + rhs.value)
182 | }
183 |
184 | public static func - (lhs: Self, rhs: Self) -> Self {
185 | return Self(lhs.value - rhs.value)
186 | }
187 | }
188 |
189 | public struct Farads: CircuitUnit, AddAndSub {
190 | public var dimension: Dimension = .capacitance
191 | public var value: Double
192 | public var unitPrefix: UnitPrefix { getPrefix(value: value) }
193 | public var base: BaseUnit = .farad
194 | public var prefixValue: Double {
195 | let power = pow(10, Double(unitPrefix.rawValue))
196 | return unitPrefix == .base ? value : value / power
197 | }
198 |
199 | public init(_ value: Double = 0) {
200 | self.value = value
201 | }
202 |
203 | public var description: String { getDescription(self) }
204 |
205 | public static func + (lhs: Self, rhs: Self) -> Self {
206 | return Self(lhs.value + rhs.value)
207 | }
208 |
209 | public static func - (lhs: Self, rhs: Self) -> Self {
210 | return Self(lhs.value - rhs.value)
211 | }
212 | }
213 |
214 | extension Double {
215 | public func volts() -> Volts {
216 | return Volts(self)
217 | }
218 | public func amperes() -> Amperes {
219 | return Amperes(self)
220 | }
221 | public func ohms() -> Ohms {
222 | return Ohms(self)
223 | }
224 | public func watts() -> Watts {
225 | return Watts(self)
226 | }
227 | public func henries() -> Henries {
228 | return Henries(self)
229 | }
230 | public func farads() -> Farads {
231 | return Farads(self)
232 | }
233 | public func hertz() -> Hertz {
234 | return Hertz(self)
235 | }
236 | }
237 |
238 |
--------------------------------------------------------------------------------
/CircuitPlay.playgroundbook/Contents/UserModules/CircuitNetwork.playgroundmodule/Sources/Network+Solver.swift:
--------------------------------------------------------------------------------
1 | import Accelerate
2 |
3 | extension CircuitNetwork {
4 | public var nodeCount: Int { nodes.count }
5 | public var voltageSourceCount: Int { models.filter{$0 is VoltageSourceModel || $0 is InductorModel}.count }
6 |
7 | public mutating func createBVector() {
8 | var values: [Double] = []
9 |
10 | var minusGroundNode = nodes
11 | minusGroundNode.removeFirst()
12 |
13 | for node in minusGroundNode {
14 | let currentSources = models
15 | .filter{ $0 is CurrentSourceModel }
16 | .filter{ model in
17 | model.connectedNodes.contains { possNode in
18 | possNode.value == node.number
19 | }
20 | }
21 | .map {
22 | $0 as! CurrentSourceModel
23 | }
24 |
25 | let capacitors = models
26 | .filter{ $0 is CapacitorModel }
27 | .filter{ model in
28 | model.connectedNodes.contains { possNode in
29 | possNode.value == node.number
30 | }
31 | }
32 | .map {
33 | $0 as! CapacitorModel
34 | }
35 |
36 | var current: Double = 0
37 |
38 | for source in currentSources {
39 | if source.positiveTerminalNode == node.number {
40 | current += source.current
41 | } else if source.negativeTerminalNode == node.number {
42 | current -= source.current
43 | }
44 | }
45 |
46 | for capacitor in capacitors {
47 | if capacitor.positiveTerminalNode == node.number {
48 | current += capacitor.equivalentCurrent
49 | } else if capacitor.negativeTerminalNode == node.number {
50 | current -= capacitor.equivalentCurrent
51 | }
52 | }
53 |
54 | values.append(current)
55 | }
56 |
57 | let voltageSources = models
58 | .filter{ $0 is VoltageSourceModel }
59 | .map { $0 as! VoltageSourceModel}
60 |
61 | for source in voltageSources {
62 | values.append(source.voltage)
63 | }
64 |
65 | let inductors = models
66 | .filter{ $0 is InductorModel }
67 | .map { $0 as! InductorModel}
68 |
69 | for inductor in inductors {
70 | values.append(inductor.equivalentVoltage)
71 | }
72 |
73 | bVector = values
74 | }
75 |
76 | public mutating func createXVector() {
77 | xVector = Array(repeating: 0, count: nodes.count + voltageSourceCount - 1)
78 | }
79 |
80 | public mutating func createConductanceMatrix() {
81 | var nodeVectors: [[Double]] = []
82 |
83 | var minusGroundNode = nodes
84 | minusGroundNode.removeFirst()
85 |
86 | var row: [Int32] = [Int32]()
87 | var column: [Int32] = [Int32]()
88 | var value: [Double] = [Double]()
89 | for i in 0..