PIComposer APIPIComposer API

Guide#

Welcome to the PIComposer API guide. This page explains how the documented interface contracts are used at runtime — in particular, how PIComposer's Dart VM extension loads and executes user-defined Blockly templates through the picomposer_procedures package.

For detail about example template that ships with PIComposer, see the companion Blockly Workspaces source page. It lists all 37 published templates, with the metadata PIComposer's runtime reads from each (type, schema, input/output contract) and either the generated procedure<id>.dart source or the generated data artifact (for templates that don't compile to a Dart procedure). Use it as a quick reference when reading this guide.

The picomposer_procedures Execution Platform#

picomposer_procedures is the Dart VM extension package that runs on top of the PIComposer Dart native runtime. It hosts user-defined procedures written as Dart functions or constructed via PIComposer's Blockly workspace and compiled to a Dart function. The package exports one library (picomposer_procedures.dart) and contains seven specialized managers — one per procedure type — plus a single VM entry point. The primary responsibility of a template manager is template registration.

// lib/picomposer_procedures.dart
export 'src/procedure_model_manager.dart';
export 'src/instance_transform_manager.dart';
export 'src/model_transform_manager.dart';
export 'src/procedure_instance_manager.dart';
export 'src/instance_template_manager.dart';
export 'src/generic_procedure_manager.dart';
export 'src/timer_procedure_manager.dart';
export 'src/picomposer_procedures_main.dart';
                        

Startup Lifecycle#

At startup, PIComposer starts the Dart VM. The Dart VM compiles the picomposer_procedures package. If there is no compilation error, PIComposer is ready to execute any procedural template. If there is a compilation error, the Dart VM shuts down.

Execution Flow#

Templates can be dispatched by the PIComposer application or invoked from other templates. When dispatched from the PIComposer application, the request flows through the C++ backend into the Dart VM via the executeProcedure entry point:

PIComposer (UI)
   |
   v
PIComposer's C++ backend
   |
   v
executeProcedure(requestId, templateId, procedureType)   [Dart VM entry point]
   |
   v
switch (procedureType)
   |
   +--> getXxxInput(requestId)          <-- pulls typed args from C++ side
   |
   v
manager.getXxxFunction(templateId)       <-- looks up registered Dart function
   |
   v
procedure(model, inst, input)            <-- user-defined Dart code runs
   |
   v
transferXxxOutput(requestId, result)     <-- pushes result back to C++ side
   |
   v
PIComposer (UI updates with the result)

The getXxxInput and transferXxxOutput calls are typed per ProcedureType (see the Procedure Types section below). The C++ side passes the strongly-typed arguments through native handles; the Dart side reads them, runs the registered function, and writes the result back.

To invoke a template procedure from another template, use a manager lookup function getXxxFunction to obtain the procedure reference, then invoke it directly:

// From inside one procedure, call another by templateId
final ref = getGenericProcedureFunction(otherTemplateId);
if (ref != null) {
  final success = ref(store, inputMap);
}

Available getXxxFunction lookups — one per ProcedureType:

  • getGenericProcedureFunction(id)bool Function(PIRocksFFIStore, Dictionary)
  • getInstanceTemplateFunction(id)IInstance Function(PIRocksFFIModel, PIInstance, Dictionary)
  • getInstanceTransfromFunction(id)IInstance Function(PIRocksFFIModel, PIInstance, Dictionary)
  • getModelTransformFunction(id)PIRocksFFIModel Function(PIRocksFFIProject, PIRocksFFIModel, Dictionary)
  • getProcedureInstanceFunction(id)IInstance Function(PIModel, Dictionary)
  • getModelProcedureFunction(id)PIIfcModel Function(PIProject, Dictionary)
  • getTimerProcedureFunction(id)bool Function(Dictionary)

In practice, 31 published Blockly templates compile down to a procedure<id>.dart file in lib/src/generated/, and another 6 (spatial, property-set, quantity-set, enum, user-defined-entity templates) emit a static data artifact instead. See the Blockly Workspaces source page for the full list.

Procedure Types#

Every Blockly template registered in PIComposer has a ProcedureType from the store_interface enum. Each type is bound to a manager in picomposer_procedures and has a fixed Dart function signature:

  • genericbool Function(PIRocksFFIStore, Dictionary). Most general form: takes the singleton store plus an arbitrary JSON-like dictionary of inputs. Returns a success flag. Examples: partial-model-to-part21, test-loop-function, export-model.
  • instanceTemplateIInstance Function(PIRocksFFIModel, PIInstance, Dictionary). Operates on an instance that has just been instantiated from a Blockly template. May transform or return the instance unchanged. Example: boolean-substraction.
  • instanceTransformIInstance Function(PIRocksFFIModel, PIInstance, Dictionary). Transforms an existing instance into a new one inside the same model. Examples: IfcProduct-translate, rotation-around-axis.
  • modelTransformPIRocksFFIModel Function(PIRocksFFIProject, PIRocksFFIModel, Dictionary). Operates on a whole model and returns a transformed model. Examples: data-transfer, wall-along-indexed-poly-curve.
  • procedureInstanceIInstance Function(PIModel, Dictionary). Creates a single new instance from input parameters (e.g., an extruded solid). Examples: ifcextruded-solid, Trapezoidal Prism, ifc-platonic-cube.
  • procedureModelPIIfcModel Function(PIProject, Dictionary). Creates a new model from input parameters. Examples: ap214-brep-box, procedural house.
  • timerbool Function(Dictionary). Driven by PIComposer's timer event system. No published templates use this type yet — see addTimerProcedureFunction in timer_procedure_manager.dart for the registration pattern.

The binding from templateId to a function is established at startup by the corresponding *_initializer.dart in lib/src/generated/, which calls the matching addXxxFunction on the manager's internal map.

The executeProcedure Entry Point#

The C++ side invokes executeProcedure by symbol name. The function is annotated so the Dart AOT compiler keeps it in the snapshot even though nothing in the Dart code calls it directly:

// lib/src/picomposer_procedures_main.dart
@pragma('vm:entry-point', 'call')
bool executeProcedure(int requestId, String templateId, int procedureType) {
  final type = ProcedureType.values[procedureType];
  try {
    switch (type) {
      case ProcedureType.generic:
        // pull (store, input) from the FFI, look up the registered function,
        // run it, transfer the result back.

Inside the switch, each case calls PIRocksFFITemplateManager.getXxxInput(requestId) to pull the strongly-typed arguments from native code, looks up the registered function via getXxxFunction(templateId), invokes it, then writes the result back with the matching transferXxxOutput(...). logMsg is an FFI function and may only be called after PIRocksFFIStore.initializeFFI has run from main().

Template User Input#

A template defines its user interaction by populating the _templateInput dictionary. The keys of _templateInput are the property names that the user will input, and the values are the fundamental data types of those inputs.

When a template is invoked by PIComposer, PIComposer presents a dialog box with an input tree where the user enters the requested values as specified by _templateInput. In the procedure, the user-entered values are passed via the global dictionary variable _input.

The Input Contract#

Each value in _templateInput is a string that names a fundamental data type. PIComposer reads the contract and renders the appropriate input control in the dialog; the runtime then casts the user-entered value to the corresponding Dart type when populating _input:

...
_templateInput valueUI control in PIComposerRuntime type in _input
"STRING"text fieldString
"INTEGER"numeric input (integer)int
"REAL"numeric input (decimal)double
"BOOL" / "BOOLEAN"checkboxbool

The contract is a single Map<String, String> literal in the procedure source, so it is fully self-describing — anyone reading the generated procedure<id>.dart on the Blockly Workspaces source page can see exactly what the user will be asked for.

Worked Example: export-model#

Take the export-model example below. The procedure declares its input contract up front by populating _templateInput with the three string entries it needs from the user:

Dictionary _templateInput = {
  "project_id": "STRING",
  "model_id":   "STRING",
  "file_name":  "STRING"
};

When the user clicks "Export" in PIComposer, the application reads this contract and builds an input dialog with three text fields. The values the user types are then passed into the procedure via the global _input dictionary, where the procedure reads them out with getDictionaryValue:

_projectId = getDictionaryValue(_input, "project_id") ?? "";
_modelId   = getDictionaryValue(_input, "model_id")   ?? "";
_fileName  = getDictionaryValue(_input, "file_name")  ?? "";

Each getDictionaryValue(_input, key) call returns the value cast to its declared fundamental type, or the fallback default if the user left the field empty (here, an empty string). The procedure's later code can use those values without further type checking because the contract guarantees their types.

Templates can declare richer shapes by using "DICTIONARY" for nested key-value inputs, or an array value for repeatable input lists. See any of the export-model / ifcextruded-solid / ap214-brep-box entries on the source page to see the same contract pattern at work in different procedure types.

Usage Examples#

The two examples below are taken verbatim (lightly trimmed) from the auto-generated procedure files in picomposer_api_impl/picomposer_procedures/lib/src/generated/. They show how a procedure looks in practice and what patterns PIComposer's Blockly templates compile into.

Model Exporter (generic procedure example)#

Template 3aZP0UfEb4SgJ38zaSslEr — type generic procedure — schema ifc4x3. Exports a model to a Part 21 (.ifc/.stp) file by paginating through all of its instances. This is the procedure that powers the "Export to STEP" workflow in PIComposer. A good demonstration of the standard input contract, file I/O, error handling, and pagination that most real-world generic procedure templates follow.

See the workspace source catalog entry for export-model for the full workspace JSON and metadata.

// Input contract declared by the Blockly template:
Dictionary _templateInput = {
  "project_id": "STRING",
  "model_id": "STRING",
  "file_name": "STRING"
};
// Read inputs (with safe defaults) and validate
_projectId = getDictionaryValue(_input, "project_id") ?? "";
_modelId   = getDictionaryValue(_input, "model_id") ?? "";
_fileName  = getDictionaryValue(_input, "file_name") ?? "";
for (var inputValue in [_projectId, _modelId, _fileName]) {
  if (inputValue.isEmpty) {
    logMsg(LogType.error, "invalid input");
    return false;
  }
}

// Open output stream, load project + model, write IFC header + DATA section
_ostream = PIFileStream();
_ostream.open(_fileName);
_project = _store.getProject(_projectId);
_project.activateProject();
_model = _project.getModel(_modelId);
_ostream.writeHeaderInstance(_model.getHeader(), ExportFormat.values[0]);
_ostream.writeString('DATA;\n');

// Paginate through all instances (1,000 per page) so very large models don't OOM
_handle = InstanceHandle.fromJson({kInstanceTypeKey: 0, "instanceId": 0});
_instances = _model.getInstancesPaginated(_handle, pageSize: 1000);
while (!_instances.isEmpty) {
  for (var inst in _instances) _printInstance(inst);
  _handle = (_instances.last).instanceHandle;
  _instances = _model.getInstancesPaginated(_handle, pageSize: 1000);
}

// Close out the Part 21 envelope
_ostream.writeString('ENDSEC;\nEND-ISO-10303-21;\n');
_ostream.flush();
_ostream.close();

IFC Extruded Solid Generator#

Template 2UdehL6ZvCmeQJdY5k180h — type procedure entity — schema ifc4x3. Creates an IfcShapeRepresentation for a rectangular extruded solid, given length, width, and height inputs. This is a good demonstration of the declarative JSON-based instance creation pattern that PIComposer's Blockly templates use heavily.

See the workspace source catalog entry for ifcextruded-solid for the full workspace JSON and metadata.

// input schema
_templateInput = { "length": "REAL", "width": "REAL", "height": "REAL" };
_x = getDictionaryValue(_input, "length") ?? 300;
_y = getDictionaryValue(_input, "width")  ?? 200;
_z = getDictionaryValue(_input, "height") ?? 2500;

// get the body 3D geometric context for the model
final _context = (_model as IIfcModel).getBody3dGeometricContext();

// create the swept area solid declaratively from a nested map
final _extrude = _model.createInstanceFromDictionary({
  "@type": "IfcExtrudedAreaSolid",
  "SweptArea": {
    "@type": "IfcRectangleProfileDef",
    "ProfileType": "AREA",
    "xDim": _x, "yDim": _y,
  },
  "ExtrudedDirection": { "DirectionRatios": [0, 0, 1] },
  "Depth": _z,
});

// style, representation, attach
final _style  = (_model as IIfcModel).createStyledItem([0.8, 0.6, 0.3, 0]);
final _inst   = _model.createInstanceFromDictionary({
  "@type": "IfcShapeRepresentation",
  "RepresentationIdentifier": "Body",
  "RepresentationType": "SweptSolid",
  "ContextOfItems": _context.instanceHandle,
});
_model.addRepItemToShape(_inst, _extrude, _style);

Source References#

Blockly Examples#

The full set of Blockly workspace templates that ship with PIComposer is listed on the examples page, with full source and metadata for each template on the Blockly Workspaces source page. The source page shows the generated procedure<id>.dart source for every runtime template (31 of the 37 published) and the generated data artifact for templates that don't compile to a Dart procedure (6 spatial/propertyset/quantityset/enum/userdefinedentity templates). A few that are particularly relevant to this guide:

  • ifcextruded-solidPIBLOCKLYPROCEDURALENTITYTEMPLATE that creates an IfcShapeRepresentation with an IfcExtrudedAreaSolid and a styled item (matches the IFC Extruded Solid example above)
  • partial-model-to-part21PIBLOCKLYGENERICPROCEDURETEMPLATE that exports part of a model to a Part 21 file
  • ap214-brep-boxPIBLOCKLYPROCEDURALMODELTEMPLATE for AP214 (ISO 10303 STEP application protocol 214)
  • wall-along-indexed-poly-curvePIBLOCKLYMODELTRANSFORMTEMPLATE that adds walls along an indexed poly curve
  • 5-storey (2m height)PIBLOCKLYSPATIALTEMPLATE showing the spatial hierarchy (Project → Building → 5 Storeys). One of the 6 templates that emit a JSON artifact rather than a Dart procedure.

Each section on the source page also includes the runtime metadata PIComposer reads — the type, schema index, and input/output contract — so you can map back to the API types and signatures in the package overviews without leaving the page.