UserText, Blocks, and Bounding Boxes

In a bit of downtime, and with considerable coffee, I’m trying to streamline some tasks that are done on a frequent basis, things that take 10 to 30 seconds here and there but are done often enough on busy days that it could save an hour or two over the long run.

Currently, I’m working on detail view titles on layouts. (shown below, in green)

Rhino is our whole ecosystem for production, we don’t shift back to AutoCAD (thankfully!) but one thing I’ve always missed is the dynamic block function.

The detail titles were previously just groups, and we’d have to manually edit the text fields one at a time. Then I made the sheet number (0.01, above) into a %<pagename>% field. To take this one step further, I’ve made the title into a block with block user text for the View Number, View Scale, and View Title pulling from the detail view itself. This part seems to work pretty well.

That said, the underline doesn’t scale with the length of the text as it would in the old dynamic blocks. I’d like to keep the underline so as to keep our current standards consistent, but I’d like to have it scale to match the longer of the 2 main text fields (title and scale).

To that end, I’ve been noodling in Python and have the block placement and text editing sorted, but the line is still giving me issues. It’s always too short or too long, in weird ways that I’m just not wrapping my head around. Even stranger, sometimes the lines are the same length and sometimes they’re different, and it seems to depend on whether I’ve included the debugging text dots…

ViewTitler.py (1.8 KB)

TitleTest 2026Mar02 Export.3dm (181.0 KB)

I’ve attached my test file, with the block in the file; I’ve also attached the Python I have so far. Is using the bounding box of each of the blocks going down the wrong path?

System Info

Rhino 8 SR29 2026-2-23 (Rhino 8, 8.29.26054.15001, Git hash:master @ f049d3238b117fab7762ea67fd9e162cc3b173c7)
License type: Commercial, build 2026-02-23
License details: Cloud Zoo

Windows 11 (10.0.26100 SR0.0) or greater (Physical RAM: 32GB)
.NET 8.0.24

Computer platform: DESKTOP

Standard graphics configuration.
Primary display and OpenGL: NVIDIA GeForce RTX 2060 (NVidia) Memory: 6GB, Driver date: 12-30-2025 (M-D-Y). OpenGL Ver: 4.6.0 NVIDIA 591.74

Accelerated graphics device with 4 adapter port(s)

  • Secondary monitor attached to adapter port 0
  • Windows Main Display attached to adapter port 1

Secondary graphics devices.
Intel(R) UHD Graphics 630 (Intel) Memory: 1GB, Driver date: 6-1-2021 (M-D-Y).

Integrated graphics device with 3 adapter port(s)

  • There are no monitors attached to this device!

OpenGL Settings
Safe mode: Off
Use accelerated hardware modes: On
GPU Tessellation is: On
Redraw scene when viewports are exposed: On
Graphics level being used: OpenGL 4.6 (primary GPU’s maximum)

Anti-alias mode: 4x
Mip Map Filtering: Linear
Anisotropic Filtering Mode: High

Vendor Name: NVIDIA Corporation
Render version: 4.6
Shading Language: 4.60 NVIDIA
Driver Date: 12-30-2025
Driver Version: 32.0.15.9174
Maximum Texture size: 32768 x 32768
Z-Buffer depth: 24 bits
Maximum Viewport size: 32768 x 32768
Total Video Memory: 6 GB

Rhino plugins that do not ship with Rhino
C:\Users\ssommerville\AppData\Roaming\McNeel\Rhinoceros\packages\8.0\SubstanceImporter\2.0.7\Substance.Win.rhp “SubstanceImporter” 2.0.7.0

Rhino plugins that ship with Rhino
C:\Program Files\Rhino 8\Plug-ins\Commands.rhp “Commands” 8.29.26054.15001
C:\Program Files\Rhino 8\Plug-ins\rdk.rhp “Renderer Development Kit”
C:\Program Files\Rhino 8\Plug-ins\RhinoScript.rhp “RhinoScript”
C:\Program Files\Rhino 8\Plug-ins\RhinoRenderCycles.rhp “Rhino Render” 8.29.26054.15001
C:\Program Files\Rhino 8\Plug-ins\RhinoRender.rhp “Legacy Rhino Render”
C:\Program Files\Rhino 8\Plug-ins\rdk_etoui.rhp “RDK_EtoUI” 8.29.26054.15001
C:\Users\ssommerville\AppData\Roaming\McNeel\Rhinoceros\packages\8.0\PanelingTools\2024.8.20.677\PanelingTools.rhp “PanelingTools”
C:\Program Files\Rhino 8\Plug-ins\NamedSnapshots.rhp “Snapshots”
C:\Program Files\Rhino 8\Plug-ins\MeshCommands.rhp “MeshCommands” 8.29.26054.15001
C:\Program Files\Rhino 8\Plug-ins\IronPython\RhinoDLR_Python.rhp “IronPython” 8.29.26054.15001
C:\Program Files\Rhino 8\Plug-ins\RhinoCycles.rhp “RhinoCycles” 8.29.26054.15001
C:\Program Files\Rhino 8\Plug-ins\RhinoCode\RhinoCodePlugin.rhp “RhinoCodePlugin” 8.29.26054.15001
C:\Program Files\Rhino 8\Plug-ins\Toolbars\Toolbars.rhp “Toolbars” 8.29.26054.15001
C:\Program Files\Rhino 8\Plug-ins\3dxrhino.rhp “3Dconnexion 3D Mouse”
C:\Program Files\Rhino 8\Plug-ins\BlockEdit.rhp “BlockEdit” 8.29.26054.15001
C:\Program Files\Rhino 8\Plug-ins\Displacement.rhp “Displacement”
C:\Program Files\Rhino 8\Plug-ins\SectionTools.rhp “SectionTools”


What I’m currently seeing. For some reason, the right end of the line is super short.

Best I have been able to come up with so far (ashamedly, having to use AI as a starting point, but then finding where it’s led me astray with nonsense):

import rhinoscriptsyntax as rs
import Rhino

#line length correction amount
correctionFactor = 0.048

detailIDs = rs.GetObjects(message="Select Detail views:", filter=32768,group=False,preselect=False,select=False)

for detail in detailIDs:
	viewscale = rs.DetailScale(detail)

	viewtitle = rs.ObjectName(detail)

	detailbox = rs.BoundingBox(detail)

	# Insert the block at the lower-left corner of the detail
	blocktitle = rs.InsertBlock("View_Title",detailbox[0],scale=(1,1,1))

	# Set the Block Attributes (ViewNumber, ViewScale, and ViewTitle)
	rs.SetUserText(blocktitle,"ViewNum","1")
	rs.SetUserText(blocktitle,"ViewScale",f"%<DetailScale(\"{detail}\",\"#=1\")>%")
	rs.SetUserText(blocktitle,"ViewTitle",viewtitle.upper())

	instance_obj = rs.coercerhinoobject(blocktitle)
	attr_objects = instance_obj.GetSubObjects()

	text_length = []

	for sub in attr_objects:
		if isinstance(sub,Rhino.DocObjects.TextObject):
			name = rs.ObjectName(sub)
			if name == "titletext" or name == "scaletext":
				text_geo = sub.Geometry
				bbox = text_geo.GetBoundingBox(True)
				text_length.append(bbox.Max.X - bbox.Min.X)
			break
	
	#Draw the underline
	circleOffset = (0.5,-0.25,0)
	lineStart = rs.PointAdd(detailbox[0],circleOffset)
	lineEnd = (lineStart[0] + max(text_length)+ correctionFactor, lineStart[1], lineStart[2])
	line = rs.AddLine(lineStart,lineEnd)
	rs.ObjectLayer(line,"000-ANNOTATIONS")
		
	#Group the underline w/ the block
	groupName = viewtitle + "_block"
	rs.AddGroup(groupName)
	rs.AddObjectsToGroup([blocktitle,line],groupName)

It’s strange to me to have to add that consistent correctionFactor. Also have to go in and clean things up a bit, but at least it gets me somewhere. Next thing - numbering.

Hi @ssommerv ,

Is your intention that the line extends:

  1. to the right aligned edge of the text above it
  2. all the way to the right aligned edge of the detail bounding box
  3. to either of the above with an optional additional amount (like shortening the line or lengthening it by a few units consistently)

I believe there is a “measure string” method that is part of RhinoCommon or possible Eto that can be leveraged for more accurately measuring the text visual length in units. I often use this in UI design code.

The intention is that the line extends to the longer of:

  • The detail title (Plan View/Right Section, shown above)
  • The scale

So in the unlikely situation where you have a really short name (DET A) and a long scale (1-1/2" = 1’ -0"), the line extends to the right end of the scale text.