Hello,
I would like to pick a single point of a point cloud in a orthographic 3D View.
I follow the method described in:
https://thebuildingcoder.typepad.com/blog/2011/07/point-cloud-snap-and-freeze.html
I use PickPoint, so I have to define a SketchPlane in the active view.
And there is the problem...
I want to position the sketch plane at the position of the camera eye position and later, filter the extracted points of the pointcloud (via a selection filter) to give me the nearest point to the sketch plane, which would be the nearest point to the camera.
However when generating the sketch plane via:
Autodesk.Revit.DB.Plane.CreateByNormalAndOrigin(uiDoc.Document.ActiveView.ViewDirection, uiDoc.Document.ActiveView.Origin)
the position of the work/sketch plane is wrong. It is not in front of everything.
But when I switch from Orthographic to Perspective
and then move/zoom/pan the camera in Perspective Projection, and then switch back to Orthographic projection, and then again start the picking of the point cloud point the placed work/sketch plane is placed correctly infront of the pointcloud:
Why is "ActiveView.Origin" in the beginning wrong and just this change from Orthographic --> Perspective (move/pan/...) --> Orthographic can fix this problem?
Thanks
For 3D selection it is often better to use :
Selection.PickObject(ObjectType.PointOnElement)
Not sure how effective this is for point clouds however although I suspect it is just as useful as pick point.
I believe what you are experiencing is the difference between how perspective and parallel projection 3D views are laid out (in terms of origin position).
View3D | Revit | Autodesk Knowledge Network
ad 1) better use Selection.PickObject(ObjectType.PointOnElement):
OK, I can use this method with a selection filter the just allow picking on pointcloud instances.
Then I can get the point, on which the reference (pointcloud instance) is hit, that is GlobalPoint.
But how do I get the "real" point cloud coordinates, resp. actual points?
Previously I used this approach with PointCloudInstance.GetPoints:
"In either case, you should be able to obtain a 3D point representing the user's selection. The point will have no real relationship to the view and the front of the point cloud. Then you could use the View.ViewDirection property to compute a small rectangle centered on the picked point, and build planes for the PointCloudFilter through the boundary lines of the rectangle and extending parallel to the view direction. That will give you a filter capable of intersecting the point cloud and returning actual points. If too many points are returned, you may need a back clipping plane added to the filter to focus on the points nearest the user's eye position. " from: https://thebuildingcoder.typepad.com/blog/2011/07/point-cloud-snap-and-freeze.html
ad 2) Difference perspective and parallel projection 3D views
What is the difference? As I thought ActiveView.Origin or GetOrientation().EyePosition would also give me the right camera location for parallel projection.
How can I overcome these behavior?
I got the below working for orthographic views using the pick point approach.
The following steps are carried out
1. Get the minimum point of the UIView from UIView.GetZoomCorners
2. Set up a work plane using view direction and min point from (1)*
3. Pick the point on the plane
4. Filter the cloud by creating a box aligned with the view direction around the picked point
5. Transform the filtered cloud points to model space and project them onto plane and find nearest to original picked point.
*Since the user can rotate the view during selection of points this invalidates the work plane position for point selection. So I'm locking the view orientation after the projection plane is set.
Public Function Obj_220821c(commandData As ExternalCommandData, ByRef message As String, elements As ElementSet) As Result
Dim IntUIApp As UIApplication = commandData.Application
Dim IntUIDoc As UIDocument = commandData.Application.ActiveUIDocument
Dim IntDoc As Document = IntUIDoc.Document
Dim IntAView As View3D = TryCast(IntUIDoc.ActiveGraphicalView, View3D)
If IntAView Is Nothing Then
Throw New NotSupportedException("View must be View3D.")
End If
Dim UIV As UIView = IntUIDoc.GetOpenUIViews.FirstOrDefault(Function(q) q.ViewId = IntAView.Id)
Dim Max As XYZ = UIV.GetZoomCorners()(1)
Dim Min As XYZ = UIV.GetZoomCorners()(0)
Dim V3D As View3D = IntAView
If V3D.IsPerspective Then
Throw New NotSupportedException("View must be orthographic.")
End If
Dim R As Reference = Nothing
Try
R = IntUIDoc.Selection.PickObject(Selection.ObjectType.Element)
Catch ex As Exception
Return Result.Cancelled
End Try
Dim El As PointCloudInstance = TryCast(IntDoc.GetElement(R), PointCloudInstance)
If El Is Nothing Then
Return Result.Cancelled
End If
Dim Relock As Boolean = False
If V3D.IsLocked = False Then
If V3D.CanBeLocked = False Then
TaskDialog.Show("Rename", "Rename view so it can be temporarily locked during point selection.")
Return Result.Cancelled
Else
Using Tx As New Transaction(IntDoc, "Lock view")
If Tx.Start = TransactionStatus.Started Then
V3D.SaveOrientationAndLock()
Tx.Commit()
End If
End Using
End If
Else
Relock = True
End If
Dim PL As Plane = Plane.CreateByNormalAndOrigin(IntAView.ViewDirection, Min)
Using tx As New Transaction(IntDoc, "Set plane")
If tx.Start = TransactionStatus.Started Then
IntAView.SketchPlane = SketchPlane.Create(IntDoc, PL)
tx.Commit()
End If
End Using
Dim GP As XYZ
Try
GP = IntUIDoc.Selection.PickPoint()
Catch ex As Exception
GoTo ResCancel
End Try
Dim Bx As XYZ = IntAView.RightDirection
Dim By As XYZ = IntAView.UpDirection
Dim Bz As XYZ = IntAView.ViewDirection
Dim SZ As Double = 0.5 / 304.8
'Create filter box around global point
'Dim BtmPlane As Plane = Plane.CreateByNormalAndOrigin(Bz, Max - (Bz * SZ)) 'NOT USED
Dim TopPlane As Plane = Plane.CreateByNormalAndOrigin(-Bz, Min + (Bz * SZ))
Dim LeftPlane As Plane = Plane.CreateByNormalAndOrigin(Bx, GP - (Bx * SZ))
Dim RightPlane As Plane = Plane.CreateByNormalAndOrigin(-Bx, GP + (Bx * SZ))
Dim FrontPlane As Plane = Plane.CreateByNormalAndOrigin(By, GP - (By * SZ))
Dim BackPlane As Plane = Plane.CreateByNormalAndOrigin(-By, GP + (By * SZ))
Dim PList As List(Of Plane) = {TopPlane, LeftPlane, RightPlane, FrontPlane, BackPlane}.ToList
Dim PCF As PointClouds.PointCloudFilter = PointClouds.PointCloudFilterFactory.CreateMultiPlaneFilter(PList, 5)
Dim PC As PointClouds.PointCollection = El.GetPoints(PCF, 0.5 / 304.8, 1000) '1000000 Max
If PC.Count = 0 Then
GoTo ResCancel
End If
'Dim PL As Plane = V3D.SketchPlane.GetPlane
Dim CP_Lst As List(Of Tuple(Of PointClouds.CloudPoint, UV, Double)) = PC.Select(Function(x) ProjectedPoint(x, El, PL)).ToList
Dim TestPt As Tuple(Of PointClouds.CloudPoint, UV, Double) = ProjectedPoint(GP, PL)
Dim Dist As Double = CP_Lst.Min(Function(k) k.Item2.DistanceTo(TestPt.Item2))
Dim CP = CP_Lst.FindAll(Function(k) k.Item2.DistanceTo(TestPt.Item2) = Dist)
If CP.Count = 0 Then
GoTo ResCancel
End If
Using tx As New Transaction(IntDoc, "XX")
If tx.Start = TransactionStatus.Started Then
Dim CPxyz As XYZ = CloudPointToXYZ(CP(0).Item1)
Dim CPa As XYZ = El.GetTransform.OfPoint(CPxyz)
CPa.DrawPoint(IntDoc) 'Extension method that plots the point for checking
If Relock = False Then
V3D.Unlock()
End If
tx.Commit()
End If
End Using
GoTo ResOK
ResCancel:
Return Result.Cancelled
ResOK:
Return Result.Succeeded
End Function
Public Function ProjectedPoint(CP As PointClouds.CloudPoint, CPInst As PointCloudInstance, ProjectionPlane As Plane) As Tuple(Of PointClouds.CloudPoint, UV, Double)
Dim Pt As New XYZ(CP.X, CP.Y, CP.Z)
Pt = CPInst.GetTransform.OfPoint(Pt)
Dim Dist As Double = Double.PositiveInfinity
Dim UV As UV = Nothing
ProjectionPlane.Project(Pt, UV, Dist)
Dim Out As New Tuple(Of PointClouds.CloudPoint, UV, Double)(CP, UV, Dist)
Return Out
End Function
Public Function ProjectedPoint(PT As XYZ, ProjectionPlane As Plane) As Tuple(Of PointClouds.CloudPoint, UV, Double)
Dim Dist As Double = Double.PositiveInfinity
Dim UV As UV = Nothing
ProjectionPlane.Project(Pt, UV, Dist)
Dim Out As New Tuple(Of PointClouds.CloudPoint, UV, Double)(Nothing, UV, Dist)
Return Out
End Function
Private Function CloudPointToXYZ(CP As PointClouds.CloudPoint) As XYZ
Return New XYZ(CP.X, CP.Y, CP.Z)
End Function
Here is an example that works with pick object. It isn't as intuitive as the other option due to no snap icon but it will pick the points out when the point cloud instance lights up as highlighted. Also has the advantage of letting the user orientate the view while picking.
It is a similar process but uses ISelection filter to find the nearest point.
I tried to add a snap icon InCanvasControl but it seems to work better without due to highlighting of instance overriding the canvas control. If you do use InCanvasControl then the centre of the image should be transparent so that clicking of the control doesn't prevent selection.
Public Function Obj_220821b(commandData As ExternalCommandData, ByRef message As String, elements As ElementSet) As Result
Dim IntUIApp As UIApplication = commandData.Application
Dim IntUIDoc As UIDocument = commandData.Application.ActiveUIDocument
Dim IntDoc As Document = IntUIDoc.Document
Dim R As Reference = Nothing
Try
R = IntUIDoc.Selection.PickObject(Selection.ObjectType.Element)
Catch ex As Exception
Return Result.Cancelled
End Try
Dim El As PointCloudInstance = IntDoc.GetElement(R)
Dim UIV As UIView = IntUIDoc.GetOpenUIViews.FirstOrDefault(Function(k) k.ViewId = IntUIDoc.ActiveGraphicalView.Id)
If UIV Is Nothing Then
Return Result.Cancelled
End If
'Dim PCSF As New PointCloudSelectionFilter(IntDoc, UIV, El, "c:\temp\SnapIco.bmp")
'Works better without snap icon due to highlighting of point cloud when selected.
Dim PCSF As New PointCloudSelectionFilter(IntDoc, UIV, El)
Dim R1 As Reference = Nothing
Try
R1 = IntUIDoc.Selection.PickObject(Selection.ObjectType.Element, PCSF)
Catch ex As Exception
GoTo Failure
End Try
If PCSF.FoundPoint.HasValue Then
Using Tx As New Transaction(IntDoc, "PT")
If Tx.Start = TransactionStatus.Started Then
Dim PTxyz As XYZ = CloudPointToXYZ(PCSF.FoundPoint)
Dim PTxyzMdl As XYZ = El.GetTransform.OfPoint(PTxyz)
PTxyzMdl.DrawPoint(IntDoc)
Tx.Commit()
End If
End Using
End If
GoTo Success
Failure:
PCSF.HideLast()
Return Result.Cancelled
Success:
PCSF.HideLast()
Return Result.Succeeded
End Function
The main part is the implementation of ISelection filter below:
Public Class PointCloudSelectionFilter
Implements Selection.ISelectionFilter
Private IntUIV As UIView = Nothing
Private IntV As View = Nothing
Private IntPC As PointCloudInstance = Nothing
Private IntFoundPoint As PointClouds.CloudPoint?
Private IntSPImg_Idx As Integer = -1
Private IntSPImg As InCanvasControlData = Nothing
Private IntTGM As TemporaryGraphicsManager = Nothing
Private IntSnapVisible As Boolean = False
Public ReadOnly Property FoundPoint As PointClouds.CloudPoint?
Get
Return IntFoundPoint
End Get
End Property
Public Sub New(D As Document, UIV As UIView, PC As PointCloudInstance, Optional SnapImagePath As String = "")
If D Is Nothing OrElse UIV Is Nothing OrElse PC Is Nothing Then
Throw New ArgumentNullException
End If
IntV = D.GetElement(UIV.ViewId)
If IntV.ViewType <> ViewType.ThreeD Then
Throw New ArgumentException("View of UIV to be View3D")
End If
IntUIV = UIV
IntPC = PC
If String.IsNullOrEmpty(SnapImagePath) = False Then
If IO.File.Exists(SnapImagePath) Then
IntSPImg = New InCanvasControlData(SnapImagePath)
IntTGM = TemporaryGraphicsManager.GetTemporaryGraphicsManager(D)
IntSPImg_Idx = IntTGM.AddControl(IntSPImg, IntV.Id)
IntTGM.SetVisibility(IntSPImg_Idx, False)
End If
End If
End Sub
Private Sub SetSnapIcon(Pos As XYZ)
If IntTGM Is Nothing Then
Exit Sub
End If
If IntSnapVisible = False Then
IntSnapVisible = True
IntTGM.SetVisibility(IntSPImg_Idx, True)
End If
IntSPImg.Position = Pos
IntTGM.UpdateControl(IntSPImg_Idx, IntSPImg)
End Sub
Private Sub HideSnapIcon()
If IntTGM Is Nothing Then
Exit Sub
End If
If IntSnapVisible = True Then
IntSnapVisible = False
IntTGM.SetVisibility(IntSPImg_Idx, False)
End If
End Sub
Public Sub HideLast()
If IntTGM Is Nothing Then
Exit Sub
End If
IntTGM.Clear()
End Sub
Private Function GetProjectionPlane() As Plane
Dim Origin As XYZ = IntUIV.GetZoomCorners(0)
Dim Dir As XYZ = IntV.ViewDirection
Return Plane.CreateByNormalAndOrigin(Dir, Origin)
End Function
Private Function ProjectedCP(PL As Plane, CP As PointClouds.CloudPoint, T As Transform) As Tuple(Of PointClouds.CloudPoint, UV, Double)
Dim Dist As Double = Double.PositiveInfinity
Dim UV As UV = Nothing
Dim CP0 As XYZ = CloudPointToXYZ(CP)
CP0 = T.OfPoint(CP0)
PL.Project(CP0, UV, Dist)
Return New Tuple(Of PointClouds.CloudPoint, UV, Double)(CP, UV, Dist)
End Function
Private Function ProjectedXYZ(PL As Plane, P As XYZ) As Tuple(Of PointClouds.CloudPoint, UV, Double)
Dim Dist As Double = Double.PositiveInfinity
Dim UV As UV = Nothing
PL.Project(P, UV, Dist)
Return New Tuple(Of PointClouds.CloudPoint, UV, Double)(Nothing, UV, Dist)
End Function
Public Function AllowElement(elem As Element) As Boolean Implements Selection.ISelectionFilter.AllowElement
' IntFoundPoint = Nothing
If elem.Document.ActiveView.Id <> IntV.Id Then
Return False
End If
If elem.GetType IsNot GetType(PointCloudInstance) Then Return False Else
Dim Inst As Instance = elem
Dim T As Transform = Inst.GetTransform
Dim GP As XYZ = elem.Document.ConvertScreenPositionToModel(IntUIV, Windows.Forms.Cursor.Position)
Dim ViewExtentModel As List(Of XYZ) = IntUIV.GetZoomCorners
Dim Min As XYZ = ViewExtentModel(0)
Dim Bx As XYZ = IntV.RightDirection
Dim By As XYZ = IntV.UpDirection
Dim Bz As XYZ = IntV.ViewDirection
Dim SZ As Double = 1 / 304.8
'Create filter box 2ft around global point
'Dim BtmPlane As Plane = Plane.CreateByNormalAndOrigin(Bz, GP - Bz) far clip not usd
Dim TopPlane As Plane = Plane.CreateByNormalAndOrigin(-Bz, Min + (Bz * SZ))
Dim LeftPlane As Plane = Plane.CreateByNormalAndOrigin(Bx, GP - (Bx * SZ))
Dim RightPlane As Plane = Plane.CreateByNormalAndOrigin(-Bx, GP + (Bx * SZ))
Dim FrontPlane As Plane = Plane.CreateByNormalAndOrigin(By, GP - (By * SZ))
Dim BackPlane As Plane = Plane.CreateByNormalAndOrigin(-By, GP + (By * SZ))
Dim PList As List(Of Plane) = {TopPlane, LeftPlane, RightPlane, FrontPlane, BackPlane}.ToList
Dim PCF As PointClouds.PointCloudFilter = PointClouds.PointCloudFilterFactory.CreateMultiPlaneFilter(PList, 5)
Dim PC As PointClouds.PointCollection = IntPC.GetPoints(PCF, 0.5 / 304.8, 100000) '1000000 max
If PC.Count = 0 Then
Return False
End If
Dim PL As Plane = GetProjectionPlane()
Dim CPs As List(Of Tuple(Of PointClouds.CloudPoint, UV, Double)) =
PC.Select(Function(q) ProjectedCP(PL, q, T)).ToList
Dim Check As Tuple(Of PointClouds.CloudPoint, UV, Double) = ProjectedXYZ(PL, GP)
Dim Dist As Double = CPs.Min(Function(q) q.Item2.DistanceTo(Check.Item2))
Dim CP_lst As List(Of Tuple(Of PointClouds.CloudPoint, UV, Double)) _
= CPs.FindAll(Function(q) q.Item2.DistanceTo(Check.Item2) = Dist)
If CP_lst.Count > 0 Then
IntFoundPoint = CP_lst(0).Item1
If IntTGM IsNot Nothing Then
Dim CPx As XYZ = CloudPointToXYZ(IntFoundPoint.Value)
CPx = T.OfPoint(CPx)
'shoft to view plane in front
Dim ProjDist As Double = Double.PositiveInfinity
PL.Project(CPx, Nothing, ProjDist)
CPx += (Bz * 2)
SetSnapIcon(CPx)
End If
Return True
Else
If IntTGM IsNot Nothing Then
HideSnapIcon()
End If
Return False
End If
End Function
Public Function AllowReference(reference As Reference, position As XYZ) As Boolean Implements Selection.ISelectionFilter.AllowReference
Return True
End Function
Private Function CloudPointToXYZ(CP As PointClouds.CloudPoint) As XYZ
Return New XYZ(CP.X, CP.Y, CP.Z)
End Function
End Class
Lastly this is key extension required:
<Extension>
Public Function ConvertScreenPositionToModel(D As Document, UIV As UIView, PT As System.Drawing.Point) As XYZ
Dim V As View = D.GetElement(UIV.ViewId)
Dim ViewExtentModel As List(Of XYZ) = UIV.GetZoomCorners
Dim T As Transform = Transform.Identity
T.BasisX = V.RightDirection
T.BasisY = V.UpDirection
T.BasisZ = V.ViewDirection
For i = 0 To 1
ViewExtentModel(i) = T.Inverse.OfPoint(ViewExtentModel(i))
Next
Dim ViewExtMin As XYZ = ViewExtentModel(0)
Dim ViewExtMax As XYZ = ViewExtentModel(1)
Dim SizeMdl As XYZ = ViewExtMax - ViewExtMin
Dim WindowExt As Rectangle = UIV.GetWindowRectangle
Dim WinW As Double = WindowExt.Right - WindowExt.Left
Dim WinH As Double = WindowExt.Bottom - WindowExt.Top
Dim CursorRelPosX As Double = PT.X - WindowExt.Left
Dim CursorRelPosY As Double = PT.Y - WindowExt.Top
Dim RatioX As Double = CursorRelPosX / WinW
Dim RatioY As Double = CursorRelPosY / WinH
Dim OffsetX As Double = SizeMdl.X * RatioX
Dim OffsetY As Double = SizeMdl.Y * RatioY
Dim TL As New XYZ(ViewExtMin.X, ViewExtMax.Y, ViewExtMin.Z)
Dim Pos As XYZ = TL + (OffsetX * XYZ.BasisX) - (OffsetY * XYZ.BasisY)
Dim Res As XYZ = T.OfPoint(Pos)
Return Res
End Function
Extension for converting mouse cursor position to model position, there are probably other ways of doing this floating about:
Hi, thanks @RPTHOMAS108 for your examples, they really helped me.
As I need to visualize my picked point cloud points, could you possible give me a the code for your extension DrawPoint?
Thanks
Yes I just draw a line in the three principle directions and then colour them:
You can do similar for the origin and basis of transforms which is quite useful also.
<Extension()>
Public Sub DrawPoint(Point As XYZ, Doc As Document, Optional PtSz As Double = 1)
Dim Ln As Double = PtSz / 2
Dim LNxx As Line = Line.CreateBound(Point - (XYZ.BasisX * Ln), Point + (XYZ.BasisX * Ln))
Dim LNyy As Line = Line.CreateBound(Point - (XYZ.BasisY * Ln), Point + (XYZ.BasisY * Ln))
Dim LNzz As Line = Line.CreateBound(Point - (XYZ.BasisZ * Ln), Point + (XYZ.BasisZ * Ln))
Dim PlaneXY As SketchPlane = SketchPlane.Create(Doc, Plane.CreateByOriginAndBasis(Point, XYZ.BasisX, XYZ.BasisY))
Dim PlaneXZ As SketchPlane = SketchPlane.Create(Doc, Plane.CreateByOriginAndBasis(Point, XYZ.BasisX, XYZ.BasisZ))
Dim MCxx As ModelCurve = Doc.Create.NewModelCurve(LNxx, PlaneXY)
Dim MCyy As ModelCurve = Doc.Create.NewModelCurve(LNyy, PlaneXY)
Dim MCzz As ModelCurve = Doc.Create.NewModelCurve(LNzz, PlaneXZ)
Const ax_xx As String = "Axis_XX_Red"
Const ax_yy As String = "Axis_YY_Green"
Const ax_zz As String = "Axis_ZZ_Blue"
MCxx.LineStyle = Doc.AddOrChangeLineStyle(ax_xx, New Color(255, 0, 0), 1, "Dash")
MCyy.LineStyle = Doc.AddOrChangeLineStyle(ax_yy, New Color(0, 255, 0), 1, "Dash")
MCzz.LineStyle = Doc.AddOrChangeLineStyle(ax_zz, New Color(0, 0, 255), 1, "Dash")
End Sub
<Extension()>
Public Function AddOrChangeLineStyle(Doc As Document, Name As String,
Optional Colour As Color = Nothing,
Optional Weight As Integer = -1,
Optional PatternName As String = Nothing) As GraphicsStyle
Dim Out As GraphicsStyle = Nothing
Dim CC As Category = Category.GetCategory(Doc, BuiltInCategory.OST_Lines)
Dim SC As Category = Nothing
If CC.SubCategories.Contains(Name) = False Then
SC = Doc.Settings.Categories.NewSubcategory(CC, Name)
Else
SC = CC.SubCategories.Item(Name)
End If
Out = SC.GetGraphicsStyle(GraphicsStyleType.Projection)
If Colour Is Nothing = False Then
Out.GraphicsStyleCategory.LineColor = Colour
End If
If Weight > 0 Then
Out.GraphicsStyleCategory.SetLineWeight(Weight, GraphicsStyleType.Projection)
End If
If String.IsNullOrEmpty(PatternName) = False Then
Dim FEC As New FilteredElementCollector(Doc)
Dim ECF As New ElementClassFilter(GetType(LinePatternElement))
Dim Els As List(Of Element) = FEC.WherePasses(ECF).ToElements
Dim Itm As Element = Els.Find(Function(x) x.Name = PatternName)
If Itm Is Nothing = False Then
Out.GraphicsStyleCategory.SetLinePatternId(Itm.Id, GraphicsStyleType.Projection)
End If
End If
Return Out
End Function
Hi,
thank you @RPTHOMAS108 for your code snippets. I tried to derive a 3D pick-ray using your ConvertScreenPositionToModel function for the ray-origin and ActiveGraphicalView.ViewDirection for its direction.
var activeView = uiDoc.ActiveGraphicalView;
var uiView = uiDoc.GetOpenUIViews().FirstOrDefault(k => k.ViewId == activeView.Id);
var viewDir = activeView.ViewDirection;
var origin = ConvertScreenPositionToModel(uiDoc.Document, uiView, Cursor.Position);
//visualize ray-origin
using (Transaction tx = new Transaction(uiDoc.Document, "rayorigin"))
{
if (tx.Start() == TransactionStatus.Started)
{
DrawPoint(origin, uiDoc.Document, 10);
tx.Commit();
}
}
//... custom components using Ray(viewDir,origin)
The direction seems to be correct, however, the origin is only correct if computed with active perspective projection. In ortho-projection the Z-part of ViewExtentModel (from GetZoomCorners) is not correct. Strangely, it is correct if I toggle perspective projection, zoom extents and return to orthographic projection or if I activate a "default" view via the "3D view cube" in orthographic projection. Can this be a bug? Some camera initialization missing in orthographic projection? Something thats only initialized when doing "zoom extents"?
Wasn't this the reason for this thread, I don't think I used view origin anywhere above?
What you experience is probably the target updating the origin when it is switches between view projection types. If you are expecting the View.Origin to be aligning with the centre of the screen, then this is probably wrong. In perspective the camera is fixed and the centre of the screen has to align with a target. In orthographic the camera is not fixed. You can pan sideways but the view origin will remain where it is since it probably doesn't need to update to carry out that projection type. We have to remember that these things are exposed to the API from the inner workings which I'm guessing would only update them if there were a reason to do so for their purposes.
Why do you need View.Origin for your task? You have the model position you are viewing from the window extents. I believe they are in model coords so you just need to average them to get an arbitrary centre.
I don't know how you can get Z from that method anyway since you are picking on a screen.
There is probably a logic to the Z values used in GetZoomCorners but you would probably have to plot the box parallel to the view to understand the logic. I think it'll fall short of the model because it is based on arbitrary target.
I think Z must be on the camera's near plane and therefore slightly in front of the camera location in model coordinates. Maybe, we can get the information from the CameraInfo class...
If you transform the two points you get from GetZoomCorners in terms of the Max/Mins into the view coordinate system you'll likely find the Z values are the same for the two points indicating they sit on a plane aligned with that system. You'll also find they share the same plane as the view origin. I don't believe this changes regardless of the projection mode.
Yes, I could also observe this behavior. The problem is, that if I don't toggle perspective mode or a default view from the "view cube" before, the viewplane cuts through the scene (and raycast from viewplane-center misses objects behind the view plane). The Z-position of the camera in model-space isn't "behind" the scene, as one would suspect, but within the scene, such that objects are geometrically behind the camera (but visible in 3D-view...). Thats the same behavior as in the starting post of this thread...
Scene: Wall starting at 0/0/0 along X-axis with length 30cm
Red view extents before perspective or "view cube"-toggle + zoom-extents
Green view extents after perspective or "view cube"-toggle + zoom-extents
Conclusio: I cannot get the correct starting point of a ray in orthographic projection to do a query on the visible spatial bounds of the 3D view...
In my examples I was switching between perspective and orthographic but I didn't do the zoom extents part.
I don't think the CameraInfo class is applicable, there is the GetViewOrientation3D that reports the eye position, up direction and forward direction. Apart from those you also have the parameters Target Elevation and Eye Elevation.
So in theory would have thought you can derive the target point from the intersection of the target elevation XY plane and the forward direction from the eye origin. Alternatively work from eye elevation down/up to target elevation then you can solve the triangle that has two known angles and a side (the side you know is distance from eye elevation to target elevation). The forward direction will allow you to form one known angle to vertical and the other known angle is a right angle i.e. you know all three angles and one side. The only issue with that is when the target and eye share the same elevation.
However I don't know if any of that helps in terms of what you are ultimately trying to achieve. As above in this original thread there may be a workaround.
Hi @RPTHOMAS108
I just wanted to say a big thank you for sharing your knowledge and code snippet. It's been really helpful to me. I was wondering if you could spare a moment to help me out. I'm trying to figure out how to select a point from a point cloud in Revit project and find its coordinates accuratly. Would you mind taking a look
using Autodesk.Revit.Attributes;
using Autodesk.Revit.DB;
using Autodesk.Revit.UI;
using Autodesk.Revit.UI.Selection;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Drawing;
using Autodesk.Revit.DB.PointClouds;
using System.Windows.Forms;
public static class ExtensionMethods
{
public static XYZ ConvertScreenPositionToModel(this Document D, UIView UIV, System.Drawing.Point PT)
{
Autodesk.Revit.DB.View V = (Autodesk.Revit.DB.View)D.GetElement(UIV.ViewId);
IList<XYZ> ViewExtentModel = UIV.GetZoomCorners();
Transform T = Transform.Identity;
T.BasisX = V.RightDirection;
T.BasisY = V.UpDirection;
T.BasisZ = V.ViewDirection;
XYZ ViewExtMin = ViewExtentModel[0];
XYZ ViewExtMax = ViewExtentModel[1];
XYZ SizeMdl = ViewExtMax - ViewExtMin;
System.Drawing.Rectangle WindowExt = System.Windows.Forms.Screen.PrimaryScreen.Bounds; // Assuming full screen. Adjust as necessary.
double WinW = WindowExt.Width;
double WinH = WindowExt.Height;
double CursorRelPosX = PT.X - WindowExt.Left;
double CursorRelPosY = PT.Y - WindowExt.Top;
double RatioX = CursorRelPosX / WinW;
double RatioY = CursorRelPosY / WinH;
double OffsetX = SizeMdl.X * RatioX;
double OffsetY = SizeMdl.Y * RatioY;
XYZ TL = new XYZ(ViewExtMin.X, ViewExtMax.Y, ViewExtMin.Z);
XYZ Pos = TL + (OffsetX * XYZ.BasisX) - (OffsetY * XYZ.BasisY);
XYZ Res = T.OfPoint(Pos);
return Res;
}
}
namespace RevitNewTest
{
[Transaction(TransactionMode.Manual)]
public class PointCloudpickpoint : IExternalCommand
{
public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)
{
UIApplication uiApp = commandData.Application;
UIDocument uiDoc = uiApp.ActiveUIDocument;
Document doc = uiDoc.Document;
Reference reference = null;
try
{
reference = uiDoc.Selection.PickObject(ObjectType.Element);
}
catch (Exception)
{
return Result.Cancelled;
}
// Yahaan par hum type check aur cast ka use kar rahe hain instead of 'is not' pattern ka.
Element elem = doc.GetElement(reference);
if (!(elem is PointCloudInstance element))
{
return Result.Failed;
}
UIView uiView = uiDoc.GetOpenUIViews().FirstOrDefault(v => v.ViewId == uiDoc.ActiveGraphicalView.Id);
if (uiView == null)
{
return Result.Cancelled;
}
PointCloudSelectionFilter filter = new PointCloudSelectionFilter(doc, uiView, element);
Reference pickedRef = null;
try
{
pickedRef = uiDoc.Selection.PickObject(ObjectType.Element, filter);
}
catch (Exception)
{
filter.HideLast();
return Result.Cancelled;
}
if (filter.FoundPoint.HasValue)
{
using (Transaction tx = new Transaction(doc, "PointCloud Transaction"))
{
if (tx.Start() == TransactionStatus.Started)
{
XYZ pointXYZ = CloudPointToXYZ(filter.FoundPoint.Value);
XYZ modelPoint = element.GetTransform().OfPoint(pointXYZ);
// DrawPoint(doc, modelPoint); // Implement this method based on your requirements
TaskDialog.Show("Coordinate Info", "Selected Point Coordinates (X, Y, Z): " + modelPoint.X.ToString() + ", " + modelPoint.Y.ToString() + ", " + modelPoint.Z.ToString());
tx.Commit();
}
}
}
filter.HideLast();
return Result.Succeeded;
}
private static XYZ CloudPointToXYZ(CloudPoint cloudPoint)
{
return new XYZ(cloudPoint.X, cloudPoint.Y, cloudPoint.Z);
}
}
public class PointCloudSelectionFilter : ISelectionFilter
{
private UIView IntUIV = null;
private View3D IntV = null;
private PointCloudInstance IntPC = null;
private CloudPoint? IntFoundPoint = null;
private int IntSPImg_Idx = -1;
private InCanvasControlData IntSPImg = null;
private TemporaryGraphicsManager IntTGM = null;
private bool IntSnapVisible = false;
public CloudPoint? FoundPoint
{
get { return IntFoundPoint; }
}
public PointCloudSelectionFilter(Document D, UIView UIV, PointCloudInstance PC, string SnapImagePath = "")
{
if (D == null || UIV == null || PC == null)
throw new ArgumentNullException();
IntV = D.GetElement(UIV.ViewId) as View3D;
if (IntV.ViewType != ViewType.ThreeD)
throw new ArgumentException("View of UIV to be View3D");
IntUIV = UIV;
IntPC = PC;
if (!string.IsNullOrEmpty(SnapImagePath) && File.Exists(SnapImagePath))
{
IntSPImg = new InCanvasControlData(SnapImagePath);
IntTGM = TemporaryGraphicsManager.GetTemporaryGraphicsManager(D);
IntSPImg_Idx = IntTGM.AddControl(IntSPImg, IntV.Id);
IntTGM.SetVisibility(IntSPImg_Idx, false);
}
}
private void SetSnapIcon(XYZ Pos)
{
if (IntTGM == null)
return;
if (!IntSnapVisible)
{
IntSnapVisible = true;
IntTGM.SetVisibility(IntSPImg_Idx, true);
}
IntSPImg.Position = Pos;
IntTGM.UpdateControl(IntSPImg_Idx, IntSPImg);
}
private void HideSnapIcon()
{
if (IntTGM == null)
return;
if (IntSnapVisible)
{
IntSnapVisible = false;
IntTGM.SetVisibility(IntSPImg_Idx, false);
}
}
public void HideLast()
{
if (IntTGM == null)
return;
IntTGM.Clear();
}
private Plane GetProjectionPlane()
{
XYZ origin = IntUIV.GetZoomCorners().First();
XYZ direction = IntV.ViewDirection;
return Plane.CreateByNormalAndOrigin(direction, origin);
}
private Tuple<CloudPoint, UV, double> ProjectedCP(Plane PL, CloudPoint CP, Transform T)
{
XYZ pointXYZ = CloudPointToXYZ(CP);
pointXYZ = T.OfPoint(pointXYZ);
UV uv; double distance;
PL.Project(pointXYZ, out uv, out distance);
return new Tuple<CloudPoint, UV, double>(CP, uv, distance);
}
private XYZ CloudPointToXYZ(CloudPoint cloudPoint)
{
return new XYZ(cloudPoint.X, cloudPoint.Y, cloudPoint.Z);
}
/*
private Tuple<CloudPoint, UV, double> ProjectedXYZ(Plane PL, XYZ P)
{
double Dist = double.PositiveInfinity;
UV UV = null;
PL.Project(P, out UV, out Dist);
return new Tuple<CloudPoint, UV, double>(null, UV, Dist);
}*/
// Adjusted to accept an XYZ point and a Transform
// Adjusted to accept an XYZ point and a Transform
private Tuple<XYZ, UV, double> ProjectedXYZ(Plane PL, XYZ point, Transform T)
{
double distance = double.PositiveInfinity;
UV uv = new UV(); // Initialize a new UV variable
XYZ transformedPoint = T.OfPoint(point);
PL.Project(transformedPoint, out uv, out distance); // Pass the initialized UV variable with out
return new Tuple<XYZ, UV, double>(transformedPoint, uv, distance);
}
public bool AllowReference(Reference reference, XYZ position)
{
return true;
}
/*private Tuple<CloudPoint?, UV, double> ProjectedXYZ(Plane PL, XYZ P)
{
UV uv; double distance;
PL.Project(P, out uv, out distance);
// Now explicitly allowing CloudPoint to be null in the tuple.
return new Tuple<CloudPoint?, UV, double>(null, uv, distance);
}*/
public bool AllowElement(Element elem)
{
if (elem.Document.ActiveView.Id != IntV.Id)
return false;
if (!(elem is PointCloudInstance))
return false;
Instance Inst = elem as Instance;
Transform T = Inst.GetTransform();
XYZ GP = elem.Document.ConvertScreenPositionToModel(IntUIV, System.Windows.Forms.Cursor.Position);
IList<XYZ> ViewExtentModel = IntUIV.GetZoomCorners();
XYZ Min = ViewExtentModel[0];
XYZ Bx = IntV.RightDirection;
XYZ By = IntV.UpDirection;
XYZ Bz = IntV.ViewDirection;
double SZ = 1 / 304.8;
Plane TopPlane = Plane.CreateByNormalAndOrigin(-Bz, Min + (Bz * SZ));
Plane LeftPlane = Plane.CreateByNormalAndOrigin(Bx, GP - (Bx * SZ));
Plane RightPlane = Plane.CreateByNormalAndOrigin(-Bx, GP + (Bx * SZ));
Plane FrontPlane = Plane.CreateByNormalAndOrigin(By, GP - (By * SZ));
Plane BackPlane = Plane.CreateByNormalAndOrigin(-By, GP + (By * SZ));
List<Plane> PList = new List<Plane> { TopPlane, LeftPlane, RightPlane, FrontPlane, BackPlane };
PointCloudFilter PCF = PointCloudFilterFactory.CreateMultiPlaneFilter(PList, 5);
PointCollection PC = IntPC.GetPoints(PCF, 0.5 / 304.8, 100000);
if (PC.Count == 0)
return false;
Plane PL = GetProjectionPlane();
List<Tuple<CloudPoint, UV, double>> CPs =
PC.Select(q => ProjectedCP(PL, q, T)).ToList();
// Assuming GP is an XYZ point and T is the transform you want to apply
Tuple<XYZ, UV, double> Check = ProjectedXYZ(PL, GP, T); // Ensure 'T' is defined and valid in this context
double Dist = CPs.Min(q => q.Item2.DistanceTo(Check.Item2));
List<Tuple<CloudPoint, UV, double>> CP_lst =
CPs.FindAll(q => q.Item2.DistanceTo(Check.Item2) == Dist);
if (CP_lst.Count > 0)
{
IntFoundPoint = CP_lst[0].Item1;
if (IntTGM != null)
{
XYZ CPx = CloudPointToXYZ(IntFoundPoint.Value);
CPx = T.OfPoint(CPx);
UV dummyUV = new UV(); // Initialize a dummy UV variable
double ProjDist = double.PositiveInfinity;
PL.Project(CPx, out dummyUV, out ProjDist); // Use the dummy UV variable
CPx += (Bz * 2);
SetSnapIcon(CPx);
}
return true;
}
else
{
if (IntTGM != null)
HideSnapIcon();
return false;
}
}
}
}
Can't find what you're looking for? Ask the community or share your knowledge.