PiComposer EXPRESS Schema Interface

This package defines a platform-agnostic interface for interacting with data models defined by the EXPRESS schema language (ISO 10303-11), the foundation for engineering data standards like IFC (Industry Foundation Classes).

As an interface-only package, it provides the complete API contract—abstract classes, methods, and properties—that other packages must implement. The actual implementation is provided by a separate package (e.g., a native C++ backend for desktop), allowing this interface to be used across different platforms simply by using the appropriate implementation package.

It serves as a crucial abstraction layer, offering a consistent, developer-friendly Dart API for accessing and manipulating complex, schema-defined data without needing to manage low-level details of data parsing, validation, and serialization.

What It Does & Why It's Useful

Working directly with EXPRESS-based data models can be complex. This library simplifies that process by providing a set of core Dart interfaces for schema interaction:

  1. Schema Definition (ISchema, IEntityDescriptor): Inspect the schema itself. Discover entities, their attributes, and relationships, which is essential for building dynamic applications that adapt to different schema versions.

  2. Data Manipulation (IInstance): This is the heart of the data model, representing an actual instance of an entity from the schema (e.g., an IfcWall or IfcDoor). The IInstance interface is designed to be powerful and flexible, offering two distinct paradigms for interacting with data:

    • Imperative API: This is a comprehensive set of low-level getters and setters for every attribute type (e.g., getBool(), setInt(), getStrings()).

    • Declarative API: This higher-level API is designed for scripting, automation, and handling complex data structures efficiently. It consists of two main approaches:

      • Path-based Access (PIAttributePath): A universal addressing system to target any attribute, no matter how deeply nested.
      • JSON-based Operations: The setAttributesByJson method allows for creating and updating complex, hierarchical object graphs in a single, declarative batch operation.

    Choosing the Right API: Performance & Use Case

    The choice between the imperative and declarative APIs is a key design decision.

    Aspect Imperative API Declarative API
    Primary Use Performance-critical data manipulation Scripting, automation, configuration
    Performance High (Direct data access) Lower (Data marshalling overhead)
    Syntax Fine-grained methods (getBool(), setInt()) Coarse-grained, path-based or JSON
    Best For Geometry processing, simulations Blockly templates, data import/export, rapid prototyping

    Guideline: Use the Declarative API for scripting, automation, and user-facing tools like Blockly where ease of use is paramount. Use the Imperative API in performance-critical, low-level code that requires heavy iteration on numerical data.

  3. Object Creation (IObjectFactory): Provides a centralized factory for creating instances of any schema object, ensuring that they are correctly initialized according to the schema rules.

The Blockly Template System

A key feature of this package is that it provides the foundation for the PIComposer Blockly Template System, a visual programming environment designed for an audience that may not have a background in software development, such as architects, engineers, and designers.

This system uses Blockly to allow users to create powerful, reusable scripts by connecting visual blocks. These templates can:

  • Generate new models or complex entity structures.
  • Transform existing data by applying rules and modifications.
  • Define custom data types and configurations.

The Declarative API is the ideal choice for this system, as it allows complex operations to be expressed concisely, which is a perfect fit for a block-based scripting environment. This empowers domain experts to define and customize application logic without writing traditional code.

In summary, the schema_interface package is a vital tool for any Dart application that needs to work with standardized engineering data, providing the necessary abstractions to make development faster, safer, and more maintainable.


API Quick Reference

Below is a high-level overview of the main elements available in the API.

Core Interfaces

  • ISchema: The root object providing access to schema definition and description services.
  • IEntityDescriptor: Describes an ENTITY type in the schema (its name, attributes, inheritance).
  • IInstance: Represents a single instance of an ENTITY. This is the primary interface for data access and manipulation.
  • IObjectFactory: A central factory for creating new instances of schema objects.
  • ISelect: Manages EXPRESS SELECT data types, which can hold one value from a set of allowed types.

Blockly Template Interfaces

  • IBlocklyTemplate: The base interface for all templates in the visual scripting system.
  • IBlocklyProcedureTemplate: For templates that define executable procedures and functions.
  • IBlocklyDictionaryTemplate: For templates that define data configurations, custom types, and object libraries.

Key Classes & Enums

  • InstanceHandle: A lightweight, serializable reference (or pointer) to an IInstance.
  • PIEnum: Represents an instance of an EXPRESS enumeration.
  • FundamentalType: An enum categorizing all fundamental EXPRESS data types (e.g., INTEGER, STRING, ENTITY, REAL, SELECT).
  • SupportedSchema: An enum listing the EXPRESS schemas supported by implementations (e.g., IFC2x3, IFC4, IFC4x3).

Getting Started

1. Add the Dependency

Add schema_interface to your pubspec.yaml file. Since this is an interface, you will also need to include a specific implementation package (e.g., schema_native for a desktop app).

dependencies:
  schema_interface: ^1.0.0
  # schema_native: ^1.0.0 # Example implementation package

2. Import the Package

import 'package:schema_interface/schema_interface.dart';

3. Obtain a Schema Instance

The entry point is typically through an implementation-specific function or a factory.

// Example: Getting a schema from a native implementation
ISchema schema = await getNativeSchema(SupportedSchema.IFC4x3);

4. Work with Data

// Get a description of the IfcWall entity
IEntityDescriptor wallDescriptor = schema.getEntityDescriptor('IfcWall');

// Use the factory to create a new instance
IObjectFactory factory = schema.objectFactory;
IInstance myNewWall = factory.createInstance('IfcWall');

// Use the Imperative API to set a single value
myNewWall.setString('Name', 'Interior Wall 42');
myNewWall.setDouble('OverallHeight', 3.2);

// Use the Declarative API to set multiple, nested attributes in one operation
myNewWall.setAttributesByJson({
  'Name': 'Interior Wall 42',
  'OverallHeight': 3.2,
  'ObjectPlacement': {
    '_type': 'IfcLocalPlacement',
    'RelativePlacement': {
        '_type': 'IfcAxis2Placement3D',
        'Location': { 'x': 10.0, 'y': 5.0, 'z': 0.0 }
    }
  }
});

Usage Examples

Example 1: Iterating over Attributes (Imperative)

void printInstanceAttributes(IInstance instance) {
  IEntityDescriptor descriptor = instance.descriptor;

  for (var attr in descriptor.attributes) {
    print('${attr.name}: ${instance.getAttribute(attr.name)}');
  }
}

Example 2: Using Path-Based Access (Declarative)

// Create a path to a deeply nested attribute
var path = PIAttributePath.fromString('ObjectPlacement.RelativePlacement.Location.X');

// Get the value using the path
double xCoord = instance.getAttributeByPath(path);

// Set the value using the path
instance.setAttributeByPath(path, 15.0);

License

This project is licensed under a commercial License.

Libraries

schema_interface
Provides interfaces for EXPRESS schema definitions—including TYPE, ENTITY, SELECT, and ENUM— represented as descriptors, and for EXPRESS data content, represented as accessors. This package also defines the interfaces for PIComposer's template system.