Why MostRecentPickIntersections fails in 3D
Looking at the source code in SelectWithMousePolicy.cs (line 539-573), the intersection logic works by:
1. Getting the PickAperature and calling aperture.ComputePolySeg() to get the selection fence as a PolySeg
2. Transforming it to UCS via sendingView.WorldToUCS
3. For each selected entity that implements IPolyseg, calling pickwindowpolyseg.Intersect(entityPolyseg, true, ints)
The problem: The HOOPS 3D PickAperature (Trimble.Vce.Hoops.PickAperature) does not implement IPolyseg and has no ComputePolySeg() method. So in a 3D view, the aperture cannot produce a polyseg, and the intersection list stays empty.
However, the selection itself still works - the 3D view does return the serial numbers of selected entities through GlobalSelection.SelectedMembers. Only the intersection point data is missing.
Implementation Strategy
After the MemberSelection completes in 3D, you have:
• ✅ The list of selected entity serial numbers
• ✅ Access to each entity's geometry via IPolyseg.ComputePolySeg()
• ✅ The user's fence polygon vertices (from the PickAperature.ModelAperatureLocation or stored pick points)
• ❌ No intersection points (empty list)z
You can replicate the intersection logic yourself:
using Trimble.Vce.Geometry;
using Trimble.Vce.Geometry.PolySeg;
using Trimble.Vce.Interfaces.SnapIn;
/// <summary>
/// Computes fence-polygon crossings for selected entities in a 3D view,
/// replicating what MostRecentPickIntersections does in 2D.
/// </summary>
public class FenceCrossingCalculator
{
private readonly IProject _project;
public FenceCrossingCalculator(IProject project)
{
_project = project;
}
/// <summary>
/// Given the user's fence polygon points and the selected entity serial numbers,
/// computes which end of each line entity was crossed by the fence.
/// </summary>
/// <param name="fencePoints">
/// The user's selection polygon vertices in model/world coordinates.
/// Collect these from the MemberSelection's pick points or the PickAperature.
/// </param>
/// <param name="selectedSerials">
/// Serial numbers of entities selected by the MemberSelection.
/// </param>
/// <returns>
/// For each entity: its serial number, the intersection points, and which end was crossed.
/// </returns>
public IEnumerable<EntityCrossingResult> ComputeCrossings(
IList<Point3D> fencePoints,
IEnumerable<uint> selectedSerials)
{
// 1. Build a PolySeg from the fence polygon
var fencePolySeg = BuildFencePolySeg(fencePoints);
if (fencePolySeg == null)
yield break;
// 2. For each selected entity, compute intersections
foreach (var serial in selectedSerials)
{
var snapIn = _project.Concordance.Lookup(serial);
if (snapIn is IPolyseg polysegProvider == false)
continue;
var entityPolySeg = polysegProvider.ComputePolySeg();
if (entityPolySeg == null)
continue;
// 3. Compute intersections between the fence and the entity
var intersections = new Intersections();
fencePolySeg.Intersect(entityPolySeg, true, intersections);
if (intersections.Count == 0)
continue;
// 4. Determine which end was crossed
var startPoint = entityPolySeg.StartPoint;
var endPoint = entityPolySeg.EndPoint;
var crossedEnd = DetermineClosestEnd(intersections, startPoint, endPoint);
yield return new EntityCrossingResult
{
SerialNumber = serial,
Intersections = intersections,
CrossedEnd = crossedEnd,
ClosestIntersectionPoint = GetClosestIntersection(intersections, crossedEnd, startPoint, endPoint)
};
}
}
/// <summary>
/// Builds a closed PolySeg from the fence polygon points.
/// </summary>
private PolySeg BuildFencePolySeg(IList<Point3D> fencePoints)
{
if (fencePoints == null || fencePoints.Count < 3)
return null;
var polyseg = new PolySeg();
// Add the polygon points. Project to 2D (XY plane) for intersection math
// since PolySeg.Intersect works in 2D.
foreach (var pt in fencePoints)
{
polyseg.Add(new Point3D(pt.X, pt.Y, 0.0));
}
// Close the polygon by adding the first point again
if (fencePoints[0].X != fencePoints[fencePoints.Count - 1].X ||
fencePoints[0].Y != fencePoints[fencePoints.Count - 1].Y)
{
polyseg.Add(new Point3D(fencePoints[0].X, fencePoints[0].Y, 0.0));
}
return polyseg;
}
/// <summary>
/// Determines which end of the entity is closest to the first intersection point.
/// </summary>
private CrossedEndType DetermineClosestEnd(
Intersections intersections,
Point3D startPoint,
Point3D endPoint)
{
// Use the first intersection point
var firstIntersection = intersections[0].Point;
var distToStart = Distance2D(firstIntersection, startPoint);
var distToEnd = Distance2D(firstIntersection, endPoint);
return distToStart <= distToEnd ? CrossedEndType.Start : CrossedEndType.End;
}
/// <summary>
/// Gets the intersection point closest to the crossed end.
/// </summary>
private Point3D GetClosestIntersection(
Intersections intersections,
CrossedEndType crossedEnd,
Point3D startPoint,
Point3D endPoint)
{
var referencePoint = crossedEnd == CrossedEndType.Start ? startPoint : endPoint;
Point3D closest = intersections[0].Point;
double minDist = Distance2D(closest, referencePoint);
for (int i = 1; i < intersections.Count; i++)
{
var dist = Distance2D(intersections[i].Point, referencePoint);
if (dist < minDist)
{
minDist = dist;
closest = intersections[i].Point;
}
}
return closest;
}
private static double Distance2D(Point3D a, Point3D b)
{
var dx = a.X - b.X;
var dy = a.Y - b.Y;
return Math.Sqrt(dx * dx + dy * dy);
}
}
public enum CrossedEndType
{
Start,
End
}
public class EntityCrossingResult
{
public uint SerialNumber { get; set; }
public Intersections Intersections { get; set; }
public CrossedEndType CrossedEnd { get; set; }
public Point3D ClosestIntersectionPoint { get; set; }
}
Usage in your command
// In your command after MemberSelection completes in a 3D view:
// 1. Check if MostRecentPickIntersections is empty (3D view case)
var pickIntersections = memberSelection.MostRecentPickIntersections;
if (pickIntersections == null || pickIntersections.Any() == false)
{
// 3D view fallback: compute crossings manually
var selectedMembers = GlobalSelection.SelectedMembers(project);
var serials = selectedMembers.OfType<ISnapIn>().Select(s => s.SerialNumber);
// Get the fence points from the pick aperture or stored mouse picks
var fencePoints = GetFencePointsFromSelection(); // your stored polygon points
var calculator = new FenceCrossingCalculator(project);
var crossings = calculator.ComputeCrossings(fencePoints, serials);
foreach (var crossing in crossings)
{
// crossing.SerialNumber - the entity
// crossing.CrossedEnd - Start or End
// crossing.ClosestIntersectionPoint - where the fence crossed near that end
// Use this to determine extension direction
ProcessEntityExtension(crossing.SerialNumber, crossing.CrossedEnd);
}
}
else
{
// 2D view: use MostRecentPickIntersections directly (existing code)
foreach (var (serial, intersections) in pickIntersections)
{
// ... existing logic ...
}
}
Key Points
1. PolySeg.Intersect is a 2D operation - it projects to XY. For 3D views, you'll want to flatten the Z coordinate before intersecting, which is essentially what WorldToUCS does in the 2D path (line 550-551 in SelectWithMousePolicy.cs). If your lines are tilted in 3D, consider applying sendingView.WorldToUCS to both the fence and entity polysegs before intersecting.
2. Collecting the fence polygon points - you need to store the user's fence/polygon pick points yourself (e.g., from MouseDownInView events), or extract them from the PickAperature.ModelAperatureLocation (for rectangular selections, gives you ptMin/ptMax which defines the 4 corners).
3. IPolyseg.ComputePolySeg() is available on any entity that has geometry (lines, arcs, polylines, etc.), so the approach works for any line-type entity you'd want to extend.
Let me know if this helps
------------------------------
Bryce Haire
------------------------------