Move along Z axis without the gumball in face view

Hello everyone!

I was wondering if there were a easier way to align, let say the superior edge of a rectangle to an horizontale surface in FaceView. As shown below my usual workflow was to relocate the Gumball and to move the rectangle from it’s new origin. But I found it not very efficient, doest it existe a option within the displacement tool to force the displacement along a given axis just like in Blender for instance, when you can press X,Y or Z when moving an object to restrict the displacement to this given axis. Checking “project” will also do the trick with the displace tool in front view but again it’s not quick & easy. I’ve seen a command next to displace named “displace vertical” , but this only works in perspective view :confused: I don’t know if this is the right place of development insights but adding this feature inside the usual displace tool would be so practical!

With kind regards,

You don’t move the gumball unless you really need to - certainly not every time that you use Move/Scale/Rotate - I usually use M/S/R as shortcuts for the operations (with the correct snaps enabled) - then you have:

  • Holding Shift to toggle Orthographic Mode (Locks to angle increments in X&Y axes)
  • Ctrl+LMB to enter Elevator Mode (locks to Z axis)
  • OSnap together with Smart Track is exceptionally powerful
  • If you hold Ctrl and hover over your OSnaps you can also use tangents and perpendiculars in construction)
  • Tab to lock the direction or distance of an operation, combined with Snaps and SmartTrack

Don’t underestimate how much information is actually published in McNeel docs - get familiar with the interface and modelling aids before making feature requests!

Thank you so much! I did’nt know about the TAB and elevator feature!

But actually I still believe it’s missing a very simple possibility to force a displacement on a specific axis in front view like MZ .. I made an Alias MV for move vertical , but again it dosn’t work in front view :confused: And elevator feature is not so intuitive to me and doesn’t work in front view I think …

The tab option is nice but again it requires to be focus and chose the axis with you mouse , I’d prefer personally a command directly on the keyboard :confused:

But thank you again !

You can just hold Shift to move in the Z from front view without any aliases? You can also Click the gumball arrow to specify a distance

The elevator feature works in front view, but it won’t have a Z movement, because front view has an XZ Construction plane - so “up” on an XZ plane would be the Y axis.

Maybe also worth reading to these - you can constrain angles, distances move to absolute or relative coordinates and lots more Accurate drawing | Rhino 3-D modeling

Yes I can hold shift but when I try to snap to some object the dispcacement will not hold the axis unless I press Tab, maybe it’s just on my Rhino?

To me it would really worth it to have axis options in the displacement tool :smiley:

The links I shared answer this question….

SmartTrack allows you to create temporary snap points and then to align to them - so in this case, you make sure OSnap and Smart Track is On. You press M to enter the Move commands, then you click the anchor point to use as the origin of the transformation. Then you hover over a point that you need to align to and you will see a little blue cross as in your screenshot - when you move the mouse away, that cross will remain and turn grey (you can hover over it again to remove it) - you can now use this point for alignment - you hold shift and bring the object close to where you need it to be and you will see that you can align to the SmartTrack points you created by hovering.

Also, the move command uses vectors - so you can use @ x,y,z to move relative to the current point or x,y,z to move to a specific cartesian coordinate - the sooner you stop treating it like blender, the sooner you can start using Rhino

And you also have SetPt which will only allow changes to selected axes

Ortho forces the cursor to move parallel to an axis or at set angle increments to an axis. My recollection is the default increments are 45 degrees.

Yes, tab locks you along a vector. That’s how Rhino works.

If you’re using SmartTrack then you don’t even have to build up the finger gymnastics of shift+tab.

Just push tab when Smart Track has you on the desired axis.

I get what you’re asking for though.. The way we have Move>Vertical, you’re saying you’d also like Move>Xaxis and Move>Yaxis option.. while I personally might use that if it were added to _Move, there are definitely ways to accomplish what you’re wanting without these additions. (and they don’t require the gumball to be activated)

Hello Jeff,

Thank you, indeed, I am not a huge fan of the TAB+SHIFT method, it requires a bit of dexterity and is not as efficient as a simple shortcut in my opinion.

But when using Smart Track, I may do wrong but, I tap the move command, select bottom edge of my object for instance and then I point the cursor to the target height and slowly move horizontally on the white guide while maintaining shift to lock vertical movement? If this is the case It’s easier for me to move the gumball to the bottom of my object and moving with green arrow… :confused:

I think I might try this method: I made an alias MZ: !_Move _Vertical , and to have it work in front view, I change the Cplane to “top CPlane”. Hope this will not lead to other problems to change the Cplane… :smiley:

It’s maybe not very elegant, but in the end I created two alias:

MZ: !_CPlane _World _Top _Move _Vertical.

CC: _CPlane _Undo
:smiley:

here’s a python script that mostly mimics the _Move command except it also has options for X and Y axis lock:

do you know how to set up a macro for use with -RunPythonScript? if not, I’ll help you out with that bit.

# -*- coding: utf-8 -*-

import Rhino
import Rhino.Geometry as rg
import Rhino.Input as ri
import Rhino.Input.Custom as ric
import scriptcontext as sc
import System


def constrain_point(base_point, target_point, mode):
    # mode: 0=Free, 1=X, 2=Y, 3=Z
    if mode == 0:
        return target_point

    dx = target_point.X - base_point.X
    dy = target_point.Y - base_point.Y
    dz = target_point.Z - base_point.Z

    if mode == 1:  # X only
        return rg.Point3d(base_point.X + dx, base_point.Y, base_point.Z)
    if mode == 2:  # Y only
        return rg.Point3d(base_point.X, base_point.Y + dy, base_point.Z)
    if mode == 3:  # Z only
        return rg.Point3d(base_point.X, base_point.Y, base_point.Z + dz)

    return target_point


def axis_line_from_base(base_pt, mode, length=100000.0):
    if mode == 1:  # X
        return rg.Line(
            base_pt + rg.Vector3d(-length, 0, 0), base_pt + rg.Vector3d(+length, 0, 0)
        )
    if mode == 2:  # Y
        return rg.Line(
            base_pt + rg.Vector3d(0, -length, 0), base_pt + rg.Vector3d(0, +length, 0)
        )
    if mode == 3:  # Z
        return rg.Line(
            base_pt + rg.Vector3d(0, 0, -length), base_pt + rg.Vector3d(0, 0, +length)
        )
    return None


def draw_geom_wire(display, geom, color, thickness=2):
    """
    Draws common Rhino geometry as wireframe in the viewport.
    (Mac-friendly).
    """
    if isinstance(geom, rg.Curve):
        display.DrawCurve(geom, color, thickness)
        return

    if isinstance(geom, rg.Brep):
        display.DrawBrepWires(geom, color, thickness)
        return

    if isinstance(geom, rg.Mesh):
        display.DrawMeshWires(geom, color)
        return

    if isinstance(geom, rg.Extrusion):
        b = geom.ToBrep(True)
        if b:
            display.DrawBrepWires(b, color, thickness)
        return

    # fallback: bounding box
    try:
        bbox = geom.GetBoundingBox(True)
        display.DrawBox(bbox, color, 1)
    except:
        pass


class MovePreviewPointGetter(Rhino.Input.Custom.GetPoint):

    def __init__(self, base_pt, geoms, axis_mode_ref):
        super(MovePreviewPointGetter, self).__init__()
        self.base_pt = base_pt
        self.geoms = geoms
        self.axis_mode_ref = axis_mode_ref  # dict holding {"value": int}

    def OnDynamicDraw(self, e):
        try:
            raw_pt = e.CurrentPoint
            mode = self.axis_mode_ref["value"]

            constrained = constrain_point(self.base_pt, raw_pt, mode)

            # Light gray rubber-band line (base -> constrained)
            e.Display.DrawLine(
                self.base_pt, constrained, System.Drawing.Color.LightGray, 1
            )

            # magenta marker at constrained point
            e.Display.DrawPoint(
                constrained, Rhino.Display.PointStyle.X, 8, System.Drawing.Color.Magenta
            )

            # blue wire preview geometry
            move_vec = constrained - self.base_pt
            xform = rg.Transform.Translation(move_vec)

            for g in self.geoms:
                g2 = g.Duplicate()
                g2.Transform(xform)
                draw_geom_wire(e.Display, g2, System.Drawing.Color.Blue, 2)

        except:
            # never let preview crash the command
            pass


def move_with_axis_lock_and_preview():
    # 1) Select objects
    go = ric.GetObject()
    go.SetCommandPrompt("Select objects to move")
    go.GroupSelect = True
    go.SubObjectSelect = False
    go.EnablePreSelect(True, True)

    res = go.GetMultiple(1, 0)
    if res != ri.GetResult.Object:
        return

    objrefs = [go.Object(i) for i in range(go.ObjectCount)]
    if not objrefs:
        return

    # Duplicate geometry for preview
    preview_geoms = []
    for objref in objrefs:
        rhobj = objref.Object()
        if rhobj and rhobj.Geometry:
            preview_geoms.append(rhobj.Geometry.Duplicate())

    if not preview_geoms:
        print("No previewable geometry found.")
        return

    # 2) Base point
    gp_base = ric.GetPoint()
    gp_base.SetCommandPrompt("Point to move from")
    r = gp_base.Get()
    if r != ri.GetResult.Point:
        return
    base_pt = gp_base.Point()

    # axis mode state
    axis_mode = {"value": 0}  # 0 free, 1 X, 2 Y, 3 Z

    # 3) Second point with preview + options
    gp = MovePreviewPointGetter(base_pt, preview_geoms, axis_mode)
    gp.SetCommandPrompt("Point to move to")

    opt_free = gp.AddOption("Free")
    opt_x = gp.AddOption("X")
    opt_y = gp.AddOption("Y")
    opt_z = gp.AddOption("Vertical")

    while True:
        res = gp.Get()

        if res == ri.GetResult.Option:
            idx = gp.OptionIndex()

            if idx == opt_free:
                axis_mode["value"] = 0
            elif idx == opt_x:
                axis_mode["value"] = 1
            elif idx == opt_y:
                axis_mode["value"] = 2
            elif idx == opt_z:
                axis_mode["value"] = 3

            continue

        if res == ri.GetResult.Point:
            raw_target = gp.Point()
            target = constrain_point(base_pt, raw_target, axis_mode["value"])
            break

        return  # cancel / escape

    # 4) Apply move transform
    move_vec = target - base_pt
    xform = rg.Transform.Translation(move_vec)

    moved_any = False
    for objref in objrefs:
        rhobj = objref.Object()
        if not rhobj:
            continue

        if sc.doc.Objects.Transform(rhobj, xform, True):
            moved_any = True

    if moved_any:
        sc.doc.Views.Redraw()


if __name__ == "__main__":
    move_with_axis_lock_and_preview()

Wow, this is amazing! Thanks so much for taking the time to make this macro!!
I’m not very familiar with this yet, but I’m will to try it out.

Thanks again! :slight_smile:

It works perfectly Thanks again ! :folded_hands:

well, I didn’t spend too much time.. I asked ChatGPT to make it :wink:

if you want to modify it or tune it up, show that .py to ChatGPT and tell it how you want it to act. (with all that dynamic preview stuff going on in it, make sure to tell it you’re on Rhino for Mac (if you are) because it matters for that)

On rhino7 for mac it’s struggling haha but on windows it woks fine :slight_smile: