Splitting imported models

07-05-2012 02:22 PM
New Contributor II
Good evening,

In order to streamline the process of calculating program at a project-wide level, we would like to be able to import models generated with other software (Rhino, for example) and use CityEngine to quickly come up with GFA by program type, FAR, etc.

To do this, we wrote a simple script that imports a model as .OBJ, splits that model into "floors" based on an input floor height, reports floor areas, and displays the models split into floors and colored by land use. No problem, right?

This works like a charm as long as you deal with small models - one block at a time, simple geometry, etc. But as it becomes more complicated, the script falls apart for some reason. Here are some screen shots from my own tests showing this problem.

1) Original Rhino file for the test...I've added 4 different model "types" to test out.

2) Single building - works great!

3) Two buildings - also works great!

4) Simple block - 8 different buildings and still working!

5) Complex block - circles, funky angles, and still working!

Some examples of this NOT working following the break.....5 images is the max per post....
0 Kudos
6 Replies
by Anonymous User
Not applicable
Original User: Daniel.OShaughnessy

6) All 4 of the previous types combined...works only if you export the complex block from Rhino with the lowest number of faces (turning the circle into more of a hexagon)

7) All 4 of the previous types combined...and exported as more detailed geometries. Here the buildings are only split into floors above a certain vertical level, with the middle portion left empty.

😎 And finally, a small "city" of blocks....notice that now only the very tops of the buildings are split into floors, with everything else left empty.

In all cases, the base retail floor is still generated.

This is not necessarily just a display issue. If you look closely at the reporting, these floors are actually not being generated and reported for some reason.

So I guess I'm wondering: is this a bug with my script? Is this a memory issue with CityEngine? Are there any workarounds to get this moving in a better direction? Any help is much appreciated.

I am happy to post the script, but since much of it is not my own work, I'd prefer just to send it to individuals who might want to take a look. Thanks!
0 Kudos
Frequent Contributor
hi Dan !

is your 'block' model 1 mesh ? ( coming from 1 obj )?

can you export objs from Rhino without triangles ( using quadrangulated geometry, not triangulated ) ?

circular forms can easily produce errors when splitting, thus any round form should be as simple as possible ( only few steps per arc )

can you post the CGA code ? ( the splitting portion )

depending on your inputs, I can help you to adapt the strategy to have greater success. 🙂
0 Kudos
by Anonymous User
Not applicable
Original User: Daniel.OShaughnessy

1) Yes, each block is 1 OBJ file

2) Not from Rhino (they always get triangulated). You can avoid the triangulation by using 3d Max instead, but a quick test of that reveals the same problem

3) OK

4) Here is the code (sorry it's kind of long). The model import is in the InsertModel rule, while the splitting occurs between Mass and Floors

@Group("Model",0) @Order(1)
attr Model_file = "my.obj"
@Group("Model",0) @Order(2) @Range("Scale to Parcel Size","Scale to Parcel and Set Height","Set Height","Do not scale (model in realworld dimensions)")
attr Model_Size = "Scale to Parcel and Set Height"
@Group("Model",0) @Order(3)
attr Model_Height = 50
@Group("Model",0) @Order(4) @Range("0","90","180","270")
attr Model_Rotation = 0

@Group("Lot Attributes",1)
attr distanceStreet = 0   // Lot setback

attr Block_Area = 0

Block -->
 set(Block_Area,geometry.area)   //PM: We have to store this value to calculate the FAR below
 report("Block Area",Block_Area)  //PM: Plus we write it out for the report
 comp(f) {inside: alignScopeToAxes InsertModel | border: O}
InsertModel -->
 // examples of different insert 'modes'
 case Model_Size == "Scale to Parcel Size":
  i(Model_file) // the insert operation fits in the model automatically and scales the height accordingly
  r(scopeCenter, 0, Model_Rotation, 0)
 case Model_Size == "Scale to Parcel and Set Height":
  r(scopeCenter, 0, Model_Rotation, 0)
 case Model_Size == "Set Target Height":
  s(0,Model_Height,0) center(xz)  // in case only height is set..
  i(Model_file)      // ..the insert operation scales the width and depth according to the given height
  r(scopeCenter, 0, Model_Rotation, 0)
  s(0,0,0) center(xyz)  // in case size is 0..
  r(scopeCenter, 0, Model_Rotation, 0)
  i(Model_file)     // ..the insert operation uses the realworld dimensions


@Group("Land Use Control",5) @Order(1) @Range("Residential","Commercial","Mixed-Use","Industrial", "Retail","Government","Cultural","Education","Hotel") //PM: @Range annotation creates drop-down
attr Land_Use = 10%: "Residential" 50%: "Commercial" 30%: "Mixed-Use" else: "Industrial" //PM: These attribute is usually controlled by image map
@Group("Building Attributes") @Order(2)
attr retailheight = 5 

@Group("Building Attributes") @Order(1)
attr floorheight = 4 

@Group("Visualization") @Range("Mass_white","Mass_color","Floors","MassAndFloors")
attr vizMode = "MassAndFloors"

@Group("Land Use Control") @Order(5) @Description("% of commercial floors in a mixed-use residential building")
attr mixedUsePercentage = 0.4 //PM: New attribute

@Group("Building Attributes")
attr getNumberOfRetailFloors =
 case Land_Use == "Residential" : 0
 case Land_Use == "Industrial" : 0
 case Land_Use == "Education" : 0
 case Land_Use == "Cultural"  : 0
 case Land_Use == "Commercial" : 1
 case Land_Use == "Mixed-Use" : 1
 else       : floor(rand(1))

getColor(myLandUse) =
 case myLandUse == "Commercial" : "#DD7300"
 case myLandUse == "Retail"  : "#D63200"
 case myLandUse == "Residential" : "#FFD255"
 case myLandUse == "Industrial" : "#720000"
 case myLandUse == "Government" : "#1887B2"
 case myLandUse == "Cultural" : "#46B8E6"
 case myLandUse == "Education" : "#8D7CC1"
 case myLandUse == "Hotel"  : "#D8ABC8"
 else       : "#888888"

calcNumberOfFloors =

//PM: handles mixed use and adds retail floors 
Mass -->
 case Land_Use == "Mixed-Use":
  split(y){ retailheight * getNumberOfRetailFloors: Floors("Retail") 
    | floorheight * mixedUsePercentage*calcNumberOfFloors : Floors("Commercial")
    | ~1: Floors("Residential") }
  split(y){ retailheight * getNumberOfRetailFloors: Floors("Retail") 
    | ~1: Floors(Land_Use) }
// PM: sets the color according the landuse-parameter and splits them in single floors using a repeat split 
Floors(myLandUse) -->
 split(y){ ~floorheight: comp(f){ bottom: FloorArea(myLandUse) } }*
FloorArea(myLandUse) -->
 report("GFA."+myLandUse,geometry.area)  //PM: the default staticts show the sum, so we just have to call it for each floor 
 report("FAR",geometry.area/Block_Area) //PM: same here (we just do the division for each floor instead for the whole sum at the end)

O -->

# Visualization

MassViz(myLandUse) --> //DOS: the idea is to have viz modes similar to those in the reporting tutorial
 case vizMode == "Mass_white" : Mass. //DOS: solid white mass
 case vizMode == "Mass_color" :    //DOS: mass colored by land use
  color(getColor(myLandUse)) Mass. 
 case vizMode == "MassAndFloors" :  //DOS: transparent mass with floors
  set(material.color.a, 0.2) Mass.
 else: NIL   //DOS: if set to "Floors" there should be no mass shown at all, only floors

FloorViz(myLandUse) -->
 case vizMode == "Mass_white" || vizMode == "Mass_color" : NIL
  color(getColor(myLandUse)) Floors.
0 Kudos
Frequent Contributor
hi !

this is actually tricky because each block is split as a whole object. better would be to have individual objects. currently, there's no way to split an obj asset into it's submeshes. I assume this produces the problems.

imagine if you have 3 buildings with 5, 15 and 25 floors --> you'd want to split each building into as many full floors as possible. but if your input is just 1 assets, the assumption is that you want to split the full object into as many floors as possible, basically ignoring the 'mathematical topology' and it's 'architectural semantics' you're interested in.

ok, the fewer polygons the better due to any precision issues which usually happen with computers.

see 2] 🙂

thanks for the code, but I guess the trick is mentioned in 1]. You're also noticing that behavior the fewer buildings you have.

--> try to work with 1 obj asset for each building, not for each block.
Maybe one of your co-workers knows a little Rhino-Script and Python in CityEngine, which may speed up the workflow with new versions / large datasets.

let me know if this all makes sense.
0 Kudos
by Anonymous User
Not applicable
Original User: Daniel.OShaughnessy


Yes, thanks for the explanation. All makes good sense.

Unfortunately, I was hoping to be able to import an entire model en masse. Despite the issue of buildings not being split into even floors, I was hoping this could give us a quick snapsnot of where we were programmatically.

It seems like Rhinoscript might be a good way to export buildings individually as OBJs. Then could we import all the buildings at once into CityEngine, or would it require knowledge of Python?
0 Kudos
Frequent Contributor
hey Dan !

sorry for the late reply, I was in holiday.

maybe it's easier if I can communicate directly with you or one of your team who knows Rhino well.

there's so many dependencies involved that it'd be easiest to have a quick video chat.

I'm not sure if you have my mail address ? otherwise, just PM me.

let me know ! 🙂
0 Kudos