Hi Tom,
As I’m far from experienced with Rhino and Python, I’ve essentially relied on Chat GPT to generate something that partially works.
Down below you’ll find a script which, from an input geometry, generates an output mesh. Initially, I applied an extremely simplistic deformation (z translation of all points), but in the future I’d like to be able to play with each point in more detail.
The problem I’m currently encountering is that I can’t generate a closed mesh from this script. In my case, I’m working with complex shapes (boat hulls) which need to be closed for CFD simulations. This is where Cage Edit is extremely powerful.
If you can think of a way to improve this code so that it generates a closed mesh, I’d love to hear from you!
Otherwise I’m going to use scriptsyntax with an auto-click to cobble together an automation that’s not very robust at first.
Thanks in advance for your help.
Etienne
import rhinoinside
rhinoinside.load()
import Rhino
from Rhino.Geometry import *
import System
import numpy as np
import os
class CageMorph:
def __init__(self, source_pts, target_pts, nx, ny, nz):
self.source_pts = np.array(source_pts).reshape((nx, ny, nz))
self.target_pts = np.array(target_pts).reshape((nx, ny, nz))
self.nx, self.ny, self.nz = nx, ny, nz
def morph_point(self, pt):
u = self._get_normalized_position(pt)
if u is None:
return pt # Hors cage
i = int(u[0] * (self.nx - 1))
j = int(u[1] * (self.ny - 1))
k = int(u[2] * (self.nz - 1))
i = min(i, self.nx - 2)
j = min(j, self.ny - 2)
k = min(k, self.nz - 2)
dx = u[0] * (self.nx - 1) - i
dy = u[1] * (self.ny - 1) - j
dz = u[2] * (self.nz - 1) - k
def get(p, q, r): return self.target_pts[i+p, j+q, k+r]
def lerp(a, b, t): return a + t * (b - a)
c000 = get(0, 0, 0)
c100 = get(1, 0, 0)
c010 = get(0, 1, 0)
c110 = get(1, 1, 0)
c001 = get(0, 0, 1)
c101 = get(1, 0, 1)
c011 = get(0, 1, 1)
c111 = get(1, 1, 1)
x00 = lerp(c000, c100, dx)
x10 = lerp(c010, c110, dx)
x01 = lerp(c001, c101, dx)
x11 = lerp(c011, c111, dx)
y0 = lerp(x00, x10, dy)
y1 = lerp(x01, x11, dy)
final = lerp(y0, y1, dz)
return final
def morph(self, geom):
"""Apply morph on any geometry"""
class SimpleMorph(Rhino.Geometry.SpaceMorph):
def __init__(self, morph_fn):
super(SimpleMorph, self).__init__()
self._morph_fn = morph_fn
def MorphPoint(self, pt):
return self._morph_fn(pt)
smorph = SimpleMorph(self.morph_point)
geom_copy = geom.Duplicate()
smorph.Morph(geom_copy)
return geom_copy
def _get_normalized_position(self, pt):
bb_min = self.source_pts[0, 0, 0]
bb_max = self.source_pts[-1, -1, -1]
if not BoundingBox(bb_min, bb_max).Contains(pt):
return None
u = (pt.X - bb_min.X) / (bb_max.X - bb_min.X)
v = (pt.Y - bb_min.Y) / (bb_max.Y - bb_min.Y)
w = (pt.Z - bb_min.Z) / (bb_max.Z - bb_min.Z)
return (u, v, w)
def open_single_brep(filepath):
model = Rhino.FileIO.File3dm.Read(filepath)
for obj in model.Objects:
geo = obj.Geometry
if isinstance(geo, Brep):
return geo
raise ValueError("No polysurface found.")
def generate_regular_grid(bbox, nx, ny, nz):
"""Create an evenly distributed grid in the bounding box"""
pts = []
for i in range(nx):
x = bbox.Min.X + i * (bbox.Max.X - bbox.Min.X) / (nx - 1)
for j in range(ny):
y = bbox.Min.Y + j * (bbox.Max.Y - bbox.Min.Y) / (ny - 1)
for k in range(nz):
z = bbox.Min.Z + k * (bbox.Max.Z - bbox.Min.Z) / (nz - 1)
pts.append(Point3d(x, y, z))
return pts
def morph_geometry_brep(brep, cage_morph):
brep_copy = brep.Duplicate()
all_meshes = []
for i in range(brep_copy.Faces.Count):
face = brep_copy.Faces[i]
mesh = Rhino.Geometry.Mesh.CreateFromBrep(face.DuplicateFace(False))[0]
for j in range(mesh.Vertices.Count):
pt = mesh.Vertices[j]
pt3d = Point3d(pt.X, pt.Y, pt.Z)
new_pt = cage_morph.morph_point(pt3d)
mesh.Vertices.SetVertex(j, new_pt)
mesh.Normals.ComputeNormals()
mesh.Compact()
all_meshes.append(mesh)
return all_meshes
def simple_deform(pt):
"""Deformation function : translation through z"""
dz = 100
return Point3d(pt.X, pt.Y, pt.Z + dz)
def apply_cage_morph(brep, nx, ny, nz):
bbox = brep.GetBoundingBox(True)
source_grid = generate_regular_grid(bbox, nx, ny, nz)
target_grid = [simple_deform(pt) for pt in source_grid]
morph = CageMorph(source_grid, target_grid, nx, ny, nz)
brep_def = morph_geometry_brep(brep, morph)
return brep_def
def save_breps(brep_original, brep_modifie, filepath_out):
model = Rhino.FileIO.File3dm()
model.Objects.AddBrep(brep_original)
for mesh in mesh_list:
model.Objects.AddMesh(mesh)
model.Write(filepath_out, 6)
print(f"✅ Model saved : {filepath_out}")
# === Exemple d'utilisation ===
if __name__ == "__main__":
try:
input_file = r"To be modified"
output_file = r"To be modified"
brep = open_single_brep(input_file)
mesh_list = apply_cage_morph(brep, nx=4, ny=4, nz=4)
save_breps(brep, mesh_list, output_file)
except Exception as e:
print("❌ Error :", e)