Adding and using Substance plugins
This page defines the interface between the Penrose runtime and an external plugin that can instantiate a Substance program.
You can find the list of supported plugins in Plugins
. To use one, add plugin "name"
as one line at the top of Style. Penrose currently only supports using no plugins or 1 plugin.
Currently plugins live in src/plugins
, each in its own directory. Make a directory there and compile your plugin if needed.
Register the plugin in Plugins
by adding an entry with the plugin name, the path to the plugin, and a command to run to invoke the plugin from that path. For example: ("ddgjs", ("plugins/mesh-plugin", "node mesh-plugin.js"))
(here is the source for the mesh plugin). Recompile Penrose.
Plugins read from Sub-user.json
in their own directory and write to Sub-instantiated.sub
in their own directory.
Penrose sends the plugin a JSON representation of the Substance program via the file path above. Penrose expects a desugared Substance program back in the file path above.
Optionally, a plugin can output some values for a Style program to access. If a plugin named plugin
outputs the file value.json
following the schema [StyVal]
defined in SubstanceJSON
, then the values can be accessed in Style by the expression plugin[string1][string2]
, for example ddg[X.name]["x"]
. All values must be floating-point values for now.
The Substance JSON schema is defined as the Haskell datatype SubSchema
in SubstanceJSON
.
Penrose will append the desugared Substance program to the original Substance program and parse + check the whole thing again.
Instantiate all objects, then make sure the objects satisfy the constraints, keeping some state along the way. For an example plugin and its approach, see plugins/mesh-plugin/mesh-plugin.js
.
Most importantly, plugin writers are responsible for respecting the Substance specification.
For example, if a Substance writer writes
Graph G
Graph C := CycleIn(G)
Then plugins that instantiate the graph G
with a concrete data structure are responsible for making sure that G
has a cycle and that the cycle is named C
.
Other assumptions and conventions:
- JSON order may not correspond to Substance program order, so treat it as unordered
- Plugin writer will refer to the Element program for the names and types
- Plugin writer never has to deal with nested expressions (Substance compiler handles naming intermediate expressions)
- Plugin writer gets the names of bound variables (such as
v
inv := f(a, b)
)
Substance program:
Set A
Set B
IsSubset(A,B)
Set C
C := Union(A, B)
Point p
PointIn(C, p)
C := AddPoint(p, C)
AutoLabel All
Substance AST:
[ Decl (TConstr "Set" []) (VarConst "A")
, Decl (TConstr "Set" []) (VarConst "B")
, ApplyP
PredicateConst
"IsSubset"
[ PE (VarE (VarConst "A")) , PE (VarE (VarConst "B")) ]
, Decl (TConstr "Set" []) (VarConst "C")
, Bind
(VarConst "C")
(ApplyFunc "Union" [ VarE (VarConst "A") , VarE (VarConst "B") ])
, Decl (TConstr "Point" []) (VarConst "p")
, ApplyP
PredicateConst
"PointIn"
[ PE (VarE (VarConst "C")) , PE (VarE (VarConst "p")) ]
, Bind
(VarConst "C")
(ApplyFunc
"AddPoint" [ VarE (VarConst "p") , VarE (VarConst "C") ])
, AutoLabel Default
]
JSON schema that's sent to plugin: (TODO: add Substance values and their types to schema)
{
"constraints": {
"predicates": [
{
"pargNames": [
"C",
"p"
],
"pname": "PointIn"
},
{
"pargNames": [
"A",
"B"
],
"pname": "IsSubset"
}
],
"functions": [
{
"fargNames": [
"p",
"C"
],
"fname": "AddPoint",
"varName": "C"
},
{
"fargNames": [
"A",
"B"
],
"fname": "Union",
"varName": "C"
}
]
},
"objects": [
{
"objName": "p",
"objType": "Point"
},
{
"objName": "C",
"objType": "Set"
},
{
"objName": "B",
"objType": "Set"
},
{
"objName": "A",
"objType": "Set"
}
]
}
Example of values.json
output for Style: (this is from the mesh plugin)
[
{
"nameVals": [
{
"propertyVal": -0.030517175058609514,
"propertyName": "x"
},
{
"propertyVal": 0.22126884822375148,
"propertyName": "y"
}
],
"subName": "K2_V3"
},
{
"nameVals": [
{
"propertyVal": -0.6160663027512623,
"propertyName": "x"
},
{
"propertyVal": 0.10231779964600873,
"propertyName": "y"
}
],
"subName": "K2_V4"
},
{
"nameVals": [
{
"propertyVal": 0.39444688949943707,
"propertyName": "x"
},
{
"propertyVal": -0.21759831048711886,
"propertyName": "y"
}
],
"subName": "j"
},
...
]
- What to do about subtyping?
- How to convert Substance forms that are currently not handles? (e.g. binding a variable, deconstructor, predicate inequality)
- Converting parametrized types?
- Eventually we want to support Substance programs that use multiple Element programs. How does that interact w/ using multiple plugins?
Found a problem or got a suggestion? Please open a GitHub issue and tag it with documentation
!