Building Complex Implicit Shapes and Surface Lattices with Python in Altair Inspire

Karthi Kandasamy
Karthi Kandasamy
Altair Employee
edited October 14 in Altair HyperWorks

Implicit Modeling

Example 1: Creating a Sphere with Gradient-Based Lattice Structure

This example creates an implicit 3D model, calculates its gradient, and uses that to generate a lattice structure on the sphere.

from hwx import inspire from hwx.common.math import Point  inspire.newModel()  # Creating an implicit part. implicit = inspire.Implicit() implicit.name = "Implicit Part1"  zero = implicit.constant(0) tolerance = implicit.constant(0.001) sphereOrigin = implicit.create3D(zero, zero, zero)  # Calculating the gradient along the X, Y, and Z axes. dXMinus = implicit.create3D(implicit.xRef() - tolerance, implicit.yRef(), implicit.zRef()) dXPlus = implicit.create3D(implicit.xRef() + tolerance, implicit.yRef(), implicit.zRef()) dX = implicit.distance(sphereOrigin, dXPlus) - implicit.distance(sphereOrigin, dXMinus)  # Similar calculations for the Y and Z axes. dYMinus = implicit.create3D(implicit.xRef(), implicit.yRef() - tolerance, implicit.zRef()) dYPlus = implicit.create3D(implicit.xRef(), implicit.yRef() + tolerance, implicit.zRef()) dY = implicit.distance(sphereOrigin, dYPlus) - implicit.distance(sphereOrigin, dYMinus)  dZMinus = implicit.create3D(implicit.xRef(), implicit.yRef(), implicit.zRef() - tolerance) dZPlus = implicit.create3D(implicit.xRef(), implicit.yRef(), implicit.zRef() + tolerance) dZ = implicit.distance(sphereOrigin, dZPlus) - implicit.distance(sphereOrigin, dZMinus)  # Normalizing the gradient to create a field for density variation. gradientFunc = implicit.abs(implicit.normalize(implicit.create3D(dX, dY, dZ)))  # Create a field to control lattice density based on the gradient. angleField = implicit.createField(implicit.vectorElement(gradientFunc, 2),                                   inputRangeMin=0, inputRangeMax=1,                                   outputRangeMin=0.1, outputRangeMax=0.9,                                   clamp=True)  # Create a sphere and apply a surface lattice to it. sphere = implicit.createSphere(radius=2) lattice = implicit.surfaceLattice(sphere, densityField=angleField, cellSizeX=20)  # Set visualization quality and add a section plane for internal structure visualization. implicit.visualizationQuality = 'HIGH' inspire.fitView() sectionPlane1 = inspire.SectionPlane() sectionPlane1.visible = True sectionPlane1.normal = (0, -1, 0)  print("Sphere with gradient-based lattice structure created.") 

Explanation:

  • A 3D implicit sphere is created.
  • Gradients along the X, Y, and Z axes are computed to understand how the surface changes in space.
  • A vector field is created based on the gradient, which is then used to control the density of a surface lattice structure on the sphere.
  • The high-quality rendering allows users to visualize the sphere and its internal lattice structure.

image

Example 2: Custom Implicit 3D Model with Spherical Distortion

This example creates a custom implicit body by distorting a sphere using sine functions.

 

from hwx import inspire from hwx.common.math import Point model = inspire.newModel()  model.variables["strength"] = "unitless", 1  # Creating an implicit part. implicit = inspire.Implicit() implicit.name = "Implicit Part1"  x = implicit.xRef() y = implicit.yRef() z = implicit.zRef()  strength = implicit.constant("strength") # Create a spherical shape and distort it with sine functions. sphereBody = implicit.sqrt(x * x + y * y + z * z) - implicit.constant(5) distortion = implicit.sin(strength * x) * implicit.sin(strength * y) * implicit.sin(strength * z) result = sphereBody + distortion  # Create a custom implicit body using the distorted result. implicit.createCustomImplicitBody(result, Point(-10, -10, -10), Point(10, 10, 10))  # Set visualization quality to high. implicit.visualizationQuality = 'HIGH' inspire.fitView() print("Custom implicit body created with spherical distortion.") 

Explanation:

  • This code generates a distorted sphere by applying sine-based modulation to the sphere's X, Y, and Z coordinates.
  • The strength variable controls the intensity of distortion.
  • The resulting body is visualized with high quality, demonstrating how implicit models can be modified using mathematical functions.

Example 3: Heart-Shaped Implicit Body

This example constructs a heart shape by combining powers and products of X, Y, and Z coordinates.

from hwx import inspire from hwx.common.math import Point inspire.newModel()  # Creating an implicit part. implicit = inspire.Implicit() implicit.name = "Implicit Part1"  # Defining constants for constructing the heart shape. x = implicit.xRef() y = implicit.yRef() z = implicit.zRef() one = implicit.constant(1) two = implicit.constant(2) three = implicit.constant(3) a = implicit.constant(9) / implicit.constant(4) b = implicit.constant(9) / implicit.constant(200)  # Combining functions to form the implicit heart shape. heart = (x ** two + a * y ** two + z ** two - one) ** three - x ** two * z ** three - b * y ** two * z ** three implicit.createCustomImplicitBody(heart, Point(-1.5, -1.5, -1.5), Point(1.5, 1.5, 1.5))  # Set visualization quality to high. implicit.visualizationQuality = 'HIGH' inspire.fitView() print("Heart-shaped implicit body created.") 

 

Explanation:

  • The heart shape is generated using a complex mathematical formula that defines the shape in 3D space.
  • By combining powers of X, Y, and Z coordinates, the heart shape is rendered as a custom implicit body.
  • The visualization quality is set to high for a clear and detailed view of the final shape.

image

Example 4: 3D Surface Lattice with Cuboid and Smooth Density Control

In this example, a surface lattice is created on a cuboid, and the lattice density is modulated using a smooth square wave function.

 

from hwx import inspire inspire.newModel()  model = inspire.getModel()  # Defining editable variables for controlling lattice density. model.variables["smoothFactor"] = "unitless", 0.25 model.variables["upperDensity"] = "unitless", 0.75 model.variables["lowerDensity"] = "unitless", 0.15 model.variables["waveLength"] = "length", 20.0  # Creating an implicit part and a cuboid. implicitPart = inspire.Implicit() implicitPart.name = "Implicit Part1" implicitPart.createCuboid(x="100 mm")  # Creating a surface lattice on the cuboid. cuboid = implicitPart.bodies[0] implicitPart.surfaceLattice(cuboid, density=0.3)  # Set up the density field with smooth square wave modulation along the X-axis. upperDensitLimit = implicitPart.constant("upperDensity") lowerDensityLimit = implicitPart.constant("lowerDensity") waveAmplitude = implicitPart.constant(0.5) * (upperDensitLimit - lowerDensityLimit) waveOffset = waveAmplitude + lowerDensityLimit smoothFactor = implicitPart.constant("smoothFactor") waveLength = implicitPart.constant("waveLength") frequency = implicitPart.constant(2.0) * implicitPart.constant(3.14159265359) / waveLength densityField = (waveAmplitude / implicitPart.atan(implicitPart.constant(1.0) / smoothFactor)) * implicitPart.atan(   implicitPart.cos(frequency * implicitPart.xRef()) / smoothFactor) + waveOffset  # Updating surface lattice properties. with implicitPart.edit():   surfaceLattice = implicitPart.bodies[0]   surfaceLattice.unitCellType = "GYROID"   surfaceLattice.densityField = densityField   surfaceLattice.unitCellSize = False   surfaceLattice.uniform = True   surfaceLattice.cellSizeX = 8.0  inspire.fitView() print("Surface lattice modified with smooth square wave density field.") 

Explanation:

  • A cuboid primitive is created and covered with a surface lattice.
  • The lattice density is controlled by a field using a smooth square wave function, which transitions between upper and lower density limits.
  • This function is modulated along the X-axis, creating smooth transitions in the lattice's density.

image

Example 5: Implicit Cuboid with Controlled Shell

In this example, we’ll create an implicit cuboid, add a surface lattice inside it, and apply a shell around the lattice. The shell thickness is controlled by a plane, above which the shell has a specific thickness, and below which the shell is absent.

############################################################################ # Creating an implicit cuboid, introducing a surface lattice, and applying # a shell. A field controls the shell thickness above and below a plane, # allowing selective shell placement based on height. ############################################################################  from hwx import inspire  # Create a new Inspire model model = inspire.newModel()  # Declare variables to control the plane height and shell thickness model.variables["planeHeight"] = "length", "10 mm" model.variables["shellThickness"] = "length", "1 mm"  # Create a blank Implicit part implicitPart = inspire.Implicit() implicitPart.name = "Implicit Part1"  # Create a primitive cuboid in the implicit part cuboid = implicitPart.createCuboid(x="100 mm")  # Define the shell thickness for areas that have a shell shellThickness = implicitPart.constant("shellThickness")  # Define no shell thickness for areas below the plane noShellThickness = implicitPart.constant(0)  # Create a plane that will define the transition for shell application plane = implicitPart.createPlane(normalX="0.0", normalY="0.0", normalZ="1.0")  # Conditional logic for shell thickness based on position relative to the plane conditionalHeight = implicitPart.constant("planeHeight") conditionalShellThickness = implicitPart.implIf(     implicitPart.greaterThan(plane, conditionalHeight), shellThickness, noShellThickness)  # Create a surface lattice with a conditional shell applied implicitPart.surfaceLattice(cuboid, shellThicknessOutField=conditionalShellThickness,                             shell=True, shellDirection="OUTWARD")  # Adjust the view to fit the model inspire.fitView()  print("Cuboid with conditional shell and lattice created.") 

Explanation:

  • Cuboid Creation: We first create a 100 mm cuboid using implicit modeling.
  • Shell Thickness Control: A user-defined plane is created at a specified height (planeHeight = 10 mm). Above this plane, a shell of 1 mm is applied, while below it, no shell is applied.
  • Lattice and Shell Integration: The surfaceLattice() function generates a lattice structure inside the cuboid. The shell is applied based on the conditional logic, which checks if a point lies above or below the plane.
  • Visualization: Finally, the cuboid, lattice, and shell are displayed, showing how implicit modeling allows control over shell thickness based on spatial location.

image

Example 6: Surface Lattice with Gradient Density Control

In this example, we explore how to create a surface lattice on a cuboid, where the density of the lattice varies in the X and Y directions. We use cosine wave functions to modulate the density, creating smooth transitions between regions of high and low density.

############################################################################ # Creating a surface lattice with gradient density control using cosine waves. # The density varies smoothly along the X and Y axes based on a smooth maximum operator. ############################################################################  from hwx import inspire inspire.newModel()  # Get the current model model = inspire.getModel()  # Declare variables to control lattice properties and smooth transitions model.variables["smoothFactor"] = "unitless", 0.01 model.variables["waveLengthX"] = "length", 20.0 model.variables["waveLengthY"] = "length", 100.0  # Create an implicit part and a cuboid implicitPart = inspire.Implicit() implicitPart.name = "Implicit Part1" implicitPart.createCuboid(x="100 mm")  # Create a basic surface lattice on the cuboid cuboid = implicitPart.bodies[0] implicitPart.surfaceLattice(cuboid, density=0.3)  # Create a gradient effect in the X-direction using a cosine wave waveLengthX = implicitPart.constant("waveLengthX") frequencyX = implicitPart.constant(2.0) * implicitPart.constant(3.14159265359) / waveLengthX gradientEffectX = implicitPart.constant(0.25) * (implicitPart.cos(frequencyX * implicitPart.xRef()) + implicitPart.constant(1))  # Create a gradient effect in the Y-direction using a cosine wave waveLengthY = implicitPart.constant("waveLengthY") frequencyY = implicitPart.constant(2.0) * implicitPart.constant(3.14159265359) / waveLengthY gradientEffectY = implicitPart.constant(0.25) * (implicitPart.cos(frequencyY * implicitPart.yRef()) + implicitPart.constant(1))  # Combine the two gradient effects using a smooth maximum function smoothFactor = implicitPart.constant("smoothFactor") combinedGradientEffect = (gradientEffectX + gradientEffectY + implicitPart.sqrt(     implicitPart.power((gradientEffectX - smoothFactor), implicitPart.constant(2)) + smoothFactor)) / implicitPart.constant(2.0)  # Update the surface lattice properties using the combined gradient effect with implicitPart.edit():     surfaceLattice = implicitPart.bodies[0]     surfaceLattice.unitCellType = "GYROID"     surfaceLattice.densityField = combinedGradientEffect     surfaceLattice.unitCellSize = False     surfaceLattice.uniform = True     surfaceLattice.cellSizeX = 16.0     surfaceLattice.coordinateSystemType = 'CARTESIAN'  # Adjust the view to fit the model inspire.fitView()  print("Surface lattice with gradient density variation created.") 

Explanation:

  • Cosine Wave Density Modulation: The density of the lattice is controlled by cosine wave functions along the X and Y axes. The wavelength of these waves (waveLengthX and waveLengthY) determines the periodicity of the density variation.
  • Smooth Maximum Operator: A smooth maximum operator is used to combine the two density fields (X and Y). The smoothFactor controls the sharpness of the transition between high and low densities, allowing for smooth gradients across the cuboid.
  • Lattice Type: The GYROID unit cell type is used for the lattice, a common choice in lattice structures for its strength and minimal surface area.
  • Adjustable Parameters: By editing the smoothFactor and waveLength, users can control how smooth or abrupt the density transitions are, giving full control over the lattice structure.

image

 

Example 7: Creating a Filter Design with Planar Lattices and Solid Rings

This script demonstrates how to create a filter design using planar lattices and solid rings. The mesh is formed by a hexagonal lattice in cylindrical coordinates, surrounded by solid rings on the top and bottom.

Key Design Variables
  • Visualization Quality (visQual): Controls the resolution of the implicit model.
  • Filter Dimensions: These include the height, inner radius, outer radius, and ring dimensions.
  • Mesh Specifications: Thickness of vertical fins, number of vertical fins, truss thickness, and truss count.
  • Smooth Unions: Fillet radii are used to smooth transitions between different parts.
from hwx import inspire  # Declare driving variables for the design visQual = "HIGH"  # Visualization Quality overallHeight = 0.04  # Filter height in meters innerRad = 0.01  # Inner radius in meters outerRad = 0.015  # Outer radius in meters ringHeight = 0.005  # Ring height in meters verticalFinThickness = 0.0005  # Thickness of vertical fins in meters verticalFinCount = 36  # Number of vertical fins radialDiamondTrussThickness = 0.001  # Truss thickness in meters diamondTrussThickness = 0.0005  # Diamond truss thickness in meters verticalTrussCount = 10  # Number of trusses filletRad = 0.00025  # Fillet radius in meters  # Initialize a new model and create the implicit part model = inspire.newModel() implicitPart = inspire.Implicit() implicitPart.name = "Implicit Filter" implicitPart.visualizationQuality = visQual  # Create the pipe for the filter fins finPipe = implicitPart.createPipe(innerRadius=innerRad, outerRadius=outerRad, height=overallHeight-2*ringHeight)  # Generate vertical fins using hexagonal lattice implicitPart.planarLattice(finPipe, thickness=verticalFinThickness, unitCellType="HEXAGON",                            coordinateSystemType="CYLINDRICAL", uniform=False, unitCellSizeX=1e-4,                            unitCellSizeY=verticalFinCount, planeVectorX=0.0, planeVectorY=0.0, planeVectorZ=1.0)  # Create diamond-shaped lattice for mesh diamondPipe = implicitPart.createPipe(innerRadius=(0.5*(innerRad+outerRad)-0.5*radialDiamondTrussThickness),                                       outerRadius=(0.5*(innerRad+outerRad)+0.5*radialDiamondTrussThickness),                                       height=overallHeight-2*ringHeight) implicitPart.planarLattice(diamondPipe, thickness=diamondTrussThickness, unitCellType="DIAMOND",                            coordinateSystemType="CYLINDRICAL", unitCellSizeX=(overallHeight-2*ringHeight)/verticalTrussCount,                            unitCellSizeY=2*3.14/verticalFinCount)  # Smoothly combine the lattices with fillet filterMesh = implicitPart.combine(implicitPart.bodies, transition="FILLET", transitionValue=filletRad)  # Add solid top and bottom rings topRing = implicitPart.createPipe(innerRadius=innerRad, outerRadius=outerRad, height=ringHeight,                                   originZ=0.5*overallHeight-0.5*ringHeight) bottomRing = implicitPart.createPipe(innerRadius=innerRad, outerRadius=outerRad, height=ringHeight,                                      originZ=-0.5*overallHeight+0.5*ringHeight)  # Combine rings into the assembly ringAssembly = implicitPart.combine([topRing, bottomRing], transition="SHARP")  # Combine the filter mesh and ring assembly fullAssembly = implicitPart.combine([filterMesh, ringAssembly], transition="FILLET", transitionValue=filletRad)  # Trim the final assembly trimPipe = implicitPart.createPipe(innerRadius=innerRad, outerRadius=outerRad, height=overallHeight) finalAssembly = implicitPart.intersect(fullAssembly, trimPipe, transition="SHARP")  # Fit the view to display the model inspire.fitView() 

 

Explanation:

  • Lattice Mesh: This code constructs two lattice structures, one hexagonal and one diamond-shaped, around a cylindrical mesh.
  • Smooth Combination: The two lattices are merged using a fillet radius to ensure smooth transitions, ensuring the final assembly has no sharp edges.
  • Solid Rings: The filter design is capped by solid rings on both the top and bottom, with the two rings combined using a sharp transition.

image

Example 8: Three-Point Bending Test Coupon

This script demonstrates the creation of a test coupon, which simulates a three-point bending test on a strut lattice structure sandwiched between two face sheets.

Key Design Variables
  • Strut Diameter (strutDia): Defines the thickness of the lattice struts.
  • Coupon Dimensions: Length, width, and thickness of the test specimen, as well as the thickness of the face sheets.
  • Lattice Configuration: The unit cell type of the lattice, as well as its dimensions along the x, y, and z axes.
  • Fillet Radius: Determines how smoothly the struts merge into the face sheets.
""" This script creates a three-point bending test coupon for a strut lattice  structure between two face sheets. The user supplies coupon dimensions and  specifies lattice properties, including details on whether to include a fillet radius between the lattice struts and the dace sheets.  """   ############################################################################### # Imports and preamble ############################################################################### from hwx import inspire  ############################################################################### # Declare driving variables for the design ###############################################################################  # Visualization Quality used to control the resolution and detail of the Implicit Model visQual = "HIGH"  # Test specimen dimensions  (in meters) strutDia = 0.001 specimenLength = 0.175 specimenWidth = 0.075 specimenThickness = 0.05  faceSheetThickness = 0.002   # Lattice unit cell, which can be any type from the default strut lattice options latticeUnitCell = 'CORNERDIAGONAL'  # Side lengths of the lattice unit cells in x, y and z (in meters) strutDia = 0.002 cellXLen = 0.0125 cellYLen = 0.0125 cellZLen = 0.0125  # Diameter of the struts in the lattice (in meters) strutDia = 0.002  # Fillet radius between lattice and face sheet (SHARP, FILLET or CHAMFER) transitionType = "FILLET" transitionSize = 0.002 # size of fillet or chamfer in meters   ############################################################################### # Main Script ###############################################################################  # Create new Inspire model model = inspire.newModel()  # Create a new implicit part for the lattice design and face sheets. implicitPart = inspire.Implicit()  # Name the implicit part implicitPart.name = "Implicit Part1"  # Set the Visualization Quality for the Implicit Part. implicitPart.visualizationQuality = visQual  # Create an implicit cuboid primitive to represent the top face sheet topFaceSheet = implicitPart.createCuboid(x=specimenLength,                                          y=specimenWidth,                                          z=faceSheetThickness,                                          uniform=False,                                          originZ=(0.5*specimenThickness + 0.5*faceSheetThickness))  # Create an implicit cuboid primitive to represent the bottom face sheet bottomFaceSheet = implicitPart.createCuboid(x=specimenLength,                                             y=specimenWidth,                                             z=faceSheetThickness,                                             uniform=False,                                             originZ=(-0.5*specimenThickness - 0.5*faceSheetThickness))  # Implicit Combine operation to union the two face sheets into a single # implict body faceSheets = implicitPart.combine(implicitPart.bodies)   # Create an implicit cuboid primitive to represent the lattice structure latticeVolume = implicitPart.createCuboid(x=specimenLength,                                           y=specimenWidth,                                           z=specimenThickness,                                           uniform=False)  # Create the strut lattice structure using the user-supplied information, and # the user preferences on whether to include a fillet radius between the # lattice struts and the face sheets. implicitPart.strutLattice(latticeVolume,                           unitCellType=latticeUnitCell,                           strutDiameter=strutDia,                           uniform=False,                           unitCellSize=True,                           cellSizeX=cellXLen,                           cellSizeY=cellYLen,                           cellSizeZ=cellZLen,                           combine=True,                           combineBody=faceSheets,                           transition=transitionType,                           distance=transitionSize                           )  # Fit the view to show the entire model inspire.fitView()

 

Explanation:

  • Test Coupon Setup: The script creates two solid face sheets and sandwiches a strut lattice structure between them.
  • Lattice Unit Cell: The user specifies a CORNERDIAGONAL unit cell, and the script generates the lattice using the provided strut diameter and unit cell sizes.
  • Smooth Transitions: A fillet radius is applied to smooth out the junctions where the lattice struts meet the face sheets.

image

Tagged: