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.

evDistance(), but for paths?

EvanReeseEvanReese Member, Mentor Posts: 2,186 ✭✭✭✭✭

I'm looking for a way to find the closest point on a path and, most importantly to find the parameter of the point found. For a single curve, I'd use evDistance(), which would return a DistanceResult containing the parameter, but this doesn't work on paths that I'm aware. Any other methods?

I considered hacking it by using opSplineThroughEdges() to make a spline I could use with evDistance(), but it only takes tangent continuous chains, and I didn't like the overhead, and approximation anyway.

Evan Reese

Best Answers

  • Jacob_CorderJacob_Corder Member Posts: 137 PRO
    Answer ✓

    FeatureScript 2491;

    import(path : "onshape/std/geometry.fs", version : "2491.0");

    //export import(path : "onshape/std/evaluate.fs", version : "2491.0");
    import(path : "onshape/std/query.fs", version : "2491.0");
    //
    import(path : "onshape/std/containers.fs", version : "2491.0");
    import(path : "onshape/std/units.fs", version : "2491.0");
    import(path : "onshape/std/math.fs", version : "2491.0");

    /**

    • Computes the minimum or maximum distance between geometry on side0 and geometry on side1. "Geometry" means entities, points, or lines.
    • When the minimum or the maximum is not uniquely defined, ties will be broken arbitrarily.
    • @example evDistance(context, { "side0" : vector(1, 2, 3) * meter, "side1" : query }).distance
    • returns the minimum distance from any entity returned by `query` to the point `(1, 2, 3) meters`.
    • @example result = evDistance(context, { "side0" : qEverything(EntityType.VERTEX), "side1" : qEverything(EntityType.VERTEX), "maximum" : true })
    • computes the pair of vertices farthest apart. `qNthElement(qEverything(EntityType.VERTEX), result.sides[0].index)`
    • queries for one of these vertices.
    • @seealso [DistanceResult]
    • @param context {Context}
    • @param definition {{
    • @field side0 : One of the following: A query, or a point (3D Length Vector), or a [Line], or a [Plane], or an array of points, or an array of [Line]s, or an array of [Plane]s. This Version Also allows for a path to be used
    • @eg `qNthElement(qEverything(EntityType.FACE), 0)` or `vector(1, 2, 3) * meter` or `line(vector(1, 0, 1) * meter, vector(1, 1, 1)` or `plane(vector(1,1,1) * meter, vector(0,0,1), vector(1,0,0))`.
    • @field extendSide0 {boolean} : If `true` and side0 is a query, bodies will be ignored and edges and faces extended to
    • their possibly infinite underlying surfaces. Defaults to `false`. @optional
    • @field side1 : Like `side0`.
    • @autocomplete `vector(0, 0, 0) * meter`
    • @field extendSide1 {boolean} : Like `extendSide0`. @optional
    • @field maximum {boolean} : If `true`, compute the maximum instead of the minimum. Defaults to `false`.
    • Not allowed to be `true` if a line is passed in in either side or if either `extend` is true. @optional
    • @field arcLengthParameterization {boolean} :
    • If true (default), the parameter returned for edges measures distance
    • along the edge, so `0.5` is the midpoint.
    • If false, use an arbitrary but faster-to-calculate parameterization.
    • This field only controls the parameter returned for edges. It does not control the
    • parameter returned for points, [Line]s, faces, or [Plane]s.
    • @optional
    • }}
      */
      export function evDistanceEx(context is Context, definition is map) returns map
      {
      var side0 = definition.side0;
      var side1 = definition.side1;
      var side0WasPath = false;
      var side1WasPath = false;
      if (definition.side0 is Path)
      {side0WasPath = true;
      }
      else if(definition.side0 is map)
      {
      if(definition.side0.edges is array)
      {
      side0WasPath=true;
      }}if (definition.side1 is Path)
      {side1WasPath = true;
      }
      else if(definition.side1 is map)
      {
      if(definition.side1.edges is array)
      {
      side1WasPath=true;
      }}
      if(side0WasPath)
      {
      side0 = qUnion(definition.side0.edges);
      }
      if(side1WasPath)
      {
      side1 = qUnion(definition.side1.edges);
      }
      var def = definition;
      def.side0 = side0;
      def.side1 = side1;
      var distRes = evDistance(context, def);
      if (side0WasPath)
      {
      var param = GetPathParameter(context, definition.side0, definition.side0.edges[distRes.sides[0].index], distRes.sides[0].parameter);
      distRes.sides[0].edgeParameter = distRes.sides[0].parameter;
      distRes.sides[0].parameter = param;
      }
      if (side1WasPath)
      {
      if(distRes.sides[1].parameter >=-.01 && distRes.sides[1].parameter <=1.01)
      {
      var param = GetPathParameter(context, definition.side1, definition.side1.edges[distRes.sides[1].index], distRes.sides[1].parameter);
      distRes.sides[1].parameter = param;distRes.sides[1].edgeParameter = distRes.sides[1].parameter;
      }
      else
      {
      println("Invalid parameter returned from evDistance. Parameter: "~distRes.sides[1].parameter);
      }}
      return distRes;
      }

    function GetPathParameter(context is Context, path is Path, edge is Query, edgeParam is number) returns number
    {
    var totalLength is ValueWithUnits = evPathLength(context, path);
    var curLen = 0 * meter;
    for (var i = 0; i < size(path.edges); i += 1)
    {
    var edLen = evLength(context, { "entities" : path.edges[i] });
    if (size(evaluateQuery(context, qIntersection([path.edges[i], edge]))) > 0)
    {
    var curParam = curLen / totalLength;
    var edgeMultiplier = edLen / totalLength;
    if (path.flipped[i])
    {
    edgeParam = 1 - edgeParam;
    }
    return curParam + (edgeParam * edgeMultiplier);
    }
    else
    {
    curLen = curLen + edLen;
    }
    }
    throw "Cannot find the edge in the path";

    }

  • Caden_ArmstrongCaden_Armstrong Member Posts: 195 PRO
    Answer ✓

    You can calculate it,

    evDistance → set one side to qunion(path.edges)
    evDistance will give you the parameter (of the closest edge) but also the index. The order of the edges is preserved with qUnion across an array, so the index will match the path index.

    get total length of the path,
    get the first X lengths with like evLength(qUnion(subarray(path.edges, index))
    get the length of the closest edge
    (check if its flipped)
    length along closest edge is len*parameter (or 1-(len*parameter) if flipped)
    add that length to the total of the previous
    partial length / total path length → parameter.

    Example:
    https://cad.onshape.com/documents/fcdb4df9ade805c9057c2f51/w/3dcd322bf609a31bd074d1cb/e/531e02b4bd7b366654bb577b

    www.smartbenchsoftware.com --- fs.place --- Renaissance
    Custom FeatureScript and Onshape Integrated Applications

Answers

  • Jacob_CorderJacob_Corder Member Posts: 137 PRO
    Answer ✓

    FeatureScript 2491;

    import(path : "onshape/std/geometry.fs", version : "2491.0");

    //export import(path : "onshape/std/evaluate.fs", version : "2491.0");
    import(path : "onshape/std/query.fs", version : "2491.0");
    //
    import(path : "onshape/std/containers.fs", version : "2491.0");
    import(path : "onshape/std/units.fs", version : "2491.0");
    import(path : "onshape/std/math.fs", version : "2491.0");

    /**

    • Computes the minimum or maximum distance between geometry on side0 and geometry on side1. "Geometry" means entities, points, or lines.
    • When the minimum or the maximum is not uniquely defined, ties will be broken arbitrarily.
    • @example evDistance(context, { "side0" : vector(1, 2, 3) * meter, "side1" : query }).distance
    • returns the minimum distance from any entity returned by `query` to the point `(1, 2, 3) meters`.
    • @example result = evDistance(context, { "side0" : qEverything(EntityType.VERTEX), "side1" : qEverything(EntityType.VERTEX), "maximum" : true })
    • computes the pair of vertices farthest apart. `qNthElement(qEverything(EntityType.VERTEX), result.sides[0].index)`
    • queries for one of these vertices.
    • @seealso [DistanceResult]
    • @param context {Context}
    • @param definition {{
    • @field side0 : One of the following: A query, or a point (3D Length Vector), or a [Line], or a [Plane], or an array of points, or an array of [Line]s, or an array of [Plane]s. This Version Also allows for a path to be used
    • @eg `qNthElement(qEverything(EntityType.FACE), 0)` or `vector(1, 2, 3) * meter` or `line(vector(1, 0, 1) * meter, vector(1, 1, 1)` or `plane(vector(1,1,1) * meter, vector(0,0,1), vector(1,0,0))`.
    • @field extendSide0 {boolean} : If `true` and side0 is a query, bodies will be ignored and edges and faces extended to
    • their possibly infinite underlying surfaces. Defaults to `false`. @optional
    • @field side1 : Like `side0`.
    • @autocomplete `vector(0, 0, 0) * meter`
    • @field extendSide1 {boolean} : Like `extendSide0`. @optional
    • @field maximum {boolean} : If `true`, compute the maximum instead of the minimum. Defaults to `false`.
    • Not allowed to be `true` if a line is passed in in either side or if either `extend` is true. @optional
    • @field arcLengthParameterization {boolean} :
    • If true (default), the parameter returned for edges measures distance
    • along the edge, so `0.5` is the midpoint.
    • If false, use an arbitrary but faster-to-calculate parameterization.
    • This field only controls the parameter returned for edges. It does not control the
    • parameter returned for points, [Line]s, faces, or [Plane]s.
    • @optional
    • }}
      */
      export function evDistanceEx(context is Context, definition is map) returns map
      {
      var side0 = definition.side0;
      var side1 = definition.side1;
      var side0WasPath = false;
      var side1WasPath = false;
      if (definition.side0 is Path)
      {side0WasPath = true;
      }
      else if(definition.side0 is map)
      {
      if(definition.side0.edges is array)
      {
      side0WasPath=true;
      }}if (definition.side1 is Path)
      {side1WasPath = true;
      }
      else if(definition.side1 is map)
      {
      if(definition.side1.edges is array)
      {
      side1WasPath=true;
      }}
      if(side0WasPath)
      {
      side0 = qUnion(definition.side0.edges);
      }
      if(side1WasPath)
      {
      side1 = qUnion(definition.side1.edges);
      }
      var def = definition;
      def.side0 = side0;
      def.side1 = side1;
      var distRes = evDistance(context, def);
      if (side0WasPath)
      {
      var param = GetPathParameter(context, definition.side0, definition.side0.edges[distRes.sides[0].index], distRes.sides[0].parameter);
      distRes.sides[0].edgeParameter = distRes.sides[0].parameter;
      distRes.sides[0].parameter = param;
      }
      if (side1WasPath)
      {
      if(distRes.sides[1].parameter >=-.01 && distRes.sides[1].parameter <=1.01)
      {
      var param = GetPathParameter(context, definition.side1, definition.side1.edges[distRes.sides[1].index], distRes.sides[1].parameter);
      distRes.sides[1].parameter = param;distRes.sides[1].edgeParameter = distRes.sides[1].parameter;
      }
      else
      {
      println("Invalid parameter returned from evDistance. Parameter: "~distRes.sides[1].parameter);
      }}
      return distRes;
      }

    function GetPathParameter(context is Context, path is Path, edge is Query, edgeParam is number) returns number
    {
    var totalLength is ValueWithUnits = evPathLength(context, path);
    var curLen = 0 * meter;
    for (var i = 0; i < size(path.edges); i += 1)
    {
    var edLen = evLength(context, { "entities" : path.edges[i] });
    if (size(evaluateQuery(context, qIntersection([path.edges[i], edge]))) > 0)
    {
    var curParam = curLen / totalLength;
    var edgeMultiplier = edLen / totalLength;
    if (path.flipped[i])
    {
    edgeParam = 1 - edgeParam;
    }
    return curParam + (edgeParam * edgeMultiplier);
    }
    else
    {
    curLen = curLen + edLen;
    }
    }
    throw "Cannot find the edge in the path";

    }

  • Caden_ArmstrongCaden_Armstrong Member Posts: 195 PRO
    Answer ✓

    You can calculate it,

    evDistance → set one side to qunion(path.edges)
    evDistance will give you the parameter (of the closest edge) but also the index. The order of the edges is preserved with qUnion across an array, so the index will match the path index.

    get total length of the path,
    get the first X lengths with like evLength(qUnion(subarray(path.edges, index))
    get the length of the closest edge
    (check if its flipped)
    length along closest edge is len*parameter (or 1-(len*parameter) if flipped)
    add that length to the total of the previous
    partial length / total path length → parameter.

    Example:
    https://cad.onshape.com/documents/fcdb4df9ade805c9057c2f51/w/3dcd322bf609a31bd074d1cb/e/531e02b4bd7b366654bb577b

    www.smartbenchsoftware.com --- fs.place --- Renaissance
    Custom FeatureScript and Onshape Integrated Applications
  • EvanReeseEvanReese Member, Mentor Posts: 2,186 ✭✭✭✭✭

    @Caden_Armstrong and @Jacob_Corder both nailed it. Thanks! I ended up writing my own function for it to make sure I really understood what was going on.

    Evan Reese
Sign In or Register to comment.