Skip to content

Commit 50cf8f9

Browse files
authored
Set @icon for models and connectors (#2183)
1 parent ad853a8 commit 50cf8f9

File tree

9 files changed

+113
-8
lines changed

9 files changed

+113
-8
lines changed

Project.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ StaticArrays = "90137ffa-7385-5640-81b9-e52037218182"
4444
SymbolicIndexingInterface = "2efcf032-c050-4f8e-a9bb-153293bab1f5"
4545
SymbolicUtils = "d1185830-fcd6-423d-90d6-eec64667417b"
4646
Symbolics = "0c5d862f-8b57-4792-8d23-62f2024744c7"
47+
URIs = "5c2747f8-b7ea-4ff2-ba2e-563bfd36b1d4"
4748
UnPack = "3a884ed6-31ef-47d7-9d2a-63182c4928ed"
4849
Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d"
4950

@@ -88,6 +89,7 @@ StaticArrays = "0.10, 0.11, 0.12, 1.0"
8889
SymbolicIndexingInterface = "0.1, 0.2"
8990
SymbolicUtils = "1.0"
9091
Symbolics = "5.0"
92+
URIs = "1"
9193
UnPack = "0.1, 1.0"
9294
Unitful = "1.1"
9395
julia = "1.6"

src/ModelingToolkit.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ using Combinatorics
3030
import IfElse
3131
import Distributions
3232
import FunctionWrappersWrappers
33+
using URIs: URI
3334

3435
RuntimeGeneratedFunctions.init(@__MODULE__)
3536

src/systems/abstractsystem.jl

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1707,7 +1707,8 @@ $(TYPEDSIGNATURES)
17071707
extend the `basesys` with `sys`, the resulting system would inherit `sys`'s name
17081708
by default.
17091709
"""
1710-
function extend(sys::AbstractSystem, basesys::AbstractSystem; name::Symbol = nameof(sys))
1710+
function extend(sys::AbstractSystem, basesys::AbstractSystem; name::Symbol = nameof(sys),
1711+
gui_metadata = get_gui_metadata(sys))
17111712
T = SciMLBase.parameterless_type(basesys)
17121713
ivs = independent_variables(basesys)
17131714
if !(sys isa T)
@@ -1731,10 +1732,11 @@ function extend(sys::AbstractSystem, basesys::AbstractSystem; name::Symbol = nam
17311732

17321733
if length(ivs) == 0
17331734
T(eqs, sts, ps, observed = obs, defaults = defs, name = name, systems = syss,
1734-
continuous_events = cevs, discrete_events = devs)
1735+
continuous_events = cevs, discrete_events = devs, gui_metadata = gui_metadata)
17351736
elseif length(ivs) == 1
17361737
T(eqs, ivs[1], sts, ps, observed = obs, defaults = defs, name = name,
1737-
systems = syss, continuous_events = cevs, discrete_events = devs)
1738+
systems = syss, continuous_events = cevs, discrete_events = devs,
1739+
gui_metadata = gui_metadata)
17381740
end
17391741
end
17401742

@@ -1767,7 +1769,7 @@ end
17671769
"""
17681770
missing_variable_defaults(sys::AbstractSystem, default = 0.0)
17691771
1770-
returns a `Vector{Pair}` of variables set to `default` which are missing from `get_defaults(sys)`. The `default` argument can be a single value or vector to set the missing defaults respectively.
1772+
returns a `Vector{Pair}` of variables set to `default` which are missing from `get_defaults(sys)`. The `default` argument can be a single value or vector to set the missing defaults respectively.
17711773
"""
17721774
function missing_variable_defaults(sys::AbstractSystem, default = 0.0)
17731775
varmap = get_defaults(sys)

src/systems/model_parsing.jl

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ end
99
(m::Model)(args...; kw...) = m.f(args...; kw...)
1010

1111
using MLStyle
12+
1213
function connector_macro(mod, name, body)
1314
if !Meta.isexpr(body, :block)
1415
err = """
@@ -23,19 +24,26 @@ function connector_macro(mod, name, body)
2324
error(err)
2425
end
2526
vs = Num[]
27+
icon = Ref{Union{String, URI}}()
2628
dict = Dict{Symbol, Any}()
2729
for arg in body.args
2830
arg isa LineNumberNode && continue
31+
if arg.head == :macrocall && arg.args[1] == Symbol("@icon")
32+
parse_icon!(icon, dict, dict, arg.args[end])
33+
continue
34+
end
2935
push!(vs, Num(parse_variable_def!(dict, mod, arg, :variables)))
3036
end
3137
iv = get(dict, :independent_variable, nothing)
3238
if iv === nothing
3339
error("$name doesn't have a independent variable")
3440
end
41+
gui_metadata = isassigned(icon) ? GUIMetadata(GlobalRef(mod, name), icon[]) :
42+
nothing
3543
quote
3644
$name = $Model((; name) -> begin
3745
var"#___sys___" = $ODESystem($(Equation[]), $iv, $vs, $([]);
38-
name)
46+
name, gui_metadata = $gui_metadata)
3947
$Setfield.@set!(var"#___sys___".connector_type=$connector_type(var"#___sys___"))
4048
end, $dict)
4149
end
@@ -123,6 +131,7 @@ end
123131
macro model(name::Symbol, expr)
124132
esc(model_macro(__module__, name, expr))
125133
end
134+
126135
function model_macro(mod, name, expr)
127136
exprs = Expr(:block)
128137
dict = Dict{Symbol, Any}()
@@ -131,25 +140,29 @@ function model_macro(mod, name, expr)
131140
vs = Symbol[]
132141
ps = Symbol[]
133142
eqs = Expr[]
143+
icon = Ref{Union{String, URI}}()
134144
for arg in expr.args
135145
arg isa LineNumberNode && continue
136146
arg.head == :macrocall || error("$arg is not valid syntax. Expected a macro call.")
137-
parse_model!(exprs.args, comps, ext, eqs, vs, ps, dict, mod, arg)
147+
parse_model!(exprs.args, comps, ext, eqs, vs, ps, icon, dict, mod, arg)
138148
end
139149
iv = get(dict, :independent_variable, nothing)
140150
if iv === nothing
141151
iv = dict[:independent_variable] = variable(:t)
142152
end
153+
gui_metadata = isassigned(icon) > 0 ? GUIMetadata(GlobalRef(mod, name), icon[]) :
154+
nothing
143155
sys = :($ODESystem($Equation[$(eqs...)], $iv, [$(vs...)], [$(ps...)];
144-
systems = [$(comps...)], name))
156+
systems = [$(comps...)], name, gui_metadata = $gui_metadata))
145157
if ext[] === nothing
146158
push!(exprs.args, sys)
147159
else
148160
push!(exprs.args, :($extend($sys, $(ext[]))))
149161
end
150162
:($name = $Model((; name) -> $exprs, $dict))
151163
end
152-
function parse_model!(exprs, comps, ext, eqs, vs, ps, dict, mod, arg)
164+
165+
function parse_model!(exprs, comps, ext, eqs, vs, ps, icon, dict, mod, arg)
153166
mname = arg.args[1]
154167
body = arg.args[end]
155168
if mname == Symbol("@components")
@@ -162,10 +175,13 @@ function parse_model!(exprs, comps, ext, eqs, vs, ps, dict, mod, arg)
162175
parse_variables!(exprs, ps, dict, mod, body, :parameters)
163176
elseif mname == Symbol("@equations")
164177
parse_equations!(exprs, eqs, dict, body)
178+
elseif mname == Symbol("@icon")
179+
parse_icon!(icon, dict, mod, body)
165180
else
166181
error("$mname is not handled.")
167182
end
168183
end
184+
169185
function parse_components!(exprs, cs, dict, body)
170186
expr = Expr(:block)
171187
push!(exprs, expr)
@@ -187,6 +203,7 @@ function parse_components!(exprs, cs, dict, body)
187203
end
188204
dict[:components] = comps
189205
end
206+
190207
function parse_extend!(exprs, ext, dict, body)
191208
expr = Expr(:block)
192209
push!(exprs, expr)
@@ -213,6 +230,7 @@ function parse_extend!(exprs, ext, dict, body)
213230
_ => error("`@extend` only takes an assignment expression. Got $body")
214231
end
215232
end
233+
216234
function parse_variables!(exprs, vs, dict, mod, body, varclass)
217235
expr = Expr(:block)
218236
push!(exprs, expr)
@@ -225,6 +243,7 @@ function parse_variables!(exprs, vs, dict, mod, body, varclass)
225243
push!(expr.args, :($name = $v))
226244
end
227245
end
246+
228247
function parse_equations!(exprs, eqs, dict, body)
229248
for arg in body.args
230249
arg isa LineNumberNode && continue
@@ -233,3 +252,30 @@ function parse_equations!(exprs, eqs, dict, body)
233252
# TODO: does this work with TOML?
234253
dict[:equations] = readable_code.(eqs)
235254
end
255+
256+
function parse_icon!(icon, dict, mod, body::String)
257+
icon_dir = get(ENV, "MTK_ICONS_DIR", joinpath(DEPOT_PATH[1], "mtk_icons"))
258+
dict[:icon] = icon[] = if isfile(body)
259+
URI("file:///" * abspath(body))
260+
elseif (iconpath = joinpath(icon_dir, body); isfile(iconpath))
261+
URI("file:///" * abspath(iconpath))
262+
elseif try
263+
Base.isvalid(URI(body))
264+
catch e
265+
false
266+
end
267+
URI(body)
268+
else
269+
error("$body is not a valid icon")
270+
end
271+
end
272+
273+
function parse_icon!(icon, dict, mod, body::Expr)
274+
_icon = body.args[end]
275+
dict[:icon] = icon[] = MLStyle.@match _icon begin
276+
::Symbol => get_var(mod, _icon)
277+
::String => _icon
278+
Expr(:call, read, a...) => eval(_icon)
279+
_ => error("$_icon isn't a valid icon")
280+
end
281+
end

test/icons/ground.svg

Lines changed: 2 additions & 0 deletions
Loading

test/icons/oneport.png

270 Bytes
Loading

test/icons/pin.png

201 Bytes
Loading

test/icons/resistor.svg

Lines changed: 13 additions & 0 deletions
Loading

test/model_parsing.jl

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
using ModelingToolkit, Test
2+
using ModelingToolkit: get_gui_metadata
3+
using URIs: URI
4+
5+
ENV["MTK_ICONS_DIR"] = "$(@__DIR__)/icons"
26

37
@connector RealInput begin
48
u(t), [input = true]
@@ -24,6 +28,7 @@ D = Differential(t)
2428
@connector Pin begin
2529
v(t) = 0 # Potential at the pin [V]
2630
i(t), [connect = Flow] # Current flowing into the pin [A]
31+
@icon "pin.png"
2732
end
2833

2934
@model OnePort begin
@@ -35,6 +40,7 @@ end
3540
v(t)
3641
i(t)
3742
end
43+
@icon "oneport.png"
3844
@equations begin
3945
v ~ p.v - n.v
4046
0 ~ p.i + n.i
@@ -46,16 +52,36 @@ end
4652
@components begin
4753
g = Pin()
4854
end
55+
@icon begin
56+
read(abspath(ENV["MTK_ICONS_DIR"], "ground.svg"), String)
57+
end
4958
@equations begin
5059
g.v ~ 0
5160
end
5261
end
5362

63+
resistor_log = "$(@__DIR__)/logo/resistor.svg"
5464
@model Resistor begin
5565
@extend v, i = oneport = OnePort()
5666
@parameters begin
5767
R = 1
5868
end
69+
@icon begin
70+
"""<?xml version="1.0" encoding="UTF-8"?>
71+
<svg xmlns="http://www.w3.org/2000/svg" width="80" height="30">
72+
<path d="M10 15
73+
l15 0
74+
l2.5 -5
75+
l5 10
76+
l5 -10
77+
l5 10
78+
l5 -10
79+
l5 10
80+
l2.5 -5
81+
l15 0" stroke="black" stroke-width="1" stroke-linejoin="bevel" fill="none"></path>
82+
</svg>
83+
"""
84+
end
5985
@equations begin
6086
v ~ i * R
6187
end
@@ -66,6 +92,7 @@ end
6692
@parameters begin
6793
C = 1
6894
end
95+
@icon "https://upload.wikimedia.org/wikipedia/commons/7/78/Capacitor_symbol.svg"
6996
@equations begin
7097
D(v) ~ i / C
7198
end
@@ -97,4 +124,16 @@ end
97124
end
98125
end
99126
@named rc = RC()
127+
128+
@test get_gui_metadata(rc.resistor).layout == Resistor.structure[:icon] ==
129+
read(joinpath(ENV["MTK_ICONS_DIR"], "resistor.svg"), String)
130+
@test get_gui_metadata(rc.ground).layout ==
131+
read(abspath(ENV["MTK_ICONS_DIR"], "ground.svg"), String)
132+
@test get_gui_metadata(rc.capacitor).layout ==
133+
URI("https://upload.wikimedia.org/wikipedia/commons/7/78/Capacitor_symbol.svg")
134+
@test OnePort.structure[:icon] ==
135+
URI("file:///" * abspath(ENV["MTK_ICONS_DIR"], "oneport.png"))
136+
@test ModelingToolkit.get_gui_metadata(rc.resistor.p).layout == Pin.structure[:icon] ==
137+
URI("file:///" * abspath(ENV["MTK_ICONS_DIR"], "pin.png"))
138+
100139
@test length(equations(structural_simplify(rc))) == 1

0 commit comments

Comments
 (0)