Fold Feature Sample

Description

Demonstrates creating a new sheet metal fold feature.

Code Samples

"""Fold Feature API sample — exercises every public interface on
FoldFeatures, FoldFeatureInput, FoldBendLineDefinitions,
FoldBendLineDefinition (temporary and persistent), and FoldFeature.

The document must contain:
  - A sheet metal body at bRepBodies.item(0)
  - A flat face suitable as the stationary face (faces.item(0) by default)
  - A sketch at sketches.item(0) with at least two sketch lines

Adjust STATIONARY_FACE_IDX, LINE0_IDX, and LINE1_IDX to match your document.
"""

import traceback
import math
import adsk.core
import adsk.fusion

app = adsk.core.Application.get()
ui = app.userInterface

TOLERANCE = 1.0e-6
BEND_ANGLE_DEG = 90.0
BEND_ANGLE_RAD = math.radians(BEND_ANGLE_DEG)

STATIONARY_FACE_IDX = 9
LINE0_IDX = 0
LINE1_IDX = 1


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------

def backToNow(design):
    design.timeline.moveToEnd()


def clearFoldFeatures(features):
    """Delete all fold features so edge/face indices are stable."""
    ff = features.foldFeatures
    while ff.count > 0:
        ff.item(0).deleteMe()
        ff = features.foldFeatures
    assert ff.count == 0
    return ff


# ---------------------------------------------------------------------------
# FoldFeatureInput properties (temporary state)
# ---------------------------------------------------------------------------

def testFoldFeatureInputProperties(foldFeatures, face, line0):
    """Tests all FoldFeatureInput and temporary FoldBendLineDefinition APIs."""

    inp = foldFeatures.createInput(face)
    assert inp is not None, "createInput returned None"

    # stationaryFace getter
    assert inp.stationaryFace is not None, "stationaryFace getter returned None"

    # isUseCornerRelief get/set
    inp.isUseCornerRelief = True
    assert inp.isUseCornerRelief is True
    inp.isUseCornerRelief = False
    assert inp.isUseCornerRelief is False

    # bendLines collection
    bendLines = inp.bendLines
    assert bendLines is not None

    # Add a bend line definition.
    angleVal = adsk.core.ValueInput.createByReal(BEND_ANGLE_RAD)
    def0 = bendLines.add(
        line0,
        angleVal,
        adsk.fusion.FoldBendLinePositionTypes.StartFoldBendLinePositionType,
        True)
    assert def0 is not None, "bendLines.add returned None"

    # isTemporary must be True for temporary definitions.
    assert def0.isTemporary is True

    # bendLine getter
    assert def0.bendLine is not None

    # bendAngle must be None for temporary definitions.
    assert def0.bendAngle is None, "bendAngle should be None on temporary definition"

    # bendAngleValue get/set — available on temporary definitions.
    bav = def0.bendAngleValue
    assert bav is not None, "bendAngleValue should not be None on temporary definition"
    newAngle = adsk.core.ValueInput.createByReal(math.radians(45.0))
    def0.bendAngleValue = newAngle
    assert def0.bendAngleValue is not None

    # Restore to 90 degrees.
    def0.bendAngleValue = adsk.core.ValueInput.createByReal(BEND_ANGLE_RAD)

    # linePosition get/set — exercise all three enum values.
    for pos in [
        adsk.fusion.FoldBendLinePositionTypes.StartFoldBendLinePositionType,
        adsk.fusion.FoldBendLinePositionTypes.CenterFoldBendLinePositionType,
        adsk.fusion.FoldBendLinePositionTypes.EndFoldBendLinePositionType,
    ]:
        def0.linePosition = pos
        assert def0.linePosition == pos, f"linePosition mismatch for {pos}"

    # Reset to Start.
    def0.linePosition = adsk.fusion.FoldBendLinePositionTypes.StartFoldBendLinePositionType

    # isBendReliefAllowed get/set.
    def0.isBendReliefAllowed = False
    assert def0.isBendReliefAllowed is False
    def0.isBendReliefAllowed = True
    assert def0.isBendReliefAllowed is True

    # count/item on the temporary collection.
    assert bendLines.count == 1
    assert bendLines.item(0) is not None

    # Identity before parameter change: repeated item() calls return the same object.
    assert bendLines.item(0) == bendLines.item(0), "Temporary definition identity failed"
    item_before_change = bendLines.item(0)
    assert item_before_change == def0

    # Change bend angle value and verify identity is preserved.
    def0.bendAngleValue = adsk.core.ValueInput.createByReal(1.047)  # ~60 deg
    assert bendLines.item(0) == bendLines.item(0), "Temporary identity broken after angle change"
    assert bendLines.item(0) == item_before_change, "Temporary identity changed after angle setter"

    # Change line position and verify identity is preserved.
    def0.linePosition = adsk.fusion.FoldBendLinePositionTypes.CenterFoldBendLinePositionType
    assert bendLines.item(0) == bendLines.item(0), "Temporary identity broken after linePosition change"
    assert bendLines.item(0) == item_before_change, "Temporary identity changed after linePosition setter"

    # Change isBendReliefAllowed and verify identity is preserved.
    def0.isBendReliefAllowed = False
    assert bendLines.item(0) == bendLines.item(0), "Temporary identity broken after isBendReliefAllowed change"
    assert bendLines.item(0) == item_before_change, "Temporary identity changed after isBendReliefAllowed setter"

    # Restore to original values.
    def0.bendAngleValue = adsk.core.ValueInput.createByReal(1.5708)  # 90 deg
    def0.linePosition = adsk.fusion.FoldBendLinePositionTypes.StartFoldBendLinePositionType
    def0.isBendReliefAllowed = True

    # deleteMe on a temporary bend line definition.
    assert def0.deleteMe(), "deleteMe on temporary definition failed"
    assert bendLines.count == 0


# ---------------------------------------------------------------------------
# Create a FoldFeature (two bend lines)
# ---------------------------------------------------------------------------

def testCreateFoldFeature(foldFeatures, face, line0, line1):
    """Creates a persistent FoldFeature and checks collection APIs."""

    inp = foldFeatures.createInput(face)
    assert inp is not None

    bendLines = inp.bendLines
    assert bendLines is not None

    angleVal = adsk.core.ValueInput.createByReal(BEND_ANGLE_RAD)

    bl0 = bendLines.add(
        line0,
        angleVal,
        adsk.fusion.FoldBendLinePositionTypes.StartFoldBendLinePositionType,
        True)
    assert bl0 is not None

    bl1 = bendLines.add(
        line1,
        adsk.core.ValueInput.createByReal(BEND_ANGLE_RAD),
        adsk.fusion.FoldBendLinePositionTypes.StartFoldBendLinePositionType,
        True)
    assert bl1 is not None

    feature = foldFeatures.add(inp)
    assert feature is not None, "foldFeatures.add returned None"

    featureName = feature.name
    assert featureName is not None and len(featureName) > 0

    # nativeObject is None in the root component context.
    assert feature.nativeObject is None

    # FoldFeatures collection.
    assert foldFeatures.count >= 1
    assert foldFeatures.item(0) is not None
    found = foldFeatures.itemByName(featureName)
    assert found is not None, f"itemByName('{featureName}') returned None"

    return feature


# ---------------------------------------------------------------------------
# Persistent FoldFeature and FoldBendLineDefinition properties
# ---------------------------------------------------------------------------

def testPersistentFeatureProperties(feature, foldFeatures, design):
    """Exercises persistent FoldFeature and FoldBendLineDefinition APIs."""

    # stationaryFace getter.
    assert feature.stationaryFace is not None

    # bendLines on the persistent feature.
    persistentBendLines = feature.bendLines
    assert persistentBendLines is not None
    assert persistentBendLines.count == 2

    # Identity: repeated item() calls on the same index must return the same object.
    assert persistentBendLines.item(0) == persistentBendLines.item(0), "Persistent definition identity failed (index 0)"
    assert persistentBendLines.item(1) == persistentBendLines.item(1), "Persistent definition identity failed (index 1)"
    assert persistentBendLines.item(0) != persistentBendLines.item(1), "Distinct indices must not be equal"

    # Roll timeline to just before the feature for editing.
    tl = feature.timelineObject
    assert tl is not None
    tl.rollTo(True)

    # isUseCornerRelief setter.
    feature.isUseCornerRelief = True
    assert feature.isUseCornerRelief is True
    feature.isUseCornerRelief = False
    assert feature.isUseCornerRelief is False

    # stationaryFace setter (set to the same face to confirm the path works).
    origFace = feature.stationaryFace
    feature.stationaryFace = origFace
    assert feature.stationaryFace is not None

    # Persistent FoldBendLineDefinition.
    pDef = persistentBendLines.item(0)
    assert pDef is not None

    # isTemporary must be False for persistent definitions.
    assert pDef.isTemporary is False

    # bendAngle (ModelParameter) — available on persistent definitions.
    bendAngleParam = pDef.bendAngle
    assert bendAngleParam is not None, "bendAngle should not be None on persistent definition"
    assert not math.isnan(bendAngleParam.value)

    # bendAngleValue must be None for persistent definitions.
    assert pDef.bendAngleValue is None, "bendAngleValue should be None on persistent definition"

    # bendLine getter.
    assert pDef.bendLine is not None

    # Capture once; identity must hold before and after every parameter change.
    def_item = persistentBendLines.item(0)
    assert def_item == persistentBendLines.item(0), "Persistent identity failed before change"
    assert persistentBendLines.item(0) == persistentBendLines.item(0), "Persistent identity failed before change"

    # Change bend angle via the ModelParameter and verify identity is preserved.
    bend_angle_param = pDef.bendAngle
    assert bend_angle_param is not None
    bend_angle_param.value = 1.047  # ~60 deg
    assert def_item == persistentBendLines.item(0), "Persistent identity failed after angle change (60 deg)"

    bend_angle_param.value = 1.5708  # restore to 90 deg
    assert def_item == persistentBendLines.item(0), "Persistent identity failed after angle restore (90 deg)"

    # linePosition get/set — verify identity after each change.
    for pos in [
        adsk.fusion.FoldBendLinePositionTypes.CenterFoldBendLinePositionType,
        adsk.fusion.FoldBendLinePositionTypes.StartFoldBendLinePositionType,
        adsk.fusion.FoldBendLinePositionTypes.EndFoldBendLinePositionType,
    ]:
        pDef.linePosition = pos
        assert pDef.linePosition == pos, f"persistent linePosition mismatch for {pos}"
        assert def_item == persistentBendLines.item(0), f"Persistent identity failed after linePosition change to {pos}"

    # Reset to Start.
    pDef.linePosition = adsk.fusion.FoldBendLinePositionTypes.StartFoldBendLinePositionType

    # isBendReliefAllowed get/set — verify identity after each toggle.
    pDef.isBendReliefAllowed = False
    assert pDef.isBendReliefAllowed is False
    assert def_item == persistentBendLines.item(0), "Persistent identity failed after isBendReliefAllowed=False"

    pDef.isBendReliefAllowed = True
    assert pDef.isBendReliefAllowed is True
    assert def_item == persistentBendLines.item(0), "Persistent identity failed after isBendReliefAllowed=True"

    backToNow(design)

    # Verify changes persisted after rolling forward.
    assert pDef.isTemporary is False
    assert pDef.linePosition == adsk.fusion.FoldBendLinePositionTypes.StartFoldBendLinePositionType
    assert pDef.isBendReliefAllowed is True


# ---------------------------------------------------------------------------
# Add and delete a bend line on a persistent feature
# ---------------------------------------------------------------------------

def testBendLineAddAndDelete(feature, line1, design):
    """Adds a bend line to a persistent feature via bendLines.add, then deletes it."""

    tl = feature.timelineObject
    assert tl is not None
    tl.rollTo(True)

    persistentBendLines = feature.bendLines
    countBefore = persistentBendLines.count

    newDef = persistentBendLines.add(
        line1,
        adsk.core.ValueInput.createByReal(BEND_ANGLE_RAD),
        adsk.fusion.FoldBendLinePositionTypes.StartFoldBendLinePositionType,
        True)
    assert newDef is not None, "bendLines.add on persistent feature returned None"

    persistentBendLines = feature.bendLines
    assert persistentBendLines.count == countBefore + 1

    # Delete the newly added bend line.
    lastDef = persistentBendLines.item(persistentBendLines.count - 1)
    assert lastDef is not None
    assert lastDef.deleteMe(), "deleteMe on persistent bend line definition failed"

    persistentBendLines = feature.bendLines
    assert persistentBendLines.count == countBefore

    backToNow(design)


# ---------------------------------------------------------------------------
# Collection methods and feature deletion
# ---------------------------------------------------------------------------

def testCollectionAndDeletion(features, featureName):
    """Verifies FoldFeatures collection APIs and feature deletion."""

    foldFeatures = features.foldFeatures
    initialCount = foldFeatures.count
    assert initialCount >= 1

    # Iterate the collection.
    for i in range(initialCount):
        feat = foldFeatures.item(i)
        assert feat is not None

    # itemByName for a name that doesn't exist.
    assert foldFeatures.itemByName("NonExistentFoldFeature_XYZ") is None

    # Delete the feature identified by name.
    feat = foldFeatures.itemByName(featureName)
    assert feat is not None
    assert feat.deleteMe(), "feature.deleteMe() failed"

    foldFeatures = features.foldFeatures
    assert foldFeatures.count == initialCount - 1


# ---------------------------------------------------------------------------
# Entry point
# ---------------------------------------------------------------------------

def run(_context):
    """Entry point called by Fusion when the script is run."""
    try:
        design = adsk.fusion.Design.cast(app.activeProduct)
        rootComp = design.rootComponent

        # The body and sketch live in component1, the only child of the root component.
        occurrence = rootComp.occurrences.item(0)
        assert occurrence is not None, "No child occurrence found under root component"
        comp1 = occurrence.component
        assert comp1 is not None, "Could not get component from occurrence"

        features = comp1.features

        # Start clean.
        foldFeatures = clearFoldFeatures(features)

        body = comp1.bRepBodies.item(0)
        assert body is not None, "No body found; open a document with a sheet metal body"

        face = body.faces.item(STATIONARY_FACE_IDX)
        assert face is not None, f"No face at index {STATIONARY_FACE_IDX}"

        sketch = rootComp.sketches.item(0)
        assert sketch is not None, "No sketch found; open a document with a sketch"

        sketchLines = sketch.sketchCurves.sketchLines
        line0 = sketchLines.item(LINE0_IDX)
        line1 = sketchLines.item(LINE1_IDX)
        assert line0 is not None, f"No sketch line at index {LINE0_IDX}"
        assert line1 is not None, f"No sketch line at index {LINE1_IDX}"

        testFoldFeatureInputProperties(foldFeatures, face, line0)

        feature = testCreateFoldFeature(foldFeatures, face, line0, line1)
        featureName = feature.name

        testPersistentFeatureProperties(feature, foldFeatures, design)

        testBendLineAddAndDelete(feature, line1, design)

        testCollectionAndDeletion(features, featureName)

        app.log('FoldSample completed successfully.')

    except Exception:
        app.log(f'Failed:\n{traceback.format_exc()}')
// Fold Feature API sample — exercises every public interface on
// FoldFeatures, FoldFeatureInput, FoldBendLineDefinitions,
// FoldBendLineDefinition (temporary and persistent), and FoldFeature.
//
// The document must contain:
//   component1 (first child occurrence of the root component):
//     - a sheet metal body at bRepBodies.item(0)
//     - faces.item(STATIONARY_FACE_IDX) suitable as the stationary face
//   root component:
//     - a sketch at sketches.item(0) with at least two sketch lines
//
// Adjust STATIONARY_FACE_IDX, LINE0_IDX, and LINE1_IDX to match your document.

#include <Core/CoreAll.h>
#include <Fusion/FusionAll.h>

using namespace adsk::core;
using namespace adsk::fusion;

static const size_t STATIONARY_FACE_IDX = 9;
static const size_t LINE0_IDX = 0;
static const size_t LINE1_IDX = 1;
static const double BEND_ANGLE_RAD = 1.5707963267948966; // 90 degrees

Ptr<Application> app;

#define CHECK(expr)                                                                                                    \
    if (!(expr))                                                                                                       \
    {                                                                                                                  \
        app->log("FAIL: " #expr);                                                                                      \
        return false;                                                                                                  \
    }
#define CHECK_PTR(ptr) CHECK((ptr) != nullptr)

// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------

static bool backToNow(const Ptr<Design>& design)
{
    auto tl = design->timeline();
    CHECK_PTR(tl);
    CHECK(tl->moveToEnd());
    return true;
}

static bool clearFoldFeatures(const Ptr<Features>& features)
{
    auto foldFeatures = features->foldFeatures();
    CHECK_PTR(foldFeatures);
    while (foldFeatures->count() > 0)
    {
        auto feat = foldFeatures->item(0);
        CHECK_PTR(feat);
        CHECK(feat->deleteMe());
        foldFeatures = features->foldFeatures();
        CHECK_PTR(foldFeatures);
    }
    CHECK(foldFeatures->count() == 0);
    return true;
}

// ---------------------------------------------------------------------------
// FoldFeatureInput properties (temporary state)
// ---------------------------------------------------------------------------

static bool testFoldFeatureInputProperties(
    const Ptr<FoldFeatures>& foldFeatures, const Ptr<BRepFace>& face, const Ptr<SketchLine>& line0)
{
    auto inp = foldFeatures->createInput(face);
    CHECK_PTR(inp);

    // stationaryFace getter
    CHECK_PTR(inp->stationaryFace());

    // isUseCornerRelief get/set
    CHECK(inp->isUseCornerRelief(true));
    CHECK(inp->isUseCornerRelief());
    CHECK(inp->isUseCornerRelief(false));
    CHECK(!inp->isUseCornerRelief());

    // bendLines collection
    auto bendLines = inp->bendLines();
    CHECK_PTR(bendLines);

    // Add a bend line definition
    auto def0 = bendLines->add(
        line0,
        ValueInput::createByReal(BEND_ANGLE_RAD),
        FoldBendLinePositionTypes::StartFoldBendLinePositionType,
        true);
    CHECK_PTR(def0);

    // isTemporary must be true for temporary definitions
    CHECK(def0->isTemporary());

    // bendLine getter
    CHECK_PTR(def0->bendLine());

    // bendAngle must be null for temporary definitions
    CHECK(!def0->bendAngle());

    // bendAngleValue — available on temporary definitions
    CHECK_PTR(def0->bendAngleValue());

    // bendAngleValue setter — change to 45 deg, then restore to 90 deg
    CHECK(def0->bendAngleValue(ValueInput::createByReal(0.7853981633974483)));
    CHECK_PTR(def0->bendAngleValue());
    CHECK(def0->bendAngleValue(ValueInput::createByReal(BEND_ANGLE_RAD)));

    // linePosition get/set — exercise all three enum values
    CHECK(def0->linePosition(FoldBendLinePositionTypes::StartFoldBendLinePositionType));
    CHECK(def0->linePosition() == FoldBendLinePositionTypes::StartFoldBendLinePositionType);
    CHECK(def0->linePosition(FoldBendLinePositionTypes::CenterFoldBendLinePositionType));
    CHECK(def0->linePosition() == FoldBendLinePositionTypes::CenterFoldBendLinePositionType);
    CHECK(def0->linePosition(FoldBendLinePositionTypes::EndFoldBendLinePositionType));
    CHECK(def0->linePosition() == FoldBendLinePositionTypes::EndFoldBendLinePositionType);
    // Reset to Start
    CHECK(def0->linePosition(FoldBendLinePositionTypes::StartFoldBendLinePositionType));

    // isBendReliefAllowed get/set
    CHECK(def0->isBendReliefAllowed(false));
    CHECK(!def0->isBendReliefAllowed());
    CHECK(def0->isBendReliefAllowed(true));
    CHECK(def0->isBendReliefAllowed());

    // count/item on the temporary collection
    CHECK(bendLines->count() == 1);
    CHECK_PTR(bendLines->item(0));

    // Identity before parameter change: repeated item() calls return the same object
    CHECK(bendLines->item(0) == bendLines->item(0));
    auto itemBeforeChange = bendLines->item(0);
    CHECK(itemBeforeChange == def0);

    // Change bend angle value and verify identity is preserved
    CHECK(def0->bendAngleValue(ValueInput::createByReal(1.047))); // ~60 deg
    CHECK(bendLines->item(0) == bendLines->item(0));
    CHECK(bendLines->item(0) == itemBeforeChange);

    // Change line position and verify identity is preserved
    CHECK(def0->linePosition(FoldBendLinePositionTypes::CenterFoldBendLinePositionType));
    CHECK(bendLines->item(0) == bendLines->item(0));
    CHECK(bendLines->item(0) == itemBeforeChange);

    // Change isBendReliefAllowed and verify identity is preserved
    CHECK(def0->isBendReliefAllowed(false));
    CHECK(bendLines->item(0) == bendLines->item(0));
    CHECK(bendLines->item(0) == itemBeforeChange);

    // Restore to original values
    CHECK(def0->bendAngleValue(ValueInput::createByReal(1.5708))); // 90 deg
    CHECK(def0->linePosition(FoldBendLinePositionTypes::StartFoldBendLinePositionType));
    CHECK(def0->isBendReliefAllowed(true));

    // deleteMe on a temporary bend line definition
    CHECK(def0->deleteMe());
    CHECK(bendLines->count() == 0);

    return true;
}

// ---------------------------------------------------------------------------
// Create a FoldFeature (two bend lines)
// ---------------------------------------------------------------------------

static bool testCreateFoldFeature(
    const Ptr<FoldFeatures>& foldFeatures,
    const Ptr<BRepFace>& face,
    const Ptr<SketchLine>& line0,
    const Ptr<SketchLine>& line1,
    Ptr<FoldFeature>& outFeature)
{
    auto inp = foldFeatures->createInput(face);
    CHECK_PTR(inp);

    auto bendLines = inp->bendLines();
    CHECK_PTR(bendLines);

    auto bl0 = bendLines->add(
        line0,
        ValueInput::createByReal(BEND_ANGLE_RAD),
        FoldBendLinePositionTypes::StartFoldBendLinePositionType,
        true);
    CHECK_PTR(bl0);

    auto bl1 = bendLines->add(
        line1,
        ValueInput::createByReal(BEND_ANGLE_RAD),
        FoldBendLinePositionTypes::StartFoldBendLinePositionType,
        true);
    CHECK_PTR(bl1);

    auto feature = foldFeatures->add(inp);
    CHECK_PTR(feature);

    auto featureName = feature->name();
    CHECK(!featureName.empty());

    // nativeObject is null in the root component context
    CHECK(!feature->nativeObject());

    // FoldFeatures collection
    CHECK(foldFeatures->count() >= 1);
    CHECK_PTR(foldFeatures->item(0));
    CHECK_PTR(foldFeatures->itemByName(featureName));

    outFeature = feature;
    return true;
}

// ---------------------------------------------------------------------------
// Persistent FoldFeature and FoldBendLineDefinition properties
// ---------------------------------------------------------------------------

static bool testPersistentFeatureProperties(
    const Ptr<FoldFeature>& feature, const Ptr<BRepFace>& stationaryFace, const Ptr<Design>& design)
{
    // stationaryFace getter
    CHECK_PTR(feature->stationaryFace());

    // bendLines on the persistent feature
    auto persistentBendLines = feature->bendLines();
    CHECK_PTR(persistentBendLines);
    CHECK(persistentBendLines->count() == 2);

    // Identity: repeated item() calls on the same index must return the same object
    CHECK(persistentBendLines->item(0) == persistentBendLines->item(0));
    CHECK(persistentBendLines->item(1) == persistentBendLines->item(1));
    CHECK(persistentBendLines->item(0) != persistentBendLines->item(1));

    // Roll timeline to just before the feature for editing
    auto tl = feature->timelineObject();
    CHECK_PTR(tl);
    CHECK(tl->rollTo(true));

    // isUseCornerRelief setter
    CHECK(feature->isUseCornerRelief(true));
    CHECK(feature->isUseCornerRelief());
    CHECK(feature->isUseCornerRelief(false));
    CHECK(!feature->isUseCornerRelief());

    // stationaryFace setter (set to the same face to confirm the path works)
    CHECK(feature->stationaryFace(stationaryFace));
    CHECK_PTR(feature->stationaryFace());

    // Persistent FoldBendLineDefinition
    auto apiDef = persistentBendLines->item(0);
    CHECK_PTR(apiDef);
    auto persistentDef = apiDef->cast<FoldBendLineDefinition>();
    CHECK_PTR(persistentDef);

    // isTemporary must be false for persistent definitions
    CHECK(!persistentDef->isTemporary());

    // bendAngle (ModelParameter) — available on persistent definitions
    CHECK_PTR(persistentDef->bendAngle());

    // bendAngleValue must be null for persistent definitions
    CHECK(!persistentDef->bendAngleValue());

    // bendLine getter
    CHECK_PTR(persistentDef->bendLine());

    // Capture once; identity must hold before and after every parameter change
    auto defItem = persistentBendLines->item(0);
    CHECK(defItem == persistentBendLines->item(0));
    CHECK(persistentBendLines->item(0) == persistentBendLines->item(0));

    // Change bend angle via the ModelParameter and verify identity is preserved
    auto bendAngleParam = persistentDef->bendAngle();
    CHECK_PTR(bendAngleParam);
    CHECK(bendAngleParam->value(1.047)); // ~60 deg
    CHECK(defItem == persistentBendLines->item(0));

    CHECK(bendAngleParam->value(1.5708)); // restore to 90 deg
    CHECK(defItem == persistentBendLines->item(0));

    // linePosition get/set — exercise all three enum values
    CHECK(persistentDef->linePosition(FoldBendLinePositionTypes::CenterFoldBendLinePositionType));
    CHECK(persistentDef->linePosition() == FoldBendLinePositionTypes::CenterFoldBendLinePositionType);
    CHECK(defItem == persistentBendLines->item(0));

    CHECK(persistentDef->linePosition(FoldBendLinePositionTypes::StartFoldBendLinePositionType));
    CHECK(persistentDef->linePosition() == FoldBendLinePositionTypes::StartFoldBendLinePositionType);
    CHECK(defItem == persistentBendLines->item(0));

    CHECK(persistentDef->linePosition(FoldBendLinePositionTypes::EndFoldBendLinePositionType));
    CHECK(persistentDef->linePosition() == FoldBendLinePositionTypes::EndFoldBendLinePositionType);
    CHECK(defItem == persistentBendLines->item(0));

    // Reset to Start
    CHECK(persistentDef->linePosition(FoldBendLinePositionTypes::StartFoldBendLinePositionType));

    // isBendReliefAllowed get/set — verify identity after each toggle
    CHECK(persistentDef->isBendReliefAllowed(false));
    CHECK(!persistentDef->isBendReliefAllowed());
    CHECK(defItem == persistentBendLines->item(0));

    CHECK(persistentDef->isBendReliefAllowed(true));
    CHECK(persistentDef->isBendReliefAllowed());
    CHECK(defItem == persistentBendLines->item(0));

    // Roll forward to the end, then verify changes persisted
    CHECK(backToNow(design));
    CHECK(!persistentDef->isTemporary());
    CHECK(persistentDef->linePosition() == FoldBendLinePositionTypes::StartFoldBendLinePositionType);
    CHECK(persistentDef->isBendReliefAllowed());

    return true;
}

// ---------------------------------------------------------------------------
// Add and delete a bend line on a persistent feature
// ---------------------------------------------------------------------------

static bool testBendLineAddAndDelete(
    const Ptr<FoldFeature>& feature, const Ptr<SketchLine>& line1, const Ptr<Design>& design)
{
    auto tl = feature->timelineObject();
    CHECK_PTR(tl);
    CHECK(tl->rollTo(true));

    auto persistentBendLines = feature->bendLines();
    CHECK_PTR(persistentBendLines);
    auto countBefore = persistentBendLines->count();

    auto newDef = persistentBendLines->add(
        line1,
        ValueInput::createByReal(BEND_ANGLE_RAD),
        FoldBendLinePositionTypes::StartFoldBendLinePositionType,
        true);
    CHECK_PTR(newDef);

    persistentBendLines = feature->bendLines();
    CHECK_PTR(persistentBendLines);
    CHECK(persistentBendLines->count() == countBefore + 1);

    // Delete the newly added bend line
    auto lastDef = persistentBendLines->item(persistentBendLines->count() - 1);
    CHECK_PTR(lastDef);
    CHECK(lastDef->deleteMe());

    persistentBendLines = feature->bendLines();
    CHECK_PTR(persistentBendLines);
    CHECK(persistentBendLines->count() == countBefore);

    CHECK(backToNow(design));
    return true;
}

// ---------------------------------------------------------------------------
// Collection methods and feature deletion
// ---------------------------------------------------------------------------

static bool testCollectionAndDeletion(const Ptr<Features>& features, const std::string& featureName)
{
    auto foldFeatures = features->foldFeatures();
    CHECK_PTR(foldFeatures);
    auto initialCount = foldFeatures->count();
    CHECK(initialCount >= 1);

    // Iterate the collection
    for (size_t i = 0; i < initialCount; ++i)
        CHECK_PTR(foldFeatures->item(i));

    // itemByName for a name that does not exist
    CHECK(!foldFeatures->itemByName("NonExistentFoldFeature_XYZ"));

    // Delete the feature identified by name
    auto feat = foldFeatures->itemByName(featureName);
    CHECK_PTR(feat);
    CHECK(feat->deleteMe());

    foldFeatures = features->foldFeatures();
    CHECK_PTR(foldFeatures);
    CHECK(foldFeatures->count() == initialCount - 1);

    return true;
}

// ---------------------------------------------------------------------------
// Entry point
// ---------------------------------------------------------------------------

extern "C" XI_EXPORT bool run(const char* context)
{
    app = Application::get();
    if (!app)
        return false;

    auto product = app->activeProduct();
    CHECK_PTR(product);
    auto design = product->cast<Design>();
    CHECK_PTR(design);
    auto rootComp = design->rootComponent();
    CHECK_PTR(rootComp);

    // The body and sketch live in component1, the first child occurrence of root.
    auto occurrences = rootComp->occurrences();
    CHECK_PTR(occurrences);
    auto occurrence = occurrences->item(0);
    CHECK_PTR(occurrence);
    auto comp1 = occurrence->component();
    CHECK_PTR(comp1);

    // Sheet metal body is in comp1.
    auto bodies = comp1->bRepBodies();
    CHECK_PTR(bodies);
    auto body = bodies->item(0);
    CHECK_PTR(body);

    auto faces = body->faces();
    CHECK_PTR(faces);
    auto stationaryFace = faces->item(STATIONARY_FACE_IDX);
    CHECK_PTR(stationaryFace);

    // Sketch is in the root component.
    auto sketches = rootComp->sketches();
    CHECK_PTR(sketches);
    auto sketch = sketches->item(0);
    CHECK_PTR(sketch);

    auto sketchLines = sketch->sketchCurves()->sketchLines();
    CHECK_PTR(sketchLines);
    auto line0 = sketchLines->item(LINE0_IDX);
    auto line1 = sketchLines->item(LINE1_IDX);
    CHECK_PTR(line0);
    CHECK_PTR(line1);

    auto features = comp1->features();
    CHECK_PTR(features);

    // Start clean — delete any existing fold features.
    CHECK(clearFoldFeatures(features));

    auto foldFeatures = features->foldFeatures();
    CHECK_PTR(foldFeatures);

    CHECK(testFoldFeatureInputProperties(foldFeatures, stationaryFace, line0));

    Ptr<FoldFeature> feature;
    CHECK(testCreateFoldFeature(foldFeatures, stationaryFace, line0, line1, feature));
    auto featureName = feature->name();

    CHECK(testPersistentFeatureProperties(feature, stationaryFace, design));

    CHECK(testBendLineAddAndDelete(feature, line1, design));

    CHECK(testCollectionAndDeletion(features, featureName));

    app->log("FoldSample completed successfully.");
    return true;
}