Help on object transform

I am working on a script which will transform multiple polysurfaces to the face of another polysurface.
This almost works, in that the objects are transformed to the plane of the face, but they lose two dimensions. So if a polysurface started at x=2.5, y=120, z=0.75, once transformed it is x=0, y=120, z=0. Clearly there is something I don’t understand about how to use PlaneToPlane transformation.
If anyone can help guide me in the right direction that would much appreciated.
Here is the script and attached is a sample file. In this sample the objective is to transform the group of objects on the wall_framing and wall_skin layers to the selected face of the object on the 02_sketch layer.

#! python 3

import Rhino
import scriptcontext as sc
import rhinoscriptsyntax as rs

def get_objects_plane(objects):

    objects_bbox = None
    for obj in objects:
        obj = rs.coercerhinoobject(obj)
        obj_bbox = obj.Geometry.GetBoundingBox(True)
        if obj_bbox.IsValid:
            if objects_bbox is None:
                objects_bbox = obj_bbox
            else:
                objects_bbox.Union(obj_bbox)
    
    if not objects_bbox or not objects_bbox.IsValid:
        print("Could not compute bounding box for the group.")
        return
    
    # Compute the center points of both bounding boxes    
    objects_center = objects_bbox.Center
                    
    # Define the coordinate system of the group
    objects_min = objects_bbox.Min
    objects_x_axis = Rhino.Geometry.Vector3d(objects_bbox.Corner(True, True, True) - objects_min)
    objects_y_axis = Rhino.Geometry.Vector3d(objects_bbox.Corner(False, True, True) - objects_min)
    objects_z_axis = Rhino.Geometry.Vector3d(objects_bbox.Corner(True, True, False) - objects_min)
    
    # Normalize the vectors
    if objects_x_axis.Length > 0:
        objects_x_axis.Unitize()
    if objects_y_axis.Length > 0:
        objects_y_axis.Unitize()
    if objects_z_axis.Length > 0:
        objects_z_axis.Unitize()
    
    # Create planes for orientation    
    objects_plane = Rhino.Geometry.Plane(objects_center, objects_x_axis, objects_y_axis)
    return objects_plane

go = Rhino.Input.Custom.GetObject()
go.SubObjectSelect = True
go.GeometryFilter = Rhino.DocObjects.ObjectType.Surface
go.SetCommandPrompt("Select face of object to transform objects to.")
go.Get()                        
res = go.Result()

objref = go.Object(0)  
geo = sc.doc.Objects.Find(objref.ObjectId)          
face = objref.Geometry()

if face.IsPlanar:
   
    res, plane = face.TryGetPlane()
    if res and plane.IsValid:
        objects = rs.GetObjects("Select objects to transform", filter = rs.filter.polysurface)

        objects_plane = get_objects_plane(objects)
        print(f"objects_plane is {objects_plane}")
        print(f"plane is {plane}")
                
        # Compute the transformation
        xform = Rhino.Geometry.Transform.PlaneToPlane(objects_plane, plane)
        
        # Apply the transformation to all group members
        for obj in objects:
            sc.doc.Objects.Transform(obj, xform, True)

transform_test.3dm (2.5 MB)

The transformations you defined in your script is ambiguous.

I would try the following

  • Select 3 points on the face to represent the origin, x & y directions of target plane
  • Create plane based on these 3 points
  • Select objects to transform (like you do right now)
  • Select 3 points relevant to the objects you selected to represent origin, x & y directions of the objects’ plane
  • Create objects_plane from 3 points
  • Set a Rotation Transform
  • Transform objects.

I hope this helps.

Here is a sample script:


def main():
    # Step 1: Select 3 points on the face (target plane)
    target_pts = rs.GetPoints(True, False, "Select 3 points for the target plane")
    if not target_pts or len(target_pts) != 3:
        print("You must select exactly 3 points.")
        return
    
    origin, x_dir_pt, y_dir_pt = target_pts
    x_vec = rs.VectorUnitize(rs.VectorCreate(x_dir_pt, origin))
    y_vec = rs.VectorUnitize(rs.VectorCreate(y_dir_pt, origin))
    plane = rs.PlaneFromFrame(origin, x_vec, y_vec)
    
    # Step 2: Select objects to transform
    objs = rs.GetObjects("Select objects to transform", preselect=True)
    if not objs:
        print("No objects selected.")
        return

    # Step 3: Select 3 reference points for objects' original position (objects_plane)
    obj_pts = rs.GetPoints(True, False, "Select 3 reference points")
    if not obj_pts or len(obj_pts) != 3:
        print("You must select exactly 3 reference points.")
        return
    
    obj_origin, obj_x_dir_pt, obj_y_dir_pt = obj_pts
    obj_x_vec = rs.VectorUnitize(rs.VectorCreate(obj_x_dir_pt, obj_origin))
    obj_y_vec = rs.VectorUnitize(rs.VectorCreate(obj_y_dir_pt, obj_origin))
    objects_plane = rs.PlaneFromFrame(obj_origin, obj_x_vec, obj_y_vec)

    # Step 4: Compute and apply the transformation
    arrXform = rs.XformRotation1(objects_plane, plane)
    rs.TransformObjects(objs, arrXform, copy=False)

main()

Hi Rajaa,
Thanks. What I was hoping to accomplish is more automation. The user selects a face and the objects to align and the script does the alignment. It seems I almost have this except that all of my objects get squashed to zero in 2 axes, which I don’t understand.
Do you think this is feasible or am I dreaming?
Cheers,
Gary

I think plane-to-plane works like you think it does, but I think something is wrong with the planes you constructed. The script below seems to work. However, I think it will only work in specific cases where the objects you want to transform and the target surfaces are oriented just like this. If you want to be able to have random orientations on either or both, it’s gonna get harrier, and bounding boxes will probably not be useful (or, at least I’ve not been able to get bounding boxes to work for that unless the objects were already nicely parallel to the worldxy plane)

#! python 3

import Rhino
import scriptcontext as sc
import rhinoscriptsyntax as rs

def get_objects_plane(objects):

    objects_bbox = None
    for obj in objects:
        obj = rs.coercerhinoobject(obj)
        obj_bbox = obj.Geometry.GetBoundingBox(True)
        if obj_bbox.IsValid:
            if objects_bbox is None:
                objects_bbox = obj_bbox
            else:
                objects_bbox.Union(obj_bbox)
    
    if not objects_bbox or not objects_bbox.IsValid:
        print("Could not compute bounding box for the group.")
        return
    
    # Compute the center points of both bounding boxes    
    objects_center = objects_bbox.Center
                    
    # Define the coordinate system of the group
    objects_min = objects_bbox.Min

    # THIS ISN'T QUITE RIGHT
    # objects_x_axis = Rhino.Geometry.Vector3d(objects_bbox.Corner(True, True, True) - objects_min)
    # objects_y_axis = Rhino.Geometry.Vector3d(objects_bbox.Corner(False, True, True) - objects_min)
    # objects_z_axis = Rhino.Geometry.Vector3d(objects_bbox.Corner(True, True, False) - objects_min)

    # THIS IS WHAT YOU WERE TRYING TO DO...
    # BUT WHILE IT NO LONGER SQUASHES THE OBJECTS, THEY ARE ORIENTED WRONG
    # objects_x_axis = Rhino.Geometry.Vector3d(objects_bbox.Corner(False, True, True) - objects_min)
    # objects_y_axis = Rhino.Geometry.Vector3d(objects_bbox.Corner(True, False, True) - objects_min)
    # objects_z_axis = Rhino.Geometry.Vector3d(objects_bbox.Corner(True, True, False) - objects_min)

    # FOR SOME REASON WE HAVE TO SWITCH UP THE AXES LIKE THIS
    objects_y_axis = Rhino.Geometry.Vector3d(objects_bbox.Corner(False, True, True) - objects_min)
    objects_z_axis = Rhino.Geometry.Vector3d(objects_bbox.Corner(True, False, True) - objects_min)
    objects_x_axis = Rhino.Geometry.Vector3d(objects_bbox.Corner(True, True, False) - objects_min)

    # IT SEEMS NORMALIZING THE VECTORS IS UNNECESSARY
    # (AT LEAST FOR WHAT THE SCRIPT DOES SO FAR)
    # # Normalize the vectors
    # if objects_x_axis.Length > 0:
    #     objects_x_axis.Unitize()
    # if objects_y_axis.Length > 0:
    #     objects_y_axis.Unitize()
    # if objects_z_axis.Length > 0:
    #     objects_z_axis.Unitize()
    
    # Create planes for orientation    
    objects_plane = Rhino.Geometry.Plane(objects_center, objects_x_axis, objects_y_axis)
    return objects_plane

go = Rhino.Input.Custom.GetObject()
go.SubObjectSelect = True
go.GeometryFilter = Rhino.DocObjects.ObjectType.Surface
go.SetCommandPrompt("Select face of object to transform objects to.")
go.Get()                        
res = go.Result()

objref = go.Object(0)  
geo = sc.doc.Objects.Find(objref.ObjectId)          
face = objref.Geometry()

if face.IsPlanar:
   
    res, plane = face.TryGetPlane()
    if res and plane.IsValid:
        objects = rs.GetObjects("Select objects to transform", filter = rs.filter.polysurface)

        objects_plane = get_objects_plane(objects)
        print(f"objects_plane is {objects_plane}")
        print(f"plane is {plane}")
                
        # Compute the transformation
        xform = Rhino.Geometry.Transform.PlaneToPlane(objects_plane, plane)

        # Apply the transformation to all group members
        for obj in objects:
            sc.doc.Objects.Transform(obj, xform, True)

Here’s a second script that can determine the plane of the target object (the orange one, right?) no matter how it is oriented. And it draws some isocurves and a planar surface on it just to prove we’ve done that, but I’m not sure how I would detect the plane of the object you want to transform. (Might be able to somehow get the U, V of the surface you click on and get the normal from that…maybe…i’m not sure).

Note that in this script, to select the face of the orange object, you would hold ctrl-shift while selecting, just like sub-selecting in regular Rhino.

#! python 3

import rhinoscriptsyntax as rs
import scriptcontext as sc
import Rhino.Geometry as rg

target_id = rs.GetObject('Select planar target surface', rs.filter.surface | rs.filter.polysurface, True, False, None, True)
bbox = rs.BoundingBox(target_id)

target = rs.coercesurface(target_id)
if target:
    if target.IsPlanar():
        # record original u,v domains of surface
        original_interval0 = target.Domain(0)
        original_interval1 = target.Domain(1)

        # unitize u,v domains
        target.SetDomain(0, rg.Interval(0,1))
        target.SetDomain(1, rg.Interval(0,1))

        # make some isocurves across the middle of the surface
        # in both the u and v directions
        iso1 = target.IsoCurve(0, 0.5)
        iso2 = target.IsoCurve(1, 0.5)

        # set the domains back to what they were
        target.SetDomain(0, original_interval0)
        target.SetDomain(0, original_interval1)

        origin = iso1.PointAtMid
        target_pln = rg.Plane(origin, iso1.PointAtEnd - origin, iso2.PointAtEnd - origin)

        pln2 = rg.PlaneSurface(target_pln, rg.Interval(-50,50), rg.Interval(-50,50))
        
        sc.doc.Objects.AddCurve(iso1)
        sc.doc.Objects.AddCurve(iso2)
        sc.doc.Objects.Add(pln2)
    else:
        print('Surface was not planar')
else:
    print('You did not select a surface')