Skip to content

Commit

Permalink
utility methods and spatial structure example
Browse files Browse the repository at this point in the history
  • Loading branch information
hlg committed Aug 1, 2011
1 parent 3eebd12 commit c573b42
Show file tree
Hide file tree
Showing 5 changed files with 238 additions and 8 deletions.
9 changes: 7 additions & 2 deletions readme.markdown
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
This is a groovy skript to write IFC geometry from collada files using OpenIfcTools. It does only cover a small subset of the collada specification (basically the part that is used when exporting solid polygonal geometry from Google Sketchup) and it writes only IFC proxy entities. However the heart of the script - IfcBuilder - can be easily used to write arbitrary IFC entities programmatically from arbitrary input data - in a compact and concious fashion. Apart from writing, the combination of Groovy and OpenIfcTools is a very handy way for ad-hoc queries on IFC data. See the the RepresentationCounter example.
This is a growing set of groovy tools to handle [IFC](http://buildingsmart-tech.org/specifications/ifc-overview/) data. GroovyIFC is currently based on the Open IFC Java Toolbox (see prerequisites). The tool set started out as dae2ifc.groovy, a skript to write IFC geometry from collada files.

People asked for it, so I decided to put it out in the wild. Hope it's gonna be useful to anybody. Comes with no warranty.
The heart of the script - IfcBuilder - can be easily used to write arbitrary IFC entities programmatically from arbitrary input data - in a compact and concious fashion. Apart from writing, the combination of Groovy and OpenIfcTools is a very handy way for ad-hoc queries on IFC data. Another use case is quick and dirty programmatic manipulation of IFC files for research or prototyping purposes (see DeepSpatialHierarchy.groovy). A set of utility methods is bundled in IfcModelHelper.

People asked for it, so I decided to put it out in the wild. Hope it'll gonna be useful to anybody. Comes with no warranty.

Prerequisites
=============
Expand Down Expand Up @@ -54,6 +56,9 @@ How to improve
==============
I'd love to see these improvements, but I'm afraid I won't find the time. Don't hesitate to fork the project.

dae2ifc.groovy:
* validate manifold Brep geometry
* optimize ifc file: write each point once only (currently multiple times - once for every polygon)

other use cases:
* sophisticated example for reading/querying IFC (e.g. representation counter)
114 changes: 114 additions & 0 deletions src/groovy/scripts/DeepSpatialHierarchy.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package scripts

import openifctools.com.openifcjavatoolbox.guidcompressor.GuidCompressor
import utils.IfcModelHelper
import openifctools.com.openifcjavatoolbox.ifc2x3tc1.*
import openifctools.com.openifcjavatoolbox.step.parser.util.ProgressEvent

/**
* @author Helga Tauscher http://github.com/hlg
* @author Robert Kühn http://github.com/rokondo
*
* This file is part of Groovy Ifc Tools, which are distributed
* under the terms of the GNU General Public License version 3
* http://github.com/hlg/GroovyIFC
* adds geometrical representation to IfcSpatialStructureElements
+ repesentation is taken from an IfcSpace matched by its @longName attribute
+ see marker.site, marker.building, marker.storey
* groups IfcSpaces to complex IfcSpaces
+ group spaces and singular spaces are matched based on their @longName attribute
+ marker.subStorey<X> (space group) consists of spaces marker.subStoreySpace<X>
* groups IfcBuildingStoreys to complex IfcBuildingStoreys
+ complex storeys are identified by their @longName attribute matching marker.storeyGroup
+ singular storeys are assigned by matching their elevation attribute
* assumes all spaces are direct children of storeys in the spatial structure of the original model
*/

def printProgress = {ProgressEvent event->
if (event.currentState == 1) println event.message
print ((event.currentState % 50) ? '.' : '.\n')
}
def model = new IfcModelHelper(progressListener: printProgress, fileName: args[0])
model.printAsciiTree()

def copyRepresentation(spaceOrigin, spaceDest) {
spaceDest.representation = spaceOrigin.representation
spaceDest.objectPlacement = spaceOrigin.objectPlacement
}

def marker = [site: 'CIB_Site', building: 'CIB_Gebaude', storeyGroup: 'CIB_Milestone', storey: 'Bruttogeschoss|BGF',
subStorey: 'Bauabschnitt', subStoreySpace: '-BA']

def spaces = model.ifcModel.getCollection(IfcSpace.class)
def storeys = model.ifcModel.getCollection(IfcBuildingStorey.class)
def site = model.ifcModel.ifcObjects.find {it instanceof IfcSite}
def building = model.ifcModel.ifcObjects.find {it instanceof IfcBuilding}

spaces.findAll {it.longName.value =~ /$marker.site/}.each { dummySite ->
copyRepresentation(dummySite, site)
model.removeDummySpace(dummySite)
}

spaces.findAll {it.longName.value =~ /$marker.building/}.each {dummyBuilding ->
copyRepresentation(dummyBuilding, building)
model.removeDummySpace(dummyBuilding)
}

spaces.findAll {it.longName.value =~ /$marker.storey/}.each { dummyStorey ->
dummyStorey.Decomposes_Inverse.each { parentStorey -> // STEP modelling WTF? set of size 0 or 1
copyRepresentation(dummyStorey, parentStorey.RelatingObject)
model.removeDummySpace(dummyStorey)
}
}

spaces.findAll {it.longName.toString() =~ /$marker.subStorey/}.each {dummySubStorey ->
dummySubStorey.Decomposes_Inverse.each { parentStorey ->
// TODO: could also be modeled as storey with compositionType PARTIAL
dummySubStorey.compositionType = new IfcElementCompositionEnum('COMPLEX')
def storeySection = dummySubStorey.longName.toString()[-1];
def subSpaces = parentStorey.relatedObjects.findAll {
it.name.value =~ /$marker.subStoreySpace$storeySection/
}
parentStorey.removeAllRelatedObjects(subSpaces)
model.builder.relAggregates {
globalId = GloballyUniqueId(GuidCompressor.newIfcGloballyUniqueId)
relatingObject = dummySubStorey
relatedObjects = subSpaces as Set
}
}
}
def sortedGroupStoreys = spaces.findAll {it.longName.toString() =~ /$marker.storeyGroup/}.collect {dummyStoreyGroup ->
dummyStoreyGroup.Decomposes_Inverse.collect { parentStorey ->
def storeyGroup = model.builder.buildingStorey {
globalId = GloballyUniqueId(GuidCompressor.newIfcGloballyUniqueId)
name = new IfcLabel(marker.storeyGroup, false)
longName = new IfcLabel(marker.storeyGroup, false)
compositionType = new IfcElementCompositionEnum('COMPLEX')
elevation = parentStorey.relatingObject.elevation
}
copyRepresentation(dummyStoreyGroup, storeyGroup)
model.removeDummySpace(dummyStoreyGroup)
storeyGroup
}
}.flatten().sort {it.elevation.value}.reverse()

storeys.groupBy { storey ->
sortedGroupStoreys.find {storey.elevation.value >= it.elevation.value}
}.each { groupDummy, storeysInGroup ->
building.IsDecomposedBy_Inverse.each { parentStorey ->
parentStorey.addRelatedObjects(groupDummy)
parentStorey.removeAllRelatedObjects(storeysInGroup)
}
model.builder.relAggregates {
globalId = GloballyUniqueId(GuidCompressor.newIfcGloballyUniqueId)
relatingObject = groupDummy
relatedObjects = storeysInGroup as Set
}
}

// save and verify the changed model by parsing it again
model.ifcModel.writeStepfile(new File(args[1]))
model.fileName = args[1]
model.printAsciiTree()
println model.ifcModel.numberOfElements
18 changes: 13 additions & 5 deletions src/groovy/dae2ifc.groovy → src/groovy/scripts/dae2ifc.groovy
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
package scripts

import utils.IfcBuilder

/* Copyright (c) 2010-2011 Helga Tauscher
* http://github.com/hlg/GroovyIFC
*
* This file is part of Groovy Ifc Tools, which are distributed
* under the terms of the GNU General Public License version 3
*/
* http://github.com/hlg/GroovyIFC
*
* This file is part of Groovy Ifc Tools, which are distributed
* under the terms of the GNU General Public License version 3
*
* This is a script to write IFC geometry from collada files using OpenIfcTools. It does only cover a small subset of
* the collada specification (basically the part that is used when exporting solid polygonal geometry from Google
* Sketchup) and it writes only IFC proxy entities.
*/

def brepBuilder = new BrepBuilder()
brepBuilder.init()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package groovy
package utils

/* Copyright (c) 2010-2011 Helga Tauscher
* http://github.com/hlg/GroovyIFC
Expand Down
103 changes: 103 additions & 0 deletions src/groovy/utils/IfcModelHelper.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package utils

import openifctools.com.openifcjavatoolbox.guidcompressor.GuidCompressor
import openifctools.com.openifcjavatoolbox.ifc2x3tc1.IfcBuildingStorey
import openifctools.com.openifcjavatoolbox.ifc2x3tc1.IfcProject
import openifctools.com.openifcjavatoolbox.ifcmodel.IfcModel
import openifctools.com.openifcjavatoolbox.step.parser.util.StepParserProgressListener
import openifctools.com.openifcjavatoolbox.step.parser.util.ProgressEvent

class IfcModelHelper {
def ifcModel = new IfcModel()
def builder = new IfcBuilder(model: ifcModel)

void setProgressListener(closure = {ProgressEvent event-> print ((event.currentState % 20) ? '.' : '.\n')}){
ifcModel.addStepParserProgressListener([progressActionPerformed: closure] as StepParserProgressListener)
}

void setFileName(String fileName) {
ifcModel.clearModel()
ifcModel.readStepFile(new File(fileName))
}

def recursePlacement(localPlacement) {
def result = localPlacement.relativePlacement.location.coordinates[2].value
if (localPlacement.placementRelTo) {
result += recursePlacement(localPlacement.placementRelTo)
}
result
}

def fixStoreyAssignment(tolerance) {
def storeys = ifcModel.getCollection(IfcBuildingStorey.class)
def rangeMap = storeys.collectEntries { [it, [it.elevation.value + tolerance[0], it.elevation.value + tolerance[1]]] }
storeys.each { storey ->
println "\n*** $storey $storey.elevation"
storey.containsElements_Inverse.each { rel ->
def matched = [:]
rel.relatedElements.each { element ->
// TODO: assertion (z-axis parallel coordinate systems)
def z = recursePlacement(element.objectPlacement)
def matchingStorey = rangeMap.find { k, v -> v[0] <= z && v[1] >= z }.key
if (matchingStorey && (storey != matchingStorey)) {
println "assigning element with z = $z to $matchingStorey"
matched[element] = matchingStorey
}
}
matched.each { element, newStorey ->
rel.removeRelatedElements(element)
if (!newStorey.containsElements_Inverse) {
builder.relContainedInSpatialStructure {
globalId = GloballyUniqueId(GuidCompressor.newIfcGloballyUniqueId)
relatingStructure = newStorey
relatedElements = [element] as Set
}
} else {
newStorey.containsElements_Inverse.each { newRel ->
newRel.addRelatedElements(element)
}
}
}
}
}

}

def printAsciiTree() {
def spatialRoot = ifcModel.ifcObjects.find {it instanceof IfcProject}
printAsciiTreeRec(spatialRoot, '')
}

def printAsciiTreeRec(spatial, prefix) {
println "$prefix-$spatial $spatial.name - ${spatial.class.name}";
def children = spatial.isDecomposedBy_Inverse?.relatedObjects?.flatten() // TODO: Ifc 2x4 nests_Inverse
if (children) {
if (children.size() > 1) {
children[0..-2].each { space ->
printAsciiTreeRec(space, prefix + ' |')
}
}
printAsciiTreeRec(children[-1], prefix + ' ')
}
}

def removeDummySpace(space) {
// workaround: there is no easy generic way to find referencing relation objects with IFC toolbox
def related = [HasAssignments: 'RelatedObjects', Decomposes: 'RelatedObjects', IsDefinedBy: 'RelatedObjects',
HasAssociations: 'RelatedObjects', ServicedBySystems: 'RelatedBuildings']
def relating = [IsDecomposedBy: 'RelatingObject', ReferencedBy: 'RelatingProduct', ContainsElements: 'RelatingStructure',
ReferencesElements: 'RelatingStructure', HasCoverings: 'RelatingSpace', BoundedBy: 'RelatingSpace']
related.each {k, v ->
space[k + '_Inverse'].collect {it}.each {
it.invokeMethod('remove' + v, space)
if (it[v].isEmpty()) ifcModel.removeIfcObject(it)
}
}
relating.each {k, v ->
def rel = space[k + '_Inverse']
if (rel) ifcModel.removeIfcObject(rel)
}
ifcModel.removeIfcObject(space)
}

}

0 comments on commit c573b42

Please sign in to comment.