
- 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.