Core: objects, contexts, properties, and data I/O¶
uLib.Core is the base layer shared by every bound scene object.
Use it for object identity, editable properties and ports, algorithms, raw
data buffers, state capture, and the in-process property event bus.
from uLib import Core, Math
box = Math.ContainerBox()
box.SetInstanceName("absorber")
box.SetSize([100.0, 50.0, 10.0])
print(box.GetClassName(), box.GetInstanceName())
Most concrete objects live in another module (for example
Math.ContainerBox) but inherit Core.Object and
Core.PropertyContainer.
Objects and object contexts¶
An ObjectsContext is the owner of a scene collection. It is the
authoritative place from which the UI, VTK wrappers, serialization, and
selection learn an object's identity. Do not maintain a second Python list as
an alternative owner.
The direct binding deliberately exposes context traversal and removal, but not
an unsafe AddObject taking a Python-owned raw pointer. Choose the
creation route that has a clear owner:
| Situation | Use |
|---|---|
| A temporary or independent value/object in a standalone script | Construct it normally, for example Math.ContainerBox(). |
| An object that must join the open gcompose scene | ctx.create(class_name, instance_name). |
| An object restored from an archived scene state | Core.RestoreContext(document, context). |
| An existing scene object that must be found or edited | ctx.find(), ctx[name], or the remote Client. |
# gcompose console or Recipe: ctx owns the new object.
detector = ctx.create("ContainerBox", "detector")
detector.Size = [100.0, 100.0, 20.0]
# Read the same ownership collection through the direct Core API.
# Objects returned by iteration are borrowed from the context.
context = Core.ObjectsContext()
assert len(context) == context.GetCount()
for object_ in context:
print(object_.GetInstanceName())
An object returned by ObjectsContext.GetObject(),
GetObjects(), or iteration is borrowed: keep its owning context
alive. RemoveObject() and Clear() remove the
context's ownership, so do not use a borrowed object after doing either.
Properties, ports, and actions¶
Every Object is a PropertyContainer. A property
describes both its value and its graph role: input/output direction, access
(value, reference, stream, or action), metadata, and connection state.
from uLib import Core, Math
box = Math.ContainerBox()
size = box.GetProperty("Size")
print(size.GetTypeName(), size.GetPortAccess())
size.SetValueFromString("100 50 10")
print(box.GetSize())
for metadata in box.GetPropertyMetadata():
print(metadata.name, metadata.typeName, metadata.portDirection)
Use a property connection to make a graph relationship. Assignment changes a
value; ConnectFrom() says that one port drives another. In the
gcompose Python facade, use ctx.connect() for stream/reference
ports or property assignment for a plain local edit.
Runtime action ports¶
AddActionPort() puts a button-like action on an object. Its Python
callable executes when the action is invoked; it is also visible to the
Properties panel and node graph when its owner is in a scene.
from uLib import Core, Math
box = Math.ContainerBox()
reset = Core.AddActionPort(
box, "Reset",
lambda: box.SetSize([1.0, 1.0, 1.0]),
label="Reset size", group="Operation")
reset() # same as reset.Invoke()
assert list(box.GetSize()) == [1.0, 1.0, 1.0]
Core.Action is the uLib scene-object flavour of an action.
Core.ActionProperty is the underlying property type. Usually an
action comes from GetProperty("Run") or
AddActionPort(); neither should be constructed independently.
Change events¶
The LocalSignalBackend lets Python observe the same property
change bus used by C++ and the UI.
from uLib import Core
def report(change):
print(change.eventKind, change.propertyName, change.valueAsString)
backend = Core.LocalSignalBackend.Instance()
backend.SubscribeCallback(report)
PropertyChangePayload carries the object/property identity,
event kind, origin, sequence, time, value, reference target, and metadata.
Use PropertyEventKind to distinguish a value edit, a graph
connection edit, a reference move, metadata change, or action invocation.
Algorithms¶
Core.Algorithm is the common control surface for processing
objects. The abstract base is normally received as a concrete domain
algorithm, such as Math.VoxImageFilter. The common controls are
run()/Run(), start(), pause(),
resume(), stop(), wait(), and
kill().
from uLib import Math
image = Math.VoxImage(Math.Vector3i([8, 8, 8]))
output = Math.VoxImage(Math.Vector3i([8, 8, 8]))
filter_ = Math.VoxImageFilterThreshold()
filter_.SetInput(image)
filter_.SetOutput(output)
filter_.SetThreshold(0.25)
filter_.run() # synchronous
print(filter_.is_running(), filter_.last_error())
auto_run and num_threads are properties of the
base class. In a scene, AutoRun allows an algorithm or Recipe to
follow input changes; streaming uses it to turn queue notifications into
consumer runs.
Raw data sources and sinks¶
DataSource owns a byte payload read from memory or a file;
DataSink owns bytes to write to memory or a file. They are useful
for portable scene assets and as input/output endpoints for objects such as
VoxImage and TessellatedMesh.
from uLib import Core
# A memory-backed source is embedded by default after set_data().
source = Core.DataSource("calibration")
source.set_data(b'{"gain": 1.25}')
assert source.get_data() == b'{"gain": 1.25}'
assert source.embedded
# Copy the payload to a sink, keep it in memory, then write it if wanted.
sink = Core.DataSink("copy")
assert sink.write_source(source)
assert sink.get_data() == source.get_data()
sink.save_to_file("calibration-copy.json")
For a file-backed source, set file_path, optionally set
format, then call load_file(). Call
embed() to capture its current bytes into the saved scene.
DataSource.embed_all(context) and
DataSink.flush_all(context, directory) apply the same operation
to every matching object in a context.
from uLib import Core
source = Core.DataSource("mesh")
source.file_path = "surface.stl"
source.format = "stl"
if source.load_file():
source.embed()
sink = Core.DataSink("result")
sink.file_path = "result.bin"
sink.append = False
sink.write_data(b"header")
sink.flush()
DataSource also implements IStreamProducer, which is
the object-stream interface, not a Python byte iterator. Its
NextItem() methods are for a decoded object stream. For raw
payload bytes use get_data()/set_data(); for graph
streaming read Streaming.
DataBuffer in a scene¶
DataBuffer is a registered scene class rather than a direct
Core.DataBuffer pybind class. Create and configure it through
gcompose's ctx. It can buffer an object conversion and, when
fed by a stream in Async mode, becomes the bounded queue that
paces a push pipeline.
# gcompose console or Recipe
buffer = ctx.create("DataBuffer", "queue")
buffer.StreamMode = "Async"
buffer.Capacity = 256
buffer.OverflowPolicy = "Block"
buffer.NotifyEvery = 16
# The complete producer -> DataBuffer -> consumer wiring is in Streaming.
The important queue properties are Capacity,
OverflowPolicy, StreamMode, NotifyEvery,
and read-only StreamQueueSize. A queue is consumptive:
stream.drain() takes items; replay iteration resets a collection
but clears a live DataBuffer queue.
Object state and persistence¶
CaptureObjectState() creates a byte blob for one object's
class-specific state, and ApplyObjectState() restores it to an
object of the named class. DecodeDocument() and
RestoreContext() serve the larger, scene-document form used by
the remote protocol.
from uLib import Core, Math
original = Math.ContainerBox()
original.SetSize([4.0, 5.0, 6.0])
blob = Core.CaptureObjectState(original)
copy = Math.ContainerBox()
Core.ApplyObjectState(copy, original.GetClassName(), blob)
assert list(copy.GetSize()) == [4.0, 5.0, 6.0]
SceneDocument holds ObjectRecord values. Records
expose their read-only document id, class name, instance name, parent id, and
payload. They are normally produced by a server response and decoded with
DecodeDocument(), not authored field-by-field in Python.
Complete Core API¶
The table lists every class exported by uLib.Core. Inherited
methods are documented with their base type rather than repeated in every
row.
| Export | Use | Smallest useful example |
|---|---|---|
Object |
base identity, hierarchy, updates | box.GetInstanceName(); box.Updated() |
PropertyContainer |
lookup, metadata, snapshots, change application | box.GetProperty("Size").GetMetadata() |
PropertyBase |
a value/reference/action port | box.GetProperty("Size").SetValueFromString("1 2 3") |
PropertyMetadata |
editable description of one property | meta = Core.PropertyMetadata(); meta.name = "Gain" |
PropertySnapshot |
a property value paired with metadata | snapshot = Core.PropertySnapshot() |
ObjectSnapshot |
snapshots of all properties on one object | snapshot = box.CreateSnapshot() |
PropertyChangePayload |
one event-bus message | payload = Core.PropertyChangePayload() |
LocalSignalBackend |
in-process event publisher/subscriber | Core.LocalSignalBackend.Instance().SubscribeCallback(callback) |
ActionProperty |
action property returned from an owner | box.GetProperty("Reset").InvokeAction() |
Action |
scene action that also updates its owner | Core.AddActionPort(box, "Reset", handler)() |
Algorithm |
base controls for a concrete algorithm | filter_.run(); filter_.wait() |
IStreamProducer |
base interface returned by a stream source | producer.HasNext(); producer.NextItem() |
DataSource |
read/hold memory or file bytes | source.set_data(b"abc") |
DataSink |
collect/write memory or file bytes | sink.write_data(b"abc"); sink.flush() |
ObjectsContext |
owner and iterable scene collection | len(context); context.GetObject(0) |
ObjectFactory |
registry inspection | Core.ObjectFactory.Instance().GetRegisteredClasses() |
ObjectRecord |
one decoded scene-document object record | record.className |
SceneDocument |
decoded document containing records | document.objects |
Timer |
wall-clock stopwatch | timer.Start(); seconds = timer.StopWatch() |
Options |
INI-style program options | options.parse_config_file("run.ini") |
TypeRegister |
framework type-registration controller | Core.TypeRegister.Controller() |
The exported enums are PortDirection,
PortAccess, ConnectionKind,
PropertyEventKind, and PropertyChangeOrigin. Their
names and UI descriptions are available through PortAccessName(),
PortAccessDescription(), ConnectionKindName(), and
ConnectionKindDescription().
The exported free functions are AddActionPort(),
DecodeDocument(), RestoreContext(),
CaptureObjectState(), and ApplyObjectState().