Grasshopper API Help - Python Script Triggering Breakpoints

Hello, I’m encountering some breakpoint messages that I would like to avoid. I believe this is because I am making changes prior to solution completion, but I am not familiar with the methods required to manage this safely & effectively. Other than these breakpoint messages, the script is working functionally as intended.

The below sample is a snippet extracted a larger project that is still in progress. I have aimed to keeping the sample code as simple and well-commented as possible for easier review. Apologies in advance if the sample is verbose and messy! Thanks in advance. :slight_smile:

Sample File:
Example.gh (18.8 KB)

Video Clip:

Description:

Context: Rhino8 Python 3 Script Editor

Objectives: Modify component output parameters based on a dictionary of {key : value} pairs (simulated in the sample as a Grasshopper data tree, input into the _data input node). Disconnect downstream nodes if there is any update to the data structure.

Issues:

  1. Disconnecting downstream nodes triggers a breakpoint, leading to errors such as “Panel () object expired during solution” and “An exception was thrown during a solution: Component Panel,” or “Collection was modified; enumeration operation may not execute.”

  2. Modifying the input data structure—specifically when reducing the number of {key:value} pairs relative to the previous input—triggers a breakpoint. An example error message is: “Output parameters Index[5] too high or too low for Component Python 3 Script.”

Adding the Python code for the component below:

# -----------------------------------------------------------------------------
# Import libraries
# -----------------------------------------------------------------------------

import Grasshopper as gh
import System
import RhinoCodePluginGH.Parameters as rcpg
import Grasshopper.Kernel as kernel

# -----------------------------------------------------------------------------
# Define functions
# -----------------------------------------------------------------------------

def warning_message():
    ghenv.Component.AddRuntimeMessage(gh.Kernel.GH_RuntimeMessageLevel.Warning, "Required input missing or invalid.")

# Define a function to add dynamic output parameters
def add_param(name,ind):
    param = rcpg.ScriptVariableParam()  # Constructor method
    param.NickName = name # Add nickname
    param.Name = name # Add name
    param.Access = kernel.GH_ParamAccess.list
    index = ind # Set position
    ghenv.Component.Params.RegisterOutputParam(param)
    ghenv.Component.Params.OnParametersChanged()

# Define a function to disconnect sources whenever the component is modified
def remove_sources():
    # Get the current Grasshopper document / component info
    doc = ghenv.Component.OnPingDocument()
    component = ghenv.Component
    output_params = component.Params.Output

    output_comp_guids = []
    for n in output_params:
        output_comp_guids.append(n.InstanceGuid)

    output_comp_guids_02 = []
    for i in output_params:
        recipients = list(i.Recipients)
        for r in recipients:
            output_comp_guids_02.append(r.InstanceGuid)
            print(r.InstanceGuid)

    removals = []

    for obj in doc.Objects:
        # Components
        if hasattr(obj, 'Params') and hasattr(obj.Params, 'Input'):
            for param_input in obj.Params.Input:
                if param_input.InstanceGuid in output_comp_guids_02:
                    for item in output_comp_guids:
                        key = param_input
                        value = item
                        tuple_ = (key,value)
                        removals.append(tuple_)
        # Parameters & Panels
        elif hasattr(obj, 'Sources'):
            sources = list(obj.Sources)
            for param_input in sources:
                if param_input.InstanceGuid in output_comp_guids:
                    key = obj
                    value = param_input.InstanceGuid
                    tuple_ = (key,value)
                    removals.append(tuple_)
        else:
            pass

    # Perform your modifications
    for item in removals:
        item[0].RemoveSource(item[1])
        item[0].ExpireSolution(False)

# -----------------------------------------------------------------------------
# Execute operations
# -----------------------------------------------------------------------------

# [0] Process & validate inputs
# ---------------------------------------------------------

# Split the key-value pairs
try:
    keys = []
    values = []
    for branch in _data.Branches:
        keys.append(str(branch[0]))
        values.append(float(branch[1]))
except:
    # Clear old outputs
    remove_sources()
    ghenv.Component.Params.Output.Clear()
    warning_message()

# Ensure inputs are available, each value has a unique keyname, each keyname is unique
if any([x==None,keys==[None],values==[None],len(keys) != len(values),len(list(set(keys))) != len(keys)]):
    remove_sources()
    ghenv.Component.Params.Output.Clear()
    warning_message()

# [1] Update the Grasshopper component
# ---------------------------------------------------------

else: # Validation passed

    # Get a list of the previous outputs
    previous_outputs = [param.Name for param in ghenv.Component.Params.Output]

    # Create a list of desired outputs
    current_outputs = keys

    # Check if there are existing outputs
    if len(previous_outputs)>0:
        print("len(previous_outputs)>0")

        # Check if existing outputs are not the same as the current
        if set(previous_outputs) != set(current_outputs):
            print("set(previous_outputs) != set(current_outputs)")

            # Disconnect components
            remove_sources()

            # Clear old outputs
            ghenv.Component.Params.Output.Clear()

            # Create new outputs
            for ind, item in enumerate (current_outputs):
                add_param(item,ind)

        # Skip if the existing and current are the same
        else:
            print("set(previous_outputs) == set(current_outputs)")
            # pass

    # Check if there are no existing outputs
    else:
        print("len(previous_outputs)==0")

        # Create new outputs
        for ind, item in enumerate (current_outputs):
            add_param(item,ind)


    # [2] Now that the component is updated, apply output values
    # ---------------------------------------------------------

    # Iterate for each output
    for index, item in enumerate(current_outputs):

        # Calculate each output value
        output_value = x + values[index]

        # Set the output result by accessing the newly created output parameter
        ghenv.Component.Params.Output[index].AddVolatileData(gh.Kernel.Data.GH_Path(0), 0, output_value)