The provision of adequate viewing standards in a stadium is a crucial factor in ensuring that seated accommodation is both safe and serves its intended purpose. Referencing the ‘Green Guide‘, which was produced by the Department for Culture, Media and Sport in the UK, this tutorial will explain how to use Dynamo to automate the seating bowl for a stadium.
Step 1: Pitch
The first step is to draw your playing field. Depending on the type of Stadium you are designing, this might be purpose-built, e.g. football only, or multi-purpose. I would recommend using a generic model family with model lines so that it will be visible in 3D. There is no need to parameterise the family as the pitch is unlikely to need to flex. The exception to this would be if the pitch were to be multi-purpose, in which case it is best to create separate generic model families for each type, and the nest these into a parent family and use the visibility parameter for graphic control.
Step 2: Point of focus
The next step is to define the point of focus. The term ‘focal point’ is a bit of a misnomer as it is not a point, but rather an imaginary line delineating the extent of the sightline. The location of the point of focus will vary depending on the sport. Football, for example, might be aligned with the nearest touchline but have filleted corners. Rugby, on the other hand, may allow the point of focus to be within the try zone. Obviously, the more of the pitch you can see, the better.
For illustrating the point of focus, we simply need to use mode lines (shown dashed above). It is important to remember that lines have a direction which will impact how they are offset. Best practice is to ensure the lines are drawn in a clockwise direction, although the Dynamo nodes shown later will ensure all curves are in the same direction.
Step 3: Sightlines
The term sightline refers to the ability of a spectator to see a predetermined focal point over the top of the heads of the spectators immediately in front. The better the quality of the sightline, the more likely it is that spectators will remain seated during the event. Ensuring adequate sightlines is, therefore, an important part of providing safe seated accommodation. The quality of a sightline is often expressed as a ‘C-Value’. The recommended C-Value for spectators varies according to the sport. The C-Values can be described as shown on the table below:
|60||Need to look between heads in front|
|90||Can see well with head tilted backwards|
Optimal viewing standard
|150||Can see well even if over spectators with hats|
As shown in the diagram above, Section 12.3 (p.109) of the Green Guide demonstrates how the C-Value can be calculated using the formula: C = ((D(N+R))/(D+T)) – R. However, typically we will have a predefined C-Value and will want to calculate the resulting riser heights (N), which will be different from row to row. To get N, we can use algebra:
C = ((D(N+R))/(D+T)) – R
R+C = (D(N+R))/D+T
((R+C)*(D+T))/D = N+R
(((R+C)*(D+T))/D)-R = N
Since we need to know the previous N value to calculate the next N value, we need to use recursion. Recursion is a computational process which requires a series of operations to be continually performed until a stopping condition is met. In Dynamo, this is best achieved using a simple Python script:
#Load required modules import clr clr.AddReference('ProtoGeometry') from Autodesk.DesignScript.Geometry import * #coverts to integer if text or number numRisers = int(IN) # Seating step width T = IN #Initial N value (starting height) N = IN #C value C = IN #Point of focus D = IN #sets counter to zero Rprev = 0 #container for output rList =  #create list of N distances (riser heights) for i in range(numRisers): Rcur = N + Rprev N = (((Rcur+C)*(D+T))/D)-Rcur rList.append(Rcur) D = D+T Rprev = Rcur #Assign your output to the OUT variable. OUT = rList
To save you the trouble of writing this, you can use the ‘Stadium.Cvalue‘ node from the BVN Dynamo package. The custom node defines the constants including Seating row depth (T); Eye offset from rear of row (A); Focus distance (F); Number or risers; Initial N value, C Value; and, Eye height (E).
The ‘frontDownturn’ input is typically the same as the initial N value (N) unless you are doing multiple tiers (refer Step 5). The Eye offset at rear of row (A) has a default value of 150mm, while Eye height (E) has a default value of 1200mm. Both of these are considered industry standard dimensions. Focal distance (F) was defined in Step 2 and relates to the sport/pitch. As you can see, the vertical height to the point of focus (R) is not explicitly defined. This is because the initial value of R can be calculated as:
R = initial N value (N) + eye height (E)
Similarly, the horizontal distance from the eye to the point of focus (D) is not explicitly defined. This is because the initial value of D can be calculated as:
D = Seating step width (T) – eye offset from rear of row (A) + focus distance (F)
Seating row depth
Seating row depth (T) relates to what the Green Guide terms a ‘clearway’ (refer Section 12.14, p.118). The clearway is the distance between the foremost projection of one seat and the back of the seat in front of it. The size of the clearway determines how safely and freely spectators and other personnel can move along rows of seats. The minimum clearway should be 400mm. It is therefore recommended that the seating row depth (T) should be at least 800mm, although 850mm is more common. (Note the key to the diagram below is different to the C-Value diagram above.)
The ‘Stadium.Cvalue’ node will output several values: the Z-coordinates of the top of the riser; the Z-coordinates of the bottom of the riser; the Z-coordinate of the eye (R); riser heights (N); and, row offsets (D). These values can then be used to generate the seating bowl profile in the next step.
Step 4: Seating bowl profile
Generally speaking, a seating bowl can be generated as a sweep using a profile and path. Typically, but not always, the path will be the line of focus to ensure consistent C-Values. The sweep path (line of focus) can be manually drawn, as described in Step 2. The sweep profile, on the other hand, can be automated now that we have the values from Step 3. There are multiple methods we can use to generate the profile curve:
Option 1 – Revit Project Environment
In this scenario, we can use the ‘Stadium.ModelCurve’ node from the BVN package to create the sweep profile directly within the Revit project environment. This method will create a polycurve in the XZ plane which will act as the sweep profile. Note, it is assumed that the Stadium is centred around the Project Base Point. If not, you’ll have to amend the script so that the sweep profile is translated into the correct position. Also, due to how Revit generates sweeps, it is unlikely that the sweep will work on a rectangular path. The path should have fillets or chamfers, which is more realistic, for Revit to generate the sweep. Once the sweep profile is created, we then have two main options for generating the sweep:
Option 1A – Direct shape with Dynamo
Using Dynamo, we can sweep the profile around the line of focus. Using the resultant solid, we can then generate either a direct shape or import instance. However, the baked geometry will have limited capacity to be modified outside of Dynamo. Furthermore, if you use Dynamo’s OOTB ‘DirectShape.ByGeometry’ node, the geometry will be tessellated, which will cause unnecessary visual complexity.
Note that if you didn’t define the materials of the direct shape in Dynamo, you’ll need to modify the ‘Dynamo’ material properties. To do this, go Manage > Settings > Materials.
While this method can be considered the most parametric in terms of pure Dynamo workflow in the sense that the results can be seen immediately within the Revit project, the result is a less user-friendly Revit element. To allow the greatest versatility and flexibility, this method is best avoided.
Option 1B – Manually create an in-place mass
We can use Dynamo to generate model lines for the profile curve and then manually create an in-place mass. To do this, go Architecture > Build > Component > Model In-place, and set family category to ‘Floors’. Then use the ‘pick lines’ command to define the profile. The creation of the in-place mass needs to be done manually due to a well-known limitation of the Revit API.
While the profile of the sweep can be locked to the model lines coming out of Dynamo, if extra rows are added, for example, the sweep will not be automatically updated to include the additional rows. However, the benefit of this workflow over Option 1A, is that the sweep will be ‘clean’ without any tessellations or triangulations. Furthermore, it allows for easy manual modifications outside of Dynamo.
If preferred, both Options 1A and 1B can be generated within a generic model family (but not a profile family). However, you will come across similar limitations, as mentioned above.
Option 2 – Revit Family Environment
In this scenario, we can use the ‘Stadium.ProfileCurve‘ node from the BVN package to create the sweep profile within a 2D profile family. First, create a new Family file by going File > New > Family > Metric Profile. Ensure that Dynamo is pointing to the new family file and not a Revit project. The script below will create detail lines in the XY plane of the profile family. Any manual modification can then be made as required, before loading the profile family into the Revit project.
Once the profile family has been loaded into the project, an in-place mass can then be created via a sweep. To do this, go Architecture > Build > Component > Model In-place, and set the family category to ‘Floors’. If the seating bowl profile needs to be updated in the future, simply re-run the Dynamo script in the profile family and reload (and overwrite) the family into the project. The sweep will then be automatically updated.
While this method is not necessarily the most parametric in the sense that you must go via the family first and then the project, it does afford the greatest flexibility for non-Dynamo users once the element is created. For this reason, this is the preferred option.
Step 5 (optional): Concourses & Multiple tiers
Stadiums will typically have multiple tiers separated by a concourse. A concourse is defined as a circulation area that provides direct access to and from viewing accommodation to which it may be linked by vomitories, passageways, stairs or ramps. It serves as a milling area for spectators for access to refreshments, entertainment and toilets, and may also be part of the entry and exit routes.
To create multiple tiers, simple repeat Step 4 but with new values. For example, while many of the values may remain constant, such as C-Value and seating row depth (T), the new tier will have a different point of focus (D) and initial N value (N). Note that although shown combined in this example, it is recommended to produce separate profiles as described in Step 4, for versatility.
Step 6: Vomitories
Section 9.1 of the Green Guide defines a vomitory as ‘an access route built into the gradient of a stand which directly links spectator accommodation to concourses, and/or routes for ingress, egress and emergency evacuation’ (p.75). Since the layout of vomitories will be different for each Stadium, this step is best done manually. To create the vomitories, first edit the seating bowl sweep created in Step 4/5, then go Create > Forms > Void Forms > Void Extrusion, and draw the vomitories in plan. The extrusion should be sufficiently high to cut through the sweep. You can create one void element with multiple vomitories or multiple voids, one for each vomitory – the choice is up to you.
Step 7: Gangways
Next, we need to add radial gangways to ensure spectators can travel from their seat to an exit. A radial gangway is defined as ‘a stepped or sloping channel for the circulation of spectators through viewing accommodation, running between terrace steps or seat rows’ (refer Section 8.2, p.66). The minimum width of a gangway should be 1200mm wide (refer Section 6.5, p.56 ) and all spectators should be within 12m of a gangway or exit, hence the maximum spacing of gangways 24m apart.
However, often the gangway distribution will be dictated by the seat spacings. Since the maximum number of seats per row is 28 seats (refer Section 12.16, p.119) and the minimum space allotted to each seat is 460mm (refer Section 12.13, p.116), this means the maximum gangway spacing is reduced to approximately 12.8m. Moreover, if the recommended 500mm seat spacing is used, the maximum gangway spacing is approximately 13.9m. In any case, the gangway spacings will usually be significantly less than the maximum 24m.
Gangways are best modelled as a single mass with multiple extrusions. To create these, go Architecture > Build > Component > Model In-place mass > Mass. Include any vomitories as essentially we are creating a void within the seating bowl where we don’t want seats. This mass will be used in the next step to create the seating line work.
Step 8: Seating row linework
Next, we need to extract the row curves of the seating bowl in preparation for the seat placement. One way of achieving this is to extract the edges from the seating bowl that we created in Step 4/5. However, this is an unnecessarily complex process as a series of sorting procedures would need to be undertaken to order the curves in a logical matter. Since we know the line of focus as well as the row offsets and Z-coordinates from Step 3, we can simply recreate the base linework. This method will give us a clean, structured list which will be easier to use.
When placing seats in a radial configuration, care needs to be taken in setting-out to prevent seats from overlapping. To achieve the desired spacing, we need to determine the front of the seat line. The ‘Stadium.CreateRows‘ custom node in the BVN package solves this though the ‘SeatDepth’ input. Essentially, the node offsets the back of row curve based on the seat depth input. The node then splits these curves with the gangway masses created in the previous step in preparation for the placement of the seats and gangway step generation.
Step 9: Gangway steps
Using the curves from the previous step and the riser heights from Step 3, we can create the gangway steps. The number of steps will vary depending on the row riser height. Section 12.11 of the Green Guide (p.115) states that:
- The goings of steps in radial gangways should not be less than 280mm and should be uniform.
- The risers of steps in radial gangways should not be more than 190mm, and should also be uniform.
A conditional statement is therefore required which states that if the going between rows is greater than the maximum step riser (190mm), then a step is required. Next, calculate the riser height of the step and determine if it is greater than the maximum (190mm). Repeat this process until the riser heights are below the maximum. To simplify this process, use the ‘Stadium.GangwayStep‘ node from the BVN package. The node will create uniform steps where the gangway voids existed (refer Step 7) which is then automatically cut by the vomitory voids (refer Step 6). To be able to select the vomitory void form for the ‘select model element’ input, you’ll need to edit the seating bowl sweep so that the void form is available.
Step 10: Place seats
The final step in creating our seating bowl is to place the seats following specific criteria:
- Justified to the edge of the gangways;
- Equally spaced between gangways; and,
- Perpendicular to the row.
Before we place any seats, we need first to ensure our seat family is correct. When placing a family in Dynamo, Revit will use the insertion point as defined by the ‘Defines Origin’ parameter within the family. The example below shows how the reference planes have been modified and renamed so that the centre (front/back) and centre (left/right) align with the front of the seat.
Seat family rotation
When rotating a family using Dynamo, Revit will use the local Z-axis regardless of where you have defined the origin. To check that the rotation point is the same as the insertion point, create a point at 0,0,0 in the family using the ‘Point.ByCoordinates’ node and ensure that the Revit background preview is enabled. Alternatively, you can load the family into the project and check the rotation there using a simple script:
Once you are happy with your seat family, ensure it is loaded into the project. To place the seats, we can use the front of seat polycurve generated in Step 8 and the ‘Stadium.PlaceSeats‘ node from the BVN package. The polycurves already accommodates any gangways or vomitories, so there is no need to do any further modification to the base geometry apart from removing the last row.
The node calculates the start and endpoint of our seating array. If we were to use the start and endpoint of the polycurve, we would have seats encroaching into the gangways, which is unacceptable. We, therefore, need to ascertain the seat width and trim the polycurve by half of the seat width at either end. This process will ensure that seats are justified to the gangways.
Next, the node divides the shortened polycurve by chord length (as opposed to along the polycurve) using the minimum seat spacing. This operation returns a preliminary result as it will generally be a number (float) rather than an integer. Since we can’t have part of a seat, the node uses the ‘Floor’ node to round down the number to the nearest whole number. Knowing how many whole seats will fit, the node then divides the shortened polycurve with the new integer value. The resultant points on the curve are the insertion point for our seat family which corresponds to the front of the seat. Note that the number of seats per row between gangways is going to differ. This scenario is most evident in the corners of the stadium where there is radial geometry.
Since it is not possible to place a family instance with the correct rotation in one step, the node first places the seats using the ‘FamilyInstance.ByPoint’ node before setting their rotation. The seats need to be rotated so that they are perpendicular to the normal along the rows. To calculate this angle, the node uses the insertion points of the seat family and the polycurves to generate the parameters along the curve using the ‘Curve.ParameterAtPoint’ node. With these values, normals are then returned using the ‘Curve.NormalAtParameter’ node.
Next, we need to calculate the difference between the orientation of the seat family and the normal vector. Before Dynamo 0.8.2, this was impossible using OOTB nodes. However, Dynamo now has the ‘Vector.AngleAboutAxis’ node. Since in this example, the seat has been modelled facing ‘down’, I need to compare the normal vector to the negative Y vector. The result of this calculation is the required seat rotation in degrees from 0 to 360. The seats are then rotated to the correct orientation using the ‘FamilyInstances.SetRotation’ node. The ‘Stadium.PlaceSeats’ node will also output the total number of seats and the number of seats per row so that you can ensure all brief and regulatory requirements are met.
While this tutorial demonstrated that it is possible to create a seating bowl using Dynamo parametrically, it highlighted the fine line between the desire to achieve full automation verse the usability of the output. In this scenario, a fully parametric definition would have been detrimental to the workflow due to the inability to edit direct shapes manually. Instead, an augmented workflow was advocated, which afforded both manual intervention and more usable Revit geometry. Computational designers must be aware of such software limitations and ensure they customise their workflow appropriately so that the entire project team can engage with the model.
To see how Dynamo can be used to create an entire stadium, check out this post.
Department for Culture, Media and Sport (2008). ‘Guide to Safety at Sports Grounds’, The Stationery Office, Norwich.