Simput rely on 3 types of managers that build on top of each other and are responsible for handling Proxy, UI and Domains.
-
ProxyManager:
- Load proxy definitions
- Create/Delete proxy
- Update proxies from a changeset
- Commit/Reset all modified proxies
- Find/Get proxy
- Import/Export state of all created proxy
- (optionaly) Map proxy state to concrete object
-
UIManager:
- Require ProxyManager and UI resolver
- Load language/layout definitions
- Get proxy state for UI
- Get proxy form for UI (include language + layout)
-
ProxyDomainManager:
- Attach to ProxyManager for adding behavior to proxy initialization and update.
- Create a ProxyDomain for each newly created proxy.
- Monitor dirty proxies during an update, so modified domains can be applied in one sweep.
- Get ProxyDomain from proxy id.
Definition handling
def load_model(self, yaml_file=None, yaml_content=None):
"""Load Data Model from YAML definition"""
def get_definition(self, proxy_type):
"""Return a loaded definition for a given object_type"""
def types(self, *with_tags):
"""List proxy_types from definition that has the set of provided tags"""
Proxy Management
def create(self, proxy_type, **initial_values):
"""
Create a new instance of a proxy using an proxy_type along with
maybe a set of property values that we want to pre-initialise using the
**kwargs approach.
"""
def delete(self, proxy_id, trigger_modified=True):
"""
Delete object along with its dependency that it is owner of
"""
def get(self, proxy_id: str) -> Proxy:
"""
return proxy instance
"""
def update(self, change_set):
"""
changeSet = [
{ id: 2143, name: 'Origin', value: [0, 1, 3] },
...
]
"""
Import / Export
def save(self, file_output=None):
"""Export state (definition+data) into a file"""
def load(self, file_input=None, file_content=None):
"""Load previously exported state from a file"""
Commit / Reset
def commit_all(self):
"""Commit all dirty proxies"""
def reset_all(self):
"""Reset all dirty proxies"""
Find / Query Proxy
def get_instances_of_type(self, proxy_type):
"""
Return all the instances of the given type
"""
def tags(self, *args):
"""List all instances containing all the listed tags"""
Core proxy properties
@property
def manager(self):
"""Return ProxyManager that owns us"""
@property
def definition(self):
"""Return Proxy definition"""
@property
def type(self):
"""Return Proxy Type"""
@property
def id(self):
"""Return Proxy ID"""
@property
def object(self):
"""Return Proxy concrete object if any"""
Advanced Read/Write properties
@property
def tags(self):
"""Return the list of tags of that proxy"""
@property
def own(self):
"""List of proxy ids we created"""
Property management
def set_property(self, name, value):
"""Update a property on that proxy"""
def get_property(self, name, default=None):
"""Return a property value"""
def list_property_names(self):
"""Return the list of property names"""
# ---------------------------------------------------------
# Attribute/Item usage
# ---------------------------------------------------------
# - Get property:
# - print(proxy_inst.prop_name)
# - print(proxy_inst["prop_name"])
# - Set property
# - proxy_inst.prop_name = 3
# - proxy_inst["prop_name"] = 3
# ---------------------------------------------------------
Commit / Reset property edit
def commit(self):
"""Flush modified properties"""
def reset(self):
"""Undo any uncommited properties"""
State management for IO and import/export
@property
def state(self):
"""Return proxy state that is easily serializable"""
@state.setter
def state(self, value):
"""Use to rebuild a proxy state from an exported state"""
def remap_ids(self, id_map):
"""Use to remap id when reloading an exported state"""
Base class for objects that can be used as value for properties. They can be passed around by copy instead of reference. A proxy still provide the same reference on which you can interact with method calls but when the value needs to be shared to another location or service, the object get serialized and recreated on the other side in a transparent manner.
The expected API is as follow
@property
def state(self):
"""
Return a serialized version of its state.
The type of ObjectValue is not included in the state and
therefore the initial instantiation is left to the proxy.
"""
@state.setter
def state(self, value):
"""Update internal data from provided serialized state"""
The UIManager needs a ProxyManager and a UIResolver at construction time.
Load language/layout definitions
def load_language(self, yaml_file=None, yaml_content=None, clear_ui=False):
"""Load langage for the objects form"""
def load_ui(self, xml_file=None, xml_content=None, clear_ui=False):
"""Load layout for the objects form"""
def clear_ui(self):
"""
Clear any loaded UI definition
=> This will force auto generation if nothing new gets loaded
"""
Data exchange for UI handling
def data(self, proxy_id):
"""Return proxy state to fill UI with"""
def ui(self, _type):
"""Return resolved layout (xml string with resolved elements+attributes)"""
A UIResolver is responsible to process the XML from a <ui/>
definition and convert it into another XML that can then be used by a UI backend (Vuetify, Qt, ...) without much processing logic.
The resolver has access to labels+helps from the language file along with the model definition that can include domains.
Below you can see how a <ui/>
element will be transformed for the Vuetify target.
Input
<ui id="Clip">
<input name="ClipFunction" />
<proxy name="ClipFunction" />
<show property="ClipFunction" domain="Scalars">
<col>
<row>
<input name="Value" />
<input name="Scalars" />
</row>
</col>
</show>
<input name="InsideOut" class="mx-2"/>
</ui>
Output
<div>
<sw-select
name="ClipFunction"
size="1"
type="proxy"
:mtime="data.mtime"
:initial="data.original['ClipFunction']"
label="Clip Function"
help="Function to use for clipping"
/>
<sw-proxy name="ClipFunction" />
<sw-show property="ClipFunction" domain="Scalars">
<v-col>
<sw-text-field
name="Value"
size="1"
type="float64"
:mtime="data.mtime"
:initial="data.original['Value']"
label="Value"
help=""
/>
<sw-select
name="Scalars"
size="1"
type="value::Array"
:mtime="data.mtime"
:initial="data.original['Scalars']"
label="Clip by Array"
help="Which field to use for clipping dataset"
/>
</v-col>
</sw-show>
<sw-switch
class="mx-2"
name="InsideOut"
size="1"
type="uint8"
:mtime="data.mtime"
:initial="data.original['InsideOut']"
label="Inside Out"
help="Toggle which side of the clip to keep"
/>
</div>
A ProxyDomainManager is responsible to add behavior on a ProxyManager by listening to proxy creation and update. For that it needs to be registered to the ProxyManager by calling pxm.add_life_cycle_listener(pdm)
.
Once registered, it will track ProxyDomain for each created proxy. Also when Proxy update happen, it will keep track of the modified one to only re-execute any domain that needs to update.
User just need to call apply_all()
after any batch update to flush the domain and set any missing property initialization.
def apply(self, *property_names):
"""
Ask domains to set values or just for one property if property_name is provided.
Return the number of properties that have been updated.
"""
def get_property_domains(self, prop_name):
"""Helper to get the map of domains linked to a property"""
@property
def state(self):
"""
Return a serializable state of the domains linked to a proxy.
This include for each property and each domain a `valid` and `available` property.
Also at the property level a list of `hints`.
state = {
ContourBy: {
FieldSelector: {
valid: True,
available: [
{ text: "Temperature", value: "Point::Temperature", ... },
...
]
},
hints: [],
},
Scalar: {
Range: {
valid: True,
available: [0.5, 123.5],
},
hints: [
{ level: 0, message: "Outside of range (0.5, 123.5)" },
],
},
}
"""
def enable_set_value(self):
"""Reset domain set so it can re-compute a default value"""
def set_value(self):
"""
Ask domain to compute and set a value to a property.
return True if the action was succesful.
"""
def available(self):
"""List the available options"""
@property
def value(self):
"""Return the current proxy property value on which the domain is bound"""
@value.setter
def value(self, v):
"""Set the proxy property value"""
def valid(self, required_level=2):
"""Return true if the current proxy property value is valid for the given level"""
@property
def level(self):
"""Return current domain level (0:info, 1:warn, 2:error)"""
@level.setter
def level(self, value):
"""Update domain level"""
@property
def message(self):
"""Associated domain message that is used for hints"""
@message.setter
def message(self, value):
"""Update domain message"""
def hints(self):
"""Return a set of (level, message) when running the validation for the info level"""
Concrete implementation of domains
initial: xyz | set name=xyz proxy to the property as default
values: | list all possible proxy that can be set
- name: xyz | proxy entry: - name
type: Representation | - proxy type
bind: Input | set self to SubProxy.Input property (optional)
name: Scalars | (optional) provide another name than its type
available: {type/name} | Which domain available list on prop to use
value: Scalar | Value that our prop needs to match to be "valid"
name: List | (optional) provide another name than its type
input: Input | Specify property on which field inspection happen
location: Point | Filtering arrays to be only on [Point/Cell/Field]
size: 1 | Number of components for available arrays
initial: first | (optional) if provided, domain will set array to prop
isA: | (optional) filter arrays by their type
- vtkDataArray | => Only numerical arrays
name: xxxx | (optional) provide another name than its type
-----------------------------------------------------------------------------
value_range: [0, 1] | Static range
-----------------------------------------------------------------------------
property: PropArray | Specify property on which an array is defined
initial: [mean, min, max] | Computation to use for setting the value
component: -1 (mag) | Component to use for range computation
name: xxxx | (optional) provide another name than its type
-----------------------------------------------------------------------------
property: Property name | When current property change reset domain on
domain: Domain name/type | property so default values could be regenerated
name: xxxx | (optional) provide another name than its type
-----------------------------------------------------------------------------
proxy: Property name containing data object proxy
property: Property on Proxy that is the data object
name: xxxx | (optional) provide another name than its type
-----------------------------------------------------------------------------
values: [{ text, value}, ...]
Concrete implementation of object values
Capture Array name and location (point/cell/field)