Showing posts with label GstarCAD. Show all posts
Showing posts with label GstarCAD. Show all posts

Friday, May 29, 2026

Fine-Tuning Selection Set Filtering with Callbacks in PyRx

As of version 2.2.58, most selection set actions in PyRx have received a powerful overload that allows developers to attach a

When executing a selection command, the callback function automatically intercepts a Db.ObjectIdArray containing the candidate objects. Your logic then evaluates these objects and returns a list of integers representing the indexes of the IDs that should be discarded from the final selection.
Key Benefits of Selection Callbacks
  • Advanced Criteria: Filter by complex properties (like geometric limits or dynamic states) that standard DXF group codes cannot check.
  • Streamlined Workflow: Eliminate the need to manually iterate over and clean up selection sets after user input.
  • Real-time Precision: Ensure your commands only process exactly what they are intended to handle.
The Complete Python Code
Here is how to implement a dynamic block filter using the new callback overload.
 
import traceback
from pyrx import Ap, Db, Ed, Ge, Rx

def dyn_filter(ids: Db.ObjectIdArray) -> list[int]:
    """
    Callback filter that evaluates a list of selected Object IDs.
    Returns a list of list indexes pointing to objects that should be removed.
    """
    itemsToRemove = []
   
    # Enumerate through the selection array to track both index and object ID
    for idx, id in enumerate(ids):
        # Open the ID as a BlockReference to inspect its properties
        ref = Db.BlockReference(id)
       
        # Check if the block reference is NOT a dynamic block
        if not ref.isDynamicBlock():
            # Flag the index for removal if it fails the condition
            itemsToRemove.append(idx)
           
    return itemsToRemove

@Ap.Command()
def pyselectfcb():
    """
    Custom PyRx command to prompt user selection using the custom filter.
    """
    try:
        # Prompt selection filtering for 'INSERT' (Block References)
        # Pass the 'dyn_filter' function as the second argument callback
        ps, ss = Ed.Editor.select([(0, "INSERT")], dyn_filter)
       
        # If the selection prompt was successful, print the result status and size
        if ps == Ed.PromptStatus.eOk:
            print(f"Selection Status: {ps}, Filtered Count: {ss.size()}")
           
    except Exception:
        # Catch and print any runtime exceptions for debugging
        print(traceback.format_exc())


 
Code Walkthrough
  1. Ed.Editor.select Overload: We pass dyn_filter as a functional parameter directly into our selection method.
  2. Indexing Strategy: The callback tracks idx. If a block fails the isDynamicBlock() check, its exact list index is recorded.
  3. Array Cleanup: PyRx reads the returned itemsToRemove list and instantly strips those matching positions out of your final selection set ss.
 
 
Only the Dynamic Blocks are selected, yay!
 

Wednesday, November 29, 2023

Pandas for data extraction, merging DataFrames, Python for AutoCAD

 

I explored Groupby and Aggregate here

import traceback
import pandas as pd
from pyrx import Ap, Ax, Db, Ed, Ge, Gi
 
@Ap.Command()
def PyRxCmd_doit():
    try:
        #setup dictionaries to hold our data
        lineMap = {"Type": [], "Layer": [], "Length": [], "QTY": []}
        circleMap = {"Type": [], "Layer": [], "Diameter": [], "QTY": []}

        #current database
        db = Db.curDb()
        model = Db.BlockTableRecord(db.modelSpaceId())

        #search model for all lines (derived from)
        lineIds = model.objectIds(Db.Line.desc())
        for id in lineIds:
            line = Db.Line(id)
            lineMap["Type"].append("Line")
            lineMap["Layer"].append(line.layer())
            lineMap["Length"].append(line.startPoint().distanceTo(line.endPoint()))
            lineMap["QTY"].append(1)

        #search model for all circles (derived from)
        circleIds = model.objectIds(Db.Circle.desc())
        for id in circleIds:
            circle = Db.Circle(id)
            circleMap["Type"].append("Circle")
            circleMap["Layer"].append(circle.layer())
            circleMap["Diameter"].append(circle.diameter())
            circleMap["QTY"].append(1)

        #create the DataFrames
        lfd = pd.DataFrame(lineMap)
        cfd = pd.DataFrame(circleMap)

        # called before concat because the column names are different
        # groupby & aggregate
        lfd = (lfd.groupby(["Type", "Layer", "Length"],
                        sort=False, as_index=False).agg({"QTY": "sum"}))

        # groupby & aggregate
        cfd = (cfd.groupby(["Type", "Layer", "Diameter"],
                        sort=False, as_index=False).agg({"QTY": "sum"}))


        # concat the DataFrames
        result = pd.concat([lfd, cfd])

        # write to excel, but don't write the index
        with pd.ExcelWriter("e:\\pandas_to_excel.xlsx") as writer:
            result.to_excel(writer, sheet_name="sheet1", index=False)

    except Exception as err:
        traceback.print_exception(err)


 


Friday, November 24, 2023

Generate plot chart from an AutoCAD table using Python for AutoCAD

 

The tools we need are matplotlib and pandas

 Step 1, is to create a pandas dataframe from an AutoCAD table, the structure is a dictionary {header : [data array]}. We’ll need to create a map (dictionary) of our headers so we can access them later. Next, iterate the data cells and put them in the appropriate bucket

 Step 2, setup our plot configuration, setup our colors. Note, we need to make sure the column we want to plot is the correct data type, if you’ll notice the astype cast. Run the plot

 Optional step 3, insert the result as a raster image

 here's the code

import PyRx as Rx
import PyGe as Ge
import PyGi as Gi
import PyDb as Db
import PyAp as Ap
import PyEd as Ed
import traceback

import matplotlib.pyplot as plt
from matplotlib.pyplot import savefig
import numpy as np
import pandas as pd


# create a dict to hold the headers
# create a dataframe {header : [data]}
def createDataFromTable(id : Db.ObjectId):
   
    data = {}
    header = {}
    table = Db.Table(id)

    # save header row so we can lookup the header name while looping the rows
    range = table.cellRange()
    range.topRow = 1
    range.bottomRow = 1

    for cell in table.getIterator(range):
        key = table.textStringFmt(cell, Db.FormatOption.kIgnoreMtextFormat)
        header[cell[1]] = key
        data[key] = []
       
    # iterate data rows, an put the data in our data map
    range = table.cellRange()
    range.topRow = 2

    for cell in table.getIterator(range):
        key = header[cell[1]]
        value = table.textStringFmt(cell, Db.FormatOption.kIgnoreMtextFormat)
        data[key].append(value)

    return pd.DataFrame(data)
       
       
def PyRxCmd_doit():
    try:
        es = Ed.Editor.entSel("\nSelect a table: ", Db.Table.desc())
        if es[0] != Ed.PromptStatus.eNormal:
            print("oof")
            return
       
        params = {
            "text.color": "crimson",
            "xtick.color": "crimson",
            "ytick.color": "crimson",
            "axes.edgecolor": "crimson",
            "axes.labelcolor": "crimson",
            "figure.facecolor": "crimson"}
       
        plt.rcParams.update(params)
       
        plt.xlabel("Item")
        plt.ylabel("Cost")
        plt.title("Cost per item")
 
        df = createDataFromTable(es[1])
        df['Unit']=df['Unit'].astype(float)
       
        df["Unit"].plot.bar()
        savefig("demo.png", transparent=True)
        plt.clf()# clear
       
        #todo insert as image
        print("yay")
    except Exception as err:
        traceback.print_exception(err)



Using DuckDB for expressions.

  After writing the last post, I was wondering if I could use DuckDB to query AutoCAD data, I.e. Layers   from pyrx import Ap , Db , Ed...