I’m trying to programmatically modify an existing DetailView in a Rhino 8 layout using RhinoCommon (.NET 7). The code successfully finds the target detail, updates its camera location, target, projection, and display mode, and then calls CommitViewportChanges() followed by CommitChanges(). However, none of these visual changes appear in the layout — the detail’s view remains unchanged even though no errors are reported. It seems like the viewport updates aren’t being reflected or synchronized correctly between the DetailViewObject, its runtime DetailView, and the layout page.
// -*- coding: utf-8 -*-
// ORIOL — Components/Layout/DetailEdit
// Author: Sebastián Valenzuela Ferry
// Description: Modifies an existing Detail by applying camera, projection, display mode,
// and frustum from a reference view, with proper commit order for Rhino 8 / .NET 7.
using Grasshopper.Kernel;
using Oriol.Core;
using Rhino;
using Rhino.DocObjects;
using Rhino.Geometry;
using System;
using System.Drawing;
using System.Linq;
using DisplayModeDescription = Rhino.Display.DisplayModeDescription;
using ViewportInfo = Rhino.DocObjects.ViewportInfo;
namespace Oriol.Components.Layout
{
public class DetailEdit : GH_Component
{
private bool _prevRun = false;
public DetailEdit()
: base("Edit Detail", "DetailEdit",
"Edits an existing Detail applying camera, projection, display mode, and frustum from a reference view.",
"ORIOL", "LAYOUT")
{ }
public override GH_Exposure Exposure => GH_Exposure.primary;
protected override void RegisterInputParams(GH_InputParamManager p)
{
p.AddBooleanParameter("Run", "R", "Executes the action (toggle or button).", GH_ParamAccess.item, false);
p.AddTextParameter("GUID", "G", "GUID of the DetailView object to edit.", GH_ParamAccess.item);
p.AddTextParameter("Display", "D", "Display mode (Wireframe, Rendered, etc.).", GH_ParamAccess.item);
p.AddBoxParameter("Target", "T", "Target box or camera focus point.", GH_ParamAccess.item);
p.AddNumberParameter("Scale", "S", "Page-to-model scale.", GH_ParamAccess.item, 1.0);
p.AddIntegerParameter("Projection", "P", "Projection type (Top, Front, Perspective...).", GH_ParamAccess.item, 0);
p.AddGenericParameter("View", "V", "View or ViewportInfo to replicate camera and frustum from.", GH_ParamAccess.item);
for (int i = 2; i <= 6; i++) p[i].Optional = true;
}
protected override void RegisterOutputParams(GH_OutputParamManager p)
{
p.AddTextParameter("Result", "R", "Operation result.", GH_ParamAccess.item);
p.AddTextParameter("Display", "D", "Final display mode.", GH_ParamAccess.item);
p.AddPointParameter("Target", "T", "Resulting camera target.", GH_ParamAccess.item);
p.AddNumberParameter("Scale", "S", "Final detail scale.", GH_ParamAccess.item);
p.AddTextParameter("Projection", "P", "Current projection name.", GH_ParamAccess.item);
p.AddPointParameter("Camera", "C", "Resulting camera location (for debugging).", GH_ParamAccess.item);
}
protected override void SolveInstance(IGH_DataAccess DA)
{
bool run = false;
string guidText = "";
string displayName = "";
BoundingBox targetBox = BoundingBox.Empty;
double scale = 1.0;
int projInt = 0;
object viewInput = null!;
DA.GetData(0, ref run);
DA.GetData(1, ref guidText);
DA.GetData(2, ref displayName);
DA.GetData(3, ref targetBox);
DA.GetData(4, ref scale);
DA.GetData(5, ref projInt);
DA.GetData(6, ref viewInput);
// Rising edge detection
bool risingEdge = run && !_prevRun;
_prevRun = run;
if (!risingEdge) return;
// === Basic validation ===
if (!Guid.TryParse(guidText, out Guid detailId))
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Error, "Invalid GUID.");
return;
}
var doc = RhinoDoc.ActiveDoc;
if (doc == null)
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Error, "No active document.");
return;
}
var detailObj = doc.Objects.FindId(detailId) as DetailViewObject;
if (detailObj == null)
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Error, "Detail with this GUID not found.");
return;
}
// Find the PageView and runtime DetailView
var page = doc.Views.GetPageViews()
.FirstOrDefault(pv => pv.GetDetailViews().Any(dv => dv.Id == detailObj.Attributes.Id));
if (page == null)
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Warning, "The Detail does not belong to any active Layout.");
return;
}
var runtimeDetail = page.GetDetailViews().FirstOrDefault(dv => dv.Id == detailObj.Attributes.Id);
if (runtimeDetail == null)
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Warning, "No active DetailView found on this page.");
return;
}
var vp = runtimeDetail.Viewport;
if (vp == null)
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Error, "Could not get Viewport from Detail.");
return;
}
// Activate layout and its detail
page.SetPageAsActive();
doc.Views.ActiveView = page;
page.SetActiveDetail(runtimeDetail.Id);
RhinoApp.Wait();
var displayMode = DisplayModeDescription.GetDisplayModes()
.FirstOrDefault(m => m.DisplayAttributes.EnglishName.Equals(displayName, StringComparison.OrdinalIgnoreCase))
?? vp.DisplayMode;
var projection = Enum.IsDefined(typeof(Rhino.Display.DefinedViewportProjection), projInt)
? (Rhino.Display.DefinedViewportProjection)projInt
: Rhino.Display.DefinedViewportProjection.None;
bool prevRedraw = doc.Views.RedrawEnabled;
doc.Views.RedrawEnabled = false;
try
{
// === 1 Base camera or reference view ===
if (viewInput == null)
{
if (targetBox.IsValid)
vp.SetCameraTarget(targetBox.Center, true);
if (projection != Rhino.Display.DefinedViewportProjection.None)
vp.SetProjection(projection, projection.ToString(), true);
}
else
{
var srcVp = ViewInterpreter.ResolveViewport(viewInput);
if (srcVp != null)
{
bool applied = ViewInterpreter.ApplyToDetail(srcVp, detailObj, true);
RhinoApp.WriteLine(applied
? "[ORIOL] Frustum and camera replicated from reference view."
: "[ORIOL] Frustum replication failed.");
}
else
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Warning, "Reference view could not be resolved.");
}
}
// === 2 Display mode and target ===
if (!string.IsNullOrWhiteSpace(displayName))
vp.DisplayMode = displayMode;
if (targetBox.IsValid)
{
vp.SetCameraTarget(targetBox.Center, true);
if (targetBox.Diagonal.Length > 0)
vp.ZoomBoundingBox(targetBox);
}
// FIRST commit viewport
runtimeDetail.CommitViewportChanges();
// === 3 Persistent scale (DetailGeometry) ===
if (vp.IsParallelProjection && scale > 0 &&
Math.Abs(scale - detailObj.DetailGeometry.PageToModelRatio) > RhinoMath.ZeroTolerance)
{
bool prevLock = detailObj.DetailGeometry.IsProjectionLocked;
detailObj.DetailGeometry.IsProjectionLocked = false;
detailObj.DetailGeometry.SetScale(1, doc.ModelUnitSystem, scale, doc.PageUnitSystem);
detailObj.DetailGeometry.IsProjectionLocked = prevLock;
}
// Synchronize persistence and save
detailObj.DetailGeometry.Viewport = new ViewportInfo(vp);
detailObj.CommitChanges();
}
catch (Exception ex)
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Error, $"Error: {ex.Message}");
DA.SetData(0, "Failed");
doc.Views.RedrawEnabled = prevRedraw;
return;
}
finally
{
doc.Views.RedrawEnabled = prevRedraw;
}
// === 4 Final Redraw ===
page.Redraw();
doc.Views.ActiveView = page;
doc.Views.Redraw();
RhinoApp.Wait();
RhinoApp.WriteLine($"[ORIOL] DetailEdit OK → Mode:{vp.DisplayMode.EnglishName} | Target:{vp.CameraTarget}");
// --- Outputs ---
DA.SetData(0, "Success");
DA.SetData(1, vp.DisplayMode.EnglishName);
DA.SetData(2, vp.CameraTarget);
DA.SetData(3, detailObj.DetailGeometry.PageToModelRatio);
DA.SetData(4, vp.Name);
DA.SetData(5, vp.CameraLocation);
}
protected override Bitmap Icon =>
Rhino.UI.DrawingUtilities.BitmapFromIconResource("Oriol.Resources.Layout.DetailEdit.png", typeof(DetailEdit).Assembly);
public override Guid ComponentGuid =>
new Guid("FA2D07D3-B77A-4BAF-BEAD-AB61E2C1207E");
}
}
Any help or insight would be greatly appreciated.
Thanks in advance to anyone who can help me figure out why the DetailView camera changes are not taking effect in Rhino 8.
PD: The view that looks green is the one you should see in detailview.
