Hi everyone,
I am working on developing a C# component in Rhino 8 that takes a list of points, texts, and images as inputs to draw icons with text on the Rhino canvas. Inspired by these amazing posts:
Custom Sprite Component - Grasshopper Developer - McNeel Forum
Grasshopper Text Dot visibility/overlap - Grasshopper - McNeel Forum
I have attempted to create the component, but I am encountering issues with scaling. Specifically, I want to set the exact size of both the text and the image, but when I zoom in and out, the icons disappear at certain zoom levels. My goal is to dynamically resize the text and the icons based on the Rhino zoom, but I have noticed that after a few zoom adjustments, the component collapses.
As a beginner in C# coding, I am unsure if my approach is correct. It seems that the component keeps recalculating every time the zoom level changes.
Here is the correct display:
When I zoom out, the icon disappears:
And here is the code I have so far:
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using Rhino;
using Rhino.Geometry;
using Grasshopper;
using Grasshopper.Kernel;
public class Script_Instance : GH_ScriptInstance
{
private List<Point3d> points;
private List<string> texts;
private List<string> icons;
private List<Rhino.Geometry.TextDot> textDots = new List<Rhino.Geometry.TextDot>();
private List<System.Drawing.Bitmap> imageBitmaps = new List<System.Drawing.Bitmap>();
private List<Point3d> iconPositions = new List<Point3d>();
private bool eventRegistered = false;
private void RunScript(List<Point3d> P, List<string> M, List<string> I)
{
// Ensure parameter names and descriptions are set
this.Component.Name = "Symbol Display (Custom)";
this.Component.NickName = "SymCus";
this.Component.Description = "Symbol display that accepts custom images and optionally draws them in the foreground";
this.Component.Params.Input[0].Name = "Point";
this.Component.Params.Input[0].NickName = "P";
this.Component.Params.Input[0].Description = "Location to add symbol";
this.Component.Params.Input[1].Name = "Message";
this.Component.Params.Input[1].NickName = "M";
this.Component.Params.Input[1].Description = "Message to show in the symbol";
this.Component.Params.Input[2].Name = "Image";
this.Component.Params.Input[2].NickName = "I";
this.Component.Params.Input[2].Description = "Image to show in the symbol";
this.points = P;
this.texts = M;
this.icons = I;
if (this.Component.Hidden)
{
ClearDrawings();
}
else
{
RegisterEvent();
UpdateTextDotsAndImages();
}
}
private void RegisterEvent()
{
if (!eventRegistered)
{
Rhino.Display.DisplayPipeline.DrawForeground -= DrawForeground;
Rhino.Display.DisplayPipeline.DrawForeground += DrawForeground;
eventRegistered = true;
}
}
private void UpdateTextDotsAndImages()
{
// Clear existing elements
textDots.Clear();
imageBitmaps.Clear();
iconPositions.Clear();
if (points == null || texts == null || icons == null || points.Count == 0 || texts.Count == 0 || icons.Count != points.Count)
return;
for (int i = 0; i < points.Count && i < texts.Count; i++)
{
var textPoint = new Point3d(points[i].X, points[i].Y, points[i].Z + 2.5);
textDots.Add(new Rhino.Geometry.TextDot(texts[i], textPoint)
{
FontFace = "Calibri",
FontHeight = 10 // Default; will be adjusted in DrawForeground
});
var image = ConvertBase64ToImage(icons[i]);
if (image != null)
imageBitmaps.Add(image);
iconPositions.Add(points[i]);
}
}
private System.Drawing.Bitmap ConvertBase64ToImage(string base64String)
{
try
{
var byteArray = Convert.FromBase64String(base64String);
using (var ms = new MemoryStream(byteArray))
{
return new System.Drawing.Bitmap(ms);
}
}
catch (Exception ex)
{
Rhino.RhinoApp.WriteLine($"Error converting base64 to image: {ex.Message}");
return null;
}
}
private void DrawForeground(object sender, Rhino.Display.DrawEventArgs e)
{
if (textDots.Count == 0 || this.Component.Hidden)
return;
if (e.Viewport.Id != Rhino.RhinoDoc.ActiveDoc.Views.ActiveView.ActiveViewportID)
return;
var cameraLocation = e.Viewport.CameraLocation;
const double baseDistance = 100.0; // Adjust base distance for scaling
for (int i = 0; i < textDots.Count; i++)
{
var dot = textDots[i];
var image = i < imageBitmaps.Count ? imageBitmaps[i] : null;
var textPoint = dot.Point;
var iconPoint = i < iconPositions.Count ? iconPositions[i] : Point3d.Unset;
// Calculate font size based on camera distance
var distanceToPoint = cameraLocation.DistanceTo(textPoint);
var scaleFactor = baseDistance / distanceToPoint;
var fontSize = Math.Max(10.0 * scaleFactor, 1.0);
// Update and draw the text dot
dot.FontHeight = (int)fontSize;
e.Display.DrawDot(dot, Color.FromArgb(255, 255, 204, 77), Color.Black, Color.Black);
// Draw the image if available
if (image != null)
{
var bitmap = new Rhino.Display.DisplayBitmap(image);
var drawList = new Rhino.Display.DisplayBitmapDrawList();
drawList.SetPoints(new List<Point3d> { iconPoint });
e.Display.DrawSprites(bitmap, drawList, 2, true);
}
}
}
private void ClearDrawings()
{
if (eventRegistered)
{
Rhino.Display.DisplayPipeline.DrawForeground -= DrawForeground;
eventRegistered = false;
}
textDots.Clear();
imageBitmaps.Clear();
iconPositions.Clear();
}
}
Any suggestions or insights on how to properly make the component works (or optimised) would be greatly appreciated. Also, it would be great to have all the icons and texts removed if the component is disabled. Thank you! @michaelvollrath @AndersDeleuran
Warning.gh (11.0 KB)