I'm trying to create a cylinder-like shape from a NURBS surface. It's mostly doing what I want, but the seam in the cylinder looks ragged:
I've verified that first column of the surface's control points, its last column, and the seam's control points are all identical:
First column of the surface:
0: (4.851, 0.000, 0.000)
1: (4.962, 0.000, 1.000)
2: (5.180, 0.000, 2.000)
3: (4.851, 0.000, 3.000)
4: (4.938, 0.000, 4.000)
Last column of the surface:
60: (4.851, 0.000, 0.000)
61: (4.962, 0.000, 1.000)
62: (5.180, 0.000, 2.000)
63: (4.851, 0.000, 3.000)
64: (4.938, 0.000, 4.000)
Seam:
0: (4.851, 0.000, 0.000)
1: (4.962, 0.000, 1.000)
2: (5.180, 0.000, 2.000)
3: (4.851, 0.000, 3.000)
4: (4.938, 0.000, 4.000)
I'm at my wits' end here. Can anyone tell what's going on here?
PS. Turning isPeriodicU=True is a stunning disaster.
Here's the code:
import math
import random
import traceback
from adsk.core import *
from adsk.fusion import *
def run(context):
app = Application.get()
ui = app.userInterface
try:
# Parameters
uCount = 12 # Number of points around the circumference of the cylinder
vCount = 5 # Number of points along the height of the cylinder
isPeriodicU = False # Use periodic NURBS around the circumference?
# Set up the grid
grid = []
for u in range(uCount):
strip = []
for v in range(vCount):
radius = random.gauss(5.0, 0.1) # * v / vCount + 1.0
angle = 2.0 * math.pi * u / uCount
x = radius * math.cos(angle)
y = radius * math.sin(angle)
z = v
point = Point3D.create(x, y, z)
strip.append(point)
grid.append(strip)
def getPoint(u: int, v: int) -> Point3D:
return grid[u % uCount][v]
# Create the NURBS surface for the barrel of the cylinder
degreeU = 1
degreeV = 1
controlPointCountU = uCount + 1 # Add one to repeat the start point
controlPointCountV = vCount
controlPoints = [
getPoint(u, v)
for u in range(controlPointCountU)
for v in range(controlPointCountV)
]
for index, controlPoint in enumerate(controlPoints):
if index % controlPointCountV == 0:
print()
print(
f"{index}: ({controlPoint.x:.3f}, {controlPoint.y:.3f}, {controlPoint.z:.3f})"
)
knotCountU = controlPointCountU + degreeU + 1
knotCountV = controlPointCountV + degreeV + 1
knotsU = [float(i) for i in range(knotCountU)]
knotsV = [float(i) for i in range(knotCountV)]
weights = []
if isPeriodicU:
propertiesU = NurbsSurfaceProperties.PeriodicNurbsSurface
else:
propertiesU = NurbsSurfaceProperties.ClosedNurbsSurface
propertiesV = NurbsSurfaceProperties.OpenNurbsSurface
# https://help.autodesk.com/view/fusion360/ENU/?guid=GUID-9f00de94-1b71-4ae3-80ca-0dde9a4f72d9
surface = NurbsSurface.create(
degreeU,
degreeV,
controlPointCountU,
controlPointCountV,
controlPoints,
knotsU,
knotsV,
weights,
propertiesU, # type: ignore
propertiesV, # type: ignore
)
# Create the NURBS curves for the top, bottom, and seam of the barrel
if isPeriodicU:
# The documentation says the number of knots should always be len(points)+degree+1,
# but the API appears to enforce len(points) when isPeriodic is True
knotCountU = controlPointCountU
# https://help.autodesk.com/view/fusion360/ENU/?guid=GUID-f41f31c5-2d11-43ad-9ad4-398ef9184684
botCurve = NurbsCurve3D.createNonRational(
controlPoints=[getPoint(u, 0) for u in range(controlPointCountU)],
degree=degreeU,
knots=[float(i) for i in range(knotCountU)],
isPeriodic=isPeriodicU,
)
topCurve = NurbsCurve3D.createNonRational(
controlPoints=[
getPoint(u, controlPointCountV - 1) for u in range(controlPointCountU)
],
degree=degreeU,
knots=[float(i) for i in range(knotCountU)],
isPeriodic=isPeriodicU,
)
seamCurve = NurbsCurve3D.createNonRational(
controlPoints=[getPoint(0, v) for v in range(controlPointCountV)],
degree=degreeV,
knots=[float(i) for i in range(knotCountV)],
isPeriodic=False,
)
print()
print("Seam:")
for index, controlPoint in enumerate(seamCurve.controlPoints):
print(
f"{index}: ({controlPoint.x:.3f}, {controlPoint.y:.3f}, {controlPoint.z:.3f})"
)
# Define the vertices
botPoint = getPoint(0, 0)
topPoint = getPoint(0, controlPointCountV - 1)
bodyDefinition = BRepBodyDefinition.create()
lumpDefinition = bodyDefinition.lumpDefinitions.add()
shellDefinition = lumpDefinition.shellDefinitions.add()
botVertex = bodyDefinition.createVertexDefinition(botPoint)
topVertex = bodyDefinition.createVertexDefinition(topPoint)
# Define the edges
botPlane = Plane.create(
Point3D.create(0.0, 0.0, botPoint.z), Vector3D.create(0.0, 0.0, -1.0)
)
topPlane = Plane.create(
Point3D.create(0.0, 0.0, topPoint.z), Vector3D.create(0.0, 0.0, 1.0)
)
botEdge = bodyDefinition.createEdgeDefinitionByCurve(
botVertex, botVertex, botCurve
)
topEdge = bodyDefinition.createEdgeDefinitionByCurve(
topVertex, topVertex, topCurve
)
seamEdge = bodyDefinition.createEdgeDefinitionByCurve(
topVertex, botVertex, seamCurve
)
# Define the faces
botFace = shellDefinition.faceDefinitions.add(botPlane, False)
botLoop = botFace.loopDefinitions.add()
botLoop.bRepCoEdgeDefinitions.add(botEdge, True)
topFace = shellDefinition.faceDefinitions.add(topPlane, False)
topLoop = topFace.loopDefinitions.add()
topLoop.bRepCoEdgeDefinitions.add(topEdge, False)
barrelFace = shellDefinition.faceDefinitions.add(surface, False)
barrelLoop = barrelFace.loopDefinitions.add()
barrelLoop.bRepCoEdgeDefinitions.add(topEdge, False)
barrelLoop.bRepCoEdgeDefinitions.add(seamEdge, False)
barrelLoop.bRepCoEdgeDefinitions.add(botEdge, True)
barrelLoop.bRepCoEdgeDefinitions.add(seamEdge, True)
# Create the body
bodyDefinition.doFullHealing = True
body = bodyDefinition.createBody()
print(f"Is valid: {bodyDefinition.isValid}")
for outcome in bodyDefinition.outcomeInfo:
print(f"Outcome: {outcome}")
# Add the body to the design
designProduct = Design.cast(app.activeProduct)
component = designProduct.activeComponent
baseFeatures = component.features.baseFeatures
baseFeature = baseFeatures.add()
baseFeature.startEdit()
component.bRepBodies.add(body, baseFeature)
baseFeature.finishEdit()
groups = component.customGraphicsGroups
while groups.count > 0:
groups.item(0).deleteMe()
graphics = groups.add()
for v in range(controlPointCountV):
assert getPoint(0, v) is getPoint(controlPointCountU - 1, v)
coords = []
for u in range(controlPointCountU):
point = getPoint(u, v)
coords.append(point.x)
coords.append(point.y)
coords.append(point.z)
coordinates = CustomGraphicsCoordinates.create(coords)
lines = graphics.addLines(coordinates, [], True)
lines.weight = 2
lines.color = CustomGraphicsSolidColorEffect.create(
Color.create(0, 127, 255, 255)
)
lines.depthPriority = 100
for u in range(controlPointCountU):
coords = []
for v in range(controlPointCountV):
point = getPoint(u, v)
coords.append(point.x)
coords.append(point.y)
coords.append(point.z)
coordinates = CustomGraphicsCoordinates.create(coords)
lines = graphics.addLines(coordinates, [], True)
lines.weight = 2
lines.color = CustomGraphicsSolidColorEffect.create(
Color.create(0, 255, 127, 255)
)
lines.depthPriority = 200
except:
ui.messageBox("Failed:\n{}".format(traceback.format_exc()))
Solved! Go to Solution.
Solved by ginavenolia. Go to Solution.
Well, it looks like I got the vertices reversed when creating the seam edge. That was horribly embarrassing.
Can't find what you're looking for? Ask the community or share your knowledge.