// // (C) Copyright 2003-2023 by Autodesk, Inc. // // Permission to use, copy, modify, and distribute this software in // object code form for any purpose and without fee is hereby granted, // provided that the above copyright notice appears in all copies and // that both that copyright notice and the limited warranty and // restricted rights notice below appear in all supporting // documentation. // // AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS. // AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF // MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC. // DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE // UNINTERRUPTED OR ERROR FREE. // // Use, duplication, or disclosure by the U.S. Government is subject to // restrictions set forth in FAR 52.227-19 (Commercial Computer // Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii) // (Rights in Technical Data and Computer Software), as applicable. // using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using Autodesk.Revit.DB; using Autodesk.Revit.UI; using TaskDialog = Autodesk.Revit.UI.TaskDialog; namespace Revit.SDK.Samples.RayTraceBounce.CS { /// /// 1. This form allowing entry of a coordinate location (X, Y, Z) within the model and a coordinate direction (i, j, k). /// 2. Launch a ray from this location in this direction to find the first intersection with a face /// 3. Calculate the reflection angle of the ray from the face and launch another ray to find the next intersection /// 4. For each ray/intersection, create a model line connecting the two points. The end result should be a series of model lines bouncing from item to item. /// 5. Provide a hard limit of say, 100 intersections, to prevent endless reflections within an enclosed space. /// 6. Write a log file of the intersection containing: the element type, id, and material of the intersected face. /// public partial class RayTraceBounceForm : System.Windows.Forms.Form { #region Class Memeber Variables /// /// current assembly name /// private static string AssemblyName = System.Reflection.Assembly.GetExecutingAssembly().Location; /// /// current assembly directory /// private static string AssemblyDirectory = Path.GetDirectoryName(AssemblyName); /// /// epsilon limit /// private static double epsilon = 0.00000001; /// /// how many bounces to run /// private static int rayLimit = 100; /// /// revit application /// private Autodesk.Revit.UI.UIApplication m_app; /// /// current document /// private Document m_doc; /// /// closet geometry reference between origin/intersection and ray/intersection /// private Autodesk.Revit.DB.ReferenceWithContext m_rClosest = null; /// /// 3D view /// private Autodesk.Revit.DB.View3D m_view = null; /// /// the count of line between origin/intersection and ray/intersection /// private int m_LineCount = 0; /// /// the count of ray between origin/intersection and ray/intersection /// private int m_RayCount = 0; /// /// the face which the ray intersect with /// private Face m_face = null; /// /// ray start from here /// private Autodesk.Revit.DB.XYZ m_origin = new Autodesk.Revit.DB.XYZ(0, 0, 0); /// /// ray direction /// private Autodesk.Revit.DB.XYZ m_direction = new Autodesk.Revit.DB.XYZ(0, 0, 0); /// /// for time calculation /// private Stopwatch m_stopWatch = new Stopwatch(); /// /// output string list /// private List m_outputInfo = new List(); /// /// trace listener /// private TraceListener m_txtListener = null; #endregion /// /// Constructor /// /// Revit application /// 3D View public RayTraceBounceForm(ExternalCommandData commandData, Autodesk.Revit.DB.View3D v) { InitializeComponent(); string logFile = AssemblyName.Replace(".dll", DateTime.Now.ToString("yyyyMMddhhmmss") + ".log"); if (File.Exists(logFile)) File.Delete(logFile); m_txtListener = new TextWriterTraceListener(logFile); Trace.Listeners.Add(m_txtListener); m_app = commandData.Application; m_doc = commandData.Application.ActiveUIDocument.Document; m_view = v; UpdateData(true); // Delete lines it created DeleteLines(); } /// /// OK button click event /// /// /// private void buttonOK_Click(object sender, EventArgs e) { DeleteLines(); if (!UpdateData(false)) { return; } m_outputInfo.Clear(); m_stopWatch.Start(); Transaction transaction = new Transaction(m_doc, "RayTraceBounce"); transaction.Start(); m_LineCount = 0; m_RayCount = 0; // Start Find References By Direction Autodesk.Revit.DB.XYZ startpt = m_origin; m_outputInfo.Add("Start Find References By Direction: "); for (int ctr = 1; ctr <= rayLimit; ctr++) { ReferenceIntersector referenceIntersector = new ReferenceIntersector(m_view); IList references = referenceIntersector.Find(startpt, m_direction); m_rClosest = null; FindClosestReference(references); if (m_rClosest == null) { string info = "Ray " + ctr + " aborted. No closest face reference found. "; m_outputInfo.Add(info); if (ctr == 1) { TaskDialog.Show("Revit", info); } break; } else { Reference reference = m_rClosest.GetReference(); Element referenceElement = m_doc.GetElement(reference); GeometryObject referenceObject = referenceElement.GetGeometryObjectFromReference(reference); Autodesk.Revit.DB.XYZ endpt = reference.GlobalPoint; if (startpt.IsAlmostEqualTo(endpt)) { m_outputInfo.Add("Start and end points are equal. Ray " + ctr + " aborted\n" + startpt.X + ", " + startpt.Y + ", " + startpt.Z); break; } else { MakeLine(startpt, endpt, m_direction, "bounce"); m_RayCount = m_RayCount + 1; string info = "Intersected Element Type: [" + referenceElement.GetType().ToString() + "] ElementId: [" + referenceElement.Id.ToString(); m_face = referenceObject as Face; if (m_face.MaterialElementId != ElementId.InvalidElementId) { Material materialElement = m_doc.GetElement(m_face.MaterialElementId) as Material; info += "] Face MaterialElement Name: [" + materialElement.Name + "] Shininess: [" + materialElement.Shininess; } else { info += "] Face.MaterialElement is null [" + referenceElement.Category.Name; } info += "]"; m_outputInfo.Add(info); Autodesk.Revit.DB.UV endptUV = reference.UVPoint; Autodesk.Revit.DB.XYZ FaceNormal = m_face.ComputeDerivatives(endptUV).BasisZ; // face normal where ray hits FaceNormal = m_rClosest.GetInstanceTransform().OfVector(FaceNormal); // transformation to get it in terms of document coordinates instead of the parent symbol Autodesk.Revit.DB.XYZ directionMirrored = m_direction - 2 * m_direction.DotProduct(FaceNormal) * FaceNormal; //http://www.fvastro.org/presentations/ray_tracing.htm m_direction = directionMirrored; // get ready to shoot the next ray startpt = endpt; } } } transaction.Commit(); m_stopWatch.Stop(); TimeSpan ts = m_stopWatch.Elapsed; string elapsedTime = String.Format("{0:00}:{1:00}:{2:00}.{3:00}", ts.Hours, ts.Minutes, ts.Seconds, ts.Milliseconds / 10); m_outputInfo.Add(elapsedTime + "\n" + "Lines = " + m_LineCount + "\n" + "Rays = " + m_RayCount); m_stopWatch.Reset(); OutputInformation(); } /// /// Cancel button click event /// /// /// private void buttonCancel_Click(object sender, EventArgs e) { Close(); } /// /// Clean up any resources being used. /// /// true if managed resources should be disposed; otherwise, false. protected override void Dispose(bool disposing) { Trace.Flush(); m_txtListener.Close(); Trace.Close(); Trace.Listeners.Remove(m_txtListener); if (disposing && (components != null)) { components.Dispose(); } base.Dispose(disposing); } /// /// Update textbox data with member variable /// /// if get/set date from control public bool UpdateData(bool updateControl) { try { if (updateControl) { textBoxLocationX.Text = Convert.ToString(0.0); textBoxLocationY.Text = Convert.ToString(0.0); textBoxLocationZ.Text = Convert.ToString(0.0); textBoxDirectionI.Text = Convert.ToString(Math.Cos(0)); textBoxDirectionJ.Text = Convert.ToString(Math.Sin(0)); textBoxDirectionK.Text = Convert.ToString(0.0); } else { if (string.IsNullOrEmpty(textBoxLocationX.Text) || string.IsNullOrEmpty(textBoxLocationY.Text) || string.IsNullOrEmpty(textBoxLocationZ.Text) || string.IsNullOrEmpty(textBoxDirectionI.Text) || string.IsNullOrEmpty(textBoxDirectionJ.Text) || string.IsNullOrEmpty(textBoxDirectionK.Text)) { TaskDialog.Show("Revit", "Value cannot be empty."); return false; } m_origin = new XYZ( Convert.ToDouble(textBoxLocationX.Text), Convert.ToDouble(textBoxLocationY.Text), Convert.ToDouble(textBoxLocationZ.Text)); m_direction = new XYZ( Convert.ToDouble(textBoxDirectionI.Text), Convert.ToDouble(textBoxDirectionJ.Text), Convert.ToDouble(textBoxDirectionK.Text)); } return true; } catch (System.Exception) { return false; } } /// /// Find the first intersection with a face /// /// /// public Autodesk.Revit.DB.ReferenceWithContext FindClosestReference(IList references) { double face_prox = Double.PositiveInfinity; double edge_prox = Double.PositiveInfinity; foreach (ReferenceWithContext r in references) { Reference reference = r.GetReference(); Element referenceElement = m_doc.GetElement(reference); GeometryObject referenceGeometryObject = referenceElement.GetGeometryObjectFromReference(reference); m_face = null; m_face = referenceGeometryObject as Face; Edge edge = null; edge = referenceGeometryObject as Edge; if (m_face != null) { if ((r.Proximity < face_prox) && (r.Proximity > epsilon)) { m_rClosest = r; face_prox = Math.Abs(r.Proximity); } } else if (edge != null) { if ((r.Proximity < edge_prox) && (r.Proximity > epsilon)) { edge_prox = Math.Abs(r.Proximity); } } } if (edge_prox <= face_prox) { // stop bouncing if there is an edge at least as close as the nearest face - there is no single angle of reflection for a ray striking a line m_outputInfo.Add("there is an edge at least as close as the nearest face - there is no single angle of reflection for a ray striking a line"); m_rClosest = null; } return m_rClosest; } /// /// Make a line from start point to end point with the direction and style /// /// start point /// end point /// the direction which decide the plane /// line style name public void MakeLine(Autodesk.Revit.DB.XYZ startpt, Autodesk.Revit.DB.XYZ endpt, Autodesk.Revit.DB.XYZ direction, string style) { try { m_LineCount = m_LineCount + 1; Line line = Line.CreateBound(startpt, endpt); // Line must lie in the sketch plane. Use the direction of the line to construct a plane that hosts the target line. XYZ rotatedDirection = XYZ.BasisX; // If the direction is not vertical, cross the direction vector with Z to get a vector rotated ninety degrees. That vector, // plus the original vector, form the axes of the sketch plane. if (!direction.IsAlmostEqualTo(XYZ.BasisZ) && !direction.IsAlmostEqualTo(-XYZ.BasisZ)) { rotatedDirection = direction.Normalize().CrossProduct(XYZ.BasisZ); } Plane geometryPlane = Plane.CreateByOriginAndBasis(direction, rotatedDirection, startpt); SketchPlane skplane = SketchPlane.Create(m_app.ActiveUIDocument.Document, geometryPlane); ModelCurve mcurve = m_app.ActiveUIDocument.Document.Create.NewModelCurve(line, skplane); m_app.ActiveUIDocument.Document.Regenerate(); //ElementArray lsArr = mcurve.LineStyles; ICollection lsArr = mcurve.GetLineStyleIds(); foreach (Autodesk.Revit.DB.ElementId eid in lsArr) { Element e = m_app.ActiveUIDocument.Document.GetElement(eid); if (e.Name == style) { mcurve.LineStyle = e; break; } } m_app.ActiveUIDocument.Document.Regenerate(); } catch (System.Exception ex) { m_outputInfo.Add("Failed to create lines: " + ex.ToString()); } } /// /// Delete all unnecessary lines /// public void DeleteLines() { int delLineNum = 0; try { SubTransaction transaction = new SubTransaction(m_app.ActiveUIDocument.Document); transaction.Start(); List list = new List(); ElementClassFilter filter = new ElementClassFilter(typeof(Autodesk.Revit.DB.CurveElement)); FilteredElementCollector collector = new FilteredElementCollector(m_app.ActiveUIDocument.Document); list.AddRange(collector.WherePasses(filter).ToElements()); foreach (Autodesk.Revit.DB.Element e in list) { ModelCurve mc = e as ModelCurve; if (mc != null) { if (mc.LineStyle.Name == "bounce" || mc.LineStyle.Name == "normal") { m_app.ActiveUIDocument.Document.Delete(e.Id); delLineNum++; } } } transaction.Commit(); } catch (System.Exception) { } } /// /// Output the information to log file /// protected void OutputInformation() { foreach (string item in m_outputInfo) { Trace.WriteLine(item); } Trace.Flush(); } } }