Parametric 6‑DOF Plane in Grasshopper

Built a 6‑DOF parametric plane setup:

  • Base: XY Plane
  • Rotation: yaw → pitch → roll using chained Rotate 3D
  • Axes: Line SDL + Unit X/Y/Z
  • Angles: degree sliders converted to radians
  • Translation: planning to use Move with dx/dy/dz
  • Intermediate previews clutter the viewport — will clean up

Screenshot attached. Posting for reference — open to suggestions.

here is a code-based solution. you need to build a plane with the vectors stored in the columns of R

from math import radians, cos, sin

def cardan_to_dcm(A: float,B: float,C: float):
	'''
	:param A: KUKA rotation around Z-Axis, also called yaw
	:param B: KUKA rotation around Y-Axis, also called pitch
	:param C: KUKA rotation around X-Axis, also called roll
	:return:
	'''

	# Rotation about the x - axis by angle C, where C > 0 indicates a counterclockwise rotation in the plane x = 0

	# Rx =          [[1,   0,           0  ],
	#                [0,   cos(C),  -sin(C)],
	#                [0,   sin(C),   cos(C)]]

	# Rotation about the y - axis by angle B, where B > 0 indicates a counterclockwise rotation in the plane y = 0

	# Ry =          [[ cos(B),   0,   sin(B)],
	#                [ 0,           1,   0  ],
	#                [-sin(B),   0,   cos(B)]]

	# Rotation about the z - axis by angle A, where A > 0 indicates a counterclockwise rotation in the plane z = 0

	# Rz =           [cos(A),   -sin(A),  0],
	#                [sin(A),    cos(A),  0],
	#                [0,            0,    1]]

	# R = Rz*Ry*Rx

	A: float = radians(A)
	B: float = radians(B)
	C: float = radians(C)

	cx,cy,cz = cos(C), cos(B), cos(A)
	sx,sy,sz = sin(C), sin(B), sin(A)

	# https://de.wikipedia.org/wiki/Eulersche_Winkel#Kardan-Winkel
	# z-y'-x'' intrinsic

	R: list[list[float]] = [[ cy*cz, cz*sx*sy - cx*sz,  cx*cz*sy + sx*sz],
	              [ cy*sz, cx*cz + sx*sy*sz, -cz*sx + cx*sy*sz],
	              [-sy   , cy*sx           ,  cx*cy]]

	return R

I’m still tryna wrap my brain around it though :face_holding_back_tears:

I need to review my trig and calc :sweat_smile: :beers:

looks like kuka robot language and some Eulersche Winkel – Wikipedia :upside_down_face: :star_struck:

:exploding_head:

hmm interesting…

:melting_face:

the reason why it does not work, is that you defined the function, but never actually call it. try it like this

def add(a,b):
    return a+b



if __name__ == "__main__":
    a = add(x,y) #this has to match the input/output of the grasshopper python node

This is where I ended up with it in the early mornin’

from math import radians, cos, sin
import Rhino.Geometry as rg

"""
Inputs:
    roll: Roll angle in degrees
    pitch: Pitch angle in degrees
    yaw: Yaw angle in degrees
    x, y, z: Origin coordinates of the plane
    axis_len: Length of preview axis lines
    sx, sy: Plane surface size

Outputs:
    plane: Constructed plane
    origin: Origin point
    x_axis: X direction vector
    y_axis: Y direction vector
    z_axis: Z direction vector
    axes: Preview axis lines
    srf: Plane surface
    rect: Rectangle curve on plane
"""

def RunScript(self, roll, pitch, yaw, x, y, z, axis_len, sx, sy):

    # --- Convert to radians ---
    roll  = radians(roll)
    pitch = radians(pitch)
    yaw   = radians(yaw)

    # --- Precompute trig ---
    cx, cy, cz = cos(roll), cos(pitch), cos(yaw)
    sx_, sy_, sz_ = sin(roll), sin(pitch), sin(yaw)

    # --- Rotation matrix (Z-Y-X intrinsic) ---
    R = [
        [cz*cy,  cz*sy_*sx_ - sz_*cx,  cz*sy_*cx + sz_*sx_],
        [sz_*cy, sz_*sy_*sx_ + cz*cx,  sz_*sy_*cx - cz*sx_],
        [  -sy_,            cy*sx_,            cy*cx    ]
    ]

    # --- Axes and origin ---
    x_axis = rg.Vector3d(R[0][0], R[0][1], R[0][2])
    y_axis = rg.Vector3d(R[1][0], R[1][1], R[1][2])
    z_axis = rg.Vector3d.CrossProduct(x_axis, y_axis)
    origin = rg.Point3d(x, y, z)

    # --- Final plane ---
    plane = rg.Plane(origin, x_axis, y_axis)

    # --- Axes as preview lines ---
    lx = rg.Line(origin, origin + x_axis * axis_len)
    ly = rg.Line(origin, origin + y_axis * axis_len)
    lz = rg.Line(origin, origin + z_axis * axis_len)
    axes = [lx, ly, lz]

    # --- Centered plane surface ---
    srf = rg.PlaneSurface(
        plane,
        rg.Interval(-sx * 0.5, sx * 0.5),
        rg.Interval(-sy * 0.5, sy * 0.5)
    )

    # --- Rectangle curve ---
    rect = rg.Rectangle3d(
        plane,
        rg.Interval(-sx * 0.5, sx * 0.5),
        rg.Interval(-sy * 0.5, sy * 0.5)
    )

    return plane, origin, x_axis, y_axis, z_axis, axes, srf, rect

This evening:

from math import radians, cos, sin
import Rhino.Geometry as rg

def RunScript(self, roll, pitch, yaw, x, y, z, axis_len, sx, sy):
    # ----- safe defaults WITHOUT changing the signature -----
    def d(v, default): return default if v is None else v
    roll     = d(roll, 0.0)
    pitch    = d(pitch, 0.0)
    yaw      = d(yaw, 0.0)
    x        = d(x, 0.0)
    y        = d(y, 0.0)
    z        = d(z, 0.0)
    axis_len = max(1e-6, d(axis_len, 10.0))
    sx       = max(1e-6, d(sx, 20.0))
    sy       = max(1e-6, d(sy, 20.0))

    # ----- angles (deg -> rad) -----
    roll, pitch, yaw = map(radians, (roll, pitch, yaw))

    # ----- trig -----
    cx, cy, cz = cos(roll),  cos(pitch), cos(yaw)
    sx_, sy_, sz_ = sin(roll), sin(pitch), sin(yaw)

    # ----- Z-Y-X intrinsic (yaw-pitch-roll) -----
    R = [
        [cy*cz,              cz*sx_*sy_ - cx*sz_,   cx*cz*sy_ + sx_*sz_],
        [cy*sz_,             cx*sy_*sz_ + cz*sx_,   cx*cz   - sx_*sy_*sz_],
        [-sy_,               cy*sx_,                cx*cy]
    ]

    # ----- axes from columns, orthonormalize -----
    x_axis = rg.Vector3d(R[0][0], R[1][0], R[2][0]); x_axis.Unitize()
    y_axis = rg.Vector3d(R[0][1], R[1][1], R[2][1]); y_axis.Unitize()
    z_axis = rg.Vector3d.CrossProduct(x_axis, y_axis); z_axis.Unitize()
    # re-derive y to guarantee right-handed frame
    y_axis = rg.Vector3d.CrossProduct(z_axis, x_axis); y_axis.Unitize()

    # ----- origin & plane -----
    origin = rg.Point3d(x, y, z)
    plane  = rg.Plane(origin, x_axis, y_axis)

    # ----- preview axes -----
    axes = [
        rg.Line(origin, origin + x_axis * axis_len),
        rg.Line(origin, origin + y_axis * axis_len),
        rg.Line(origin, origin + z_axis * axis_len),
    ]

    # ----- centered surface & rectangle -----
    ix = rg.Interval(-sx * 0.5, sx * 0.5)
    iy = rg.Interval(-sy * 0.5, sy * 0.5)
    srf  = rg.PlaneSurface(plane, ix, iy)
    rect = rg.Rectangle3d(plane, ix, iy)

    return plane, origin, x_axis, y_axis, z_axis, axes, srf, rect

didn’t think I’d end up with this many inputs/outputs lol :melting_face:

I decided to pick up the ball on this again this morning. And dabbled in the ETO Framework aspect.

pretty cool :smiling_face_with_sunglasses:

But… what is it for?

Rhino doesn’t have datum planes.

Every other CAD package hands you three in the tree on day one and lets you add more — offset, angled, through three points — that you name, sketch on, and reference for the life of the model. In Rhino a CPlane is a viewport setting, a named CPlane is a bookmark you restore by hand, and anything you can actually click is a trimmed surface pretending to be a plane. None of them are geometry that other things depend on.

So: six numbers in, a real frame out — origin, three axes, sized rectangle — live in the viewport and bakeable. Rotation is yaw about Z, then pitch about the new Y, then roll about the new X, so the numbers match machine and robot convention and still mean something outside Rhino.

Storing the result as a named CPlane is the next step, so it covers the persist-and-restore side too.

Mostly though, it’s an experiment in a niche — scratching at something Rhino leaves open.

I didn’t really understand this.

What I think your saying, is that geometry in other CAD programs is dependent on persistent Cplanes which, when you change after the creation of the geometry, will also transform the geometry associated with that Cplane? Is that right?

So you’re just defining transformations? And a visual aid for them?

I think so yes. Basically if I work on a design that requires a sketch to be on a plane at a certain angle, elevation, position, etc. and I need to make adjustments to those 6 degrees of freedom, then as a designer there should be intuitive ways to do that – in Rhino these tools are not readily available out of the box, cause the devs are focused on the overly advanced parametric grasshopper tech instead of basic parametric tech. They skipped all the basics, and they making me learn to code :sob:

Yes, mostly this is like a test trial to see what’s possible in regards to things like parametrics based on basic parametric modeling.

So I’ll also look into kangaroo, but I think I might end up doing more ETO Frameworks and probably end up going to C# and doing actual plugins.

I just think some previous developments on the whole “sketches” thing was taking the wrong approach, so I’m looking into ways I can get involved and have influence in a meaningful way from my own perspective.

I love the ‘3D all the time’ character of Rhino, and I think it can evolve so that, with a few basic constraints and some ‘object properties’ upgrades, Rhino can reach a unique level of basic style parametrics that will fulfill a whole bunch of things the solid modeler community would like to have.

Are you using blocks?

no I’ve not really got into using blocks much :face_with_diagonal_mouth:

what am I missing?

Blocks have their own construction plane. You can create a sketch on the XY plane and import it as a block.

When you work with the same bolts and nuts (hardware) all the time and have many instances in a file, the file size can be kept low by using linked blocks.

are you serious rn :exploding_head:

I don’t always have the privilege of simple geometry in my biggest challenges.

At the moment I was just thinking about a few basic sketch positions.

I might be able to share more info, but have to be careful with IP.

i.e.

so the idea here is relative to points of data located on separate planes of particular positions, angles, elevations etc.

From what I read above, I had the impression you’re not using blocks and the sketch on a plane is just a simple example.

yes you blew my mind. i didn’t know blocks have a sketch plane…

I will have to ponder this and see how I can approach it.

A block instance with its transformation…

if you wanted to see the mess…

this file is based on one that’s a yr old, where I was pondering this…

sketch_plane_experiment.gh (21.8 KB)

It basically solves your problem only it uses (@martinsiegrist) x-y-z Euler coordinates?

well I mean it’s good to know more about blocks, but I’m not 100% convinced that it fulfills the tasks I’m looking for.

of course good to know Rhino’s abilities in all correlative categories though. It demonstrates and confirms to me that Rhino is capable of much more things for future goals.

eventually I’m going to be looking into similar things whereby Rhino object properties can be editable similar to how mastercam treats objects.