Demonstrates reading and modifying configuration rules using the typed ConfigurationRules API.
Before running: Run in the DESIGN workspace from Scripts and Add-Ins (Python, C++, or TypeScript), unless the script states otherwise.
"""
Sample: read and modify Configuration Rules via the typed Fusion API.
Prerequisites:
- The active design must be a configured design (createConfiguredDesign +
at least one column).
- The Fusion Design Extension must be enabled.
Run from the Fusion Script Editor (Text Commands > Scripts and Add-Ins).
Notes:
- The manager is reached via Design.configurationRulesManager. It returns null
for designs that are not configured designs.
- Rules are stored remotely, so every read (getRules / itemById / itemByIds) is
a discrete round-trip that returns a ConfigurationRulesResult; iterate its
rules array rather than indexing the manager.
- Rule bodies are typed block trees (ConfigurationRuleStatement[]), built through
the static create factories on each concrete class. There is no JSON body
channel on the public surface.
- Always inspect the issues array on a returned result, even on success: a
non-empty array means at least one block silently degraded to a placeholder.
- Issues and statuses are different things. An issue reports what went wrong with
the call you just made; a status reports what the rules engine found when the
rules last ran. Statuses are read separately, via getStatuses /
getStatusesByRuleId / getStatusesByConfigurationId.
"""
import adsk.core
import adsk.fusion
import traceback
def _issue_summary(issues):
"""Render a list of ConfigurationRulesIssue objects for printing."""
parts = []
for issue in issues:
index = issue.ruleIndex if issue.hasRuleIndex else "-"
parts.append(
f" code={issue.code} ruleId={issue.ruleId} "
f"ruleIndex={index} columnId={issue.columnId} message={issue.message}"
)
return "\n".join(parts) if parts else " (none)"
def _severity_name(severity):
"""Render a ConfigurationRuleStatusSeverities value for printing."""
severities = adsk.fusion.ConfigurationRuleStatusSeverities
if severity == severities.ErrorConfigurationRuleStatusSeverity:
return "ERROR"
if severity == severities.WarningConfigurationRuleStatusSeverity:
return "WARNING"
return "INFO"
def _status_summary(statuses):
"""Render a list of ConfigurationRuleStatus objects for printing."""
parts = []
for status in statuses:
# blockId is empty when the status describes the rule as a whole rather
# than one block of it.
block = status.blockId if status.blockId else "-"
parts.append(
f" [{_severity_name(status.severity)}] code={status.code} "
f"ruleId={status.ruleId} configurationId={status.configurationId} "
f"blockId={block} message={status.message}"
)
return "\n".join(parts) if parts else " (none)"
def _first_column_id(design):
"""Best-effort: return a column id from the configured design, or '' if none."""
table = design.configurationTopTable
if table is None:
return ""
columns = table.columns
if columns is None or columns.count == 0:
return ""
return columns.item(0).id
def _report_statuses(cr, design):
"""Print what the rules engine found the last time the rules ran.
Only problems are reported, so an empty result is the healthy case. These are
local reads — they never trigger a rules run — and each result is a
point-in-time snapshot, so re-query rather than holding on to one.
"""
all_statuses = cr.getStatuses()
print(
f"=== Statuses: {len(all_statuses.statuses)} "
f"({all_statuses.errorCount} error, {all_statuses.warningCount} warning, "
f"{all_statuses.infoCount} info) ==="
)
print(_status_summary(all_statuses.statuses))
# Per-rule health. A rule the engine is not running — deleted, or inactive and
# therefore excluded from the rule set — returns None rather than an empty
# result, so check for None before reading counts. A non-None result with
# errorCount == 0 means the rule was applied to every configuration.
print("=== Rule health ===")
for rule in cr.getRules().rules:
rule_statuses = cr.getStatusesByRuleId(rule.id)
if rule_statuses is None:
print(f" '{rule.name}': not running (deleted or inactive)")
elif rule_statuses.errorCount:
print(f" '{rule.name}': turned off on {rule_statuses.errorCount} configuration(s)")
else:
print(f" '{rule.name}': applied everywhere")
# Per-configuration health. The result also carries statuses that name no
# configuration at all — a rule with an unfilled socket is a problem for every
# configuration — so entries with an empty configurationId are normal here.
rows = design.configurationTopTable.rows
print(f"=== Configuration health: {rows.count} configuration(s) ===")
for index in range(rows.count):
row = rows.item(index)
config_statuses = cr.getStatusesByConfigurationId(row.id)
if config_statuses is None:
# Only reachable if the row is no longer on the top table.
continue
state = "OK" if config_statuses.errorCount == 0 else f"{config_statuses.errorCount} error(s)"
print(
f" '{row.name}': {state} "
f"({config_statuses.warningCount} warning, {config_statuses.infoCount} info)"
)
def _build_example_body(column_id):
"""A minimal typed body: set the given column to the fixed string 'Default'.
Build bodies from the create factories on the concrete statement classes
(ConfigurationRuleSetBlock / ConfigurationRuleIfBlock /
ConfigurationRuleCommentBlock) and assign the array to ConfigurationRule.body.
"""
set_block = adsk.fusion.ConfigurationRuleSetBlock.create()
set_block.columnId = column_id
set_block.operatorType = adsk.fusion.ConfigurationRuleSetOperators.EqualsConfigurationRuleSetOperator
set_block.value = adsk.fusion.ConfigurationRuleStringValue.create("Default")
return [set_block]
def run(context):
ui = None
try:
app = adsk.core.Application.get()
ui = app.userInterface
design = adsk.fusion.Design.cast(app.activeProduct)
if design is None:
ui.messageBox("No active Fusion design.")
return
cr = design.configurationRulesManager
if cr is None:
ui.messageBox("This design is not a configured design with rules.")
return
# -------------------------------------------------------------------
# Read: getRules() fetches every rule in one round-trip; iterate the
# returned ConfigurationRulesResult.
# -------------------------------------------------------------------
snapshot = cr.getRules()
print(f"=== Rules: {len(snapshot.rules)} ===")
for rule in snapshot.rules:
print(f" id={rule.id} name={rule.name} active={rule.isActive}")
if snapshot.issues:
print("Issues from read:")
print(_issue_summary(snapshot.issues))
# Single id lookup via itemById; the result carries the matched rule.
if snapshot.rules:
first_id = snapshot.rules[0].id
single = cr.itemById(first_id)
if single.rule:
print(f"itemById('{first_id}') -> name={single.rule.name}")
# -------------------------------------------------------------------
# Single-op conveniences: addRule(name) and deleteRule(id).
# addRule returns a ConfigurationRuleResult: the new rule plus any
# non-fatal issues. Inspect issues even on success.
# -------------------------------------------------------------------
add_result = cr.addRule("Sample-Convenience")
new_rule = add_result.rule
print(f"addRule() -> id={new_rule.id}, issues={len(add_result.issues)}")
if add_result.issues:
print(_issue_summary(add_result.issues))
deleted = cr.deleteRule(new_rule.id)
print(f"deleteRule('{new_rule.id}') -> {deleted}")
# -------------------------------------------------------------------
# Bulk modify: ConfigurationRulesModifyInput.create() + a typed rule.
# Applied atomically in one transaction (deletes -> reorder -> adds/updates).
# The returned result carries the post-mutation descriptors for adds and
# updates, so server-assigned ids come back in the same call.
# -------------------------------------------------------------------
column_id = _first_column_id(design)
if not column_id:
print("No configuration columns found; skipping the bulk-modify example.")
_report_statuses(cr, design)
return
modify = adsk.fusion.ConfigurationRulesModifyInput.create()
# Add a brand-new rule with a populated, typed body. A rule with an empty
# id is treated as an add on submit.
added = adsk.fusion.ConfigurationRule.create()
added.name = "Sample-Bulk-Add"
added.comment = "Created from ConfigurationRulesSample"
added.isActive = True
added.body = _build_example_body(column_id)
rules_to_submit = [added]
# Update an existing rule by resubmitting it with its id intact. Submitting
# a rule with a non-empty id replaces that rule's name/comment/active/body.
if snapshot.rules:
existing = snapshot.rules[0]
existing.name = existing.name + " (updated)"
rules_to_submit.append(existing)
modify.rules = rules_to_submit
result = cr.modifyRules(modify)
print(f"modifyRules -> {len(result.rules)} affected rule(s), {len(result.issues)} issue(s)")
for rule in result.rules:
print(f" affected: id={rule.id} name={rule.name}")
print(_issue_summary(result.issues))
_report_statuses(cr, design)
except Exception:
if ui:
ui.messageBox("Configuration rules sample failed:\n" + traceback.format_exc())