This is page 2 of 3. Use http://codebase.md/always-tinkering/rhinomcpserver?lines=false&page={x} to view the full context.
# Directory Structure
```
├── .gitignore
├── code_architecture.md
├── combined_mcp_server.py
├── diagnose_rhino_connection.py
├── log_manager.py
├── LOGGING.md
├── logs
│ └── json_filter.py
├── mcpLLM.txt
├── NLog.config
├── README.md
├── RHINO_PLUGIN_UPDATE.md
├── RhinoMcpPlugin
│ ├── Models
│ │ └── RhinoObjectProperties.cs
│ ├── Properties
│ │ └── AssemblyInfo.cs
│ ├── RhinoMcpCommand.cs
│ ├── RhinoMcpPlugin.cs
│ ├── RhinoMcpPlugin.csproj
│ ├── RhinoSocketServer.cs
│ ├── RhinoUtilities.cs
│ └── Tools
│ ├── GeometryTools.cs
│ └── SceneTools.cs
├── RhinoMcpPlugin.Tests
│ ├── ImplementationPlan.md
│ ├── Mocks
│ │ └── MockRhinoDoc.cs
│ ├── README.md
│ ├── RhinoMcpPlugin.Tests.csproj
│ ├── test.runsettings
│ ├── Tests
│ │ ├── ColorUtilTests.cs
│ │ ├── RhinoMcpPluginTests.cs
│ │ ├── RhinoSocketServerTests.cs
│ │ └── RhinoUtilitiesTests.cs
│ └── Utils
│ └── ColorParser.cs
├── RhinoPluginFixImplementation.cs
├── RhinoPluginLoggingSpec.md
├── run-combined-server.sh
├── scripts
│ ├── direct-launcher.sh
│ ├── run-combined-server.sh
│ └── run-python-server.sh
└── src
├── daemon_mcp_server.py
├── socket_proxy.py
└── standalone-mcp-server.py
```
# Files
--------------------------------------------------------------------------------
/RhinoPluginFixImplementation.cs:
--------------------------------------------------------------------------------
```csharp
using Rhino;
using Rhino.Commands;
using Rhino.PlugIns;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NLog;
namespace RhinoMcpPlugin
{
// Main plugin class
public class RhinoMcpPluginCommand : PlugIn
{
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
private SocketServer socketServer;
private CommandHandlers commandHandlers;
public override PlugInLoadTime LoadTime => PlugInLoadTime.AtStartup;
public RhinoMcpPluginCommand()
{
Instance = this;
}
public static RhinoMcpPluginCommand Instance { get; private set; }
protected override LoadReturnCode OnLoad(ref string errorMessage)
{
try
{
Logger.Info("RhinoMcpPlugin loading...");
Logger.Info($"Rhino version: {RhinoApp.Version}");
Logger.Info($"Current directory: {System.IO.Directory.GetCurrentDirectory()}");
// Check document status
var docCount = RhinoDoc.OpenDocuments().Length;
Logger.Info($"Open document count: {docCount}");
Logger.Info($"Active document: {(RhinoDoc.ActiveDoc != null ? "exists" : "null")}");
// Create an empty document if none exists
if (RhinoDoc.ActiveDoc == null)
{
Logger.Info("No active document found. Creating a new document...");
RhinoApp.RunScript("_New", false);
if (RhinoDoc.ActiveDoc == null)
{
Logger.Error("CRITICAL: Failed to create a new document");
errorMessage = "Failed to create a new Rhino document. The plugin requires an active document.";
return LoadReturnCode.FailedToLoad;
}
Logger.Info($"New document created successfully: {RhinoDoc.ActiveDoc.Name}");
}
// Initialize command handlers
commandHandlers = new CommandHandlers();
// Initialize socket server
socketServer = new SocketServer(commandHandlers);
var success = socketServer.Start();
Logger.Info($"Socket server started: {success}");
// Register document events
RhinoDoc.NewDocument += OnNewDocument;
RhinoDoc.CloseDocument += OnCloseDocument;
RhinoDoc.BeginOpenDocument += OnBeginOpenDocument;
RhinoDoc.EndOpenDocument += OnEndOpenDocument;
// Check all essential components
var componentStatus = new Dictionary<string, bool> {
{ "SocketServer", socketServer != null },
{ "CommandHandlers", commandHandlers != null },
{ "RhinoDoc", RhinoDoc.ActiveDoc != null }
};
foreach (var component in componentStatus)
{
Logger.Info($"Component {component.Key}: {(component.Value ? "OK" : "NULL")}");
}
Logger.Info("RhinoMcpPlugin loaded successfully");
return LoadReturnCode.Success;
}
catch (Exception ex)
{
Logger.Error(ex, $"Error during plugin load: {ex.Message}");
errorMessage = ex.Message;
return LoadReturnCode.FailedToLoad;
}
}
protected override void OnShutdown()
{
try
{
Logger.Info("RhinoMcpPlugin shutting down...");
// Unregister document events
RhinoDoc.NewDocument -= OnNewDocument;
RhinoDoc.CloseDocument -= OnCloseDocument;
RhinoDoc.BeginOpenDocument -= OnBeginOpenDocument;
RhinoDoc.EndOpenDocument -= OnEndOpenDocument;
// Stop socket server
if (socketServer != null)
{
socketServer.Stop();
Logger.Info("Socket server stopped");
}
Logger.Info("RhinoMcpPlugin shutdown complete");
}
catch (Exception ex)
{
Logger.Error(ex, $"Error during plugin shutdown: {ex.Message}");
}
}
private void OnNewDocument(object sender, DocumentEventArgs e)
{
Logger.Info($"New document created: {e.Document.Name}");
Logger.Info($"Active document: {(RhinoDoc.ActiveDoc != null ? RhinoDoc.ActiveDoc.Name : "null")}");
}
private void OnCloseDocument(object sender, DocumentEventArgs e)
{
Logger.Info($"Document closed: {e.Document.Name}");
Logger.Info($"Remaining open documents: {RhinoDoc.OpenDocuments().Length}");
// If this was the last document, create a new one
if (RhinoDoc.OpenDocuments().Length == 0)
{
Logger.Info("No documents remaining. Creating a new document...");
RhinoApp.RunScript("_New", false);
Logger.Info($"New document created: {(RhinoDoc.ActiveDoc != null ? RhinoDoc.ActiveDoc.Name : "null")}");
}
}
private void OnBeginOpenDocument(object sender, DocumentOpenEventArgs e)
{
Logger.Info($"Beginning to open document: {e.FileName}");
}
private void OnEndOpenDocument(object sender, DocumentOpenEventArgs e)
{
Logger.Info($"Finished opening document: {e.Document.Name}");
}
}
// Socket server class to handle communication
public class SocketServer
{
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
private TcpListener server;
private bool isRunning;
private CommandHandlers commandHandlers;
private CancellationTokenSource cancellationTokenSource;
public bool IsRunning => isRunning;
public SocketServer(CommandHandlers handlers)
{
commandHandlers = handlers;
cancellationTokenSource = new CancellationTokenSource();
}
public bool Start(int port = 9876)
{
try
{
Logger.Info($"Socket server initializing on port {port}");
server = new TcpListener(IPAddress.Loopback, port);
server.Start();
isRunning = true;
// Start accepting clients in a background task
Task.Run(() => AcceptClientsAsync(cancellationTokenSource.Token), cancellationTokenSource.Token);
Logger.Info($"Socket server started successfully on port {port}");
return true;
}
catch (Exception ex)
{
Logger.Error(ex, $"Failed to start socket server: {ex.Message}");
return false;
}
}
public void Stop()
{
try
{
Logger.Info("Stopping socket server...");
isRunning = false;
cancellationTokenSource.Cancel();
server?.Stop();
Logger.Info("Socket server stopped");
}
catch (Exception ex)
{
Logger.Error(ex, $"Error stopping socket server: {ex.Message}");
}
}
private async Task AcceptClientsAsync(CancellationToken cancellationToken)
{
while (isRunning && !cancellationToken.IsCancellationRequested)
{
try
{
var client = await server.AcceptTcpClientAsync();
Logger.Info($"Client connected from {client.Client.RemoteEndPoint}");
// Handle each client in a separate task
_ = Task.Run(() => HandleClientAsync(client, cancellationToken), cancellationToken);
}
catch (OperationCanceledException)
{
// Normal cancellation, do nothing
Logger.Info("Client acceptance loop cancelled");
break;
}
catch (Exception ex)
{
Logger.Error(ex, $"Error accepting client: {ex.Message}");
// Short delay before trying again
await Task.Delay(1000, cancellationToken);
}
}
}
private async Task HandleClientAsync(TcpClient client, CancellationToken cancellationToken)
{
using (client)
{
try
{
using (var stream = client.GetStream())
{
var buffer = new byte[4096];
while (isRunning && !cancellationToken.IsCancellationRequested)
{
// Read command from client
var data = new List<byte>();
int bytesRead;
do
{
bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken);
if (bytesRead > 0)
{
data.AddRange(buffer.Take(bytesRead));
}
} while (stream.DataAvailable);
if (bytesRead == 0)
{
// Client disconnected
Logger.Info("Client disconnected");
break;
}
// Process command and send response
var commandJson = Encoding.UTF8.GetString(data.ToArray());
Logger.Info($"Received command: {commandJson}");
string responseJson = ProcessCommand(commandJson);
Logger.Debug($"Sending response: {responseJson}");
var responseBytes = Encoding.UTF8.GetBytes(responseJson);
await stream.WriteAsync(responseBytes, 0, responseBytes.Length, cancellationToken);
}
}
}
catch (OperationCanceledException)
{
// Normal cancellation
Logger.Info("Client handler cancelled");
}
catch (Exception ex)
{
Logger.Error(ex, $"Error handling client: {ex.Message}");
}
finally
{
try
{
client.Close();
}
catch { /* ignore */ }
}
}
}
private string ProcessCommand(string commandJson)
{
try
{
// Parse command JSON
JObject command = JObject.Parse(commandJson);
string commandType = command["type"]?.ToString().ToLowerInvariant();
JObject parameters = command["params"] as JObject ?? new JObject();
// Special case for health check
if (commandType == "health_check")
{
return commandHandlers.HandleHealthCheck(parameters);
}
// Ensure we have an active document before processing commands
if (RhinoDoc.ActiveDoc == null)
{
Logger.Error("CRITICAL: No active document available for command processing");
return JsonConvert.SerializeObject(new
{
error = "No active Rhino document. Please open a document before executing commands."
});
}
// Process regular commands
switch (commandType)
{
case "ping":
return JsonConvert.SerializeObject(new { result = "pong" });
case "get_scene_info":
return commandHandlers.HandleGetSceneInfo(parameters);
case "create_box":
return commandHandlers.HandleCreateBox(parameters);
case "create_sphere":
return commandHandlers.HandleCreateSphere(parameters);
case "create_cylinder":
return commandHandlers.HandleCreateCylinder(parameters);
case "clear_scene":
return commandHandlers.HandleClearScene(parameters);
case "create_layer":
return commandHandlers.HandleCreateLayer(parameters);
default:
Logger.Warn($"Unknown command type: {commandType}");
return JsonConvert.SerializeObject(new { error = $"Unknown command type: {commandType}" });
}
}
catch (NullReferenceException ex)
{
Logger.Error(ex, $"NULL REFERENCE processing command: {commandJson}");
Logger.Error($"Stack trace: {ex.StackTrace}");
Logger.Error($"Context: ActiveDoc={RhinoDoc.ActiveDoc != null}");
return JsonConvert.SerializeObject(new { error = $"Error processing command: {ex.Message}" });
}
catch (Exception ex)
{
Logger.Error(ex, $"Error processing command: {ex.Message}");
return JsonConvert.SerializeObject(new { error = $"Error processing command: {ex.Message}" });
}
}
}
// Class to handle specific commands
public class CommandHandlers
{
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
public string HandleHealthCheck(JObject parameters)
{
try
{
Logger.Info("Performing health check...");
var healthStatus = new Dictionary<string, object> {
{ "PluginLoaded", true },
{ "RhinoVersion", RhinoApp.Version.ToString() },
{ "ActiveDocument", RhinoDoc.ActiveDoc != null },
{ "OpenDocumentCount", RhinoDoc.OpenDocuments().Length },
{ "SocketServerRunning", true },
{ "MemoryUsage", System.GC.GetTotalMemory(false) / 1024 / 1024 + " MB" },
{ "SdkVersion", typeof(RhinoApp).Assembly.GetName().Version.ToString() }
};
Logger.Info($"Health check results: {JsonConvert.SerializeObject(healthStatus)}");
return JsonConvert.SerializeObject(new {
success = true,
result = healthStatus
});
}
catch (Exception ex)
{
Logger.Error(ex, $"Exception during health check: {ex.Message}");
return JsonConvert.SerializeObject(new {
error = $"Health check failed: {ex.Message}"
});
}
}
public string HandleGetSceneInfo(JObject parameters)
{
Logger.Debug("Processing get_scene_info request");
try
{
var doc = RhinoDoc.ActiveDoc;
if (doc == null)
{
Logger.Error("CRITICAL: RhinoDoc.ActiveDoc is NULL");
return JsonConvert.SerializeObject(new {
error = "No active Rhino document. Please open a document first."
});
}
Logger.Debug("Accessing document objects...");
var objectCount = doc.Objects.Count;
var layerCount = doc.Layers.Count;
Logger.Info($"Retrieved scene info: {objectCount} objects, {layerCount} layers");
return JsonConvert.SerializeObject(new {
success = true,
result = new {
objectCount = objectCount,
layerCount = layerCount,
documentName = doc.Name,
activeLayer = doc.Layers.CurrentLayer.Name
}
});
}
catch (NullReferenceException ex)
{
Logger.Error(ex, $"NULL REFERENCE in get_scene_info: {ex.Message}");
Logger.Error($"Stack trace: {ex.StackTrace}");
return JsonConvert.SerializeObject(new {
error = $"Error processing command: {ex.Message}"
});
}
catch (Exception ex)
{
Logger.Error(ex, $"Exception in get_scene_info: {ex.Message}");
return JsonConvert.SerializeObject(new {
error = $"Error getting scene info: {ex.Message}"
});
}
}
public string HandleCreateBox(JObject parameters)
{
Logger.Debug($"Processing create_box with parameters: {parameters}");
try
{
// Log parameter extraction
Logger.Debug("Extracting parameters...");
double cornerX = parameters.Value<double>("cornerX");
double cornerY = parameters.Value<double>("cornerY");
double cornerZ = parameters.Value<double>("cornerZ");
double width = parameters.Value<double>("width");
double depth = parameters.Value<double>("depth");
double height = parameters.Value<double>("height");
string color = parameters.Value<string>("color");
// Log document access
Logger.Debug("Accessing Rhino document...");
var doc = RhinoDoc.ActiveDoc;
if (doc == null)
{
Logger.Error("CRITICAL: RhinoDoc.ActiveDoc is NULL");
return JsonConvert.SerializeObject(new {
error = "No active Rhino document. Please open a document first."
});
}
// Run on main UI thread
bool success = false;
object result = null;
// Execute on the main Rhino UI thread
RhinoApp.InvokeOnUiThread(new Action(() =>
{
try
{
// Log geometric operations with safeguards
Logger.Debug("Creating geometry...");
var corner = new Rhino.Geometry.Point3d(cornerX, cornerY, cornerZ);
var box = new Rhino.Geometry.Box(
new Rhino.Geometry.Plane(corner, Rhino.Geometry.Vector3d.ZAxis),
new Rhino.Geometry.Interval(0, width),
new Rhino.Geometry.Interval(0, depth),
new Rhino.Geometry.Interval(0, height)
);
// Verify box was created
if (box == null || !box.IsValid)
{
Logger.Error($"Box creation failed: {(box == null ? "null box" : "invalid box")}");
result = new { error = "Failed to create valid box geometry" };
return;
}
// Log document modification
Logger.Debug("Adding to document...");
var id = doc.Objects.AddBox(box);
if (id == Guid.Empty)
{
Logger.Error("Failed to add box to document");
result = new { error = "Failed to add box to document" };
return;
}
// Apply color if specified
if (!string.IsNullOrEmpty(color))
{
System.Drawing.Color objColor = System.Drawing.Color.FromName(color);
if (objColor.A > 0)
{
var objAttributes = new Rhino.DocObjects.ObjectAttributes();
objAttributes.ColorSource = Rhino.DocObjects.ObjectColorSource.ColorFromObject;
objAttributes.ObjectColor = objColor;
doc.Objects.ModifyAttributes(id, objAttributes, true);
}
}
// Update views
doc.Views.Redraw();
// Log successful operation
Logger.Info($"Successfully created box with ID {id}");
result = new { success = true, objectId = id.ToString() };
success = true;
}
catch (Exception ex)
{
Logger.Error(ex, $"Error in UI thread: {ex.Message}");
result = new { error = $"Error in UI thread: {ex.Message}" };
}
}));
if (success)
{
return JsonConvert.SerializeObject(result);
}
else
{
return JsonConvert.SerializeObject(result ?? new { error = "Unknown error creating box" });
}
}
catch (NullReferenceException ex)
{
Logger.Error(ex, $"NULL REFERENCE in create_box: {ex.Message}");
Logger.Error($"Stack trace: {ex.StackTrace}");
return JsonConvert.SerializeObject(new {
error = $"Error processing command: {ex.Message}"
});
}
catch (Exception ex)
{
Logger.Error(ex, $"Exception in create_box: {ex.Message}");
return JsonConvert.SerializeObject(new {
error = $"Error processing command: {ex.Message}"
});
}
}
public string HandleCreateSphere(JObject parameters)
{
Logger.Debug($"Processing create_sphere with parameters: {parameters}");
try
{
// Extract parameters
Logger.Debug("Extracting parameters...");
double centerX = parameters.Value<double>("centerX");
double centerY = parameters.Value<double>("centerY");
double centerZ = parameters.Value<double>("centerZ");
double radius = parameters.Value<double>("radius");
string color = parameters.Value<string>("color");
// Access document
Logger.Debug("Accessing Rhino document...");
var doc = RhinoDoc.ActiveDoc;
if (doc == null)
{
Logger.Error("CRITICAL: RhinoDoc.ActiveDoc is NULL");
return JsonConvert.SerializeObject(new {
error = "No active Rhino document. Please open a document first."
});
}
// Execute on UI thread
bool success = false;
object result = null;
RhinoApp.InvokeOnUiThread(new Action(() =>
{
try
{
// Create geometry
Logger.Debug("Creating sphere geometry...");
var center = new Rhino.Geometry.Point3d(centerX, centerY, centerZ);
var sphere = new Rhino.Geometry.Sphere(center, radius);
if (!sphere.IsValid)
{
Logger.Error("Invalid sphere geometry");
result = new { error = "Failed to create valid sphere geometry" };
return;
}
// Add to document
Logger.Debug("Adding sphere to document...");
var id = doc.Objects.AddSphere(sphere);
if (id == Guid.Empty)
{
Logger.Error("Failed to add sphere to document");
result = new { error = "Failed to add sphere to document" };
return;
}
// Apply color if specified
if (!string.IsNullOrEmpty(color))
{
System.Drawing.Color objColor = System.Drawing.Color.FromName(color);
if (objColor.A > 0)
{
var objAttributes = new Rhino.DocObjects.ObjectAttributes();
objAttributes.ColorSource = Rhino.DocObjects.ObjectColorSource.ColorFromObject;
objAttributes.ObjectColor = objColor;
doc.Objects.ModifyAttributes(id, objAttributes, true);
}
}
// Update views
doc.Views.Redraw();
Logger.Info($"Successfully created sphere with ID {id}");
result = new { success = true, objectId = id.ToString() };
success = true;
}
catch (Exception ex)
{
Logger.Error(ex, $"Error in UI thread: {ex.Message}");
result = new { error = $"Error in UI thread: {ex.Message}" };
}
}));
if (success)
{
return JsonConvert.SerializeObject(result);
}
else
{
return JsonConvert.SerializeObject(result ?? new { error = "Unknown error creating sphere" });
}
}
catch (Exception ex)
{
Logger.Error(ex, $"Exception in create_sphere: {ex.Message}");
return JsonConvert.SerializeObject(new {
error = $"Error creating sphere: {ex.Message}"
});
}
}
public string HandleCreateCylinder(JObject parameters)
{
Logger.Debug($"Processing create_cylinder with parameters: {parameters}");
try
{
// Extract parameters
double baseX = parameters.Value<double>("baseX");
double baseY = parameters.Value<double>("baseY");
double baseZ = parameters.Value<double>("baseZ");
double height = parameters.Value<double>("height");
double radius = parameters.Value<double>("radius");
string color = parameters.Value<string>("color");
// Access document
var doc = RhinoDoc.ActiveDoc;
if (doc == null)
{
Logger.Error("CRITICAL: RhinoDoc.ActiveDoc is NULL");
return JsonConvert.SerializeObject(new {
error = "No active Rhino document. Please open a document first."
});
}
// Execute on UI thread
bool success = false;
object result = null;
RhinoApp.InvokeOnUiThread(new Action(() =>
{
try
{
// Create geometry
var basePt = new Rhino.Geometry.Point3d(baseX, baseY, baseZ);
var topPt = new Rhino.Geometry.Point3d(baseX, baseY, baseZ + height);
var cylinder = new Rhino.Geometry.Cylinder(
new Rhino.Geometry.Circle(basePt, radius),
height
);
if (!cylinder.IsValid)
{
Logger.Error("Invalid cylinder geometry");
result = new { error = "Failed to create valid cylinder geometry" };
return;
}
// Add to document
var id = doc.Objects.AddCylinder(cylinder);
if (id == Guid.Empty)
{
Logger.Error("Failed to add cylinder to document");
result = new { error = "Failed to add cylinder to document" };
return;
}
// Apply color if specified
if (!string.IsNullOrEmpty(color))
{
System.Drawing.Color objColor = System.Drawing.Color.FromName(color);
if (objColor.A > 0)
{
var objAttributes = new Rhino.DocObjects.ObjectAttributes();
objAttributes.ColorSource = Rhino.DocObjects.ObjectColorSource.ColorFromObject;
objAttributes.ObjectColor = objColor;
doc.Objects.ModifyAttributes(id, objAttributes, true);
}
}
// Update views
doc.Views.Redraw();
Logger.Info($"Successfully created cylinder with ID {id}");
result = new { success = true, objectId = id.ToString() };
success = true;
}
catch (Exception ex)
{
Logger.Error(ex, $"Error in UI thread: {ex.Message}");
result = new { error = $"Error in UI thread: {ex.Message}" };
}
}));
if (success)
{
return JsonConvert.SerializeObject(result);
}
else
{
return JsonConvert.SerializeObject(result ?? new { error = "Unknown error creating cylinder" });
}
}
catch (Exception ex)
{
Logger.Error(ex, $"Exception in create_cylinder: {ex.Message}");
return JsonConvert.SerializeObject(new {
error = $"Error creating cylinder: {ex.Message}"
});
}
}
public string HandleClearScene(JObject parameters)
{
Logger.Debug($"Processing clear_scene with parameters: {parameters}");
try
{
bool currentLayerOnly = parameters.Value<bool>("currentLayerOnly");
// Access document
var doc = RhinoDoc.ActiveDoc;
if (doc == null)
{
Logger.Error("CRITICAL: RhinoDoc.ActiveDoc is NULL");
return JsonConvert.SerializeObject(new {
error = "No active Rhino document. Please open a document first."
});
}
// Execute on UI thread
bool success = false;
object result = null;
RhinoApp.InvokeOnUiThread(new Action(() =>
{
try
{
int deletedCount = 0;
if (currentLayerOnly)
{
// Get current layer index
int currentLayerIndex = doc.Layers.CurrentLayerIndex;
// Delete objects on current layer
var objectsToDelete = new List<Guid>();
foreach (var rhObj in doc.Objects)
{
if (rhObj.Attributes.LayerIndex == currentLayerIndex)
{
objectsToDelete.Add(rhObj.Id);
deletedCount++;
}
}
foreach (var id in objectsToDelete)
{
doc.Objects.Delete(id, true);
}
Logger.Info($"Deleted {deletedCount} objects from current layer");
}
else
{
// Delete all objects
deletedCount = doc.Objects.Count;
doc.Objects.Clear();
Logger.Info($"Cleared all {deletedCount} objects from document");
}
// Update views
doc.Views.Redraw();
result = new {
success = true,
deletedCount = deletedCount,
currentLayerOnly = currentLayerOnly
};
success = true;
}
catch (Exception ex)
{
Logger.Error(ex, $"Error in UI thread: {ex.Message}");
result = new { error = $"Error in UI thread: {ex.Message}" };
}
}));
if (success)
{
return JsonConvert.SerializeObject(result);
}
else
{
return JsonConvert.SerializeObject(result ?? new { error = "Unknown error clearing scene" });
}
}
catch (Exception ex)
{
Logger.Error(ex, $"Exception in clear_scene: {ex.Message}");
return JsonConvert.SerializeObject(new {
error = $"Error clearing scene: {ex.Message}"
});
}
}
public string HandleCreateLayer(JObject parameters)
{
Logger.Debug($"Processing create_layer with parameters: {parameters}");
try
{
string name = parameters.Value<string>("name");
string color = parameters.Value<string>("color");
if (string.IsNullOrEmpty(name))
{
Logger.Error("Layer name is required");
return JsonConvert.SerializeObject(new {
error = "Layer name is required"
});
}
// Access document
var doc = RhinoDoc.ActiveDoc;
if (doc == null)
{
Logger.Error("CRITICAL: RhinoDoc.ActiveDoc is NULL");
return JsonConvert.SerializeObject(new {
error = "No active Rhino document. Please open a document first."
});
}
// Execute on UI thread
bool success = false;
object result = null;
RhinoApp.InvokeOnUiThread(new Action(() =>
{
try
{
// Check if layer already exists
int existingIndex = doc.Layers.Find(name, true);
if (existingIndex >= 0)
{
Logger.Warning($"Layer '{name}' already exists");
var layer = doc.Layers[existingIndex];
result = new {
success = true,
layerId = layer.Id.ToString(),
layerName = layer.Name,
alreadyExisted = true
};
doc.Layers.SetCurrentLayerIndex(existingIndex, true);
success = true;
return;
}
// Create new layer
var newLayer = new Rhino.DocObjects.Layer();
newLayer.Name = name;
// Set color if specified
if (!string.IsNullOrEmpty(color))
{
System.Drawing.Color layerColor = System.Drawing.Color.FromName(color);
if (layerColor.A > 0)
{
newLayer.Color = layerColor;
}
}
// Add layer to document
int index = doc.Layers.Add(newLayer);
if (index < 0)
{
Logger.Error($"Failed to add layer '{name}' to document");
result = new { error = $"Failed to add layer '{name}' to document" };
return;
}
// Set as current layer
doc.Layers.SetCurrentLayerIndex(index, true);
Logger.Info($"Successfully created layer '{name}' with index {index}");
result = new {
success = true,
layerId = newLayer.Id.ToString(),
layerName = newLayer.Name,
layerIndex = index
};
success = true;
}
catch (Exception ex)
{
Logger.Error(ex, $"Error in UI thread: {ex.Message}");
result = new { error = $"Error in UI thread: {ex.Message}" };
}
}));
if (success)
{
return JsonConvert.SerializeObject(result);
}
else
{
return JsonConvert.SerializeObject(result ?? new { error = $"Unknown error creating layer '{name}'" });
}
}
catch (Exception ex)
{
Logger.Error(ex, $"Exception in create_layer: {ex.Message}");
return JsonConvert.SerializeObject(new {
error = $"Error creating layer: {ex.Message}"
});
}
}
}
}
```