I’m sure this has been answered by someone else before, and apologies if this is a bit of a novice question, but I am trying to create a system which uses text dots to report positions of an attributed curve and uses record history to capture changes to the curve to inform changes to the text within. Here’s my command class thus far! - Bear in mind I’m a bit new to the world of Rhino Common, but dug for quite a while and couldn’t seem to find a method that would work similar to the way that Rhinodoc.SelectObjects works but for object transformations?
Right now the command lets the user place a point and attributes a text dot with elevational data for it (z location). It writes to history and allows moving the point to update the text and location of the text dot. However, if the text dot is moved independently of the point, it loses association. I’m using a dictionary to tie the GUIDs of the points and the dots together. The intended behavior is that moving either object would move the other! Currently, I’m cheating and updating the text dot when the point object is selected!
Any help or guidance is most appreciated. ChatGPT has been my instructor and confidant working on this so efforts to put explanations in layman’s terms are greatly appreciated!
public class ElevationTarget : Command
{
static ElevationTarget g_command_instance;
private const int HISTORY_VERSION = 20131107;
// Dictionary to store each point and its corresponding TextDot ID
private Dictionary<Guid, Guid> pointTextDotMap = new Dictionary<Guid, Guid>();
// Global offset for elevation
private static double globalOffset = 0.0;
public ElevationTarget()
{
g_command_instance = this;
}
public static ElevationTarget Instance => g_command_instance;
public override string EnglishName => "ElevationTarget";
protected override Result RunCommand(RhinoDoc doc, RunMode mode)
{
// Ask the user if they want to modify the global offset
Rhino.Input.Custom.GetOption getOption = new Rhino.Input.Custom.GetOption();
getOption.SetCommandPrompt("Select an option");
Rhino.Input.Custom.OptionDouble globalOffsetOption = new Rhino.Input.Custom.OptionDouble(globalOffset);
getOption.AddOptionDouble("GlobalOffset", ref globalOffsetOption);
// Let the user choose whether they want to modify the global offset
var getOptionResult = getOption.Get();
if (getOptionResult == Rhino.Input.GetResult.Option)
{
globalOffset = globalOffsetOption.CurrentValue;
UpdateAllTextDots(doc); // Update all TextDots immediately to reflect the new global offset
}
// Let the user pick a point interactively in the viewport
Point3d pickedPoint;
var getPointResult = Rhino.Input.RhinoGet.GetPoint("Click to place a new point", false, out pickedPoint);
// If the user cancels or fails to pick a point, exit the command
if (getPointResult != Rhino.Commands.Result.Success)
return getPointResult;
// Create a new point in the document at the picked location (Point3d used directly)
Point point = new Point(pickedPoint); // This is valid since Point3d is used as input
Guid pointId = doc.Objects.AddPoint(pickedPoint); // Add the point to the document using Point3d
// Update the elevation display as a TextDot for this new point
UpdateTextDot(doc, pickedPoint, pointId); // Use Point3d here as the argument for TextDot
// Subscribe to the SelectObjects event to monitor selection changes
RhinoDoc.SelectObjects += OnSelectObjects;
// Subscribe to the DeleteRhinoObject event to handle manual deletion of TextDots
RhinoDoc.DeleteRhinoObject += OnDeleteRhinoObject;
doc.Views.Redraw();
return Rhino.Commands.Result.Success;
}
protected override bool ReplayHistory(Rhino.DocObjects.ReplayHistoryData replay)
{
Rhino.DocObjects.ObjRef objref = null;
if (!ReadHistory(replay, ref objref))
return false;
Point3d pointLocation = objref.Point().Location;
if (pointLocation == Point3d.Unset)
return false;
// Update the elevation display as a TextDot
UpdateTextDot(RhinoDoc.ActiveDoc, pointLocation, objref.ObjectId);
return true;
}
private bool ReadHistory(Rhino.DocObjects.ReplayHistoryData replay, ref Rhino.DocObjects.ObjRef objref)
{
if (HISTORY_VERSION != replay.HistoryVersion)
return false;
objref = replay.GetRhinoObjRef(0);
if (objref == null)
return false;
return true;
}
private bool WriteHistory(Rhino.DocObjects.HistoryRecord history, Rhino.DocObjects.ObjRef objref)
{
if (!history.SetObjRef(0, objref))
return false;
return true;
}
private void UpdateTextDot(RhinoDoc doc, Point3d pointLocation, Guid pointId)
{
// Get the current Z-elevation of the point
double elevation = pointLocation.Z;
// Convert the Z-elevation to the current document's units
Rhino.UnitSystem docUnitSystem = doc.ModelUnitSystem;
double elevationInUnits = UnitConverter.ConvertToMeters(elevation, docUnitSystem);
// Apply the global offset
double adjustedElevation = elevationInUnits + globalOffset;
// Create the text for the TextDot with the adjusted elevation and the global offset
string text = $"Elevation: {adjustedElevation:F2} {docUnitSystem.ToString()}\nGlobal Offset: {globalOffset:F2} {docUnitSystem.ToString()}";
// Get the location of the point (for the TextDot)
Point3d textDotLocation = pointLocation;
// If there's already a TextDot for this point, delete the old one
if (pointTextDotMap.ContainsKey(pointId) && pointTextDotMap[pointId] != Guid.Empty)
{
Guid oldTextDotId = pointTextDotMap[pointId];
// Attempt to delete the old TextDot object
var oldTextDotObj = doc.Objects.Find(oldTextDotId);
if (oldTextDotObj != null)
{
doc.Objects.Delete(oldTextDotId, true);
}
// Ensure we update the map after deletion
pointTextDotMap[pointId] = Guid.Empty;
}
// Create a new TextDot for this point, showing the adjusted elevation and global offset
TextDot textDot = new TextDot(text, textDotLocation);
// Create an ObjectAttributes object to assign attributes to the TextDot
var objectAttributes = new ObjectAttributes();
// Check if the "LEVELS" layer exists
Layer levelsLayer = doc.Layers.FindName("LEVELS");
if (levelsLayer != null)
{
// If "LEVELS" layer exists, set the TextDot to that layer using ObjectAttributes
objectAttributes.LayerIndex = levelsLayer.Index;
}
else
{
// Otherwise, use the current layer of the point
var pointObject = doc.Objects.Find(pointId);
if (pointObject != null)
{
objectAttributes.LayerIndex = pointObject.Attributes.LayerIndex;
}
}
// Add the new TextDot to the document with the assigned ObjectAttributes and update the dictionary with its ID
Guid textDotId = doc.Objects.AddTextDot(textDot, objectAttributes);
pointTextDotMap[pointId] = textDotId; // Map the point ID to the new TextDot ID
// Ensure that the scene is updated
doc.Views.Redraw();
}
private void UpdateAllTextDots(RhinoDoc doc)
{
// Collect the keys (point IDs) in a separate list to avoid modifying the dictionary during enumeration
List<Guid> pointIds = pointTextDotMap.Keys.ToList();
// Loop through the collected point IDs
foreach (var pointId in pointIds)
{
// Find the point object by its ID
Rhino.DocObjects.RhinoObject pointObject = doc.Objects.Find(pointId);
if (pointObject != null && pointObject is Rhino.DocObjects.PointObject pointObject2)
{
Point point = pointObject2.Geometry as Point;
if (point != null)
{
UpdateTextDot(doc, point.Location, pointId); // Pass Point3d here
}
}
}
}
private void OnSelectObjects(object sender, Rhino.DocObjects.RhinoObjectSelectionEventArgs e)
{
RhinoDoc doc = RhinoDoc.ActiveDoc;
// Get the selected objects
var selectedObjects = doc.Objects.GetSelectedObjects(false, false);
// Iterate over all selected objects
foreach (var selectedObject in selectedObjects)
{
// Check if the selected object is a point and is in our dictionary
if (selectedObject is Rhino.DocObjects.PointObject pointObject && pointTextDotMap.ContainsKey(pointObject.Id))
{
// Update the corresponding TextDot for this point
Point point = pointObject.Geometry as Point;
if (point != null)
{
UpdateTextDot(doc, point.Location, pointObject.Id); // Use Point3d location
}
}
}
}
// Handle manual deletion of TextDots
private void OnDeleteRhinoObject(object sender, Rhino.DocObjects.RhinoObjectEventArgs e)
{
// If the deleted object is a TextDot, we need to remove it from the dictionary
if (e.TheObject is TextDotObject textDot)
{
// Use the ObjectId to get the GUID of the TextDot
Guid textDotId = e.TheObject.Id;
// Find the point ID associated with this TextDot and remove it from the dictionary
var entry = pointTextDotMap.FirstOrDefault(kv => kv.Value == textDotId);
if (entry.Key != Guid.Empty)
{
// Remove the mapping from the dictionary
pointTextDotMap.Remove(entry.Key);
}
// Perform any additional cleanup if necessary
}
}
}
}