What's new

Release: Sketch Find Plane

bolsover

Senior Member
Hi Nate
I thought it might be interesting to reproduce your rather nice script for finding finding face/plane for a sketch in c#.

Recreating your script is almost trivial - but it led me to expose an issue..

If you run your script against the attached file and select sketch2 or sketch3 you will see an exception being thrown. For other sketches it seems to work just fine.

I get the same problem in c#.

The AlibreX IADSketch interface has a property SketchPlane.
The documentation for AlibreX.IADSketch.SketchPlane : Gets the target proxy containing the object used to define sketch plane. The sketch plane can be a design plane or a planar face and occurrence, if any.

Its my guess that the 'if any' is the issue. It looks like it is not mandatory for a sketch to have an associated plane.

All the best

DB
 

Attachments

  • BarrelArbour.AD_PRT
    552 KB · Views: 2

bolsover

Senior Member
Nate
Just out of interest, I modified your script to bring it closer to the c# I was using...

Python:
Win = Windows()
Options = []
Options.append(['Sketch', WindowsInputTypes.Sketch, ''])
Values = Win.OptionsDialog('Find source plane/face from sketch', Options, 200)
if Values == None:
    sys.exit()
if Values[0] == None:
    print('None selected')
    sys.exit()
print('Selecting source plane/face from sketch')
ThisSketch = Values[0]
PRT = ThisSketch.GetPart()
AD_Sketch = ThisSketch._Sketch

AD_TargetProxy = AD_Sketch.SketchPlane

print(AD_TargetProxy.DisplayName)

AD_Session = AD_Sketch.Session
AD_NOC = AD_Sketch.Root.NewObjectCollector()
AD_NOC.Add(AD_Sketch.SketchPlane)
AD_Session.Select(AD_NOC)
print(PRT.Selections[0])
print('Done')

The problem is with the call to AD_TargetProxy = AD_Sketch.SketchPlane.

DB
 

simonb65

Alibre Super User
In C#, you tried finding out what the returned TargetProxy is, i.e.

C#:
    IADTargetProxy targetProxy = sketch.SketchPlane;

    IADOccurrence occurrence = targetProxy.Occurrence;
    object target = targetProxy.Target;
    string displayName = targetProxy.DisplayName;

    Console.WriteLine("TargetProxy name : " + displayName);

    if (target is IADFace)
    {
        // sketch is on a parts face
        IADFace face = target as IADFace;
        Console.WriteLine("TargetProxy is a Face");
        // do something
    }

    if (target is IADDesignPlane)
    {
        // sketch is on either the XY-Plane, YZ-Plane or XZ-Plane or a user added plane
        IADDesignPlane designPalne = target as IADDesignPlane;
        Console.WriteLine("  Target is an Design Plane");
        // do something
    }

    // will most likely never be any of these !
    if (target is IADPlane)
    {
        // sketch is on a user added plane
        IADPlane plane = target as IADPlane;
        Console.WriteLine("TargetProxy is a Plane");
        // do something
    }

    if (target is IADEdge)
    {
        IADEdge edge = target as IADEdge;
        Console.WriteLine("TargetProxy is an Edge");
        // do something
    }

    if (target is IADPartFeature)
    {
        IADPartFeature partFeature = target as IADPartFeature;
        Console.WriteLine("TargetProxy is a Part Feature");
        // do something
    }

    // etc

An IADTargetProxy has many interfaces, it could be one of those! ... or does it just return null when not a DesignPlane or Face?

The Python interpreter in AlibreScript may not handle anything other than Face or DesignPlane .

Try outputting the displayName for the TargetProxy and see if that gives any clues.

@bolsover , what kind of sketch gives you neither a Face or DesignPlane ? EDIT : removed dumb question!!!

NOTE: The first sketch TargetProxy is always a DesignPlane, subsequent sketches are on user added DesignPlane or Faces.
 
Last edited:

simonb65

Alibre Super User
It appears that out of all the sketch properties, SketchPlane is the only one that throws a COM exception on certain sketches. I too am now baffled by the "IF ANY" comment. I would have expected either SketchPlane to return null or the entity that the sketch is referenced to or created on!

If it can be 'nothing' or 'unassigned by COM' then how or what is the sketch referenced by? and how do you get that programmatically? @Max, can anyone of your dev team spare a few minutes to shed some light on this?

Dumping the sketch properties ...

C#:
IADSketch sketch = target as IADSketch;


message.Append(sketch.Figures + "\r\n");
message.Append(sketch.IsActive + "\r\n");
message.Append(sketch.IsClosed + "\r\n");
message.Append(sketch.IsConsumed + "\r\n");
message.Append(sketch.IsSuppressed + "\r\n");
message.Append(sketch.Type + "\r\n");
message.Append(sketch.Root + "\r\n");


// this is the only property that throws an exception in certain sketches!
try { message.Append(sketch.SketchPlane + "\r\n"); }
catch (System.Runtime.InteropServices.COMException) { }


message.Append(sketch.SketchPlaneNormal + "\r\n");
message.Append(sketch.SketchConstraints + "\r\n");

View of the dumped sketch data ...

1647377333927.png
Bottom line should say "No TargetProxy (sketch.SketchPlane) supplied by COM!"
 

NateLiquidGravity

Alibre Super User
Yeah, in my testing I thought I found a problem with this script using sketches I assumed were created using Interdesign Relationships. Meaning the sketch was created on the face of another part but maybe that face no longer exists or at least isn't accessible when not in an assembly session. I did make some improvements after and wrapped that in a try/except to cleanly alert the user but haven't uploaded it yet.
 

simonb65

Alibre Super User
Just a bit more investigation ... and interestingly, in @bolsover s model and it would be the same for similarly created sketch figures, the two sketches that have no valid or accessible TargetProxy's (Sketch<2> and Sketch<3>) are both sketch figures that, whilst drawn on a reference Face (Face<23>), they do not actually lie on the face but outside it ...

1647423750246.png
1647424078508.png

If you add a sketch figure that lies directly on the face, i.e. ...

1647423888753.png

It then gives you a TargetProxy (the Face!). If this relationship is known internally, why isn't it exposed in the original case above? Why only when the figures overlap the reference Face?

1647423950832.png

An interesting other issue is that Sketch<8> reports having a TargetProxy of Plane<9>, but if you query it's consuming feature (which should be Hole<10>), it throws an exception when you access the ConsumingFeature property of the sketch! ...

1647425077374.png

It would be more friendly if these functions returned null's if there is no data, as throwing and handling exceptions is very slow performance wise.

Also, in all cases here, I don't know why the properties that throw exception aren't reporting the relevant Face/Design Planes or Consuming Feature! Is the hole tool not updating the Consuming Feature in the sketch when it consumes it? Bug?
These appear to be bugs, but if it's intentional behaviour, it has no reason or logic that makes sense to me.
 
Last edited:

simonb65

Alibre Super User
P.S. Apologies @NateLiqGrav if the thread has become a bit C# ish, but I presume the same issues we can see developing Add-Ons, etc in C# are probably the same limitations/issues that AlibreScript will be seeing internally too!
 

bolsover

Senior Member
@simon65 Some good detective work there finding a 'cause' of the exceptions. I guess that sketches are actually tied to the OriginPoint or maybe the SketchPlaneNormal.
I'm curious as to how you are calling the C# code... Are you executing from the python scripting interface or from a standalone app? Either way some more sample code would be good. FYI, I live just over the hills from you - Combs.

All the best, David
 

simonb65

Alibre Super User
@simon65 Some good detective work there finding a 'cause' of the exceptions. I guess that sketches are actually tied to the OriginPoint or maybe the SketchPlaneNormal.
I'm curious as to how you are calling the C# code... Are you executing from the python scripting interface or from a standalone app? Either way some more sample code would be good. FYI, I live just over the hills from you - Combs.
Hi David, Combs between Whaley and Chapel? small world! ... couple of nice pubs there :)

This testing is with a simple Add-On dll. I originally wrote my own Add-On way-way back in C++, but my main 'productive' language of choice today is C#, so after your first post I decided to create a C# based Add-On (for simplicity) and I'm now porting my C++ code over to it. It's far easier in C# dealing with COM interfaces rather than having to query for them all the time like you have to in C++. The 2D/3D UI rendering stuff is much easier and slicker to in C#. You also don't have the issue of managing strings (BSTR), allocating memory resources, etc!

I also use C# in standalone applications to call Alibre through the API. It's a bit more long winded as you need to handle the connection hook, etc, but my plan is to take the external applications I've written and make it a single Add-On with lots of utilities, like you've done.

To develop, I create the Add-on dll in VS2019, post build copies the dll to the add-on directory in Alibre. When you run, it runs Alibre as the debug shell. Most of what you created in your GIT repository for the basic add-on framework isn't needed and the menu definition can be simplified and made more flexible and you don't need to create your forms globally, you can create them on the fly, like you would with normal dialog boxes, etc.

This is an example of drawing a triangle, plus some text on the screen using C++ and the same functionality in C#, this is adapted from the C++ examples supplied in the AddOn part of this forum...

C++:
// Internal method to draw triangle using the Alibre supplied IADAddOnCanvasDisplay interface (this is new in AD version 2019).
// This will use Hoops Visualize rendering engine that Alibre Design's rendering platform is now built on.
bool DrawTriangle_Hoops(IADAddOnCanvasDisplay* pCanvasDisplay, LPWSTR pImageFilePath, bool bDrawTexture)
{
    float vertices[] = { -5.0f, 0.0f, 0.0f,  5.0f, 0.0f, 0.0f,  0.0f, 5.0f, 0.0f };
    float normals[] = { 0.0f, 0.0f, 1.0f,  0.0f, 0.0f, 1.0f,  0.0f, 0.0f, 1.0f };
    int indices[] = { 3, 0, 1, 2 };
    float color[] = { 0.0f, 0.5f, 0.0f };

    float vertexUVs[] = { 0.0f,0.0f,   1.0f,1.0f,   0.5f,1.0f };

    float fltOffset = m_nGlobalDrawCounter * 5.0f;     // Offset Triangle everytime
    D3DXMATRIX WM;
    D3DXMatrixTranslation(&WM, fltOffset, 0.0f, 0.0f);
    double transform[] = {    WM.m[0][0], WM.m[0][1], WM.m[0][2],
                            WM.m[1][0], WM.m[1][1], WM.m[1][2],
                            WM.m[2][0], WM.m[2][1], WM.m[2][2],
                            WM.m[3][0], WM.m[3][1], WM.m[3][2] };

    SAFEARRAY* saVertices = NULL;
    SAFEARRAY* saNormals = NULL;
    SAFEARRAY* saIndices = NULL;
    SAFEARRAY* saRGB = NULL;
    SAFEARRAY* saTransform = NULL;
    SAFEARRAY* saVertexUVparams = NULL;

    if (getSafeArrayFromArray<float>(vertices, (long)9, VT_R4, &saVertices) != S_OK)
        return false;
    if (getSafeArrayFromArray<float>(normals, (long)9, VT_R4, &saNormals) != S_OK)
        return false;
    if (getSafeArrayFromArray<int>(indices, (long)4, VT_I4, &saIndices) != S_OK)
        return false;
    if (getSafeArrayFromArray<float>(color, (long)3, VT_R4, &saRGB) != S_OK)
        return false;
    if (getSafeArrayFromArray<double>(transform, (long)12, VT_R8, &saTransform) != S_OK)
        return false;

    LONG64 result;
    LONG64 mySegment;
    BSTR mySegmentName = SysAllocString(L"My Triangle");
    pCanvasDisplay->AddSubSegment(NULL, mySegmentName, &mySegment);
    pCanvasDisplay->SetSegmentTransform(mySegment, VARIANT_TRUE, &saTransform);
    pCanvasDisplay->SetSegmentColor(mySegment, 0, 128, 0, 255);        // dark green

    if (!bDrawTexture)
    {
        /////////
        // NOTE: TO DISPLAY COLORED MESH UNCOMMENT THE FOLLOWING LINE (CALL TO "DrawMesh") AND COMMENT OUT THE CODE BLOCK BELOW IT THAT DRAWS TEXTURED MESH
        /////////
        pCanvasDisplay->DrawMesh(mySegment, &saVertices, &saNormals, &saIndices, &result);
    }
    else
    {
        /////////
        // NOTE: TO DISPLAY TEXTURED MESH UNCOMMENT THE CODE BLOCK BELOW AFTER COMMENTING THE CALL TO "DrawMesh" (ABOVE)
        ////////
        //
        // START TEXTURE BLOCK
        if (getSafeArrayFromArray<float>(vertexUVs, (long)6, VT_R4, &saVertexUVparams) != S_OK)
            return false;
        BSTR textureName = SysAllocString(L"Wood");
        BSTR imagePath = SysAllocString(pImageFilePath);
        pCanvasDisplay->DefineTexture(mySegment, textureName, ImageFormat_JPEG, imagePath);
        pCanvasDisplay->SetFaceTexture(mySegment, textureName);
        pCanvasDisplay->DrawTexturedMesh(mySegment, &saVertices, &saNormals, &saVertexUVparams, &saIndices, &result);
        SafeArrayDestroy(saVertexUVparams);
        SysFreeString(textureName);
        SysFreeString(imagePath);
        // END TEXTURE BLOCK
    }

    // Draw a triangular wire mesh
    pCanvasDisplay->DrawPolyline(mySegment, &saVertices, &result);

    // EXAMPLE 1: set line weight
    pCanvasDisplay->SetLineWeight(mySegment, 2.0f);

    // EXAMPLE 2: Toggle foreground rendering
    pCanvasDisplay->ToggleForegroundRendering(mySegment, true);

    long radius = 15;
    pCanvasDisplay->DrawMarker(mySegment, vertices[0], vertices[1], vertices[2], radius, MarkerType::MarkerType_MARKER_RECT);
    pCanvasDisplay->DrawMarker(mySegment, vertices[3], vertices[4], vertices[5], radius, MarkerType::MarkerType_MARKER_RECT);

    // Draw text
    CAlibreHoops::DrawTextW(pCanvasDisplay, L"X = 1.25", 10, 10);
    CAlibreHoops::DrawTextW(pCanvasDisplay, L"Y = 0.00", 10, 30);
    CAlibreHoops::DrawTextW(pCanvasDisplay, L"Z = 2.76", 10, 50);

    SafeArrayDestroy(saVertices);
    SafeArrayDestroy(saNormals);
    SafeArrayDestroy(saIndices);
    SafeArrayDestroy(saRGB);
    SafeArrayDestroy(saTransform);
    SysFreeString(mySegmentName);

    return true;
}

C#:
        // Internal method to draw triangle using the Alibre supplied IADAddOnCanvasDisplay interface (this is new in AD version 2019).
        // This will use Hoops Visualize rendering engine that Alibre Design's rendering platform is now built on.
        private bool DrawTriangle_Hoops(IADAddOnCanvasDisplay canvas, string imageFilePath, bool bDrawTexture)
        {
            long segment;

            Array vertices = new float[] { -5.0f, 0.0f, 0.0f, 5.0f, 0.0f, 0.0f, 0.0f, 5.0f, 0.0f };
            Array normals = new float[] { 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f };
            Array indices = new int[] { 3, 0, 1, 2 };
            Array color = new float[] { 0.0f, 0.5f, 0.0f };
            Array vertexUVs = new float[] { 0.0f, 0.0f, 1.0f, 1.0f, 0.5f, 1.0f };

            float fltOffset = m_nGlobalDrawCounter * 5.0f;   // Offset Triangle everytime

            Array transform = new double [] { 
                1, 0, 0,
                0, 1, 0,
                0, 0, 1,
                fltOffset, 0, 0 };

            segment = canvas.AddSubSegment(0, "Triangle");
            canvas.SetSegmentTransform(segment, true, ref transform);
            canvas.SetSegmentColor(segment, 0, 128, 0, 255);     // dark green
            canvas.SetSegmentReflectivity(segment, 80);

            if (!bDrawTexture)
            {
                // draw coloured mesh
                canvas.DrawMesh(segment, ref vertices, ref normals, ref indices);
            }
            else
            {
                // draw textured
                string textureName = "Wood";
                canvas.DefineTexture(segment, textureName, ImageFormat.JPEG, m_imageFilePath);
                canvas.SetFaceTexture(segment, textureName);
                canvas.DrawTexturedMesh(segment, ref vertices, ref normals, ref vertexUVs, ref indices);
            }

            // Draw a triangular wire mesh
            canvas.DrawPolyline(segment, ref vertices);

            // EXAMPLE 1: set line weight
            canvas.SetLineWeight(segment, 2.0f);

            // EXAMPLE 2: Toggle foreground rendering
            canvas.ToggleForegroundRendering(segment, true);

            int radius = 15;
            canvas.DrawMarker(segment, (float)vertices.GetValue(0), (float)vertices.GetValue(1), (float)vertices.GetValue(2), radius, MarkerType.MARKER_RECT);
            canvas.DrawMarker(segment, (float)vertices.GetValue(3), (float)vertices.GetValue(4), (float)vertices.GetValue(5), radius, MarkerType.MARKER_RECT);

            string text = DateTime.Now.ToString("HH:mm:ss");

            segment = canvas.AddSubSegment(0, "Mesh Text");
            canvas.DrawTextMesh(segment, text, 5, 5, 5, TextAlignment.TopLeft, "Miso", 20.0f, HOOPS.COLOR_ARGB(255, 0, 0, 255), true, false, false);

            segment = canvas.AddSubSegment(0, "Screen Text");
            canvas.DrawScreenText(segment, text, 200, 200, TextAlignment.TopLeft, "Miso", 20.0f, HOOPS.COLOR_ARGB(255, 0, 0, 255), true);
            canvas.DrawScreenText(segment, "X = 1.25", 10, 10, TextAlignment.TopLeft, "Miso", 12.0f, HOOPS.COLOR_ARGB(255, 128, 128, 128), true);
            canvas.DrawScreenText(segment, "Y = 0.00", 10, 30, TextAlignment.TopLeft, "Miso", 12.0f, HOOPS.COLOR_ARGB(255, 128, 128, 128), true);
            canvas.DrawScreenText(segment, "Z = 2.76", 10, 50, TextAlignment.TopLeft, "Miso", 12.0f, HOOPS.COLOR_ARGB(255, 128, 128, 128), true);

            return true;
        }
 

idslk

Alibre Super User
I appreciate the entusiasm but can you make a separate topic.
:) ...may it makes sense to make "Script Releases" under Resources instead? Often a "discussion" will often take place in a thread below the script...I remember that Albie0803 has released one there.
 

NateLiquidGravity

Alibre Super User
:) ...may it makes sense to make "Script Releases" under Resources instead? Often a "discussion" will often take place in a thread below the script...I remember that Albie0803 has released one there.
I encourage discussion relevant to the original topic (like tests to determine the cause of the SketchPlane error) but I'd prefer other topics have their own thread - for both keeping this thread cleaner and also others might find those topics interesting if their threads are named appropriately. This is the way.
 

Max

Administrator
Staff member
From the dev team :

The SketchPlane API property on an IADSketch points to the source object like a DesignPlane or a planar Face on which the sketch was originally created. However, the sketch can get disassociated from its SketchPlane under some circumstances where the SketchPlane is unavailable. For instance, if the associated planar face gets removed from the model due to a subsequent edit to the model. To handle these situations, the sketch always caches a static copy of sketch plane geometry.


Gets the target proxy containing the object used to define sketch plane. The sketch plane can be a design plane or a planar face and occurrence, if any.

The "if any" refers only to the occurrence and not to the design plane or planar face. The occurrence will usually be null as the DesignPlane or Face associated with the sketch will come from the same part. Occurrence will exist only when modeling in the context of an assembly in which case the SketchPlane could be a planar face on a different part.
 
Top