Surface Operations
Surface Operations Tutorial.
This tutorial demonstrates operations on 3D surface meshes including creation of primitives, import/export, transformations, boolean operations, mesh processing, diagnostics, and conversion.
Prerequisites
- Volvicon application must be running
- For some operations, an active volume is required
Listing Surfaces​
all_surfaces = app.get_all_surface_names()
all_volumes = app.get_all_volume_names()
print(f"Available surfaces: {all_surfaces}")
print(f"Available volumes: {all_volumes}")
Importing and Exporting Surfaces​
# Supported import formats: stl, ply, obj, wrl, vrml, gltf, glb, amf, step, stp, iges, igs, vtp, xyz
# Supported export formats: stl, ply, obj, wrl, vrml, u3d, 3mf, amf, gltf, step, stp, iges, igs, vtp, vtk, byu, x3d, iv, xyz
# Import a single surface file
# surface_name = surface_operations.import_surface_from_disk(r'C:\tmp\cropBox.stl')
# Import multiple surface files
# surface_names = surface_operations.import_surfaces_from_disk([
# r'C:\tmp\cropBox.stl',
# r'C:\tmp\cropBox.stl'
# ])
# Export a single surface to file
# surface_operations.export_surface_to_disk('cropBox (1)', r'C:\output\model.stl', False) # isASCII=False (bool)
# Export multiple surfaces to a directory.
# Returns True only when every listed surface was written, so the result is worth checking.
# if not surface_operations.export_surfaces_to_disk(['cropBox (1)', 'cropBox (2)'], r'C:\output', 'stl', False): # isASCII=False
# print("One or more surfaces could not be exported.")
Creating Primitive Surfaces​
# Create a sphere
# sphere_name = surface_operations.create_sphere(
# "MySphere", # name (str)
# 10.0, # radius (float)
# [0.0, 0.0, 0.0], # center [x, y, z]
# 36, # rings (int, latitude resolution)
# 36 # sectors (int, longitude resolution)
# )
# Create a box
# box_name = surface_operations.create_box(
# "MyBox", # name (str)
# 20.0, # width (float)
# 15.0, # height (float)
# 10.0, # depth (float)
# [0.0, 0.0, 0.0], # center [x, y, z]
# 10 # resolution (int)
# )
# Create a cube
# cube_name = surface_operations.create_cube(
# "MyCube", # name (str)
# 15.0, # edgeLength (float)
# [0.0, 0.0, 0.0], # center [x, y, z]
# 10 # resolution (int)
# )
# Create a cylinder
# cylinder_name = surface_operations.create_cylinder(
# "MyCylinder", # name (str)
# 5.0, # radius (float)
# 20.0, # height (float)
# [0.0, 0.0, 0.0], # center [x, y, z]
# 36, # resolution (int)
# True, # capping (bool)
# 2 # direction (int: 0=X, 1=Y, 2=Z)
# )
# Create a cone
# cone_name = surface_operations.create_cone(
# "MyCone", # name (str)
# 8.0, # radius (float, base radius)
# 15.0, # height (float)
# [0.0, 0.0, 0.0], # center [x, y, z]
# 36, # resolution (int)
# True, # capping (bool)
# 2 # direction (int: 0=X, 1=Y, 2=Z)
# )
# Create a torus
# torus_name = surface_operations.create_torus(
# "MyTorus", # name (str)
# 3.0, # innerRadius (float, ring radius)
# 10.0, # outerRadius (float, total radius)
# [0.0, 0.0, 0.0], # center [x, y, z]
# 36, # resolution (int)
# 2 # direction (int: 0=X, 1=Y, 2=Z)
# )
# Create a tube (hollow cylinder)
# tube_name = surface_operations.create_tube(
# "MyTube", # name (str)
# 10.0, # outerRadius (float)
# 2.0, # thickness (float, wall thickness)
# 20.0, # height (float)
# [0.0, 0.0, 0.0], # center [x, y, z]
# 36, # resolution (int)
# 2 # direction (int: 0=X, 1=Y, 2=Z)
# )
# Create a capsule (cylinder with hemispherical ends)
# capsule_name = surface_operations.create_capsule(
# "MyCapsule", # name (str)
# 5.0, # radius (float)
# 15.0, # height (float, cylindrical part)
# [0.0, 0.0, 0.0], # center [x, y, z]
# 36, # resolution (int)
# 2 # direction (int: 0=X, 1=Y, 2=Z)
# )
# Create an ellipsoid
# ellipsoid_name = surface_operations.create_ellipsoid(
# "MyEllipsoid", # name (str)
# 10.0, # radiusX (float)
# 7.0, # radiusY (float)
# 5.0, # radiusZ (float)
# [0.0, 0.0, 0.0], # center [x, y, z]
# 36, # rings (int)
# 36 # sectors (int)
# )
# Create a capped sphere (sphere with cut)
# capped_sphere_name = surface_operations.create_capped_sphere(
# "MyCappedSphere", # name (str)
# 10.0, # radius (float)
# 90.0, # cutAngleDeg (float, 0-180)
# [0.0, 0.0, 0.0], # center [x, y, z]
# 36, # rings (int)
# 36 # sectors (int)
# )
# Create a disk
# disk_name = surface_operations.create_disk(
# "MyDisk", # name (str)
# 0.0, # innerRadius (float, 0 for solid disk)
# 10.0, # outerRadius (float)
# [0.0, 0.0, 0.0], # center [x, y, z]
# 18, # radialResolution (int)
# 36, # circumferentialResolution (int)
# 2 # direction (int: 0=X, 1=Y, 2=Z)
# )
# Create a plane
# plane_name = surface_operations.create_plane(
# "MyPlane", # name (str)
# 50.0, # width (float)
# 50.0, # height (float)
# [0.0, 0.0, 0.0], # center [x, y, z]
# 1, # resolution (int)
# 2 # direction (int: 0=X, 1=Y, 2=Z)
# )
# Create an arrow
# arrow_name = surface_operations.create_arrow(
# "MyArrow", # name (str)
# 20.0, # totalLength (float)
# 1.0, # shaftRadius (float)
# 5.0, # tipLength (float)
# 3.0, # tipRadius (float)
# [0.0, 0.0, 0.0], # center [x, y, z]
# 32, # resolution (int)
# 2 # direction (int: 0=X, 1=Y, 2=Z)
# )
# Create a platonic solid
# platonic_name = surface_operations.create_platonic_solid(
# "MyIcosahedron", # name (str)
# api.PlatonicSolidType.Icosahedron, # solidType (Tetrahedron, Octahedron, Icosahedron, Dodecahedron)
# 10.0, # radius (float)
# [0.0, 0.0, 0.0] # center [x, y, z]
# )
Creating Projected 3D Text​
# Project text onto an existing target as a separate surface object. The target
# is not modified and no Boolean operation is performed.
# text_params = api.ProjectedTextParams()
# text_params.font_family = "Arial"
# text_params.font_size_points = 48
# text_params.bold = False
# text_params.italic = False
# text_params.text_scale = 1.0
# text_params.center = [0.0, 0.0, 5.0] # world-space text center
# text_params.surface_normal = [0.0, 0.0, 1.0] # projection direction and local Z axis
# text_params.text_direction = [1.0, 0.0, 0.0] # text baseline direction
# text_params.width_scale = 1.0
# text_params.height_scale = 1.0
# text_params.thickness = 2.0
# text_params.overlap_thickness = 0.5
# text_params.emboss = True # False extends the text inward
# projected_text_name = surface_operations.create_projected_text(
# "MyBox", # target surface name
# "LOT 42", # UTF-8 text
# text_params,
# "Projected Label" # optional result name
# )
Surface Mesh Information​
# if all_surfaces:
# basic_surface_mesh_info : api.BasicSurfaceMeshInfo = surface_operations.get_basic_surface_mesh_info(all_surfaces[0])
# print(f"Vertices: {basic_surface_mesh_info.vertex_count}")
# print(f"Triangles: {basic_surface_mesh_info.polygon_count}")
# print(f"BBox surface area: {basic_surface_mesh_info.bounding_box_area}")
# print(f"BBox volume: {basic_surface_mesh_info.bounding_box_volume}")
Surface Geometry Data​
# if all_surfaces:
# geometry_data : api.SurfaceGeometryData = surface_operations.get_geometry_data(all_surfaces[0])
# print(f"Vertex count: {len(geometry_data.vertices)}")
# print(f"Color count: {len(geometry_data.vertex_colors)}")
# print(f"Triangle count: {len(geometry_data.cells)}")
#
# # Replace the mesh using vertices, triangle indices, and optional RGB colors.
# new_geometry = api.SurfaceGeometryData()
# new_geometry.vertices = [
# [0.0, 0.0, 0.0],
# [10.0, 0.0, 0.0],
# [0.0, 10.0, 0.0],
# [0.0, 0.0, 10.0],
# ]
# new_geometry.cells = [
# [0, 1, 2],
# [0, 1, 3],
# ]
# new_geometry.vertex_colors = [
# [1.0, 0.0, 0.0],
# [0.0, 1.0, 0.0],
# [0.0, 0.0, 1.0],
# [1.0, 1.0, 0.0],
# ]
# new_geometry.cell_colors = [
# [0.2, 0.4, 0.6],
# [0.6, 0.4, 0.2],
# ]
# surface_operations.set_geometry_data(all_surfaces[0], new_geometry)
Geometric Transformations​
# if all_surfaces:
# # Rotate around axis
# surface_operations.rotate(
# [all_surfaces[0]], # surfaceNames (list)
# 45.0, # angleDegrees (float)
# [0.0, 0.0, 1.0], # axis [x, y, z]
# [0.0, 0.0, 0.0], # center [x, y, z] (ignored if aroundObjectCentroid=True)
# True # aroundObjectCentroid (bool)
# )
#
# # Reorient with XYZ translation, XYZ Euler rotation angles, and an optional rotation center
# surface_operations.reorient(
# [all_surfaces[0]], # surfaceNames (list)
# [10.0, 5.0, 0.0], # translation [x, y, z] in mm
# [0.0, 0.0, 45.0], # rotationAnglesDegrees [x, y, z]
# [0.0, 0.0, 0.0], # rotationCenter [x, y, z] (ignored if aroundObjectCentroid=True)
# True # aroundObjectCentroid (bool)
# )
#
# # Translate
# surface_operations.translate(
# [all_surfaces[0]], # surfaceNames (list)
# [10.0, 5.0, 0.0] # translation [x, y, z] in mm
# )
#
# # Scale
# surface_operations.scale(
# [all_surfaces[0]], # surfaceNames (list)
# [1.5, 1.5, 1.5] # scaleFactors [x, y, z]
# )
#
# # Mirror across axes
# surface_operations.mirror(
# [all_surfaces[0]], # surfaceNames (list)
# True, # mirrorX (bool)
# False, # mirrorY (bool)
# False # mirrorZ (bool)
# )
#
# # Mirror surface across a custom plane
# surface_operations.mirror_to_plane(
# [all_surfaces[0]], # surfaceNames (list)
# [0.0, 0.0, 0.0], # planeOrigin [x, y, z] in mm
# [1.0, 0.0, 0.0] # planeNormal [x, y, z] (unit vector)
# )
#
# # Apply 4x4 transformation matrix
# # matrix = [1,0,0,0, 0,1,0,0, 0,0,1,0, 10,20,30,1] # Translation example
# # surface_operations.transform([all_surfaces[0]], matrix)
#
# # Move to center of active volume
# surface_operations.move_to_active_volume_center([all_surfaces[0]])
Boolean Operations​
# Combine surfaces using Union, Intersection, or Difference
# if len(all_surfaces) >= 2:
# # Union - combine both surfaces
# result = surface_operations.boolean_operation(
# all_surfaces[0], # surfaceNameInputA (str)
# [all_surfaces[1]], # surfaceNamesInputB (list)
# "Union_Result", # surfaceNameResult (str, used if createNewSurface=False)
# api.SurfaceBooleanOperation.Union,
# True # createNewSurface (bool)
# )
#
# # Intersection - keep only overlapping regions
# result = surface_operations.boolean_operation(
# all_surfaces[0],
# [all_surfaces[1]],
# "Intersection_Result",
# api.SurfaceBooleanOperation.Intersection,
# True
# )
#
# # Difference - subtract second from first
# result = surface_operations.boolean_operation(
# all_surfaces[0],
# [all_surfaces[1]],
# "Difference_Result",
# api.SurfaceBooleanOperation.Difference,
# True
# )
# Voxel-based boolean (more robust for complex geometry)
# if len(all_surfaces) >= 2:
# result = surface_operations.voxel_boolean_operation(
# all_surfaces[0], # surfaceNameInputA (str)
# [all_surfaces[1]], # surfaceNamesInputB (list)
# "VoxelBoolean_Result", # surfaceNameResult (str)
# api.SurfaceBooleanOperation.Union,
# api.SurfaceVoxelizeMethod.Fast, # voxelizationMethod (Fast or Accurate)
# [1, 1, 1], # voxelSpacing [x, y, z]
# True, # smoothArtifacts (bool)
# True # createNewSurface (bool)
# )
Clipping and Cutting with Primitive ROIs​
# Clip and cut both take a primitive object (an ROI) as the region of interest,
# so create the primitive first and then pass its name.
primitive_operations = app.get_primitive_operations()
# --- Clip ----------------------------------------------------------------
# clip_with_primitive() uses the analytic region of the primitive, the same
# region the Clip tool uses:
# plane -> clips with the plane
# sphere -> clips with the sphere
# cylinder -> clips with an infinite cylinder along its axis (no caps)
# cube / cuboid -> clips with the six faces
# Other primitive types have no analytic region; use cut_with_primitives() for those.
#
# Each region supports the same options the Clip tool offers:
#
# Primitive closed invert retainClippedRegion
# plane yes yes when closed is False
# cube / cuboid yes when closed is False when closed is False
# sphere no yes no
# cylinder no yes no
#
# Passing an option a primitive does not support raises an exception rather
# than being ignored silently.
# if all_surfaces:
# plane_roi = primitive_operations.create_plane(
# [0, 0, 0], # center [x, y, z]
# 100.0, # width
# 100.0, # height
# "ClipPlane" # name
# )
#
# # Keep the half of the mesh on one side of the plane.
# surface_operations.clip_with_primitive(
# [all_surfaces[0]], # surfaceNames (list)
# plane_roi, # primitiveName (str)
# False, # closed: cap the result so it stays watertight
# False, # invert: keep the other side
# False # retainClippedRegion: add the removed part as a new surface
# )
#
# # Closed clipping caps the opening so the result stays watertight. It needs a
# # closed and manifold input, and it is available for the plane, cube and cuboid
# # primitives only. Use the Diagnostics and Fixes tool, or diagnose_surface(),
# # to check a surface first.
# surface_operations.clip_with_primitive([all_surfaces[0]], plane_roi, True, True)
#
# # Retaining the clipped region splits the surface in two. It is available for
# # the plane, cube and cuboid primitives when closed is False.
# surface_operations.clip_with_primitive([all_surfaces[0]], plane_roi, False, False, True)
#
# cuboid_roi = primitive_operations.create_cuboid([0, 0, 0], 20.0, 20.0, 20.0, "ClipCuboid")
# surface_operations.clip_with_primitive([all_surfaces[0]], cuboid_roi, False, True, True)
#
# # Closed cuboid clipping always keeps what is inside the box, so invert and
# # retainClippedRegion are not available for it.
# surface_operations.clip_with_primitive([all_surfaces[0]], cuboid_roi, True)
#
# # A sphere or cylinder ROI supports invert only.
# sphere_roi = primitive_operations.create_sphere([0, 0, 0], 10.0, "ClipSphere")
# surface_operations.clip_with_primitive([all_surfaces[0]], sphere_roi, False, True)
# Surfaces the region would leave without any geometry are reported and left
# unchanged, so a badly placed ROI never empties a surface.
# --- Cut -----------------------------------------------------------------
# cut_with_primitives() subtracts the mesh of each primitive from each surface.
# It works with every primitive type, and it follows the mesh rather than an
# analytic region, so a cylinder ROI cuts as a capped cylinder.
# if all_surfaces:
# cone_roi = primitive_operations.create_cone(
# [0, 0, 0], # point1 [x, y, z]
# [0, 0, 20], # point2 [x, y, z]
# 5.0, # radius
# "CutCone" # name
# )
#
# surface_operations.cut_with_primitives(
# [all_surfaces[0]], # surfaceNames (list)
# [cone_roi], # primitiveNames (list)
# False # retainCutRegion: add the removed part as a new surface
# )
Mesh Processing - Remesh​
# if all_surfaces:
# # Standard remesh
# surface_operations.remesh(
# [all_surfaces[0]], # surfaceNames (list)
# api.SurfaceRemeshMethod.Adaptive, # method (Adaptive or Regular)
# api.SurfaceRemeshQuality.Medium, # quality (Medium, High, Maximum)
# 100, # density (int, 11-100, percentage of original vertex count)
# False # preserveEdges (bool)
# )
#
# # Voxel-based remesh
# surface_operations.voxel_remesh(
# [all_surfaces[0]], # surfaceNames (list)
# api.SurfaceVoxelizeMethod.Fast, # voxelizationMethod (Fast or Accurate)
# [1, 1, 1], # voxelSpacing [x, y, z]
# True # smoothArtifacts (bool)
# )
#
# # Screened Poisson surface reconstruction remesh (works on surfaces and point clouds).
# # Preserves details and the overall shape; recommended for most applications.
# params = api.SurfaceScreenedPoissonRemeshParams()
# params.depth = 8 # Detail level, 4-14 (int, higher = more detail)
# params.scale = 1.1 # Reconstruction region / input bounds ratio, 1.0-2.0 (float)
# params.samples_per_node = 1.5 # Smoothing control, 1.0-30.0 (float, larger = smoother)
# params.point_weight = 2.0 # Point fitting strength, 0-20 (float)
# params.confidence = False # Use normal magnitudes as confidence weights (bool)
# surface_operations.poisson_remesh_screened([all_surfaces[0]], params)
#
# # Unscreened (original) Poisson surface reconstruction remesh.
# # Produces very smooth surfaces; may over-smooth detailed regions.
# params = api.SurfaceUnscreenedPoissonRemeshParams()
# params.depth = 8 # Detail level, 4-14 (int, higher = more detail)
# params.scale = 1.25 # Reconstruction region / input bounds ratio, 1.0-2.0 (float)
# params.samples_per_node = 1.0 # Smoothing control, 1.0-30.0 (float, larger = smoother)
# params.solver_divide = 8 # 6-12 (int, reduces memory usage)
# params.iso_divide = 8 # 6-12 (int, reduces extraction memory usage)
# params.confidence = False # Use normal magnitudes as confidence weights (bool)
# surface_operations.poisson_remesh_unscreened([all_surfaces[0]], params)
Mesh Processing - Smooth​
# if all_surfaces:
# surface_operations.smooth_smart(
# [all_surfaces[0]], # surfaceNames (list)
# 20, # Number of iterations (int)
# 0.01, # Smoothing factor 0.0001-1.0 (float)
# True, # Enable feature edge smoothing (bool)
# 85 # Feature angle in degrees 0-180 (float)
# )
Mesh Processing - Reduce (Decimate)​
# if all_surfaces:
# # General-purpose reduction/decimation (preserves quality)
# surface_operations.reduce(
# [all_surfaces[0]], # surfaceNames (list)
# 50.0, # desiredPercentage (float, 0-100)
# True, # useDesiredPercentage (bool)
# 3000 # desiredTriangles (int, used if useDesiredPercentage=False)
# )
#
# # Feature-based reduction/decimation (preserves sharp edges)
# surface_operations.reduce_by_features(
# [all_surfaces[0]], # surfaceNames (list)
# 60.0, # featureAngle (float, degrees)
# 50.0, # desiredPercentage (float, 0-100)
# True, # useDesiredPercentage (bool)
# 3000 # desiredTriangles (int)
# )
#
# # Reduce/Decimate by joining nearby points (merges vertices that are close to each other)
# surface_operations.reduce_by_nearby_points(
# [all_surfaces[0]], # surfaceNames (list)
# 0.5 # nearbyPointDistance (float)
# )
Mesh Processing - Subdivide​
# if all_surfaces:
# surface_operations.subdivide(
# [all_surfaces[0]], # surfaceNames (list)
# api.SurfaceSubdivisionMethod.Loop, # method (Linear, Loop, Butterfly, Adaptive)
# 1, # subdivisions (int, ignored for Adaptive)
# 1.0 # maxEdgeLength (float, for Adaptive method)
# )
Mesh Processing - Hollow​
# if all_surfaces:
# hollow_names = surface_operations.hollow(
# [all_surfaces[0]], # surfaceNames (list)
# api.SurfaceHollowMethod.Fast, # method (Fast or Robust)
# api.SurfaceHollowDirection.Inward, # direction (Inward or Outward)
# 2.0, # thickness (float, wall thickness)
# api.SurfaceHollowResolution.Medium, # resolution (Low, Medium, High, Maximum)
# True # createNewSurface (bool)
# )
Mesh Processing - Fill Holes​
# if all_surfaces:
# surface_operations.fill_holes(
# all_surfaces[0], # surfaceName (str)
# 100, # maxHoleSize (int, in surface units)
# api.SurfaceHoleFillingMethod.Angle # method (Angle, Area, or EarCut)
# )
Mesh Processing - Merge and Split​
# if len(all_surfaces) >= 2:
# # Merge multiple surfaces into one
# merged_name = surface_operations.merge(
# all_surfaces[:2], # surfaceNames (list, at least 2)
# "Merged_Surface", # targetSurfaceName (str)
# True, # mergePoints (bool)
# False, # removeInputSurfaces (bool)
# True # createNewSurface (bool)
# )
# if all_surfaces:
# # Split surface into disconnected regions
# split_names = surface_operations.split(
# all_surfaces[0], # surfaceName (str)
# 0, # numLargestShells (int, 0 = all shells)
# False # removeInputSurface (bool)
# )
Mesh Processing - Filter Shells​
# if all_surfaces:
# params = api.SurfaceFilterShellsParams()
# params.largest_shells = 3 # Number of largest shells to keep (int)
# # params.min_num_triangles = 100 # Minimum triangles (int)
# # params.min_volume = 10.0 # Minimum volume (float)
# # params.min_area = 5.0 # Minimum area (float)
# # params.retain_inside_shells = True # For InsideOrOutside method (bool)
#
# surface_operations.filter_shells(
# all_surfaces[0],
# api.SurfaceFilterShellsMethod.LargestShells, # method
# params
# )
Surface Diagnostics and Repair​
# if all_surfaces:
# # Configure diagnostic checks
# checks = api.SurfaceDiagnosticsChecks()
# checks.unify = True # Unify coincident vertices and remove unused vertices (bool)
# checks.shells = True # Check for shells (bool)
# checks.noise_shells = True # Check for noise shells (bool)
# checks.invalid_triangles = True # Check for invalid triangles (bool)
# checks.holes = True # Check for holes (bool)
# checks.intersection_triangles = True # Check for self-intersections (bool)
# checks.inverted_normals = True # Check for inverted normals (bool)
# checks.boundary_edges = True # Check for boundary edges (bool)
# checks.non_manifold_edges = True # Check for non-manifold edges (bool)
#
# # Configure repair behavior. These options are used by fix_surface only.
# checks.separate_intersection_regions = True # Separate a repaired intersection when no safe outer surface exists (bool)
# checks.extract_largest_separated_region = True # Keep only the largest part created by that separation (bool)
# checks.force_manifold_repair = True # May remove geometry around unresolved problem edges (bool)
#
# # Run diagnostics
# result : api.SurfaceDiagnosticsResult = surface_operations.diagnose_surface(all_surfaces[0], checks)
# print(f"Shells: {result.shells}")
# print(f"Noise shells: {result.noise_shells}")
# print(f"Invalid triangles: {result.invalid_triangles}")
# print(f"Holes: {result.holes}")
# print(f"Self-intersections: {result.intersection_triangles}")
# print(f"Inverted normals: {result.inverted_normals}")
# print(f"Boundary edges: {result.boundary_edges}")
# print(f"Non-manifold edges: {result.non_manifold_edges}")
#
# # Fix surface issues
# # Ordinary valid disconnected parts are preserved. The defaults can still
# # remove small noise, separated repair fragments, or geometry around
# # unresolved non-manifold edges, so review the repaired surface.
# surface_operations.fix_surface(all_surfaces[0], checks)
Surface Registration (Alignment)​
# if len(all_surfaces) >= 2:
# # Point-based registration (ICP)
# results = surface_operations.point_matching_registration(
# [all_surfaces[0]], # movableSurfaceNames (list)
# all_surfaces[1], # fixedObjectName (str)
# api.RegistrationTargetObject.Surface, # targetObjectType (Surface or Mask)
# api.RegistrationMode.RigidBody, # registrationMode (RigidBody, Similarity, Affine)
# True, # startByMatchingCentroids (bool)
# 1000 # maximumLandmarks (int)
# )
# for row in results:
# registration_row : api.GlobalRegistrationResultRow = row
# print(f"Movable: {registration_row.movable_surface}, Fixed: {registration_row.fixed_target}, RMSE: {registration_row.rmse_millimeters} mm")
#
# # Feature-based registration
# results = surface_operations.feature_matching_registration(
# [all_surfaces[0]], # movableSurfaceNames (list)
# all_surfaces[1], # fixedObjectName (str)
# api.RegistrationTargetObject.Surface, # targetObjectType
# 3, # globalIterations (int)
# 10.0, # overlapRadius (float)
# 0.001 # curvatureThreshold (float)
# )
# for row in results:
# registration_row : api.GlobalRegistrationResultRow = row
# print(f"Movable: {registration_row.movable_surface}, Fixed: {registration_row.fixed_target}, RMSE: {registration_row.rmse_millimeters} mm")
#
# # Combined point + feature registration
# results = surface_operations.point_plus_feature_matching_registration(
# [all_surfaces[0]], # movableSurfaceNames (list)
# all_surfaces[1], # fixedObjectName (str)
# api.RegistrationTargetObject.Surface, # targetObjectType
# api.RegistrationMode.RigidBody, # registrationMode
# True, # startByMatchingCentroids (bool)
# 1000, # maximumLandmarks (int)
# 3, # globalIterations (int)
# 10.0, # overlapRadius (float)
# 0.001 # curvatureThreshold (float)
# )
# for row in results:
# registration_row : api.GlobalRegistrationResultRow = row
# print(f"Movable: {registration_row.movable_surface}, Fixed: {registration_row.fixed_target}, RMSE: {registration_row.rmse_millimeters} mm")
Project to Plane​
# if all_surfaces:
# surface_operations.project_to_plane(
# [all_surfaces[0]], # surfaceNames (list)
# [0.0, 0.0, 0.0], # planeOrigin [x, y, z]
# [0.0, 0.0, 1.0] # planeNormal [x, y, z]
# )
Convert Surface to Mask​
# Requires an active volume to be loaded
# if all_surfaces and all_volumes:
# app.set_active_volume(all_volumes[0])
# mask_names = surface_operations.convert_to_mask(
# [all_surfaces[0]], # surfaceNames (list)
# api.SurfaceVoxelConversionMethod.Filled, # voxelizationMethod (Filled, ThickContour, ThinContour, LineContour)
# True # smoothArtifacts (bool)
# )
# print(f"Created masks: {mask_names}")
Convert Surface to Volume Mesh​
# if all_surfaces:
# # Set volume mesh parameters first
# params = api.VolumeMeshParams()
# params.resolution = api.VolumeMeshResolution.Moderate # VeryCoarse, Coarse, Moderate, Fine, VeryFine, Custom
# params.optimize_surface = True # Remesh surface before meshing (bool)
# params.surface_opt_steps = 3 # Surface optimization steps (int)
# params.volume_opt_steps = 3 # Volume optimization steps (int)
# params.growth_rate = 0.3 # Mesh growth rate 0.0-1.0 (float)
# # For Custom resolution:
# # params.max_element_size = 5.0 # Maximum element size (float)
# # params.min_element_size = 1.0 # Minimum element size (float)
#
# surface_operations.set_volume_mesh_parameters(all_surfaces[0], params)
# current_params : api.VolumeMeshParams = surface_operations.get_volume_mesh_parameters(all_surfaces[0])
# print(f"Volume mesh resolution: {current_params.resolution}")
#
# # Convert to volume mesh
# volume_mesh_names = surface_operations.convert_to_volume_mesh(
# [all_surfaces[0]], # surfaceNames (list)
# api.VolumeMeshMethod.Auto3D # method (Auto3D or Grid3D)
# )
# print(f"Created volume meshes: {volume_mesh_names}")
Render Properties​
# surface_render_properties_operations = surface_operations.get_render_properties_operations()
# if all_surfaces:
# # Set representation mode
# surface_render_properties_operations.set_representation([all_surfaces[0]], api.SurfaceRepresentation.Solid) # Points, Wireframe, Solid, SolidEdges
#
# # Set color (RGB values 0.0-1.0)
# surface_render_properties_operations.set_color([all_surfaces[0]], [0.8, 0.2, 0.2]) # Red
#
# # Set random color
# surface_render_properties_operations.set_random_color([all_surfaces[0]])
#
# # Set opacity (0.0-1.0)
# surface_render_properties_operations.set_opacity([all_surfaces[0]], 0.8)
#
# # Set interpolation
# surface_render_properties_operations.set_interpolation([all_surfaces[0]], api.SurfaceInterpolation.Phong) # Flat, Gouraud, Phong
print("Surface operations tutorial completed successfully.")
Related Resources​
- API Reference - API documentation
- Quick Reference - Common methods at a glance