AIS. Connecting objects.

by - 02:06

(This post was written 2+ months ago but got stuck in my drafts folder. Sorry for that)

The Nokia's once famous motto 'Connecting people' has probably gone forever (especially given recent acquisition by Microsoft), but let us grab its idea. This post will be about a nice 'connecting' concept offered by the AIS (Application Interactive Services) package, part of the visualization mechanism of Open CASCADE. Unfortunately, like many other gems scattered across the product, this one sits virtually undocumented and therefore its powerful features are significantly underutilized in OCC-based apps.

This is about AIS_ConnectedInteractive and AIS_MultipleConnected, which allows you to construct derived visual representations from already computed (or to be computed) ones. Imagine an assembly consisting of various sub-assemblies, which in their turn may consist of further sub-assemblies, and so on. Eventually each sub-assembly is broken down into part(s). Each part or sub-assembly are 'instantiated' in a parent assembly, where an instance is a reference to a referred part or assembly plus an attached transformation. Here is an example of an assembly (from STEP test suite as1*.stp):
The next image is an assembly tree where you can see instances of various sub-assemblies.
Then also goes a sub-assembly l-bracket-assembly consisting of instance of a part named l_bracket and 3 instances of nut-bolt-assembly (each with different transformation), where each is a combination of two instantiated parts (bolt and nut).
AIS_ConnectedInteractive and AIS_MultipleConnected offer efficient way to compute visual representations of such complex assemblies.

AIS_ConnectedInteractive
AIS_ConnectedInteractive essentially implements the proxy pattern (or alike) and contains a reference to another AIS_InteractiveObject and transformation matrix.

Handle_AIS_InteractiveObject aRefObject =
 CreateRepresentation (...); TopLoc_Location aLoc = ...; Handle_AIS_ConnectedInteractive anInstance =  new AIS_ConnectedInteractive; anInstance->Connect (theObject, aLoc);

Note that you don't need to know details of aRefObject representation, you just attach it to anInstance.
Thus, AIS_ConnectedInteractive is well suited to create visual representation of an instance in the assembly hierarchy.

AIS_MultipleConnected
AIS_MultipleConnected follows the composite pattern and allows to combine multiple representations into one. Therefore it is an efficient way to construct visual representation of the (sub-)assembly.

Handle_AIS_MultipleConnectedInteractive anAssebly =
 new AIS_MultipleConnectedInteractive; anAssebly->Connect (aChild1); anAssebly->Connect (aChild2);
...

Again, this allows to abstract from internal details of each child object.

I use the above approach to construct visual representations of objects in a new data model designed in CAD Exchanger (currently planned to be made part of public API in version 3.0), and so far it works great.

To further abstract the creation of a final representation of the root object (or any interim one selected in a tree browser), there is a factory method that returns a handle to an object of the base type AIS_InteractiveObject.

Handle_AIS_InteractiveObject anObject =
 ModelPrs_InteractiveObjectFactory::Create (...);

Thus, the callers do not know the real type of the object that will be created – it can be either AIS_MultipleConnected, or AIS_ ConnectedInteractive, or AIS_Shape, or any other subclass of AIS_InteractiveObject.

The key benefits of *Connected* are in reusing representations of referred objects, instead of recomputing them. This allows to:
  • Reduce computation time (as creation of part representations takes the greatest time)
  • Reduce memory footprint (you do not have to create extra objects supporting the representations)
  • Reduce code size (you do not have to design extra classes for composite objects, only parts representations are really necessary)
  • Achieve greater flexibility and abstract approach (internal implementation details of referred objects are hidden and can change independently from instances and assemblies) 

To have a quick test, you might want to try DRAWEXE:
pload ALL
wedge w 1 2 3 0.5
vinit
vdisplay w
vconnect iw -5 0 0 1 0 0 0 0 1 w #creates AIS_ConnectedInteractive of AIS_Shape
box b 2 0 0 3 1 2
vconnect a -5 0 0 1 0 0 0 0 1 w b #creates AIS_ConnectedInteractive of AIS_MultipleConnected

At last, some limitations to be aware of:
  • The referred objects must belong to the same AIS_InteractiveContext. This is unfortunate but is not specific to *Connected*.
  • Setting attributes to a referred object (e.g. part) affects the referring representation (which is sort of expected). Setting an attribute to a referring object seems to have no effect.
  • Some glitches with selection (as reproduced in DRAW). 

Currently these are not affecting CAD Exchanger development, so I did not investigate these in greater details.

Anyway, hopefully these hints will be helpful for those dealing with complex data structures and GUI. If you already worked with the *Connected* and have some experience to share, it would be great to hear your comments.

Thanks!

You May Also Like

10 comments

  1. Roman!
    Can you give a sample of code. For instance: there is a box and on the top two another boxes connected with first. When I move (translate) the bottom box, all three boxes should move together.

    ReplyDelete
  2. Hi Roman!
    can you please provide the full code of this simple application?

    thanks

    ReplyDelete
  3. Probably I'm doing something wrong, but I find it quite useless as it is.
    I'm trying to use it do display a sort of "block" object (same as autocad's block entity, to clarify).
    Block entity provides the single interactive objects on request and join them to a multiple connected one.
    The 'insert' object queries block for multiple connected interactive, apply a transformation and displays it.
    It should be all ok, but.... interactive objects DON'T store the transformation, they just apply it to presentation, and only when object is already displayed.
    So, gahering the object, apply the transform and then display it simply don't work, the object appears as non-transformed.
    I will probably try changing my code path, so displaying before and then apply the transform, but it makes it cumbersome and will require to review all my code just to be able to display correctly this object.....

    ReplyDelete
  4. Hello !

    Not sure if I follow the expressed concerns.
    You should be able to create connected interactive objects *before* they ever get computed/displayed in the context.
    Perhaps the following excerpt would be helpful (and address the prior requests which went unaddressed so far)

    For AIS_ConnectedInteractive :

    /*! Combines transformations attached to \a theAncestors. If no transformations are attached
    then returns \a theObject, ortherwise - AIS_ConnectedInteractive.
    */
    static Handle_AIS_InteractiveObject Transform (
    const Handle_AIS_InteractiveObject& theObject,
    const ModelPrs_InteractiveObjectFactoryHelper::ObjectCollectionType& theAncestors)
    {
    if (theObject.IsNull())
    return theObject; //AIS_ConnectedInteractive not always checks that it is not null
    TopLoc_Location aLoc;
    ModelPrs_InteractiveObjectFactoryHelper::CombineTransformations (theAncestors, aLoc);
    if (aLoc.IsIdentity())
    return theObject;
    Handle_AIS_ConnectedInteractive r = new AIS_ConnectedInteractive;
    r->Connect (theObject, aLoc);
    return r;
    }

    For AIS_MultipleConnected:

    void RepresentationVisitor::Append (const Handle_AIS_InteractiveObject& theObject)
    {
    if (myResult.IsNull())
    myResult = theObject;
    else {
    if (myResult->IsKind (STANDARD_TYPE (AIS_MultipleConnectedInteractive))) {
    __CADEX_DEFINE_HANDLE_BY_STATIC_CAST(AIS_MultipleConnectedInteractive, aM, myResult);
    aM->Connect (theObject);
    } else {
    Handle_AIS_MultipleConnectedInteractive aM = new AIS_MultipleConnectedInteractive;
    aM->Connect (myResult);
    aM->Connect (theObject);
    myResult = aM;
    }
    }
    }

    In the above examples theObject has never been displayed yet, it has just been created on the fly.

    Also you can query the location using API of PrsMgr_PresentableObject which is a superclass of AIS_InteractiveObject:
    - HasLocation()
    - Location()

    Hope this helps. Good luck!
    Roman

    ReplyDelete
  5. Hi Roman,

    you're right, I solved it just before rading your comment ;-)
    I tried to set object transformation and not location, and it didn't work.
    Now it's all ok, besides selection.... the object don't get highlighted by mouse as it should be.

    ReplyDelete
  6. Hi MicioMax,
    Glad you've figured it out. Regarding selection you might want to try in DRAW (see the commands above). If that is reproducible (and AFAIR there were some glitches indeed) you might want to file a bug in Mantis.
    CAD Exchanger currently does not provide selection so I have not been affected by that (yet).
    Would be good to learn what you end up with.
    Good luck!
    Roman

    ReplyDelete
  7. Hi Roman,

    I'm still on it... by now no selection for it, and some other glitches when used with shapes.
    I use a custom build system, so no DRAW to test commands, just my application.
    BTW, if you like to look at a screenshot of it just look here :
    http://www.ultimatepp.org/forum/index.php?t=msg&&th=6218&goto=42379#msg_42379
    It's starting to be an usable 3d cad.

    ReplyDelete
  8. Looking into OCC code, selection is simply not implemented for multiple connected interactives....
    I'll try to cook one myself

    ReplyDelete
  9. Hi Roman,

    If you're interested in it, I implemented the missing selection :

    //=======================================================================
    //function : ComputeSelection
    //purpose :
    //=======================================================================
    void AIS_MultiInteractive::ComputeSelection(const Handle(SelectMgr_Selection) &aSel, const Standard_Integer aMode)
    {
    if (!(HasLocation() || HasConnection()))
    return;

    aSel->Clear();

    for(int iRef = 1; iRef <= myReferences.Length(); iRef++)
    {
    Handle(AIS_InteractiveObject) myReference = myReferences(iRef);

    if (!myReference->HasSelection(aMode))
    myReference->UpdateSelection(aMode);

    const Handle(SelectMgr_Selection)& TheRefSel = myReference->Selection(aMode);

    Handle(SelectMgr_EntityOwner) OWN = new SelectMgr_EntityOwner(this);

    Handle(Select3D_SensitiveEntity) SE3D, SNew;

    if (TheRefSel->IsEmpty())
    myReference->UpdateSelection(aMode);

    for (TheRefSel->Init();TheRefSel->More();TheRefSel->Next())
    {
    SE3D = Handle(Select3D_SensitiveEntity)::DownCast(TheRefSel->Sensitive());

    if (!SE3D.IsNull())
    {
    // Get the copy of SE3D
    SNew = SE3D->GetConnected(myLocation);

    if (aMode == 0)
    {
    SNew->Set(OWN);
    // In case if SE3D caches some location-dependent data
    // that must be updated after setting OWN
    SNew->SetLocation(myLocation);
    }

    aSel->Add(SNew);
    }
    }
    }
    }

    (I made another class instead of fiddling with OCC one, but it's just a copy of it with the function implemented).

    Ciao

    Max

    ReplyDelete