I'm running into an issue where the block subdivision types in CityEngine does not seem to have the functionality I need for my current project. I'm looking to write a rule that allows for greater control in the parceling stage. I need to be able to create a controlled mosaic of zoning types within a block and have a unique parceling for each of those different zoning types. I currently have a CGA rule that allows me to split the block in two (A and B) and then have additional splits within A and B and the ability to rotate the orientation of A and B. Each of the subblock areas can then be assigned a zoning type (color as placeholders in the images). Once I'm at this stage I cannot select individual parcels, unlike those created using one of the existing subdivision types. Additionally, the polygons I've created are not aware of street edges and do not have the option to Set First Edge. After assigning a zoning type the subblock area should be divided into parcels according to specific zoning parameters (minimum & maximum lot widths, setbacks, etc) as drawn in image 4.
The other option I'm considering is using roads with widths of 0m to subdivide the whole block into several CityEngine blocks so that I can preserve the Set First Edge function. I'd like to avoid this option if possible as I want to have dynamic sliders that would allow users configure subblock zoning dimensions.
Hi Brendan,
For this, you'd have to write pretty complex CGA code to encode all the design functionality you need in an efficient manner.
The 'Dynamic City Layouts' toolset is quite limited in this regard.
Maybe we can help with more advanced CGA code.
Have a great day!
matt
------------------------------------------------------------------------------------------------------------------
matthias buehler || ceo & founder || dipl. arch. eth zurich
------------------------------------------------------------------------------------------------------------------
vrbn AG – official esri partner || www.vrbn.io || winterthurerstrasse 53 || 8610 uster || switzerland
+41 78 930 04 07 || matthias.buehler@vrbn.io || @vrbnio @mattb3d
What kind of complex CGA would make it possible while keeping it dynamic?
The 'Dynamic City Layouts' toolset is super limited, but it's the only way I know of that calculates the Streets Edges automatically, sadly.
If I do it in CGA, all the final lot shapes share the same street edge of the original block instead of having their own dedicated one (lots in the center of the block should not have a street edge).
It would be SO awesome to be able to have more control on the Dynamic subdividsub of blocks into lots, something where you can specify the percentage of occurrence of big lots, medium and small lots..
Right now you can make the lots less regular in size, but big and small lots appear at random and in a even number, while it is quite more probable that there are a few giant buildings and many small houses.
Hi Matt,
I've written a CGA rule that allows the user to control the splitting of a block into a series of shapes. Once the final subblock units have been set I've used a python script to convert them to static parcels. One problem I'm having is saving the area of each of these areas as an object attribute in the newly created shapes (for subsequent FAR calculations). Once they are static shapes I need to manually remove some excess shapes (there are extra subblock areas created for each rectangle in the block, including the block itself). Finally, I've tried writing an Export (Reporting) Python module that creates a new object attribute for each subblock unit and assigns the area to that attribute.
Any advice you can provide is greatly appreciated!
Brendan
_______________________________________________________________________________________
CGA rule for controlled subdivision of a block:
/**
* File: subblock.cga
* Created: 20 Jul 2017 18:58:27 GMT
* Author: bcbd
*/
version "2017.0"
import Zoning : "Zoning.cga"
@hidden
attr myZoning = ""
attr zoningResidentialArea = 0
attr zoningCommercialArea = 0
attr zoningInstitutitonalArea = 0
attr zoningMixed_UseArea = 0
attr zoningIndustrialArea = 0
@Group("Initial Block: First Split", 1)
attr A_B_Split = false
attr rotate_AB = false
@Range(0,200)
attr A_Depth = 20
@Range("Residential", "Commercial", "Institutional", "Mixed_Use", "Industrial")
attr zoningA = "Residential"
@Range("Residential", "Commercial", "Institutional", "Mixed_Use", "Industrial")
attr zoningB = "Residential"
@Group("A: Second Split", 3)
attr A1_A2_Split = false
attr rotate_ZoneA = false
@Range(0,100)
attr A2_Width = 20
attr A3_Split = false
@Range(0,100)
attr A3_Width = 20
@Range("Residential", "Commercial", "Institutional", "Mixed_Use", "Industrial")
attr zoningA1 = "Residential"
@Range("Residential", "Commercial", "Institutional", "Mixed_Use", "Industrial")
attr zoningA2 = "Residential"
@Range("Residential", "Commercial", "Institutional", "Mixed_Use", "Industrial")
attr zoningA3 = "Residential"
@Group("B: Second Split", 4)
attr B1_B2_Split = false
attr rotate_ZoneB = false
@Range(0,100)
attr B2_Width = 20
attr B3_Split = false
@Range(0,100)
attr B3_Width = 20
@Range("Residential", "Commercial", "Institutional", "Mixed_Use", "Industrial")
attr zoningB1 = "Residential"
@Range("Residential", "Commercial", "Institutional", "Mixed_Use", "Industrial")
attr zoningB2 = "Residential"
@Range("Residential", "Commercial", "Institutional", "Mixed_Use", "Industrial")
attr zoningB3 = "Residential"
////////////////////////// BEGINNING OF SPLIT
@StartRule
block -->
case A_B_Split == true :
case rotate_AB == false :
split(x) {A_Depth : zoneA | ~1 : zoneB}
Reporting
NIL ///////////////////
else :
split(z) {A_Depth : zoneA | ~1 : zoneB}
Reporting ///////////////////
NIL
else :
colorPicker(zoningA)
Reporting ///////////////////
NIL
zoneA -->
case A1_A2_Split == true && A3_Split == false :
case rotate_ZoneA == false :
split(x) {A2_Width : zoneA1 | ~1 : zoneA2}
Reporting ///////////////////
NIL
else :
split(z) {A2_Width : zoneA1 | ~1 : zoneA2}
Reporting ///////////////////
NIL
case A1_A2_Split == true && A3_Split == true :
case rotate_ZoneA == false :
split(x) {A2_Width : zoneA1 | ~1 : zoneA2 | A3_Width : zoneA3}
Reporting ///////////////////
NIL
else :
split(z) {A2_Width : zoneA1 | ~1 : zoneA2 | A3_Width : zoneA3}
Reporting ///////////////////
NIL
else :
colorPicker(zoningA)
Reporting ///////////////////
NIL
zoneB -->
case B1_B2_Split == true && B3_Split == false :
case rotate_ZoneB == false :
split(x) {B2_Width : zoneB1 | ~1 : zoneB2}
Reporting ///////////////////
NIL
else :
split(z) {B2_Width : zoneB1 | ~1 : zoneB2}
Reporting ///////////////////
NIL
case B1_B2_Split == true && B3_Split == true :
case rotate_ZoneB == false :
split(x) {B2_Width : zoneB1 | ~1 : zoneB2 | B3_Width : zoneB3}
Reporting ///////////////////
NIL
else :
split(z) {B2_Width : zoneB1 | ~1 : zoneB2 | B3_Width : zoneB3}
Reporting ///////////////////
NIL
else :
colorPicker(zoningB)
Reporting ///////////////////
NIL
#ZONE A
zoneA1 -->
colorPicker(zoningA1)
zoneA2 -->
colorPicker(zoningA2)
zoneA3 -->
colorPicker(zoningA3)
#ZONE B
zoneB1 -->
colorPicker(zoningB1)
zoneB2 -->
colorPicker(zoningB2)
zoneB3 -->
colorPicker(zoningB3)
colorPicker(zoningType)-->
case zoningType == "Residential" :
Zoning.Residential
label("Residential")
set(myZoning, "Residential")
//set(zoningResidentialArea, geometry.area)
print(myZoning + " Area: " + geometry.area)
case zoningType == "Commercial" :
Zoning.Commercial
set(myZoning, "Commercial")
label("Commercial")
//set(zoningCommercialArea, geometry.area)
print(myZoning + " Area: " + geometry.area)
case zoningType == "Institutional" :
Zoning.Institutional
label("Institutional")
set(myZoning, "Institutional")
//set(zoningInstitutitonalArea, geometry.area)
print(myZoning + " Area: " + geometry.area)
case zoningType == "Mixed_Use" :
Zoning.Mixed_Use
label("Mixed_Use")
set(myZoning, "Mixed_Use")
//set(zoningMixed_UseArea, geometry.area)
print(myZoning + " Area: " + geometry.area)
case zoningType == "Industrial" :
Zoning.Industrial
label("Industrial")
set(myZoning, "Industrial")
//set(zoningIndustrialArea, geometry.area)
print(myZoning + " Area: " + geometry.area)
else : NIL
Reporting -->
report("Area_Residential", zoningResidentialArea)
report("Area_Commercial", zoningCommercialArea)
report("Area_Institutional", zoningInstitutitonalArea)
report("Area.Mixed_Use", zoningMixed_UseArea)
report("Area.Industrial", zoningIndustrialArea)
_______________________________________________________________________________________
Script to convert selected block into static subblock fragments:
'''
Created on 2017-07-26
@author: bcbd
'''
from scripting import *
# get a CityEngine instance
ce = CE()
def convertModelsToShapes():
models = ce.selection()
shapes = ce.convertModelsToShapes(models)
newShapes = ce.separateFaces(shapes)
#cleanupSettings = CleanupShapesSettings()
#LIST ALL CLEANUP SETTINGS TO BE APPLIED
#cleanupSettings.setRemoveCoplanarEdges(True) #DOES THIS EVEN WORK?
#cleanedShapes = ce.cleanupShapes(newShapes,cleanupSettings)
#print "new shape converted from model: "+ce.getName(newShapes[0])
def subblockSelection():
shapes = ce.getObjectsFrom(ce.scene, ce.isShape)
if __name__ == '__main__':
convertModelsToShapes()
pass
_______________________________________________________________________________________
Once excess subblock units are removed (currently manual):
Script to calculate parcel area:
attr parcelArea_Calc = geometry.area
Lot -->
set(parcelArea_Calc, geometry.area)
report("Parcel_Area", parcelArea_Calc)
print("Parcel Area: " + parcelArea_Calc)
color(0.2,0.2,0.2) #color orange if CGA rule is complete
_______________________________________________________________________________________
Script to generate new object attributes (parcel area) for each subblock unit. Not yet complete.
'''
Created on 2017-07-27
@author: bcbd
'''
from scripting import *
# Get a CityEngine instance
ce = CE()
REPORT = ""
def initExport(exportContextOID):
global REPORT
REPORT = "Name,parcelArea\n"
parcelArea()
# Called for each shape after generation.
def finishModel(exportContextOID, shapeOID, modelOID):
shape = Shape(shapeOID)
model = Model(modelOID)
global REPORT
i = 0
totalArea = 0.0
reports = model.getReports()
if 'Parcel_Area' in reports.keys():
for area in reports['Parcel_Area']:
#totalArea +=area
i+=1
REPORT += "%s,%d\n" % (ce.getName(shape), area)
#ce.addAttributeLayer(model, 'parcelArea', area)
else :
REPORT += "%s,%d\n" % (model, 0)
def finishExport(exportContextOID):
filename = ce.toFSPath("models/"+"/reportdata.txt")
FILE = open(filename, "w")
FILE.write(REPORT)
FILE.close()
'''
if(model.getReports().has_key('Parcel_Area')):
l = len(model.getReports()['Parcel_Area'])
for i in range(0,l):
parcelArea = model.getReports()['Parcel_Area']
l = ce.addAttributeLayer('Parcel_Area')
ce.setAttribute(shape, "/ce/rule/Parcel_Area", parcelArea)
# Called after all shapes are generated.
def finishExport(exportContextOID):
ctx = ScriptExportModelSettings(exportContextOID)
parcelArea()
'''
def parcelArea():
objects = ce.getObjectsFrom(ce.selection, ce.isShape)
for o in objects:
ce.setRuleFile(ce.selection(), 'calculateParcelArea.cga') # assign CGA rule calcParcelArea
ce.setStartRule(ce.selection(), 'Lot')
ce.generateModels(ce.selection())
hey,
well, the overall strategy you chose seems feasible, but of course using Python makes things a lot more complex... I'm not sure what you precisely need to reach, but can't you add further CGA attrs for more control deeper down in CGA?
Greets,
matt
Hey Matt,
I understand the Python will make things more complex but I believe it will be necessary to automate many parts of my project. I’m interested in using CityEngine to model scenarios in two ways. Firstly, what metrics we can be reported from building form, such a FAR, passive and active zones, and thermal shape factor. Secondly, how can metrics be used to generate form, such as setting FAR and having CityEngine produce a variety of building forms that would suit the FAR condition. So far the most successful and flexible example that I’ve come across is your tutorial “GFA & FAR Calculations with CityEngine” on Youtube: https://www.youtube.com/watch?v=gnefAyqUQVE&t=2503s&index=3&list=PL34n_T3uZ_mMzFk4aV7Lo6EgxoQGZEcSg
Around 33:15 in the tutorial you made a new parcel polygon beneath your building, applied calcArea.cga, and manually added the parcel area printed to the console as a new object attribute. At 37:15 you mention that “object attributes cannot be set directly with CGA” and that a Python script might be used to keep the value up-to-date. I’ll be working with hundreds of parcels so I want to write a Python script. From what I understand I need to use a Python exporting (reporting) script to report parcel area values, automate the creation of a new object attribute (parcelArea) for each parcel shape and assign parcelArea from the reported value. Is this correct?
Thanks,
Brendan
Hi,
wow, this video is still being watched .. seems I did something right!
re your last question:
yes, that's exactly correct.
thing is:
shape + rule ==> model
this is a one way street: CGA can NOT modify the initial shape or it's initial attribution.
--> to change the attribution of the shape based on metric coming from the model, the only way is using the python script based exporter ..
so I think you're on the right track.
cheers,
matt
------------------------------------------------------------------------------------------------------------------
matthias buehler || ceo & founder || dipl. arch. eth zurich
------------------------------------------------------------------------------------------------------------------
vrbn AG – official esri partner || www.vrbn.io || winterthurerstrasse 53 || 8610 uster || switzerland
+41 78 930 04 07 || matthias.buehler@vrbn.io || @vrbnio @mattb3d
Hi Matt,
Thanks for your help. I was having trouble with the Python export (reporting) module so I ended up writing a script that uses Gauss' polygon area formula/shoelace formula to batch calculate the area of polygons. It uses the ce.getVertices function to retrieve the unstructured vertices list from a polygon [x1, y1, z1, x2, y2, z2,... xN, yN, zN] and converts it to the format [[x1, z1], [x2, z2],...[xN, zN]]. The polygon area is then saved as an object attribute to the original shape.
_____________________________________________________________________________________________
''' Created on 2017-08-03 @author: bcbd SELECT ANY NUMBER OF 2D POLYGONS AND HIT F9 RETURNS THE CALCULATED AREA OF THE POLYGON, STORED AS AN OBJECT ATTRIBUTE ''' from scripting import * # get a CityEngine instance ce = CE() def toShape(): shapes = ce.getObjectsFrom(ce.selection, ce.isShape) for shape in shapes: ce.setRuleFile(ce.selection(), 'calculateParcelArea.cga') # assign CGA rule calcParcelArea ce.setStartRule(ce.selection(), 'Lot') ce.generateModels(ce.selection()) parcelArea = toParcelArea() ce.setAttribute(shape, 'parcelArea', parcelArea) def toParcelArea(): parcels = ce.getObjectsFrom(ce.selection, ce.isShape) for parcel in parcels: #TO CALCULATE PARCEL AREA XYZ_coords = ce.getPosition(parcel) # GETS THE CENTROID COORDINATES FOR EACH PARCEL vertices = ce.getVertices(parcel) #retrieve unstructured list with coordinates of vertices verticesRestructured = restructureList(vertices) parcelArea = polygonArea(verticesRestructured) ce.setAttribute(parcel, 'parcelArea', parcelArea) def restructureList(vertices): results = [] #create empty list to store restructured list [[x1,z1],[x2,z2],[x3,z3]] print "Unstructured list in format: [x1,y1,z1,x2,y2,z2,x3,y3,z3...xN,yN,zN]" print vertices i = 0 #initialize counter for first while loop j = 0 #initialize counter for second while loop coord_XZ = [] #create empty coord pair list to store each coord pair [x1,z1] while i < len(vertices): # while the counter is less than the length of the unstructured list while j <= 2: #while the counter is less than equal to 2 (for unstructured X, Y, and Z coordinates coord_XZ.append(vertices) #adds the value from unstructured list to coord pair list i = i + 1 j = j + 1 j = 0 del coord_XZ[1] #removes the Y (height) coordinate from the vertex results.append(coord_XZ) #appends X,Y coordinates for one vertex coord_XZ = [] #RESET COORD LIST TO NULL doesnt work #print "------" #reset print "Restructured list in format: [[x1,z1], [x2,z2], [x3,z3]...[xN,zN]]" print results print "-----------------" print "The area of the polygon is: " print polygonArea(results) print "-----------------" return results def polygonArea(corners): #GAUSSIAN POLYGON AREA FUNCTION n = len(corners) # of corners area = 0.0 for i in range(n): j = (i + 1) % n area += corners[0] * corners[1] area -= corners [0] * corners[1] area = abs(area) / 2.0 return area if __name__ == '__main__': toParcelArea() pass
Maybe this doesn't work for you, but would it be possible to put a rule on top of your parcel shapes which sets an attribute to store the parcel area and then create the different buildings on that parcel? For example, in the rule, you could split a parcel into 4 lots (or n lots), where each lot gets a building. The buildings could be created using attributes for setbacks or height. These attributes can be set randomly (or by some schema) in the rule for each of the 4 buildings. Then, the user can change the height or setbacks for each of the 4 buildings using local edits if desired. If all 4 buildings are created in the same rule (at the parcel level), then you have access to the parcel area, and you wouldn't need to run python scripts to store reported values in object attributes. With local edits in 2017.0, you will have to create a handle for each attribute you want to be able to change locally. It's just an idea, and I haven't tried this out yet, so maybe there are caveats that I haven't thought about.
Local edits tutorial:
Tutorial 20: Local edits—CityEngine Tutorials | ArcGIS Desktop
As for the Python way, I wrote a response to your question here: