Welcome to the Onshape forum! Ask questions and join in the discussions about everything Onshape.

First time visiting? Here are some places to start:
  1. Looking for a certain topic? Check out the categories filter or use Search (upper right).
  2. Need support? Ask a question to our Community Support category.
  3. Please submit support tickets for bugs but you can request improvements in the Product Feedback category.
  4. Be respectful, on topic and if you see a problem, Flag it.

If you would like to contact our Community Manager personally, feel free to send a private message or an email.

Options

setExternalDisambiguation - Multiple Face Replacements on a Solid Body

Jonathan_HutchinsonJonathan_Hutchinson Member Posts: 65 PRO
edited August 2023 in FeatureScript
I'm trying to create a feature that will approximate a number of curved surfaces, usually just 4 sided, with simplified planar surfaces using ReplaceFace. Taking three vertices from a face and turning it into a plane which replaces a face.

If there's only one curved face in the solid body, then it works fine. But I can't get it to extend to multiples and get the external disambiguation error. My guess is that because I'm changing faces within the entity as I go, that it almost invalidates the 'original' list of faces that I obtain. Apologies for my plain speech explanation.

Over simplified use case example below where a curved face is approximated by a plane.



/**
 * Feature: Planarise Faces
 * @param faceArray {Query} : A `query` of faces to evaluate raycasts to.
 * @param plFace {plane} : A `plane` to extrude up to.
 *
 * definition.thick1/definition.thick2/definition.extrusionDepth
 *
 */
annotation { "Feature Type Name" : "Planarise Faces" }
export const fPlanariseFaces = defineFeature(function(context is Context, id is Id, definition is map)
    precondition
    {
        annotation { "Name" : "Feature with Face Entities to Planarise", "Filter" : EntityType.FACE, "MaxNumberOfPicks" : 1 }
        definition.feature is FeatureList;
    }
    {
        const featureBodies = qCreatedBy(definition.feature, EntityType.FACE);
        const featureBodiesArray = evaluateQuery(context, featureBodies);
        var faceQuery = qNothing();

        for (var face in featureBodiesArray)
        {
            try silent
            {
                evPlane(context, {
                            "face" : face
                        });
            }

            catch
            {
                faceQuery = qUnion([faceQuery, face]);
            }
        }

        addDebugEntities(context, faceQuery, DebugColor.GREEN);

        forEachEntity(context, id + "planariseFace", faceQuery, function(faceEntity is Query, id is Id)
            {
                PlanariseFaces(context, id, faceQuery);
            });

    });

/**
 * Get non-planar faces by trying to evaluate a plane and adding it to a query.
 * In a sense similar to qNonCapEntity.
 * @param query {Query} : A `query` of non-planar faces to planarise.
 *
 *  returns: `Query`
 */
export function GetNonPlanarFaces(context, query)
{
    const qArray = evaluateQuery(context, query); //Evaluate the query to enable iteration.
    var faceQuery = qNothing();

    for (var face in qArray)
    {
        try silent
        {
            evPlane(context, {
                        "face" : face
                    });
        }

        catch
        {
            faceQuery = qUnion([faceQuery, face]);
        }
    }

    return faceQuery;
}

/**
 * Generate a planar approximation of a face based upon the position of 3 arbitrarily selected vertices via `qAdjacent`.
 * Replace the face in question with this planar representation.
 *
 * @param faces {Query} : A `query` of non-planar faces to planarise.
 *
 */
export function PlanariseFaces(context, id, faces)
{
    forEachEntity(context, id + "planariseFace", faces, function(faceEntity is Query, id is Id)
        {
            // Perform operations with the entity
            var vertices = qAdjacent(faceEntity, AdjacencyType.VERTEX, EntityType.VERTEX);

            const pO = evVertexPoint(context, {
                        "vertex" : qNthElement(vertices, 0)
                    });

            const pX = evVertexPoint(context, {
                        "vertex" : qNthElement(vertices, 1)
                    });

            const pY = evVertexPoint(context, {
                        "vertex" : qNthElement(vertices, 2)
                    });

            const xDir = vector(pX - pO);
            const yDir = vector(pY - pO);
            const normDir = cross(yDir, xDir);
            // debug(context, plane(pO, normDir), DebugColor.BLUE);

            opPlane(context, id + "simpleBestFitPlane", {
                        "plane" : plane(pO, normDir)
                    });

            try silent
            {
                opReplaceFace(context, id + "replaceFaceWithPositiveSense", {
                            "replaceFaces" : faceEntity,
                            "templateFace" : qCreatedBy(id + "simpleBestFitPlane", EntityType.FACE),
                            "oppositeSense" : false
                        });
            }
            catch
            {
                opReplaceFace(context, id + "replaceFaceWithNegativeSense", {
                            "replaceFaces" : faceEntity,
                            "templateFace" : qCreatedBy(id + "simpleBestFitPlane", EntityType.FACE),
                            "oppositeSense" : true
                        });
            }

            opDeleteBodies(context, id + "deleteSimpleBestFitPlane", {
                        "entities" : qCreatedBy(id + "simpleBestFitPlane", EntityType.FACE)
                    });
        });
}

EDIT:

Fixed this because I had some ForEachEntity calls wrapped up. The only thing I think I need to add, is some kind of check so that a user is warned that a face cannot be replaced in the case of an approximation creating self-intersecting geometry.

Tagged:

Best Answers

  • Options
    NeilCookeNeilCooke Moderator, Onshape Employees Posts: 5,436
    Answer ✓
    You could either query another face on the solid before you delete face (or maybe query an edge or vertex of the face you are deleting) or try startTracking
    Senior Director, Technical Services, EMEAI
  • Options
    Alex_KempenAlex_Kempen Member Posts: 244 EDU
    Answer ✓
    Assuming `qCreatedBy(id + "deleteFaceId", EntityType.BODY)` doesn't work, another solution may be to create a tracking query using `startTracking` on the solid part prior to the opDeleteFace - after the delete operation, the tracking query should then evaluate to the new sheet body.

    `makeRobustQuery` is another option in the standard toolbox of query robustness, but that tends to be for cases where a subfeature "corrupts" a query that would otherwise be valid. Actually, come to think of it, `makeRobustQuery` might actually apply to your original situation above - probably worth a shot.
    CS Student at UT Dallas
    Alex.Kempen@utdallas.edu
    Check out my FeatureScripts here:



Answers

  • Options
    Alex_KempenAlex_Kempen Member Posts: 244 EDU
    First off, you can use qGeometry with GeometryType.PLANE to check for planar faces - there's no need to resort to evPlane in a try silent loop:
    export function qNonPlanarFaces(query is Query) returns Query
    {
    return query->qSubtraction(query->qGeometry(GeometryType.PLANE)); }

    With that out of the way, one of the golden rules of FeatureScript is that there is no way for a query to resolve to something that doesn't exist, since queries only ever evaluate to geometry which is physically present in the part studio at the time of evaluation. However, that doesn't really seem to apply here since your original non planar face queries should still be valid (replacing a different face shouldn't affect them) and qAdjacent should also function okay. However, it's possible the setExternalDisambiguation used by forEachEntity is giving you trouble since that's really for associating operations with each user input in a robust way (think one hole, extrude, rib, frame, etc. per user selection rather than doing them all in an arbitrary order that shuffles when your user reorders the query). A hallmark test for this is creating a mate connector on each result of your feature, and then unselecting the first entity from your feature; if you've done it right, only the mate connector associated with the first entity should break.

    Since you're taking features as inputs, rather than individual faces, this also doesn't really apply, and so your problems might be solved by using just a regular for loop.That being said, it would be a lot more robust if you did take individual faces, so that might also be worth a try (although I can see how it could get tedious when you have a great many faces).

    Looking at your code, I'll also point out there isn't any handling for cases where you have a curved face which isn't adjacent to three or more vertices; in such a case, evVertexPoint will throw a (somewhat obtuse) error. That probably isn't the root cause of your issue, but it might be worth looking in to as well.

    CS Student at UT Dallas
    Alex.Kempen@utdallas.edu
    Check out my FeatureScripts here:



  • Options
    Jonathan_HutchinsonJonathan_Hutchinson Member Posts: 65 PRO
    Thank you for this really detailed feedback. and the advice for removing the try silent! I think it worked one time and i was content enough to run with it.

    I have a similar quandry with regards to no longer existent geometry. I've ran an opDeleteFace on a part with the intention to leave it open. I'm then going to boolean it with some different faces (opFillSurfaces), but after the face deletion I'm not able to get back the newly created sheet body (formerly a solid body). Is there anything available to try and overcome this type of situation?
  • Options
    NeilCookeNeilCooke Moderator, Onshape Employees Posts: 5,436
    Answer ✓
    You could either query another face on the solid before you delete face (or maybe query an edge or vertex of the face you are deleting) or try startTracking
    Senior Director, Technical Services, EMEAI
  • Options
    Alex_KempenAlex_Kempen Member Posts: 244 EDU
    Answer ✓
    Assuming `qCreatedBy(id + "deleteFaceId", EntityType.BODY)` doesn't work, another solution may be to create a tracking query using `startTracking` on the solid part prior to the opDeleteFace - after the delete operation, the tracking query should then evaluate to the new sheet body.

    `makeRobustQuery` is another option in the standard toolbox of query robustness, but that tends to be for cases where a subfeature "corrupts" a query that would otherwise be valid. Actually, come to think of it, `makeRobustQuery` might actually apply to your original situation above - probably worth a shot.
    CS Student at UT Dallas
    Alex.Kempen@utdallas.edu
    Check out my FeatureScripts here:



  • Options
    Jonathan_HutchinsonJonathan_Hutchinson Member Posts: 65 PRO
    edited August 2023
    My first applied use of startTracking!

    @Alex_Kempen - for a function that you would want to use as regularly as this (at least, I would anyway), how would you go about organising it? In terms of  a clean place for imports. Would it be one feature studio in a one separate doc with several convenience functions?

    @NeilCooke - do you have know if there is any FS call to be able to generate a plane of best fit given a bunch of n vertices?
  • Options
    Jonathan_HutchinsonJonathan_Hutchinson Member Posts: 65 PRO
    edited August 2023
    Also @Alex_Kempen ; regarding the below:

    Looking at your code, I'll also point out there isn't any handling for cases where you have a curved face which isn't adjacent to three or more vertices; in such a case, evVertexPoint will throw a (somewhat obtuse) error. That probably isn't the root cause of your issue, but it might be worth looking in to as well.

    Could you explain exactly what you mean? Shouldn't it be impossible to have a curved face not adjacent to three or more vertices, since this would just be like a collapsed edge? And would the option be to use qOwnedByBody(face, EntityType.VERTEX); instead, to get vertices of the face rather than ones implied as 'adjacent'?
  • Options
    Alex_KempenAlex_Kempen Member Posts: 244 EDU
    It's possible for faces to have less than three adjacent vertices. For example, the end face of a cylinder has 0 adjacent vertices, while the end face of an extruded semicircle has only two.

    qOwnedByBody won't work for your purposes since faces don't actually own the vertices/edges they're surrounded by.

    In regards to document management, there's various tradeoffs to using more vs. less documents. With additional documents, the primary benefit is the increasingly separate version histories. The primary downside is the hassle associated with keeping each document in sync with each other, as updating references can be quite tedious.

    Personally, I keep all of my code in two documents; one is the backend, with all of my libraries and code, and one is the frontend, which actually exposes the scripts. I have a set of python scripts connected to the API which I use to keep FeatureScript synced to the latest version (since studios get unhappy if the studios are at different versions) and to execute my release workflow, which creates a version in the backend, updates the frontend to point to the new version, and then creates a version in the frontend.

    This approach is convenient since I'm able to keep versions of all of my released scripts and use (and update) my libraries freely without having to update a bunch of imports manually. It also makes it easier for me to keep my codebase pegged to a specific version of FeatureScript. That being said, a lot of my workflow is greatly assisted by my python scripts, so it's definitely not for everyone.

    For most people, I would recommend keeping code in one document as much as possible, and only breaking stuff out as it becomes necessary. The most important thing is to avoid the need to create a version and update an external import every time you want to test if something works, as having to do that over and over while trying to troubleshoot a bug is a surefire way to drive you mad.
    CS Student at UT Dallas
    Alex.Kempen@utdallas.edu
    Check out my FeatureScripts here:



Sign In or Register to comment.