Skip to content

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().