Jump to content

Adding choices to Pop-up parameter


tsw

Recommended Posts

I'd like to add choices to a pop-up parameter of my custom plugin by reading from a worksheet. Do I need to have an event-enabled plugin to do this? (I suspect I do...)

 

My plugin is currently working with hard-coded choices in the pop-up parameter. Not sure I'm ready to make the leap into event-enabled plugins, so I wanted to confirm.

Link to comment

I recently indexed the entire Vectorworks official scripting documentation from GitHub using DeepWiki - it's a service that creates an AI-powered chat interface for any GitHub repository's documentation. (<Vectorworks/developer-scripting>)
Instead of hunting through dozens of markdown files trying to figure out event-enabled plugins, I just asked DeepWiki directly and it guided me through the entire process.
I used it to build this complete testWorksheet.py example that does exactly what you're asking for - dynamically populating popup choices from worksheet data.

 

# testWorksheet.py
import vs

# Event constants
kObjOnInitXProperties = 5
kParametricRecalculate = 3
kObjOnWidgetPrep = 41
kObjOnAddState = 44  # Added constant for clarity

# Property constants
kObjXPropHasUIOverride = 8
kObjXHasCustomWidgetVisibilities = 12
kObjXPropAcceptStates = 18           # Added constant for clarity
kParametricEnableStateEventing = 590 # Added constant for clarity

# Event result constants
kObjectEventHandled = -8

# Configuration constants
POPUP_PARAM_NAME = "WorksheetValues"  # Change to match your popup parameter name
WORKSHEET_NAME_PARAM = "WorksheetName"


def read_popup_values_from_worksheet(worksheet_name: str, start_row: int = 2, end_row: int = 20, column: int = 1):
    """
    How/Why: Reads a clean list of values from a specific worksheet column so a popup can reflect external data.
    - Uses Vectorworks worksheet API safely, ignoring empty cells and trimming strings.
    - Keeps row/column 1-based to match VW conventions.
    """
    popup_values = []

    # Locate worksheet
    ws_h = vs.GetObject(worksheet_name)
    if not ws_h:
        vs.AlrtDialog(f'Worksheet "{worksheet_name}" not found')
        return popup_values

    # Collect values (strings prioritized; numbers converted)
    for row in range(start_row, end_row + 1):
        try:
            if not vs.IsValidWSCell(ws_h, row, column):
                continue

            if vs.IsWSCellString(ws_h, row, column):
                s = vs.GetWSCellStringN(ws_h, row, column)
                if s and s.strip():
                    popup_values.append(s.strip())
            elif vs.IsWSCellNumber(ws_h, row, column):
                val = vs.GetWSCellValue(ws_h, row, column)
                popup_values.append(str(val))
        except Exception as e:
            # Keep robust on malformed cell content
            vs.AlrtDialog(f"Worksheet read error at row {row}, col {column}: {e}")

    return popup_values


def populate_popup_from_worksheet(widget_id: int, worksheet_name: str):
    """
    How/Why: Rebuilds the popup choices to reflect current worksheet content during WidgetPrep,
    ensuring OIP shows up-to-date options without forcing recalculation.
    """
    # Reset current popup items
    vs.vsoWidgetPopupClear(widget_id)

    # Validate input
    if not worksheet_name or not worksheet_name.strip():
        return

    # Configure read window
    start_row = 2   # Skip header
    end_row = 20    # Adjust for your data
    column = 1      # 1-based

    # Read and add
    values = read_popup_values_from_worksheet(worksheet_name, start_row, end_row, column)

    for value_id, display_text in enumerate(values):
        # Note: API takes integer IDs; documentation may show string
        vs.vsoWidgetPopupAdd(widget_id, value_id, display_text)

    # Optional separator + edit affordance
    if values:
        vs.vsoWidgetPopupAdd(widget_id, len(values), "-")
        vs.vsoWidgetPopupAdd(widget_id, len(values) + 1, "Edit Worksheet...")


def create_plugin_geometry():
    """
    How/Why: Keeps geometry creation isolated so ParametricRecalculate stays tidy and testable.
    Replace this with your actual object creation code.
    """
    # Example placeholder geometry
    vs.Rect(0, 0, 100, 100)
    vs.Circle(50, 50, 25)
    vs.Line((0, 0), (100, 100))
    # 3D example:
    # vs.Box(0, 0, 0, 100, 100, 50)


def main():
    """
    How/Why: Central event router that follows VW's event-enabled plugin rules.
    - InitX: declare capabilities and insert OIP widgets
    - AddState: only add state (no other logic)
    - WidgetPrep: populate dynamic UI (popups, visibilities, etc.)
    - Recalculate: build/update geometry and clear plugin state
    """
    event, message = vs.vsoGetEventInfo()

    # Get plugin info (may be incomplete very early during init; do not exit early)
    ok, plugin_name, plugin_handle, record_handle, wall_handle = vs.GetCustomObjectInfo()

    # Map param name to OIP widget ID (param index is 0-based; widget ID is 1-based)
    # Guard for name lookup to avoid exceptions during very early init
    popup_param_index = None
    if plugin_name:
        idx = vs.vsoParamName2Index(plugin_name, POPUP_PARAM_NAME)
        if idx is not None and idx >= 0:
            popup_param_index = idx + 1

    if event == kObjOnInitXProperties:
        # Enable extended behaviors and insert OIP widgets
        vs.SetObjPropVS(kObjXPropHasUIOverride, True)
        vs.SetObjPropVS(kObjXHasCustomWidgetVisibilities, True)
        vs.SetPrefInt(kParametricEnableStateEventing, 1)
        vs.SetObjPropVS(kObjXPropAcceptStates, True)
        vs.vsoInsertAllParams()

    elif event == kObjOnAddState:
        # VW rule: only add current state here
        if plugin_handle:
            _ = vs.vsoStateAddCurrent(plugin_handle, message)

    elif event == kObjOnWidgetPrep:
        # Rebuild dynamic popup content
        if popup_param_index is not None and plugin_handle and plugin_name:
            ws_name = vs.GetRField(plugin_handle, plugin_name, WORKSHEET_NAME_PARAM)
            if ws_name and ws_name.strip():
                populate_popup_from_worksheet(popup_param_index, ws_name)

        # Must mark as handled
        vs.vsoSetEventResult(kObjectEventHandled)

    elif event == kParametricRecalculate:
        # Main geometry update
        create_plugin_geometry()

        # Clear state after successful recalc
        if plugin_handle:
            vs.vsoStateClear(plugin_handle)


# PIOs are executed by VW; keep for standalone testing if needed:
# if __name__ == "__main__":
#     main()

 

 

This is honestly a game-changer for learning Vectorworks scripting. Tools like LLMs (ChatGPT/Claude/Gemini, etc) and now DeepWiki make it so much easier than the old days of digging through scattered documentation and forum posts. You can literally have a conversation with the official docs!

 

This link is another prompt I asked it to prepare docs on event-enabled plugins using the script as context - really helpful for understanding the full workflow.

https://deepwiki.com/search/earlier-we-built-this-code-tog_a8da7663-214c-428f-8690-e5863ffcac2b?mode=fast

 

Basically:

Event-Enabled Plugin Quick Reference

Your testWorksheet.py demonstrates a parametric plugin that handles four key events to customize the Object Info Palette and manage dynamic content.

Core Events

Event 5 (InitXProperties) - Plugin setup

  • Enable UI customization with SetObjPropVS(kObjXPropHasUIOverride, True)
  • Add all parameters as widgets with vsoInsertAllParams()

Event 44 (AddState) - State tracking

  • Only call vsoStateAddCurrent(plugin_handle, message) - nothing else!

Event 41 (WidgetPrep) - Dynamic UI updates

  • Populate your popup with vsoWidgetPopupClear() and vsoWidgetPopupAdd()
  • Must end with vsoSetEventResult(-8)

Event 3 (ParametricRecalculate) - Geometry creation

  • Create your geometry here
  • Always call vsoStateClear(plugin_handle) when finished


Key Documentation Links:

Object Events Overview - Complete event system guide

Parametric Custom Shape Pane Popup - Dynamic popup examples

Plug-in with widget basic example - Python widget tutorial

 

Widget Tips:

Use vsoParamName2Index(plugin_name, param_name) + 1 to get widget IDs

Parameter index is 0-based, widget ID is 1-based (hence the +1)


The pattern is simple: setup (5) → track changes (44) → update UI (41) → create geometry (3).

 

  • Like 1
Link to comment
  • 2 weeks later...

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...