Se rendre au contenu
CTech Digital
  • Accueil
  • Services Odoo
  • Services d'IA
  • Contactez-nous
  • 0
  • Nederlands (BE) English (UK) Français
CTech Digital
  • 0
    • Accueil
    • Services Odoo
    • Services d'IA
    • Contactez-nous
  • Nederlands (BE) English (UK) Français

Unified properties

  • Tous les blogs
  • Daily blog
  • Unified properties
  • 26 novembre 2025 par
    CTech Metrology, Luc Wens

    Node Metadata Systems Analysis

    This document provides an overview of the existing data structures used for storing node metadata in CTrack V5.0. The goal is to identify commonalities and design a unified metadata system that can serve as a mini-database for each node.

    Executive Summary

    CTrack currently uses four distinct systems for storing node metadata:

    SystemPurposeLocationObserver Support
    CFeatureMeasurement results with unitsCTrack_Data/feature.hNo
    CPropertyBaseSerializable configuration propertiesCTrack_Data/DataProperty.hNo
    ObservableProperty<T>Observable typed propertiesCTrack_Data/ObservableProperty.hYes
    Direct member variablesColors, file paths, UUIDsVarious classesNo

    1. CFeature System

    Class Diagram

    Usage Locations

    FileUsageFeature Types
    CalcDoorslam.cppDoor slam analysis resultsAngle, Position, Velocity, Acceleration
    CalcTrajectory.cppTrajectory deviationMax deviation (mm)
    CalcGeomFit.cppGeometry fitting resultsResidual, NumPoints
    CalcAlignNPoint.cppAlignment qualityMax Dev, St Dev
    Alignment_*.cppGeometry dimensionsRadius, Width, Height, Length, Angle
    CalcPolyFit.cppPolynomial coefficientsC0, C1, C2...

    Feature Value Types

    XML Serialization

    Features are serialized as a single attribute in the node's XML:

    <Node features="Name1=Unit1:Value1;Name2=Unit2:Value2;..."/>
    

    2. CPropertyBase System

    Class Diagram

    Usage Pattern

    The CPropertyBase system is primarily used for:

    1. Serializing configuration settings to XML
    2. Populating BCG property windows for user editing
    3. String-based value conversion

    XML Output Format

    <PROPERTY name="PropertyName" value="PropertyValue"/>
    

    3. ObservableProperty System

    Class Diagram

    Observer Integration

    4. External File References

    Current Implementation

    External files are stored as simple CString member variables with manual path conversion:

    Path Handling Pattern

    // Writing to XML - convert to relative
    CString RelativePath = makepathrelative(GetPgmPtr(), m_FilePath);
    GetSetAttribute(pXML, ATTRIB_FILE_PATH, RelativePath, Read);
    
    // Reading from XML - convert to absolute
    m_FilePath = makepathabsolute(GetPgmPtr(), RelativePath);
    

    Files Referenced

    ClassMemberFile TypesPurpose
    CCalcTrajectoryNominalm_FilePath.igs, .stepNominal trajectory
    CDeviceOutputFilem_FilePath.txt, .csvData export
    CDeviceOutputUncertaintym_FilePath.txtUncertainty report
    CCreaformModelm_ModelPathCreaform model3D scanner model
    C3DOCCm_CADFile.stp, .igsCAD visualization

    5. Color Properties

    5.1 Current Implementation

    5.2 Current Color Usage

    ClassMemberPurpose
    C3DOCCm_Color3D object display color
    CCalcTrajectoryNominalm_FixColorFixed trajectory color
    CTriggerVisibilitym_ColorSchemeVisibleVisibility indicator colors

    5.3 Advanced Color System Design

    The unified NodeProperty system introduces an advanced color model that supports multiple color sources and inheritance.

    Color Source Types

    Color Source Behavior

    SourceDescriptionUse Case
    OwnUse the explicitly set RGBA colorDefault for most nodes
    FromFileUse colors embedded in external file (e.g., CAD)CAD files with material colors
    HeatMapCalculate color from data channel valueMeasured trajectories, deviation display
    MasterInherit color from ancestor nodeTest comparison, grouped visualization

    Color Resolution Flow

    Master Color Inheritance

    The Master color concept allows hierarchical color inheritance for comparing grouped data:

    Key behavior:

    • Child nodes with Source: Master display using their ancestor's master color
    • The node's own color (OwnColor: Blue) is preserved but not displayed
    • Switching back to Source: Own restores the individual color
    • Master color lookup traverses up the tree until a node with IsMasterColorProvider = true is found

    Color Property Structure

    struct ColorProperty
    {
        // Core RGBA value (always stored, even when using other sources)
        uint8_t red   = 128;
        uint8_t green = 128;
        uint8_t blue  = 128;
        uint8_t alpha = 255;
    
        // Color source selection
        ColorSource source = ColorSource::Own;
    
        // Heat map configuration (when source == HeatMap)
        struct HeatMapConfig
        {
            std::string channelKey;      // Property key for value lookup
            double      minValue = 0.0;
            double      maxValue = 1.0;
            HeatMapPalette palette = HeatMapPalette::Rainbow;
            bool        autoRange = true;  // Auto-calculate min/max from data
        };
        std::optional<HeatMapConfig> heatMap;
    
        // Master color settings
        bool isMasterColorProvider = false;  // This node provides master color to descendants
    
        // Methods
        COLORREF GetEffectiveColor(CNode* context) const;
        COLORREF GetOwnColor() const;
        void     SetOwnColor(COLORREF color);
        void     SetSource(ColorSource src);
    };
    

    XML Serialization for Colors

    <!-- Simple own color -->
    <C key="DisplayColor" rgba="#FF5733FF" source="own"/>
    
    <!-- Use colors from CAD file -->
    <C key="DisplayColor" rgba="#808080FF" source="fromFile"/>
    
    <!-- Heat map based on deviation channel -->
    <C key="DisplayColor" rgba="#808080FF" source="heatMap">
        <HeatMap channel="Deviation" min="0.0" max="5.0" palette="blueToRed" autoRange="true"/>
    </C>
    
    <!-- Inherit from master -->
    <C key="DisplayColor" rgba="#0000FFFF" source="master"/>
    
    <!-- Master color provider node -->
    <C key="DisplayColor" rgba="#FF0000FF" source="own" isMaster="true"/>
    

    API Examples for Colors

    // Set a simple color
    node->GetPropertyDB().SetColor("DisplayColor", RGB(255, 87, 51));
    
    // Configure to use CAD file colors
    auto* colorProp = node->GetPropertyDB().Get("DisplayColor");
    colorProp->SetColorSource(ColorSource::FromFile);
    
    // Configure heat map coloring
    colorProp->SetColorSource(ColorSource::HeatMap);
    colorProp->SetHeatMapConfig({
        .channelKey = "Deviation",
        .minValue = 0.0,
        .maxValue = 5.0,
        .palette = HeatMapPalette::BlueToRed,
        .autoRange = true
    });
    
    // Set up master color inheritance
    testNode->GetPropertyDB().Get("DisplayColor")->SetMasterColorProvider(true);
    childNode->GetPropertyDB().Get("DisplayColor")->SetColorSource(ColorSource::Master);
    
    // Get the effective display color (resolves source)
    COLORREF displayColor = node->GetPropertyDB().GetEffectiveColor("DisplayColor");
    
    // Get the stored own color (ignores source)
    COLORREF ownColor = node->GetPropertyDB().GetColor("DisplayColor");
    

    Color Source Selection UI

    6. NODE_UUID References

    Design Decision: Node references (UUIDs) remain as separate member variables in their respective classes. They are NOT included in the unified NodeProperty system because:

    • They represent structural relationships in the node tree
    • They are managed by the existing dependency and parent/child systems
    • They have specific semantics (deletion cascade, copy behavior) that differ from properties

    Reference Types

    Reference Storage Patterns

    PatternExamplePurpose
    Single UUIDm_UUID6DOFCalibrationDirect node reference
    UUID Vectorm_arUUIDMarkersMultiple related nodes
    Dependency arraym_arDependingOnUUIDDeletion cascade
    Feature UUIDCFeature.m_UUIDBack-reference to owner

    These patterns remain unchanged in the new architecture.

    7. Data Flow Overview

    8. Unified Metadata Design Considerations

    Requirements Matrix

    RequirementCFeatureCPropertyBaseObservablePropertyProposed Unified
    Value + Unit✅❌❌✅
    Text values✅✅✅✅
    XML persistence❌ (manual)✅❌✅
    Property window❌✅Partial✅
    Change notification❌❌✅✅
    Type safety❌Template✅✅
    Validation❌❌✅✅
    External file tracking❌❌❌✅
    Color support❌❌❌✅
    Batch updates❌❌✅✅

    9. Unified NodeProperty System Design

    9.1 Design Philosophy

    The unified NodeProperty system provides each CNode with a mini-database of typed metadata that is:

    1. Observable: Integrates with the existing Observer pattern for change notifications
    2. Backwards Compatible: Reads legacy CFeature and CPropertyBase XML formats
    3. Efficient: Writes optimized, grouped XML format
    4. Flexible: Supports system properties, user-defined properties, and various value types
    5. Unit-Aware: Stores base units only; UI handles conversion and display
    6. Keyed Access: Database-like access by type and name

    9.2 Property Value Types

    9.3 Property Flags and Attributes


    9.4 NodeProperty Class Design


    9.5 NodePropertyDatabase Class Design

    The NodePropertyDatabase serves as the mini-database for each node, providing keyed access by type and name:

    9.6 Integration with CNode

    9.7 Observer Pattern Integration

    The NodePropertyDatabase integrates with the existing Observer pattern through PropertySubject. This builds on the Observer pattern infrastructure documented in Doc/ObserverPattern.md.

    PropertyObserverObserverManagerPropertySubject/CNodeNodePropertyNodePropertyDatabaseApplication CodePropertyObserverObserverManagerPropertySubject/CNodeNodePropertyNodePropertyDatabaseApplication Codealt[Not in batch mode][In batch mode]SetNumeric("MaxDev", 0.5, "mm")Find or create propertyUpdate valueReturn old valueNotifyPropertyChange("MaxDev", old, new)Dispatch(PropertyChange)OnChange(PropertyChange)Update UI/ViewQueue change for batch notification

    PropertyChange Event Structure

    The PropertyChange event (defined in ChangeTypes.h) carries all information needed for observers:

    struct PropertyChange
    {
        PropertyChangeType type;      // ValueChanged, PropertyAdded, PropertyRemoved, etc.
        NODE_UUID          nodeId;    // Which node's property changed
        std::string        propertyName;
        std::string        propertyPath;  // For nested: "Settings.Display.Color"
        std::any           oldValue;
        std::any           newValue;
        std::string        propertyType;  // For UI type hints
        bool               isBatchUpdate = false;
    };
    

    Observer Categories

    Properties integrate with the existing category system:

    Batch Updates

    For bulk property changes (e.g., loading from XML or recalculation), batch mode prevents notification storms:

    // Batch update example - single notification at end
    node->GetPropertyDB().BeginBatch();
    node->SetFeature("MaxAngle", "deg", maxAngle);
    node->SetFeature("Overshoot", "mm", overshoot);
    node->SetFeature("MaxVelocity", "mm/s", maxVel);
    node->SetFeature("MaxAcceleration", "mm/s2", maxAcc);
    node->GetPropertyDB().EndBatch();  // Single batched PropertyChange notification sent here
    

    The batch notification uses the existing OnBatchChanges() mechanism from the Observer base class, allowing observers to decide between incremental updates or full rebuild.

    9.8 Typed Access Methods

    The database provides strongly-typed access with compile-time type checking:

    // Type-safe numeric access - always returns base unit value
    double maxDev = propDB.GetNumeric("MaxDeviation", 0.0);  // Returns in base unit (mm)
    
    // Type-safe with category filtering
    auto features = propDB.GetByCategory(PropertyCategory::Feature);
    for (auto* prop : features) {
        if (prop->HasUnit()) {
            // UI converts from base unit to display unit using CUnits
            double displayValue = GetUnits()->Convert(prop->GetNumeric(), prop->GetBaseUnit());
        }
    }
    
    // User-defined property access
    auto userProps = propDB.GetUserDefined();
    

    9.9 XML Serialization

    New Optimized Format (Writing)

    When writing, properties are grouped by category for efficient parsing:

    <Node name="DoorSlamCalc" uuid="12345">
        <Properties version="2">
            <!-- Features grouped together -->
            <Features>
                <F key="MaxAngle" unit="deg" value="145.3"/>
                <F key="Overshoot" unit="mm" value="2.1"/>
                <F key="MaxVelocity" unit="mm/s" value="1250.0"/>
            </Features>
    
            <!-- Settings grouped together -->
            <Settings>
                <S key="TrimAngle" type="double" value="5.0"/>
                <S key="FilterEnabled" type="bool" value="true"/>
            </Settings>
    
            <!-- External files - paths stored as relative -->
            <Files>
                <File key="TrajectoryFile" path="data/nominal.igs"/>
                <File key="CADModel" path="models/door.step"/>
            </Files>
    
            <!-- Colors stored as hex -->
            <Colors>
                <C key="DisplayColor" value="#FF5733"/>
            </Colors>
    
            <!-- User-defined properties -->
            <UserDefined>
                <U key="CustomerNote" type="text" value="Approved by QA" visible="true"/>
                <U key="TestPriority" type="int" value="1" readonly="false"/>
            </UserDefined>
        </Properties>
    </Node>
    

    Legacy Format Reading (Backwards Compatibility)

    The system transparently reads legacy formats:

    <!-- Legacy CFeature format (version 1) -->
    <Node features="MaxAngle=deg:145.3;Overshoot=mm:2.1"/>
    
    <!-- Legacy CPropertyBase format -->
    <Node>
        <PROPERTY name="TrimAngle" value="5.0"/>
    </Node>
    
    <!-- Mixed legacy format -->
    <Node features="Radius=mm:50.0" filepath="models/part.step" color="255,128,0">
        <PROPERTY name="Enabled" value="true"/>
    </Node>
    

    The ReadXML() method detects the format version and dispatches to appropriate parsers:

    bool NodePropertyDatabase::ReadXML(TiXmlElement* pXML, const CString& projectRoot)
    {
        // Check for new format
        TiXmlElement* pProperties = pXML->FirstChildElement("Properties");
        if (pProperties)
        {
            int version = 1;
            pProperties->Attribute("version", &version);
            if (version >= 2)
                return ReadXML_V2(pProperties, projectRoot);
        }
    
        // Fall back to legacy format reading
        CString featuresAttr;
        if (GetAttribute(pXML, "features", featuresAttr))
            ReadLegacyFeatures(featuresAttr);
    
        // Read legacy PROPERTY elements
        for (auto* pProp = pXML->FirstChildElement("PROPERTY"); pProp;
             pProp = pProp->NextSiblingElement("PROPERTY"))
        {
            ReadLegacyProperty(pProp);
        }
    
        return true;
    }
    

    9.10 Property Flags Detail

    FlagDescriptionDefaultPersisted
    VisibleShow in UI (property window, grids)trueYes
    ReadOnlyCannot be modified by userfalseYes
    UserDefinedCreated by user, not systemfalseYes
    PersistentSave to XMLtrueN/A
    ShowInPropertyWindowDisplay in BCG property windowtrueNo
    ShowInFeatureGridDisplay in feature overview gridCategory-dependentNo
    ExportableInclude in CSV/Excel exportstrueNo

    9.11 Unit Handling

    Important Design Decision: Properties store base units only (e.g., mm, deg, mm/s). The UI layer is responsible for:

    1. Retrieving the user's preferred display unit from CUnits
    2. Converting values for display using GetUnits()->GetConversionFactor()
    3. Converting user input back to base units before storing

    This matches the existing CFeature behavior where GetValue(true) returns base unit and GetValue(false) applies conversion.

    Base units are stored as strings matching the existing unit constants:

    Unit TypeBase UnitExample Display Units
    Lengthmminch, m, ft
    Angledegrad, arcmin
    Velocitymm/sm/s, inch/s
    Accelerationmm/s2m/s², g
    Timesms, min
    TemperatureCF, K

    9.12 User-Defined Properties

    Users can add custom metadata to any node. These properties:

    • Are flagged with PropertySource::User and UserDefined = true
    • Are always persisted to XML
    • Can be marked visible/hidden and readonly/editable
    • Support all value types

    9.13 Complete Class Hierarchy

    10. API Examples

    10.1 Feature Operations (Replacing CFeature)

    // Old CFeature way
    SetFeature("MaxAngle", CFeature(UNIT_ANGLE, maxAngle));
    double val = GetFeatureValue("MaxAngle", true);  // base unit
    
    // New NodeProperty way - simple form
    GetPropertyDB().SetNumeric("MaxAngle", maxAngle, UNIT_ANGLE);
    double val = GetPropertyDB().GetNumeric("MaxAngle", 0.0);
    
    // New NodeProperty way - with full control
    NodeProperty prop;
    prop.SetNumeric(maxAngle, UNIT_ANGLE);
    prop.SetCategory(PropertyCategory::Feature);
    prop.SetSource(PropertySource::Calculation);
    prop.SetDisplayName("Maximum Opening Angle");
    prop.SetDescription("The maximum angle reached during door slam");
    prop.SetGroup("Results");
    prop.SetVisible(true);
    prop.SetReadOnly(true);  // Calculation results should not be user-editable
    GetPropertyDB().Add("MaxAngle", prop);
    

    10.2 User-Defined Properties

    // User adds custom metadata through UI or API
    node->AddUserProperty("CustomerID", TextValue("ACME-001"));
    node->AddUserProperty("TestPriority", IntValue(1));
    node->AddUserProperty("ApprovedBy", TextValue("John Doe"));
    node->AddUserProperty("ApprovalDate", TextValue("2024-01-15"));
    
    // Make some properties read-only after setting
    node->GetProperty("ApprovedBy")->SetReadOnly(true);
    
    // Query user properties for display in custom property grid
    for (auto* prop : node->GetPropertyDB().GetUserDefined()) {
        if (prop->IsVisible()) {
            AddToUserPropertyGrid(prop);
        }
    }
    

    10.3 File Path Properties

    // Set external file reference
    GetPropertyDB().SetFilePath("CADModel", "C:/Projects/CTrack/models/assembly.step");
    
    // On XML save - automatically converts to relative path based on project root
    // <File key="CADModel" path="models/assembly.step"/>
    
    // On XML load - automatically converts to absolute path
    // Uses projectRoot parameter passed to ReadXML()
    
    // Check if file has been modified externally
    auto* prop = GetPropertyDB().Get("CADModel");
    if (prop && prop->IsFileModified()) {
        // Prompt user to reload CAD model
        ReloadCADModel(prop->GetFilePath());
        prop->UpdateFileTimestamp();
    }
    

    10.4 Iteration and Filtering

    // Iterate all properties in insertion order
    for (auto& [key, prop] : node->GetPropertyDB()) {
        if (prop.IsVisible() && prop.ShowInPropertyWindow()) {
            AddToPropertyWindow(prop);
        }
    }
    
    // Get only exportable features for CSV export
    auto features = node->GetPropertyDB().GetByCategory(PropertyCategory::Feature);
    for (auto* prop : features) {
        if (prop->IsExportable()) {
            ExportToCSV(prop->GetKey(), prop->GetNumeric(), prop->GetBaseUnit());
        }
    }
    
    // Get all visible properties for property window
    auto visible = node->GetPropertyDB().GetVisible();
    
    // Get settings that can be edited
    auto settings = node->GetPropertyDB().GetByCategory(PropertyCategory::Setting);
    for (auto* prop : settings) {
        if (!prop->IsReadOnly()) {
            AddEditableProperty(prop);
        }
    }
    

    10.5 Observer Integration Example

    class FeatureGridObserver : public CategoryObserver<ChangeCategory::Property>
    {
    public:
        void OnChange(const ChangeEvent& change) override
        {
            const PropertyChange* propChange = GetChangeAs<PropertyChange>(change);
            if (!propChange) return;
    
            switch (propChange->type)
            {
                case PropertyChangeType::ValueChanged:
                    // Find cell in grid and update value
                    UpdateCell(propChange->nodeId, propChange->propertyName, propChange->newValue);
                    break;
    
                case PropertyChangeType::PropertyAdded:
                    // Add new row to grid
                    AddRow(propChange->nodeId, propChange->propertyName);
                    break;
    
                case PropertyChangeType::PropertyRemoved:
                    // Remove row from grid
                    RemoveRow(propChange->nodeId, propChange->propertyName);
                    break;
    
                case PropertyChangeType::AllPropertiesChanged:
                    // Full refresh needed
                    RefreshNode(propChange->nodeId);
                    break;
            }
        }
    
        void RedrawView() override
        {
            m_grid.Invalidate();
        }
    
        void RebuildView() override
        {
            m_grid.Clear();
            LoadAllFeatures();
        }
    
        bool AcceptsChange(const ChangeEvent& change) const override
        {
            // Only accept property changes for Feature category
            const PropertyChange* propChange = std::get_if<PropertyChange>(&change);
            if (propChange)
            {
                // Could filter by property category here
                return true;
            }
            return false;
        }
    };
    

    10.6 Batch Processing Example

    void CCalcDoorslam::PerformCalculation(CTaskScheduler* pTaskManager)
    {
        // ... calculation logic ...
    
        // Batch all feature updates - single notification at end
        GetPropertyDB().BeginBatch();
    
        SetFeature("MaxAngle", UNIT_ANGLE, MaxOpenAngle);
        SetFeature("Angle1", UNIT_ANGLE, Angle1);
        SetFeature("Angle2", UNIT_ANGLE, Angle2);
        SetFeature("Overshoot", UNIT_MM, Overshoot);
        SetFeature("OvershootAngle", UNIT_ANGLE, OvershootAngle);
        SetFeature("OvershootWorldX", UNIT_MM, OvershootVectorWorld.r(1, 1));
        SetFeature("OvershootWorldY", UNIT_MM, OvershootVectorWorld.r(2, 1));
        SetFeature("OvershootWorldZ", UNIT_MM, OvershootVectorWorld.r(3, 1));
        SetFeature("Doordrop", UNIT_MM, Doordrop);
        SetFeature("MaxVelocity", UNIT_POS_VEL, MaxVelocity);
        SetFeature("MaxAcceleration", UNIT_POS_ACC, MaxAcceleration);
    
        GetPropertyDB().EndBatch();
        // Single PropertyChange event with isBatchUpdate=true sent here
        // Observers can choose to RebuildView() or process individually
    }
    

    11. Migration Strategy

    Phase 1: Core Implementation

    Phase 2: XML Serialization

    Phase 3: Observer Integration

    Phase 4: Gradual Migration

    ComponentCurrent SystemMigration Approach
    CCalcDoorslamSetFeature() callsRedirect to GetPropertyDB().SetNumeric()
    CAlign* geometrySetFeature() for dimensionsSame
    C3DOCCm_Color, m_CADFile membersMove to property database
    Property windowsCPropertyBase populationPopulate from NodePropertyDatabase
    Feature gridCFeatureOverviewQuery GetByCategory(Feature)

    Phase 5: Deprecation Path

    1. Mark CFeature methods as [[deprecated]]
    2. Mark CPropertyBase methods as [[deprecated]]
    3. Provide compile-time warnings with migration hints
    4. Remove in future major version (V6.0)

    12. Summary

    The unified NodeProperty system provides:

    CapabilityImplementation
    Mini-database per nodeNodePropertyDatabase with keyed access
    Type safetyPropertyValue variant with typed accessors
    Observable changesIntegration with PropertySubject and ObserverManager
    Backwards compatibilityLegacy XML format readers for CFeature and CPropertyBase
    Efficient storageGrouped XML output format (version 2)
    User extensibilityUserDefined flag and PropertySource::User
    Visibility controlPropertyFlags bitfield
    Unit awarenessBase unit storage only; UI handles conversion via CUnits
    File trackingFilePathValue with relative/absolute conversion
    Color supportColorValue with RGBA, source selection (Own/FromFile/HeatMap/Master), and inheritance
    Batch operationsBeginBatch()/EndBatch() for bulk updates

    This design unifies CFeature, CPropertyBase, ObservableProperty, and scattered member variables into a single, consistent, observable metadata system that serves as each node's personal database while maintaining full backwards compatibility with existing project files.

    in Daily blog
    # CTrack
    CTrack Robotics
    Droits d'auteur © CTech Solutions
    Nederlands (BE) | English (UK) | Français
    Généré par Odoo - Le #1 Open Source eCommerce