Hello, this is a code request to help automate a workflow that I’m doing a lot of recently. It has to do with triggering the Mapping Widget when a target object is selected, checking for box mapping and turning it off. So that it acts as a toggle. In detail the script would work something like this:
With an object selected, I will trigger the alias and call the script (I never trigger the widget w/o an existing selection that the widget is to act on).
Script checks if there is a mapping widget active.
If active, script turns the mapping widget off (this would be the toggle).
If no widget active it checks the selected item for box mapping.
if no box mapping, then it applies bounding box box mapping.
When / If box mapping is assigned to the active object then the mapping widget is turned on.
After widget adjustment I trigger the same alias to go to step 1… to turn the widget off.
Yes, this is doable from RhinoCommon and Python. The mapping widget itself is driven by two commands, _MappingWidget <channel> and _MappingWidgetOff, and everything else (checking for box mapping, applying a bounding-box box mapping) is straightforward with the Rhino.Render.TextureMapping class.
I put together a toggle script that does what you described. A couple of notes:
There isn’t a clean, non-interactive way to ask whether the widget is currently shown, so I track the on/off state in scriptcontext.sticky. _MappingWidgetOff is harmless when nothing is up, so the toggle stays reliable even if the state ever gets out of sync.
It acts on the first selected object and uses mapping channel 1.
import System
import Rhino
import scriptcontext as sc
from Rhino.Render import TextureMapping, TextureMappingType
from Rhino.Geometry import Plane, Interval, Vector3d
CHANNEL = 1
def has_box_mapping(obj):
for ch in (obj.GetTextureChannels() or []):
tm = obj.GetTextureMapping(ch)
if tm and tm.MappingType == TextureMappingType.BoxMapping:
return ch
return None
def apply_bbox_box_mapping(obj):
bb = obj.Geometry.GetBoundingBox(True)
c = bb.Center
plane = Plane(c, Vector3d.XAxis, Vector3d.YAxis)
dx = Interval(bb.Min.X - c.X, bb.Max.X - c.X)
dy = Interval(bb.Min.Y - c.Y, bb.Max.Y - c.Y)
dz = Interval(bb.Min.Z - c.Z, bb.Max.Z - c.Z)
obj.SetTextureMapping(CHANNEL, TextureMapping.CreateBoxMapping(plane, dx, dy, dz, True))
def toggle():
doc = sc.doc
sel = list(doc.Objects.GetSelectedObjects(False, False))
if not sel:
print("Select an object first.")
return
obj = sel[0]
key = "mapwidget_" + str(obj.Id)
doc.Objects.UnselectAll()
doc.Objects.Select(obj.Id)
if sc.sticky.get(key, False):
Rhino.RhinoApp.RunScript("_MappingWidgetOff", False)
sc.sticky[key] = False
else:
ch = has_box_mapping(obj)
if ch is None:
apply_bbox_box_mapping(obj)
ch = CHANNEL
Rhino.RhinoApp.RunScript("_MappingWidget %d" % ch, False)
sc.sticky[key] = True
doc.Views.Redraw()
toggle()
Genius! Many thanks. I can’t believe you did this;-).
After a few tests I realized that my request did not cover all use cases:
At times I select a set of objects or a grouped set of objects and want to run this script. Is it possible to have the script do it’s thing on multiple selected objects. As of now, the script down-selects to one object.
As to the toggle off state. Maybe we simplify this a bit… Get around the need for “scriptcontext.sticky.” I tend to have the widget selected at the end of the texture tweak operation. It makes more sense to call the off state here by toggling the alias. The selected item will be the widget (note that this could be multiple widgets if multiple objects were selected to act upon at start).