- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report
When attemping to use the third occurance of a child component to create a circular pattern, the first child, not the third child, is what actually ends up being rotated.
I've been trying to debug this for ages and would really very greatful if anyone could help.
In a very brief overview of the following script I draw one section of a geodesic dome with patches, and then rotate it 3 times using CircularPatternFeatures. This would then create something like a segment of an orange that could be rotated 5 times around the Z axis to create a complete sphere.
To create the second and third sections I can just rotate the first section around different axis, however to get the fourth and bottom section I need to rotate the third section I just created and this is where I am failing.
I use a function to rotate an occurance of a component and it prints out the name of the occurance that it is rotating. This confirms I'm passing in the correct occurance, yet still that is not what is actually rotated.
The bug must either be on line 109 (section3 = self.root.occurrences.item(2)) or in the function rotateTriangleSegment() which is on line 421.
I realise this is not a debugging service, however if anyone can help me resolve this then I'll give back to the community by releasing a plugin to generate geodesic domes.
(I can make this work - but only by abandoning having a top level component (3v geodesic sphere) - and it seems like that would clutter up users document tree too much so would really love to get this to work.)
Here is an image in an attempt to help communicate clearly:
This shows what is going wrong and what I'm trying to achive
Doc tree. Segment 4 is in the wrong place.
Here is my script:
# Author - Felix Eve
# Description - Draw a 3v geodesic dome
import adsk.core, adsk.fusion, adsk.cam, traceback, math
def run(context):
ui = None
try:
app = adsk.core.Application.get()
ui = app.userInterface
# Create a document
fusionDocType = adsk.core.DocumentTypes.FusionDesignDocumentType
doc = app.documents.add(fusionDocType)
product = app.activeProduct
design = adsk.fusion.Design.cast(product)
# Get the root component of the active design
root = design.rootComponent
geodesic = Geodesic(root, 2)
geodesic.render()
except:
if ui:
ui.messageBox('Failed:\n{}'.format(traceback.format_exc()))
class Geodesic:
"""Creates 3 frequency a geodesic sphere"""
def __init__(self, rootComponent, radius=1):
"""Constructor to setup references and create the component everything
will live in and run setup()"""
self.realRoot = rootComponent
# Set the radius as a property of the class
self.radius = radius
# Set the frequency
self.frequency = 3
# Set dihedral angle
self.dihedralAngle = (63.434948823 / self.frequency) * (math.pi / 180)
# The angle to rotate everything to get a flat base
self.offsetAngle = 58.xxx-xxxxxxxx
# Create the component everything else will live in
location = adsk.core.Matrix3D.create()
occurrence = rootComponent.occurrences.addNewComponent(location)
self.root = occurrence.component
self.root.name = '3v Geodesic sphere'
# Create a reference to sketches
self.sketches = self.root.sketches
# Run setup
self.setup()
def setup(self):
"""Run setup tasks - creates sketches, set scale"""
self.tLines = {} # Triangle lines
# Create sketch to hold a segment of the geodesic sphere
self.segmentSketch = self.sketches.add(self.root.xYConstructionPlane)
self.segmentSketch.name = 'Geodesic segment'
# Create sketch to hold the icosahedron
self.icoSketch = self.sketches.add(self.root.xYConstructionPlane)
self.icoSketch.name = 'Icosahedron'
# Golden ratio
self.GR = (1 + math.sqrt(5)) / 2
# The radius of the sphere
initialRadius = math.sqrt(1 + math.pow(self.GR, 2))
# Set scale
self.factor = self.radius / initialRadius
# Set origin 3d point
self.originPoint3D = adsk.core.Point3D.create(0, 0, 0)
def render(self):
"""Actually draw the geodesic sphere"""
self.drawIcosahedron()
self.drawConstructionLines()
self.drawSegment()
self.rotateFlat(self.icoSketch)
self.rotateFlat(self.segmentSketch)
# Use segment sketch to create patches
section1 = self.createPatches()
# Create first rotated occurrence - rotate seg1 round axis H
axis = self.constructionLines['H']
self.rotateTriangleSegment(section1, axis, '(360 / 5) * 4')
# Create second rotated occurrence - rotate seg1 round axis C
axis = self.constructionLines['C']
self.rotateTriangleSegment(section1, axis, '(360 / 5) * 2')
# Create third rotated occurrence - rotate seg3 round axis L
section3 = self.root.occurrences.item(2)
axis = self.constructionLines['L']
# Why does this line rotate the wrong segment‽‽‽‽‽‽‽‽‽‽‽
self.rotateTriangleSegment(section3, axis, '(360 / 5) * 1')
# Delete other axis so it's obvious which one axis L is that was used
# in the last optionation
axis = self.constructionLines['C'].deleteMe()
axis = self.constructionLines['D'].deleteMe()
axis = self.constructionLines['H'].deleteMe()
def drawIcosahedron(self):
"""Create all the points of the icosahedron"""
icoSketch = self.icoSketch
# Create origin point on icosahedron sketch
self.icoOriginPoint = icoSketch.sketchPoints.add(self.originPoint3D)
# Create dictionary to hold icosahedron points
self.icoPoints = {}
GR = self.GR
points = {
'A': [0, 1, GR],
'B': [0, -1, GR], # Bottom
'C': [0, -1, -GR],
'D': [0, 1, -GR], # Top
'E': [GR, 0, 1],
'F': [-GR, 0, 1],
'G': [-GR, 0, -1],
'H': [GR, 0, -1],
'I': [1, GR, 0],
'J': [-1, GR, 0],
'K': [-1, -GR, 0],
'L': [1, -GR, 0],
}
# Loop points and create them on the base sketch
for (name, coords) in points.items():
x = coords[0] * self.factor
y = coords[1] * self.factor
z = coords[2] * self.factor
point = adsk.core.Point3D.create(x, y, z)
self.icoPoints[name] = icoSketch.sketchPoints.add(point)
def drawConstructionLines(self):
"""Create somes constructions lines from the origin to certain points
of the icosahedron that we'll later use as axis of rotation"""
origin = self.icoOriginPoint
sketchLines = self.icoSketch.sketchCurves.sketchLines
lineC = sketchLines.addByTwoPoints(origin, self.icoPoints['C'])
lineC.isConstruction = True
lineD = sketchLines.addByTwoPoints(origin, self.icoPoints['D'])
lineD.isConstruction = True
lineH = sketchLines.addByTwoPoints(origin, self.icoPoints['H'])
lineH.isConstruction = True
lineL = sketchLines.addByTwoPoints(origin, self.icoPoints['L'])
lineL.isConstruction = True
self.constructionLines = {}
self.constructionLines['D'] = lineD
self.constructionLines['H'] = lineH
self.constructionLines['L'] = lineL
self.constructionLines['C'] = lineC
def drawSegment(self):
"""Draw one segment of the geodesic dome that sits within triangle
C.D.H. of the icosahedron"""
# Create triangle sides - genereated (intermediary) points are returned
sides = []
sides.append(self.createTriangleSide('C', 'D', 's1'))
sides.append(self.createTriangleSide('D', 'H', 's2'))
sides.append(self.createTriangleSide('H', 'C', 's3'))
# Create lines that go across the corners of the triangle
self.createLine(sides[0][0], sides[2][1], 's3_s1_c')
self.createLine(sides[0][1], sides[1][0], 's1_s2_c')
self.createLine(sides[2][0], sides[1][1], 's2_s3_c')
# Find the centre of the triangle pushed to the outer sphere
centrePoint = self.getExtendedCentre('C', 'D', 'H')
# Create lines to the centre
for sideN, side in enumerate(sides):
for pointN, point in enumerate(side):
lineName = 's' + str(sideN + 1) + '_c' + str(pointN + 1)
self.createLine(centrePoint, point, lineName)
def createTriangleSide(self, point1Name, point2Name, sideName):
"""Create all the lines that make up one side of one of the icosahedron
triangles. Each point is pushed to the outside of the sphere. To do
this we need to make a plane through these three points and a sketch
so when we draw an arc it knows which direction to go in."""
lineName = point1Name + point2Name
# Create plane through 2 points and origin and sketch
plane, sketch = self.createSketchOnPlane(point1Name, point2Name)
# Find where points in 3d space intersect with our new plane
entities = []
entities.append(self.icoPoints[point1Name])
entities.append(self.icoPoints[point2Name])
intersectPos = sketch.intersectWithSketchPlane(entities)
# Draw arcs to find locations for new points
sketchArcs = sketch.sketchCurves.sketchArcs
centre = self.originPoint3D
start = intersectPos[0]
arcs = []
for i in range(1, self.frequency):
angle = self.dihedralAngle * i
arc = sketchArcs.addByCenterStartSweep(centre, start, angle)
arcs.append(arc)
# Create all the points along the edge
points = []
limit = self.frequency + 1
for i in range(0, limit):
# If first or last item use the icosahedron point position
if (i == 0 or i == limit-1):
index = 0 if (i == 0) else 1
point = self.create3dPointFromObject(intersectPos[index])
# Else use the arc end points for the intermediary points
else:
index = i - 1
point = self.create3dPointFromObject(arcs[index].endSketchPoint)
points.append(point)
# Initialize an array to hold the new generated points we've made
generatedPoints = []
# Draw the lines
for i in range(0, 3):
lineName = sideName + '_l' + str(i + 1)
line = self.createLine(points[i], points[i + 1], lineName)
if(i > 0):
lnSwG = line.startSketchPoint.worldGeometry
point3D = adsk.core.Point3D.create(lnSwG.x, lnSwG.y, lnSwG.z)
point = self.segmentSketch.sketchPoints.add(point3D)
generatedPoints.append(point)
# Draw icosahedron line using first and last point
self.icoSketch.sketchCurves.sketchLines.addByTwoPoints(points[0], points[-1])
# Clean up undeeded lines, planes and sketches
for arc in arcs:
arc.deleteMe()
plane.deleteMe()
sketch.deleteMe()
return generatedPoints
def createSketchOnPlane(self, point1Name, point2Name):
"""Create a plane through the 2 points passed in and the origin and
create a sketch on that plane"""
lineName = point1Name + point2Name
# Create construction plane input
planeInput = self.root.constructionPlanes.createInput()
# Add construction plane by three points
point1 = self.root.originConstructionPoint
point2 = self.icoPoints[point1Name]
point3 = self.icoPoints[point2Name]
planeInput.setByThreePoints(point1, point2, point3)
plane = self.root.constructionPlanes.add(planeInput)
plane.name = 'plane ' + lineName
# Add sketch on new plane
sketch = self.sketches.add(plane)
sketch.name = 'sketch ' + lineName
return plane, sketch
def create3dPointFromObject(self, object):
"""Returns a 3D point created from the world geometry of the object
passed in"""
x = object.worldGeometry.x
y = object.worldGeometry.y
z = object.worldGeometry.z
return adsk.core.Point3D.create(x, y, z)
def createLine(self, point1, point2, name):
"""Create one line of the geodesic and store a named reference to it in
the tLines dictionary"""
sketchLines = self.segmentSketch.sketchCurves.sketchLines
self.tLines[name] = sketchLines.addByTwoPoints(point1, point2)
return self.tLines[name]
def getExtendedCentre(self, point1Name, point2Name, point3Name):
"""Find the centre of the 3 points passed in, draw a line from the
origin through that point the length of the radius and then return a
point at that location"""
# Find the centre of the triangle
centrePoint = self.getCentrePoint(point1Name, point2Name, point3Name)
# Create a vector through the triangle centre and extend it's length to the radius
pointGeo = centrePoint.worldGeometry
vector = adsk.core.Vector3D.create(pointGeo.x, pointGeo.y, pointGeo.z)
vector.normalize()
vector.scaleBy(self.radius)
# Now we can delete the triangle centre point
centrePoint.deleteMe()
# Create a point at the end of the vector
point3D = adsk.core.Point3D.create(vector.x, vector.y, vector.z)
return self.segmentSketch.sketchPoints.add(point3D)
def getCentrePoint(self, point1Name, point2Name, point3Name):
"""Find the centre of the 3 points passed in and return a sketchPoint
located there"""
points = []
points.append(self.icoPoints[point1Name])
points.append(self.icoPoints[point2Name])
points.append(self.icoPoints[point3Name])
ax = ay = az = 0
for point in points:
ax += point.worldGeometry.x
ay += point.worldGeometry.y
az += point.worldGeometry.z
point3D = adsk.core.Point3D.create(ax / 3, ay / 3, az / 3)
return self.segmentSketch.sketchPoints.add(point3D)
def rotateFlat(self, sketch):
"""The icosahedron and first geodesic dome segment are initially
created on a bit of an angle. This function rotates them so the base on
the dome would be flat aginst the xy plane."""
all = adsk.core.ObjectCollection.create()
for c in sketch.sketchCurves:
all.add(c)
for p in sketch.sketchPoints:
all.add(p)
vector1 = adsk.core.Vector3D.create(0, 1, 0)
vector2 = adsk.core.Vector3D.create(0, 0, 1)
axis = vector1.crossProduct(vector2)
axis.transformBy(sketch.transform)
origin = sketch.origin
origin.transformBy(sketch.transform)
angle = (self.offsetAngle + 90) * (math.pi / 180)
mat = adsk.core.Matrix3D.create()
mat.setToRotation(angle, axis, origin)
sketch.move(all, mat)
def createPatches(self):
"""Create a patch for each triangle in the geodesic segment. Put in
patch in a new child component"""
location = adsk.core.Matrix3D.create()
occurrence = self.root.occurrences.addNewComponent(location)
comp = occurrence.component
comp.name = 'Segment'
# Bottom row
self.createPatchFrom3Lines('s1_l1', 's3_l3', 's3_s1_c', comp)
self.createPatchFrom3Lines('s3_s1_c', 's1_c1', 's3_c2', comp)
self.createPatchFrom3Lines('s3_c2', 's3_c1', 's3_l2', comp)
self.createPatchFrom3Lines('s3_c1', 's2_c2', 's2_s3_c', comp)
self.createPatchFrom3Lines('s2_s3_c', 's3_l1', 's2_l3', comp)
# Middle row
self.createPatchFrom3Lines('s1_l2', 's1_c1', 's1_c2', comp)
self.createPatchFrom3Lines('s1_c2', 's1_s2_c', 's2_c1', comp)
self.createPatchFrom3Lines('s2_c1', 's2_c2', 's2_l2', comp)
# Top row
self.createPatchFrom3Lines('s1_l3', 's1_s2_c', 's2_l1', comp)
return occurrence
def createPatchFrom3Lines(self, line1Name, line2Name, line3Name, comp):
"""Create and return a patch based off the 3 line names passed in"""
lines = adsk.core.ObjectCollection.create()
lines.add(self.tLines[line1Name])
lines.add(self.tLines[line2Name])
lines.add(self.tLines[line3Name])
# Create a patch from our lines collection
patches = comp.features.patchFeatures
newBodyOp = adsk.fusion.FeatureOperations.NewBodyFeatureOperation
patchInput = patches.createInput(lines, newBodyOp)
return patches.add(patchInput)
def rotateTriangleSegment(self, occurrence, axis, angleString):
"""Rotate the component around the axis by the angle and create a copy.
Returns the new component."""
print('Rotating occurrence with name ' + occurrence.name)
# Create input entities for circular pattern
inputEntites = adsk.core.ObjectCollection.create()
inputEntites.add(occurrence)
# Create the input for circular pattern
circularFeats = self.root.features.circularPatternFeatures
circularFeatInput = circularFeats.createInput(inputEntites, axis)
# Set the quantity of the elements
circularFeatInput.quantity = adsk.core.ValueInput.createByReal(2)
# Set the angle of the circular pattern
circularFeatInput.totalAngle = adsk.core.ValueInput.createByString(angleString)
# Set symmetry of the circular pattern
circularFeatInput.isSymmetric = False
# Create the circular pattern
return circularFeats.add(circularFeatInput)
Solved! Go to Solution.