Skip to main content

Associativity in AutoCAD


CAD applications often implement features or entities which are associative in nature, for example creating an extrude surface in AutoCAD 2011, by extruding a profile creates associative extrude surface. When you modify the profile the extruded surface will update itself to follow the profile.
AutoCAD developers now can develop fascinating applications using new associative framework. There are APIs available in C++ as well as .NET to implement associative features in AutoCAD. You can download the latest ObjectARX to make use of associative framework.
The associative framework in AutoCAD helps us to maintain relationship between objects. Relations are represented as hierarchical Networks of Objects, Actions and Dependencies between them. The core building blocks are:
  • Action - Defines behavior or intelligence of application or feature.
  • Dependency- Maintains associativity/ relations between objects and notifies changes on dependent objects.
  • Network- The associative model
  • Object- A regular AutoCAD AcDbObject/AcDbEntity being controlled by an associative action.
Now let's code a simple action where we want to establish a relationship between a line and a circle, where length of line controls diameter of the circle. For this we need to implement an Action class derived from AcDbAssocActionBody, which provides the evaluation logic.
//A custom action derived from AcDbAssocActionBody, establishes relationship
//between a line and a circle. Length of the line drives diameter of the circle.
//
class MyActionBody : public AcDbAssocActionBody
{
public:
ACRX_DECLARE_MEMBERS(MyActionBody);

virtual void evaluateOverride();

static Acad::ErrorStatus createInstance(AcDbObjectId lineId, AcDbObjectId circleId, AcDbObjectId& actionBodyId);
};

Establishing relationship between objects:

This action depends on both line and circle, so we need to attach dependencies on both line and circle. Since we are going to read the length of line and write/update diameter of the circle, so we attach read dependency on line and write dependency on circle. These dependencies are owned by our action and they will ensure that line is driving entity and circle is the driven entity.

Acad::ErrorStatus MyActionBody::createInstance(AcDbObjectId lineId, 
    AcDbObjectId circleId, AcDbObjectId& actionBodyId)
{
    //Get the space to which network/action needs to be added.
    AcDbObjectId ownerSpaceId;
    {
        AcDbObjectPointer< AcDbEntity > pEntity(lineId, AcDb::kForRead);
        if(!eOkVerify(pEntity.openStatus()))
            return pEntity.openStatus();
        ownerSpaceId = pEntity->ownerId();
    }

    //create a new action and action body on the given space.
    AcDbObjectId actionId;
    AcDbAssocActionBody::createActionAndActionBodyAndPostToDatabase(
        MyActionBody::desc(), ownerSpaceId, actionId, actionBodyId);

    AcDbObjectPointer< AcDbAssocAction > pAction(actionId, AcDb::kForWrite);
    if(!eOkVerify(pAction.openStatus()))
        return pAction.openStatus();

    //Read dependency on line, to read it's length.
    AcDbAssocDependency* pDep = new AcDbAssocDependency();
    pAction->database()->addAcDbObject(pDep);
    pDep->attachToObject(lineId);
    pDep->setIsReadDependency(true);
    pDep->setIsWriteDependency(false);
    pAction->addDependency(pDep->objectId());
    pDep->close();

    //Write dependency on circle to set it's radius equal to length of line.
    pDep = new AcDbAssocDependency();
    pAction->database()->addAcDbObject(pDep);
    pDep->attachToObject(circleId);
    pDep->setIsReadDependency(false);
    pDep->setIsWriteDependency(true);
    pAction->addDependency(pDep->objectId());
    pDep->close();

    return Acad::eOk;
}

Evaluation logic:

Now the evaluation of action needs to ensure that the desired behavior is maintained after every evaluation. This requires implementation of evaluateOverride() method on the action body class.

void MyActionBody::evaluateOverride()
{
    AcDbAssocEvaluationCallback* pCallback = this->currentEvaluationCallback();

    AcDbObjectIdArray depIds;
    Acad::ErrorStatus es = getDependencies(true, true, depIds);
    AcDbObjectId lineId, circleId;
    for(int i = 0; i < depIds.length(); ++i)
    {
        AcDbObjectPointer< AcDbAssocDependency > pDep(depIds[i], AcDb::kForRead);
        if(!eOkVerify(pDep.openStatus()))
        {
            //Reporting error to the callback.
            if(NULL != pCallback)
            {
                AcDbObjectPointer< AcDbAssocAction > pAction(this->parentAction(), AcDb::kForRead);
                pCallback->setActionEvaluationErrorStatus(pAction, pDep.openStatus(), depIds[i]);
            }
            setStatus(kFailedToEvaluateAssocStatus);
            return;
        }

        if(pDep->status() == kErasedAssocStatus)
        {
            //If any dependent object is erased, the corresponding dependency
            //will be mared as kErasedAssocStatus. For this example, we need to
            //erase the action if dependency is to be erased.
            setStatus(kErasedAssocStatus);
            return;
        }

        if(!pDep->isWriteDependency()) //read only dependency
            lineId = pDep->dependentOnObject();
        else if(!pDep->isReadDependency()) //write only dependency
            circleId = pDep->dependentOnObject();
    }

    assert(!lineId.isNull() && !circleId.isNull());
    if(lineId.isNull() || circleId.isNull())
    {
        setStatus(kFailedToEvaluateAssocStatus);
        return; //you may report some error here.
    }
    
    //Evaluate dependencies
    evaluateDependencies();

    double length = 0.0;
    //Use AcDbAssocObjectPointer to open line for read, so that
    //we get the cloned copy created by dragger, when line is dragged.
    AcDbAssocObjectPointer< AcDbLine > pLine(lineId, AcDb::kForRead);
    if(!eOkVerify(pLine.openStatus()))
    {
        setStatus(kFailedToEvaluateAssocStatus);
        return; //you may report some error here.
    }

    AcGePoint3d start, end;
    es = pLine->getStartPoint(start);
    es = pLine->getEndPoint(end);

    length = start.distanceTo(end);

    //Use AcDbAssocObjectPointer to open circle for write, so that
    //we get the clone of circle managed by dragger used to draws updated 
    //circle when line is dragged.
    AcDbAssocObjectPointer< AcDbCircle > pCircle(circleId, AcDb::kForWrite);
    if(eOkVerify(pCircle.openStatus()))
        pCircle->setRadius(length/2);

    setStatus(kIsUpToDateAssocStatus); //Finally set the status as uptodate.
}

Comments

  1. Interesting stuff Sharad:)

    Couple of questions:
    1. What does evaluateDependencies do in ln 46?

    2. If we want the same dependency between circle and line, for example, change in circles diameter, should modify the length of the line too. Both should be "write" dependencies? In that case how would we know which dependencyId corresponds to which object in evaluateOverride?

    ReplyDelete
  2. Thanks Kartik for visiting my blog. Here are the answers for your questions.

    1. Each dependency may have some evaluation logic and the simplest logic can be to set it's state as uptodate. evaluateDependencies() will mark each dependency status uptodate after evaluation. In certain cases dependencies may cach some values, for example in the above example network evaluation will be take place even if you change the color of line. Since you just wanted to track changes in length, you can have your custom dependency attached on line that caches the value of length. This custom dependency can update it's cache when evaluateDependencies is called. I'll explain dependecies in more detail in my next blog.

    2. If want to update line length to circle diameter as well, then you need to attach read and write dependencies to both line and circle. In that situation you can look at the objectClass() of dependentOnObject to know the type of object, else you may choose to cache object id of line and circle with your custom action. But I prefer not to duplicate the information as long as possible.

    ReplyDelete
  3. Hi Sharad,
    I am an Architect, working in Spore for last 7 years. I would like to get in touch with you regarding Guidance on Computational Design. Would really appreciate if you could give me your contact details. my email id is ar.chandergupta@gmail.com.
    Thanks.
    Chander.

    ReplyDelete

Post a Comment

Popular posts from this blog

Linear Regression in Dynamo

Today we are collecting more data than ever but making sense out of the data is very challenging. Machine learning helps us analyze the data and build an analytical model to help us with future prediction. To create a machine learning prediction model we usually develop a hypothesis based on our observation of the collected data and fine tune the model to reduce the cost function by training the model. One of the very simple hypothesis we can develop by assuming a linear relationship between input and output parameters.

Suppose we have data on housing price and we assume that housing prices are linearly related to the floor area of the house then we can use linear regression to predict the price of a house with specific floor area. One of the most important steps towards building the hypothesis is being able to visualize the data and understand the trend. So let first draw a scatter plot of our data as shown in the figure below. I am using Grapher package to draw the scatter plot.
T…

Polynomial regression and model comparison

Welcome back, in my previous post I described how we can perform linear regression using normal equation in Dynamo and I left with a question "what if the input and output are not linearly dependent?” Let’s say we have a hypothesis that the housing price doesn’t depend on floor area and number of rooms linearly but it has a following relationship as y = a_{0} * x_{0} + a_{1} * x_{1} + a_{2} * x_{2} + a_{3} * x_{1} * x_{2} + a_{4} * x_{1}^2 + a_{5} * x_{2}^2 \: where x_{0} = 1, \: x_{1} is floor area and x_{2} is number of rooms. Then we can introduce few new parameters x_{3} = x_{1} * x_{2}, \: x_{4} = x_{1}^2, \: x_{5} = x_{2}^2 and then perform the linear regression to find the coefficient matrix.

The Dynamo graph to setup the feature vector looks as follows. Once the feature vector is setup rest all is same as previous linear regression example.


Note that the price prediction for a given floor area and rooms we need to again construct the same feature vector. The price predic…

DesignScript

It’s been a really long time that I wrote about something. For past many years, I have been busy working on a domain specific language called DesignScript and it's visual programming interface Dynamo. I am now moving to a world of reality-capture and I’ll miss working on this technology. I find this technology very useful and every now and then I’ll keep coming to it for some geometric and programming experiment. Recently I am learning machine learning and wondering if I can use Dynamo to explain some of the concepts of machine learning. I’ll explore it further but in the mean time let me explain what is DesignScript and Dynamo.DesignScript is a scripting language intended to provide programming capability to designers. Designers can now describe their design-intent effectively using this language. Unlike other procedural languages, it provides two great features such as associativity and replication. The associativity in expressions helps it preserve the intent whereas the replic…