Generating a Surface from Scratch, specklePy

Hi, I’m looking for some help or examples on how I can generate a surface in speckle from scratch.
I have a set of xy points in python, which form a closed 2D surface, and I’d like to generate geometry I can use to share with other programs (rhino/revit/etabs/etc.)

I’m sure this is a relatively easy task, but I haven’t been able to find any examples showing how it’s done, and am finding the inputs for Surface/Brep components a little nebulous.

Do you absolutely need to define a brep surface and not a mesh? If you do we can circle back.

You can define a surface mesh from a set of points using separate libraries such as scipy.spatial or matplotlib.tri for triangulation.

Below is an example using simple Delaunay triangulation from SciPy to define a surface:

Steps:
1. Generate or load a set of points in 3D space.
2. Use Delaunay triangulation to create a surface mesh.
3. Visualise the surface using Matplotlib.

import numpy as np
import matplotlib.pyplot as plt
from scipy.spatial import Delaunay

# Generate a set of random 3D points
num_points = 100
x = np.random.rand(num_points)
y = np.random.rand(num_points)
z = np.sin(x * 2 * np.pi) * np.cos(y * 2 * np.pi)  # Some function for surface shape

points = np.vstack((x, y)).T  # 2D projection for triangulation
tri = Delaunay(points)  # Compute the triangulation

# Plot the surface
fig = plt.figure(figsize=(10, 6))
ax = fig.add_subplot(111, projection='3d')

ax.plot_trisurf(x, y, z, triangles=tri.simplices, cmap="viridis", edgecolor='k')

ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')
plt.show()
  • Delaunay(points): Creates a triangulated mesh from 2D projections of the points.

  • plot_trisurf: Plots the triangulated surface.

We’ll need to translate the figure faces and vertices into a Speckle Mesh but that can then be used in any displayable object. Depends what you need to do onward from there.

If you need a smoother surface, you can use interpolation (e.g., scipy.interpolate.griddata) instead of direct triangulation. Let me know if you need that!

We could of course use rhino3dm (it just occurred to me) you don’t need a rhino licence to do so — forgive my rustiness but:

rhino3dm.NurbsSurface.CreateFromPoints(points, num_u, num_v, degree_u, degree_v)

From there I would mine our open source conversions in csharp and translate to Python (again apologies I’ve never done this in anger):

Bonus points for looking at Next Gen conversions

So to answer, I’m not 100% sure and would welcome input.
I definitely don’t need a pure or trimmed surface as rhino would define it.

My ultimate goal is to take polygons defined by xy points into a structural analysis program (SAFE, Etabs), or possibly Revit, where they will become a wall/floor element/loading surface. My feeling is a mesh would be sufficient, and I’ll play around with that a bit.

I know generally we use the custom structural Element2D etc. for CSI elements. For my case, I just want to transfer the geometry, and let the user define properties later, hence my trying to generate surfaces. It seems like CSI programs can handle receiving agnostic lines/nodes, and I’m assuming it can do the same with surfaces / meshses? Maybe that’s a bad assumption however.

Good point regarding rhino3dm, this might save me some time!

Hi @cslotboom,

Good to see you in our community :slight_smile:

Here is a simple Specklepy script that generates one polygon representing a slab and one mesh representing a wall. I added both and show attached a property “name”, you can modify it to align with your needs.

from specklepy.api.client import SpeckleClient
from specklepy.api.credentials import get_default_account
from specklepy.objects.geometry import Point, Polyline, Mesh
from specklepy.objects.base import Base
from specklepy.api import operations
from specklepy.transports.server import ServerTransport
from specklepy.core.api.inputs.version_inputs import CreateVersionInput

# initialize the Speckle client
client = SpeckleClient(host="app.speckle.systems")
    
# authenticate with your account
account = get_default_account()
client.authenticate_with_account(account)
    
# create points for a polygon
points = [
    Point(x=0, y=5, z=0),
    Point(x=10, y=5, z=0),
    Point(x=10, y=15, z=0),
    Point(x=0, y=15, z=0),
    Point(x=0, y=5, z=0)  # close the polygon by repeating first point
]
    
# create a polyline from points representing a slab
polygon = Polyline.from_points(points)
polygon.closed = True
polygon.name = "Slab"

# now create a mesh representing a wall
vertices = [
    0, 0, 0,    
    10, 0, 0,   
    10, 0, 10,  
    0, 0, 10    
]

faces = [
    3, 0, 1, 2,  
    3, 0, 2, 3   
]

mesh = Mesh.create(
    vertices=vertices,
    faces=faces,
    colors=[],
    texture_coordinates=[]
)

mesh.name = "Wall"

# create a base object to hold our geometries
base = Base()
base.polygon = polygon
base.mesh = mesh

# replace these with your actual project and model IDs
project_id = "YOUR_PROJECT_ID"
model_id = "YOUR_MODEL_ID"
    
# create a server transport
transport = ServerTransport(project_id, client)
    
# send the object to the server
obj_id = operations.send(base, [transport])
    
# create a version using the new API
version_input = CreateVersionInput(
    objectId=obj_id,
    modelId=model_id,
    projectId=project_id,
    message="Added a polygon",
    sourceApplication="py"

)

# send the version to the server
version_id = client.version.create(version_input)
    

This script will create one polyline and one surface.

Hope this helps. Feel free to ask if you have further questions.

1 Like