Minor housekeeping

This commit is contained in:
Dion Moult
2022-04-25 19:37:03 +10:00
parent aa3325c010
commit 8aff4badf7
12 changed files with 0 additions and 0 deletions
Binary file not shown.
+109
View File
@@ -0,0 +1,109 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
#
# This file is part of BlenderBIM Add-on.
#
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# BlenderBIM Add-on is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell
import ezdxf
class Dxf2Ifc:
def execute(self):
self.create_ifc_file()
doc = ezdxf.readfile("input.dxf")
model = doc.modelspace()
products = []
for entity in model:
print(entity)
if entity.get_mode() == "AcDbPolyFaceMesh":
ifc_faces = []
for face in entity.faces():
ifc_faces.append(
self.file.createIfcFace(
[
self.file.createIfcFaceOuterBound(
self.file.createIfcPolyLoop(
[
self.file.createIfcCartesianPoint((face[index].dxf.location))
for index in range(len(face) - 1)
]
),
True,
)
]
)
)
representation = self.file.createIfcProductDefinitionShape(
None,
None,
[
self.file.createIfcShapeRepresentation(
self.subcontext,
"Body",
"Brep",
[self.file.createIfcFacetedBrep(self.file.createIfcClosedShell(ifc_faces))],
)
],
)
products.append(
self.file.create_entity(
"IfcBuildingElementProxy",
**{
"GlobalId": ifcopenshell.guid.new(),
"Name": entity.dxf.layer,
"ObjectPlacement": self.placement,
"Representation": representation,
}
)
)
else:
print("Not yet implemented")
self.file.createIfcRelContainedInSpatialStructure(
ifcopenshell.guid.new(), None, None, None, products, self.site
)
self.file.write("test.ifc")
def create_ifc_file(self):
self.file = ifcopenshell.file()
units = self.file.createIfcUnitAssignment([self.file.createIfcSIUnit(None, "LENGTHUNIT", None, "METRE")])
self.origin = self.file.createIfcAxis2Placement3D(
self.file.createIfcCartesianPoint((0.0, 0.0, 0.0)),
self.file.createIfcDirection((0.0, 0.0, 1.0)),
self.file.createIfcDirection((1.0, 0.0, 0.0)),
)
self.placement = self.file.createIfcLocalPlacement(None, self.origin)
self.context = self.file.createIfcGeometricRepresentationContext(None, "Model", 3, 1.0e-05, self.origin)
self.subcontext = self.file.createIfcGeometricRepresentationSubcontext(
"Body", "Model", None, None, None, None, self.context, None, "MODEL_VIEW", None
)
self.project = self.file.create_entity(
"IfcProject",
**{
"GlobalId": ifcopenshell.guid.new(),
"Name": "DXF Conversion",
"RepresentationContexts": [self.context],
"UnitsInContext": units,
}
)
self.site = self.file.create_entity(
"IfcSite",
**{"GlobalId": ifcopenshell.guid.new(), "Name": "DXF Conversion Site", "ObjectPlacement": self.placement}
)
self.file.createIfcRelAggregates(ifcopenshell.guid.new(), None, None, None, self.project, [self.site])
dxf2ifc = Dxf2Ifc()
dxf2ifc.execute()
+120
View File
@@ -0,0 +1,120 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
#
# This file is part of BlenderBIM Add-on.
#
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# BlenderBIM Add-on is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import xml.sax, json, copy, pathlib
from bs4 import BeautifulSoup
import sys
sys.setrecursionlimit(100)
class IfcElementHandler(xml.sax.ContentHandler):
def __init__(self):
self.elements = {}
self.current_element_name = None
self.enums = {}
self.current_enum_name = None
self.attribute_stack = []
def startElement(self, name, attrs):
if name == "xs:element" and "substitutionGroup" in attrs:
self.elements[attrs["name"]] = {
"description": self.get_description(attrs["name"]),
"is_abstract": True if "abstract" in attrs else False,
"parent": attrs["substitutionGroup"][len("ifc:") :],
"attributes": [],
}
self.current_element_name = attrs["name"]
elif name == "xs:simpleType" and "name" in attrs and "Enum" in attrs["name"]:
self.current_enum_name = attrs["name"]
self.enums[self.current_enum_name] = []
elif name == "xs:enumeration" and self.current_enum_name:
self.enums[self.current_enum_name].append(attrs["value"].upper())
elif name == "xs:attribute" and self.current_element_name and "name" in attrs and "type" in attrs:
self.elements[self.current_element_name]["attributes"].append(
{
"name": attrs["name"],
"type": attrs["type"].replace("ifc:", ""),
}
)
def endDocument(self):
elements = {}
for name, data in self.elements.items():
for index, attribute in enumerate(data["attributes"]):
data["attributes"][index] = self.resolve_enums(attribute)
for name, data in self.elements.items():
if data["is_abstract"]:
continue
if self.is_an_ifcproduct(data):
self.attribute_stack = []
self.get_parent_attributes(data)
elements[name] = copy.deepcopy(data)
elements[name]["attributes"] = copy.deepcopy(self.attribute_stack)
self.elements = elements
def get_description(self, name):
try:
filenames = pathlib.Path("io_export_ifc/schema/ifc4-add2-tc1/ifc4-add2-tc1/html/schema/").glob(
"**/{}.htm".format(name.lower())
)
for filename in filenames:
with open(filename, "r") as file:
soup = BeautifulSoup(file, "html.parser")
for detail in soup.find_all("details"):
if detail.summary.string == "Entity definition" and detail.p:
return str(detail.p.text.replace("\n", " "))
return None
except:
return None
# print('Failed to get description for {}'.format(name))
return None
def resolve_enums(self, attribute):
if attribute["type"] in self.enums:
attribute["is_enum"] = True
attribute["enum_values"] = self.enums[attribute["type"]]
return attribute
attribute["is_enum"] = False
attribute["enum_values"] = []
return attribute
def get_parent_attributes(self, data):
self.attribute_stack.extend(data["attributes"])
if data["parent"] != "IfcProduct": # For now, we treat attributes above IfcProduct in a special way
self.get_parent_attributes(self.elements[data["parent"]])
def is_an_ifcproduct(self, data):
if data["parent"] == "IfcProduct":
return True
else:
for name, parent_data in self.elements.items():
if name == data["parent"]:
return self.is_an_ifcproduct(parent_data)
return False
xsd_path = "io_export_ifc/schema/IFC4.xsd"
handler = IfcElementHandler()
parser = xml.sax.make_parser()
parser.setContentHandler(handler)
parser.parse(xsd_path)
print(json.dumps(handler.elements, indent=4))
+198
View File
@@ -0,0 +1,198 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
#
# This file is part of BlenderBIM Add-on.
#
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# BlenderBIM Add-on is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy
import uuid
import math
import sys
# sys.path.append('C:\Program Files\Python37\Lib\site-packages')
import lxml
import bspy
from bspy import Gbxml
class GbxmlExporter:
def __init__(self):
self.gbxml = Gbxml()
self.campus = None
def export(self):
print("# Start export")
self.campus = self.gbxml.add_element(self.gbxml.root(), "Campus")
self.campus.set("id", "campus-1")
name = self.gbxml.add_element(self.campus, "Name", "My project")
location = self.gbxml.add_element(self.campus, "Location")
self.gbxml.add_element(location, "ZipcodeOrPostalCode", "G20 0SP")
self.gbxml.add_element(location, "Name", "London/Heathrow")
self.gbxml.add_element(location, "Latitude", "51.480000")
self.gbxml.add_element(location, "Longitude", "-0.450000")
self.gbxml.add_element(location, "Elevation", "24.000000")
building = self.gbxml.add_element(self.campus, "Building")
building.set("id", str(uuid.uuid4()))
building.set("buildingType", "Office")
for object in bpy.context.selected_objects:
self.create_space(object, building)
# hardcoded test
construction = self.gbxml.add_element(self.gbxml.root(), "Construction")
construction.set("id", "defaultconstruction")
self.gbxml.add_element(construction, "Name", "test construction name")
u_value = self.gbxml.add_element(construction, "U-value", "0.42")
u_value.set("unit", "WPerSquareMeterK")
layer = self.gbxml.add_element(construction, "LayerId")
layer.set("layerIdRef", "defaultlayer")
layer = self.gbxml.add_element(self.gbxml.root(), "Layer")
layer.set("id", "defaultlayer")
material = self.gbxml.add_element(layer, "MaterialId")
material.set("materialIdRef", "defaultmaterial")
material = self.gbxml.add_element(self.gbxml.root(), "Material")
material.set("id", "defaultmaterial")
thickness = self.gbxml.add_element(material, "Thickness", "0.2")
thickness.set("unit", "Meters")
self.gbxml.add_element(material, "Name", "test material name")
r_value = self.gbxml.add_element(material, "R-value", "0.13")
r_value.set("unit", "SquareMeterKPerW")
self.append_template("C:/cygwin64/home/moud308/Projects/New Folder/presets/light-schedule.xml")
self.append_template("C:/cygwin64/home/moud308/Projects/New Folder/presets/window-types.xml")
with open("C:/cygwin64/home/moud308/Projects/New Folder/out.xml", "w") as out:
out.write(self.gbxml.xmlstring())
print("# Validation results: {}".format(self.gbxml.validate()))
print("# Finish export")
def append_template(self, file):
parser = lxml.etree.XMLParser(remove_blank_text=True)
template = lxml.etree.parse(file, parser).findall(".")[0]
for child in template.getchildren():
self.gbxml.root().append(child)
def create_space(self, object, building):
space = self.gbxml.add_element(building, "Space")
space.set("id", object.name)
space.set("lightScheduleIdRef", "aim0130") # hardcoded
self.gbxml.add_element(space, "Name", object.name)
light_power_per_area = self.gbxml.add_element(space, "LightPowerPerArea", "5") # hardcoded test
light_power_per_area.set("unit", "WattPerSquareMeter")
calculated_area = 0
shell_geometry = self.gbxml.add_element(space, "ShellGeometry")
shell_geometry.set("id", "shellid")
closed_shell = self.gbxml.add_element(shell_geometry, "ClosedShell")
vertices_in_vg = self.get_vertices_in_vg(object, 0)
for polygon in object.data.polygons:
# First vg is reserved for surfaces
if object.vertex_groups and not self.is_polygon_in_vg(polygon, vertices_in_vg):
continue
calculated_area += polygon.area
self.create_poly_loop(object, polygon, closed_shell)
self.create_space_boundary(object, polygon, space)
self.create_surface(object, polygon)
self.gbxml.add_element(space, "Area", str(calculated_area))
self.gbxml.add_element(space, "Volume", str(self.get_volume(object)))
def get_vertices_in_vg(self, object, vg_index):
return [v.index for v in object.data.vertices if vg_index in [g.group for g in v.groups]]
# Can move into a common Blender helper class?
def is_polygon_in_vg(self, polygon, vertices_in_vg):
for v in polygon.vertices:
if v not in vertices_in_vg:
return False
return True
def create_space_boundary(self, object, polygon, parent):
space_boundary = self.gbxml.add_element(parent, "SpaceBoundary")
space_boundary.set("isSecondLevelBoundary", "true")
space_boundary.set("surfaceIdRef", "surface-{}-{}".format(object.name, polygon.index))
planar_geometry = self.gbxml.add_element(space_boundary, "PlanarGeometry")
self.create_poly_loop(object, polygon, planar_geometry)
def create_surface(self, object, polygon):
surface = self.gbxml.add_element(self.campus, "Surface")
surface.set("id", "surface-{}-{}".format(object.name, polygon.index))
surface.set("surfaceType", "ExteriorWall")
surface.set("constructionIdRef", "defaultconstruction")
adjacent_space_id = self.gbxml.add_element(surface, "AdjacentSpaceId")
adjacent_space_id.set("spaceIdRef", object.name)
rectangular_geometry = self.gbxml.add_element(surface, "RectangularGeometry")
self.gbxml.add_element(
rectangular_geometry, "Azimuth", str(math.degrees(math.atan2(polygon.normal[0], polygon.normal[1])))
)
self.gbxml.add_element(
rectangular_geometry, "Tilt", str(math.degrees(math.atan2(polygon.normal[2], polygon.normal[1])) - 90)
)
planar_geometry = self.gbxml.add_element(surface, "PlanarGeometry")
self.create_poly_loop(object, polygon, planar_geometry)
for vg in object.vertex_groups:
if "/".join(vg.name.split("/")[0:2]) == "openings/{}".format(polygon.index):
vertices_in_vg = self.get_vertices_in_vg(object, vg.index)
for p in object.data.polygons:
if self.is_polygon_in_vg(p, vertices_in_vg):
self.create_opening(object, p, surface)
def create_opening(self, object, polygon, parent):
opening = self.gbxml.add_element(parent, "Opening")
opening.set("id", "opening-{}-{}".format(object.name, polygon.index))
opening.set("windowTypeIdRef", "STD_EX11") # harcoded
opening.set("openingType", "FixedWindow") # hardcoded
planar_geometry = self.gbxml.add_element(opening, "PlanarGeometry")
self.create_poly_loop(object, polygon, planar_geometry)
def create_poly_loop(self, object, polygon, parent):
poly_loop = self.gbxml.add_element(parent, "PolyLoop")
for vertice in polygon.vertices:
cartesian_point = self.gbxml.add_element(poly_loop, "CartesianPoint")
for coord in [0, 1, 2]:
coordinate = self.gbxml.add_element(cartesian_point, "Coordinate")
coordinate.text = str(object.data.vertices[vertice].co[coord])
def get_volume(self, o):
volume = 0
ob_mat = o.matrix_world
me = o.data
me.calc_loop_triangles()
for tf in me.loop_triangles:
tfv = tf.vertices
if len(tf.vertices) == 3:
tf_tris = ((me.vertices[tfv[0]], me.vertices[tfv[1]], me.vertices[tfv[2]]),)
else:
tf_tris = (me.vertices[tfv[0]], me.vertices[tfv[1]], me.vertices[tfv[2]]), (
me.vertices[tfv[2]],
me.vertices[tfv[3]],
me.vertices[tfv[0]],
)
for tf_iter in tf_tris:
v1 = ob_mat @ tf_iter[0].co
v2 = ob_mat @ tf_iter[1].co
v3 = ob_mat @ tf_iter[2].co
volume += v1.dot(v2.cross(v3)) / 6.0
return volume
gbxml_exporter = GbxmlExporter()
gbxml_exporter.export()
@@ -0,0 +1,188 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
#
# This file is part of BlenderBIM Add-on.
#
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# BlenderBIM Add-on is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy
import ifcopenshell
import ifcopenshell.api
import blenderbim.tool as tool
class LibraryGenerator:
def generate(self):
ifcopenshell.api.pre_listeners = {}
ifcopenshell.api.post_listeners = {}
self.file = ifcopenshell.api.run("project.create_file")
self.project = ifcopenshell.api.run(
"root.create_entity", self.file, ifc_class="IfcProject", name="BlenderBIM Demo"
)
self.library = ifcopenshell.api.run(
"root.create_entity", self.file, ifc_class="IfcProjectLibrary", name="BlenderBIM Demo Library"
)
ifcopenshell.api.run(
"project.assign_declaration", self.file, definition=self.library, relating_context=self.project
)
ifcopenshell.api.run("unit.assign_unit", self.file, length={"is_metric": True, "raw": "METERS"})
model = ifcopenshell.api.run("context.add_context", self.file, context_type="Model")
plan = ifcopenshell.api.run("context.add_context", self.file, context_type="Plan")
self.representations = {
"body": ifcopenshell.api.run(
"context.add_context",
self.file,
context_type="Model",
context_identifier="Body",
target_view="MODEL_VIEW",
parent=model,
),
"annotation": ifcopenshell.api.run(
"context.add_context",
self.file,
context_type="Plan",
context_identifier="Annotation",
target_view="PLAN_VIEW",
parent=plan,
),
}
self.material = ifcopenshell.api.run("material.add_material", self.file, name="Unknown")
self.create_layer_type("IfcWallType", "DEMO50", 0.05)
self.create_layer_type("IfcWallType", "DEMO100", 0.1)
self.create_layer_type("IfcWallType", "DEMO200", 0.2)
self.create_layer_type("IfcWallType", "DEMO300", 0.3)
self.create_layer_type("IfcCoveringType", "DEMO10", 0.01)
product = self.create_layer_type("IfcCoveringType", "DEMO20", 0.02)
pset = ifcopenshell.api.run("pset.add_pset", self.file, product=product, name="EPset_Parametric")
ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, properties={"LayerSetDirection": "AXIS2"})
product = self.create_layer_type("IfcCoveringType", "DEMO30", 0.03)
pset = ifcopenshell.api.run("pset.add_pset", self.file, product=product, name="EPset_Parametric")
ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, properties={"LayerSetDirection": "AXIS3"})
self.create_layer_type("IfcRampType", "DEMO200", 0.2)
profile = self.file.create_entity("IfcCircleProfileDef", ProfileType="AREA", Radius=0.3)
self.create_profile_type("IfcPileType", "DEMO1", profile)
self.create_layer_type("IfcSlabType", "DEMO150", 0.2)
self.create_layer_type("IfcSlabType", "DEMO250", 0.3)
profile = self.file.create_entity("IfcRectangleProfileDef", ProfileType="AREA", XDim=0.5, YDim=0.6)
self.create_profile_type("IfcColumnType", "DEMO1", profile)
profile = self.file.create_entity(
"IfcCircleHollowProfileDef", ProfileType="AREA", Radius=0.25, WallThickness=0.005
)
self.create_profile_type("IfcColumnType", "DEMO2", profile)
profile = self.file.create_entity(
"IfcRectangleHollowProfileDef",
ProfileType="AREA",
XDim=0.075,
YDim=0.15,
WallThickness=0.005,
InnerFilletRadius=0.005,
OuterFilletRadius=0.005,
)
self.create_profile_type("IfcColumnType", "DEMO3", profile)
profile = self.file.create_entity(
"IfcIShapeProfileDef",
ProfileName="DEMO-I",
ProfileType="AREA",
OverallWidth=0.1,
OverallDepth=0.2,
WebThickness=0.005,
FlangeThickness=0.01,
FilletRadius=0.005,
)
self.create_profile_type("IfcBeamType", "DEMO1", profile)
profile = self.file.create_entity(
"IfcCShapeProfileDef",
ProfileName="DEMO-C",
ProfileType="AREA",
Depth=0.2,
Width=0.1,
WallThickness=0.0015,
Girth=0.03,
InternalFilletRadius=0.005,
)
self.create_profile_type("IfcBeamType", "DEMO2", profile)
self.create_type("IfcWindowType", "DEMO1", {"body": "Window", "annotation": "Window-Annotation"})
self.create_type("IfcDoorType", "DEMO1", {"body": "Door", "annotation": "Door-Annotation"})
self.create_type("IfcFurnitureType", "BUNNY", {"body": "Bunny", "annotation": "Bunny-Annotation"})
self.file.write("blenderbim-demo-library.ifc")
def create_layer_type(self, ifc_class, name, thickness):
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class=ifc_class, name=name)
rel = ifcopenshell.api.run("material.assign_material", self.file, product=element, type="IfcMaterialLayerSet")
layer_set = rel.RelatingMaterial
layer = ifcopenshell.api.run("material.add_layer", self.file, layer_set=layer_set, material=self.material)
layer.LayerThickness = thickness
ifcopenshell.api.run("project.assign_declaration", self.file, definition=element, relating_context=self.library)
return element
def create_profile_type(self, ifc_class, name, profile):
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class=ifc_class, name=name)
rel = ifcopenshell.api.run("material.assign_material", self.file, product=element, type="IfcMaterialProfileSet")
profile_set = rel.RelatingMaterial
material_profile = ifcopenshell.api.run(
"material.add_profile", self.file, profile_set=profile_set, material=self.material
)
ifcopenshell.api.run("material.assign_profile", self.file, material_profile=material_profile, profile=profile)
ifcopenshell.api.run("project.assign_declaration", self.file, definition=element, relating_context=self.library)
def create_type(self, ifc_class, name, representations):
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class=ifc_class, name=name)
for rep_name, obj_name in representations.items():
obj = bpy.data.objects.get(obj_name)
representation = ifcopenshell.api.run(
"geometry.add_representation",
self.file,
context=self.representations[rep_name],
blender_object=obj,
geometry=obj.data,
total_items=max(1, len(obj.material_slots)),
)
styles = []
for slot in obj.material_slots:
style = ifcopenshell.api.run("style.add_style", self.file, name=slot.material.name)
ifcopenshell.api.run(
"style.add_surface_style",
self.file,
style=style,
ifc_class="IfcSurfaceStyleRendering",
attributes=tool.Style.get_surface_rendering_attributes(slot.material),
)
styles.append(style)
if styles:
ifcopenshell.api.run(
"style.assign_representation_styles", self.file, shape_representation=representation, styles=styles
)
ifcopenshell.api.run(
"geometry.assign_representation", self.file, product=element, representation=representation
)
ifcopenshell.api.run("project.assign_declaration", self.file, definition=element, relating_context=self.library)
LibraryGenerator().generate()
@@ -0,0 +1,104 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
#
# This file is part of BlenderBIM Add-on.
#
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# BlenderBIM Add-on is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy
import ifcopenshell
import ifcopenshell.api
import blenderbim.tool as tool
class LibraryGenerator:
def generate(self):
ifcopenshell.api.pre_listeners = {}
ifcopenshell.api.post_listeners = {}
self.file = ifcopenshell.api.run("project.create_file")
self.project = ifcopenshell.api.run(
"root.create_entity", self.file, ifc_class="IfcProject", name="BlenderBIM Demo"
)
self.library = ifcopenshell.api.run(
"root.create_entity", self.file, ifc_class="IfcProjectLibrary", name="BlenderBIM Demo Library"
)
ifcopenshell.api.run(
"project.assign_declaration", self.file, definition=self.library, relating_context=self.library
)
ifcopenshell.api.run("unit.assign_unit", self.file, length={"is_metric": True, "raw": "METERS"})
model = ifcopenshell.api.run("context.add_context", self.file, context_type="Model")
self.representations = {
"body": ifcopenshell.api.run(
"context.add_context",
self.file,
context_type="Model",
context_identifier="Body",
target_view="MODEL_VIEW",
parent=model,
),
"clearance": ifcopenshell.api.run(
"context.add_context",
self.file,
context_type="Model",
context_identifier="Clearance",
target_view="MODEL_VIEW",
parent=model,
),
}
self.create_type("IfcBuildingElementProxyType", "Site Shed 3x6m", {"body": "Site Shed 3x6m"})
self.create_type("IfcBuildingElementProxyType", "Site Shed 3x12m", {"body": "Site Shed 3x12m"})
self.create_type(
"IfcBuildingElementProxyType",
"Mobile Crane 50T",
{"body": "Mobile Crane 50T", "clearance": "Mobile Crane 50T - Clearance"},
)
self.file.write("blenderbim-site-library.ifc")
def create_type(self, ifc_class, name, representations):
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class=ifc_class, name=name)
for rep_name, obj_name in representations.items():
obj = bpy.data.objects.get(obj_name)
representation = ifcopenshell.api.run(
"geometry.add_representation",
self.file,
context=self.representations[rep_name],
blender_object=obj,
geometry=obj.data,
total_items=max(1, len(obj.material_slots)),
)
styles = []
for slot in obj.material_slots:
style = ifcopenshell.api.run("style.add_style", self.file, name=slot.material.name)
ifcopenshell.api.run(
"style.add_surface_style",
self.file,
style=style,
ifc_class="IfcSurfaceStyleRendering",
attributes=tool.Style.get_surface_rendering_attributes(slot.material),
)
styles.append(style)
if styles:
ifcopenshell.api.run(
"style.assign_representation_styles", self.file, shape_representation=representation, styles=styles
)
ifcopenshell.api.run(
"geometry.assign_representation", self.file, product=element, representation=representation
)
ifcopenshell.api.run("project.assign_declaration", self.file, definition=element, relating_context=self.library)
LibraryGenerator().generate()
@@ -0,0 +1,122 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
#
# This file is part of BlenderBIM Add-on.
#
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# BlenderBIM Add-on is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
# See bug #1300. Dodgy quick results that we should rebuild later.
import json
import ifcopenshell
import ifcopenshell.util.schema
filepath = "IFC4.exp"
schema2x3 = ifcopenshell.ifcopenshell_wrapper.schema_by_name("IFC2X3")
schema4 = ifcopenshell.ifcopenshell_wrapper.schema_by_name("IFC4")
entity_to_type_map = {}
entity = None
is_in_where_rule = False
with open(filepath) as fp:
line = fp.readline()
while line:
if "ENTITY " in line:
entity = line[len("ENTITY ") : -1]
elif "END_ENTITY" in line:
is_in_where_rule = False
elif entity and ("CorrectTypeAssigned" in line or "CorrectStyleAssigned" in line):
is_in_where_rule = True
elif entity and is_in_where_rule and "IN TYPEOF" in line and "RelatingType" in line:
type_class = line.split("'")[1].split(".")[1]
if type_class == "IFCTRANFORMERTYPE":
# Fix typo in schema
type_class = "IFCTRANSFORMERTYPE"
type_class = schema4.declaration_by_name(type_class).name()
entity_to_type_map.setdefault(entity, []).append(type_class)
line = fp.readline()
# This type of hacky express parsing doesn't accomodate subtypes, so...
def get_inherited_map(declaration):
if not ifcopenshell.util.schema.is_a(declaration, "IfcObject"):
return
if declaration.supertype().name() in entity_to_type_map:
return entity_to_type_map[declaration.supertype().name()]
return get_inherited_map(declaration.supertype())
for declaration in schema4.declarations():
if not hasattr(declaration, "supertype"):
continue
if not ifcopenshell.util.schema.is_a(declaration, "IfcObject"):
continue
if declaration.is_abstract():
continue
if declaration.name() in entity_to_type_map:
continue
inherited_map = get_inherited_map(declaration)
if inherited_map:
entity_to_type_map[declaration.name()] = inherited_map
type_to_entity_map = {value: [key] for key in entity_to_type_map for value in entity_to_type_map[key]}
# IFC2X3 doesn't seem to define this in EXPRESS, so let's just guess
entity_to_type_map2x3 = {}
def guess_type_declaration(declaration):
if not ifcopenshell.util.schema.is_a(declaration, "IfcObject"):
return
try:
type_declaration = schema2x3.declaration_by_name(declaration.name() + "Type")
return type_declaration
except:
pass
try:
type_declaration = schema2x3.declaration_by_name(declaration.name() + "Style")
return type_declaration
except:
pass
return guess_type_declaration(declaration.supertype())
for declaration in schema2x3.declarations():
if not hasattr(declaration, "supertype"):
continue
type_declaration = guess_type_declaration(declaration)
if not type_declaration:
continue
if not type_declaration.is_abstract():
entity_to_type_map2x3.setdefault(declaration.name(), []).append(type_declaration.name())
for subtype in type_declaration.subtypes():
if not subtype.is_abstract():
entity_to_type_map2x3.setdefault(declaration.name(), []).append(subtype.name())
type_to_entity_map2x3 = {value: [key] for key in entity_to_type_map2x3 for value in entity_to_type_map2x3[key]}
with open("entity_to_type_map_4.json", "w") as f:
json.dump(entity_to_type_map, f, indent=4)
with open("entity_to_type_map_2x3.json", "w") as f:
json.dump(entity_to_type_map2x3, f, indent=4)
+206
View File
@@ -0,0 +1,206 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
#
# This file is part of BlenderBIM Add-on.
#
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# BlenderBIM Add-on is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
"""This script converts the computer iterpretable listing ifcXML XSD into a JSON file"""
import xml.etree.ElementTree as ET
import collections
import json
class IFC4Extractor:
def __init__(self, xsd_file):
self.xsd_file = xsd_file
tree = ET.parse(self.xsd_file)
self.root = tree.getroot()
self.ns = {"xs": "http://www.w3.org/2001/XMLSchema"}
self.elements = {}
self.filters = []
self.filtered_elements = {}
def extract(self):
for element in self.root.findall("xs:element", self.ns):
print("Processing {}".format(element.attrib["name"]))
if not "substitutionGroup" in element.attrib or self.is_descendant_from_class(element, "uos"):
continue
data = {
"is_abstract": self.is_abstract(element),
"parent": element.attrib["substitutionGroup"].replace("ifc:", ""),
"attributes": self.get_attributes(element),
"complex_attributes": self.get_complex_attributes(element),
}
self.elements[element.attrib["name"]] = data
for filter in self.filters:
if self.is_descendant_from_class(element, filter) and not data["is_abstract"]:
self.filtered_elements.setdefault(filter, {})[element.attrib["name"]] = data
def export(self, filename):
final = {}
for filter in self.filters:
final.update(self.filtered_elements[filter])
with open(filename, "w") as file:
file.write(json.dumps(collections.OrderedDict(sorted(final.items())), indent=4))
def is_descendant_from_class(self, element, class_name):
if element is None or "substitutionGroup" not in element.attrib or "type" not in element.attrib:
return False
if element.attrib["substitutionGroup"] == "ifc:{}".format(class_name) or element.attrib[
"type"
] == "ifc:{}".format(class_name):
return True
return self.is_descendant_from_class(self.get_parent_element(element), class_name)
def is_abstract(self, element):
return True if "abstract" in element.attrib else False
def get_attributes(self, element, attributes=None):
if attributes is None:
attributes = []
if element.attrib["substitutionGroup"] != self.get_ifcroot_parent_name():
attributes = self.get_attributes(self.get_parent_element(element), attributes)
for attribute in self.root.findall(self.get_attribute_xpath(element), self.ns):
try:
attributes.append(
{
"name": attribute.attrib["name"],
"type": attribute.attrib["type"].replace("ifc:", ""),
"is_enum": self.is_enum(attribute),
"enum_values": self.get_enum_values(attribute),
}
)
except KeyError as e:
print("Attribute {} is missing key {}".format(attribute.attrib, e))
return attributes
def get_attribute_xpath(self, element):
return "./xs:complexType[@name='{}']/xs:complexContent/xs:extension/xs:attribute[@name][@type]".format(
element.attrib["name"]
)
def get_ifcroot_parent_name(self):
return "ifc:Entity"
def get_complex_attributes(self, element, attributes=None):
if attributes is None:
attributes = []
if element.attrib["substitutionGroup"] != self.get_ifcroot_parent_name():
attributes = self.get_complex_attributes(self.get_parent_element(element), attributes)
for attribute in self.root.findall(
"./xs:complexType[@name='{}']/xs:complexContent/xs:extension/xs:sequence/xs:element[@name]".format(
element.attrib["name"]
),
self.ns,
):
if "type" in attribute.attrib:
attributes.append(
{
"name": attribute.attrib["name"],
"type": attribute.attrib["type"].replace("ifc:", ""),
"is_select": False,
"select_types": [],
}
)
else:
type_element = attribute.find("./xs:complexType/xs:sequence/xs:element[@ref]", self.ns)
is_select = False
select_types = []
if not type_element:
# Handle select (i.e. group) attributes
type_element = attribute.find("./xs:complexType/xs:group", self.ns)
if type_element is not None:
is_select = True
select_types = [
e.attrib["ref"].replace("ifc:", "").replace("-wrapper", "")
for e in self.root.findall(
"./xs:group[@name='{}']/xs:choice/xs:element[@ref]".format(
type_element.attrib["ref"].replace("ifc:", "")
),
self.ns,
)
]
if type_element is not None:
attributes.append(
{
"name": attribute.attrib["name"],
"type": type_element.attrib["ref"].replace("ifc:", ""),
"is_select": is_select,
"select_types": select_types,
}
)
return attributes
def get_parent_element(self, element):
return self.root.find(
"./xs:element[@name='{}']".format(element.attrib["substitutionGroup"].replace("ifc:", "")), self.ns
)
def is_enum(self, attribute):
return "Enum" in attribute.attrib["type"]
def get_enum_values(self, attribute):
if not self.is_enum(attribute):
return []
values = []
for enumeration in self.root.findall(
"./xs:simpleType[@name='{}']/xs:restriction/xs:enumeration".format(
attribute.attrib["type"].replace("ifc:", "")
),
self.ns,
):
values.append(enumeration.attrib["value"].upper())
return values
def is_ifc_version(self, version):
return version in self.xsd_file
class IFC2X3Extractor(IFC4Extractor):
# IFC2X3 seems to store regular attributes where IFC4 stores complex attributes
def get_attribute_xpath(self, element):
return "./xs:complexType[@name='{}']/xs:complexContent/xs:extension/xs:sequence/xs:element[@name]".format(
element.attrib["name"]
)
def get_ifcroot_parent_name(self):
return "ex:Entity"
# IFC2X3 does not seem to store complex attributes in the XSD file
def get_complex_attributes(self, element, attributes=None):
return []
filename_filters = {
"IfcContext_IFC4.json": ["IfcContext"],
"IfcElement_IFC4.json": ["IfcElement"],
"IfcSpatialElement_IFC4.json": ["IfcSpatialElement"],
"IfcGroup_IFC4.json": ["IfcGroup"],
"IfcStructural_IFC4.json": ["IfcStructuralActivity", "IfcStructuralItem"],
"IfcMaterialDefinition_IFC4.json": ["IfcMaterialDefinition"],
"IfcParameterizedProfileDef_IFC4.json": ["IfcParameterizedProfileDef"],
"IfcBoundaryCondition_IFC4.json": ["IfcBoundaryCondition"],
"IfcElementType_IFC4.json": ["IfcElementType", "IfcSpatialElementType"],
"IfcAnnotation_IFC4.json": ["IfcAnnotation"],
"IfcPositioningElement_IFC4.json": ["IfcGrid", "IfcGridAxis"], # IfcPositioningElement in the future
}
for filename, filters in filename_filters.items():
extractor = IFC4Extractor("IFC4_ADD2.xsd")
extractor.filters = filters
# extractor = IFC2X3Extractor("IFC2X3.xsd")
extractor.extract()
extractor.export(filename)
+90
View File
@@ -0,0 +1,90 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
#
# This file is part of BlenderBIM Add-on.
#
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# BlenderBIM Add-on is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import os
import re
import html
import json
from pathlib import Path
import ifcopenshell
class Describer:
def describe(self):
# BuildingSMART does not provide a computer interpretable set of
# descriptions. They provide HTML docs, which contained malformed /
# invalid HTML. Therefore, this dodgy hack was written.
schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name("IFC4")
self.html_sources = {}
self.get_html_sources()
self.entity_descriptions = {}
self.enum_descriptions = {}
for entity in schema.entities():
name = entity.name()
self.get_entity_description(name)
for attribute in entity.attributes():
try:
attribute = attribute.type_of_attribute().declared_type()
except:
continue
if isinstance(attribute, str):
continue
if "Enum" in attribute.name() and "Enumeration" not in attribute.name():
self.get_enum_descriptions(attribute)
with open("entity_descriptions.json", "w") as f:
f.write(json.dumps(self.entity_descriptions, indent=4))
with open("enum_descriptions.json", "w") as f:
f.write(json.dumps(self.enum_descriptions, indent=4))
def get_html_sources(self):
html_dir = "/home/dion/Projects/IfcOpenShell/src/blenderbim/descriptions/IFC4_3/RC1/HTML"
for filename in Path(html_dir).rglob("*.htm"):
if "lexical" not in str(filename):
continue
name = os.path.basename(filename)[0:-4]
self.html_sources[name] = filename
def get_entity_description(self, name):
if name.lower() not in self.html_sources:
return
with open(self.html_sources[name.lower()]) as f:
for line in f:
if "Entity definition" in line:
self.entity_descriptions[name] = html.unescape(
re.sub("<.*?>", "", line.strip().replace("Entity definition", ""))
)
def get_enum_descriptions(self, enum):
if enum.name().lower() not in self.html_sources:
return
print(enum.name())
print(dir(enum))
for item in enum.enumeration_items():
with open(self.html_sources[enum.name().lower()]) as f:
for line in f:
if "<td>" + item + "</td>" in line:
self.enum_descriptions.setdefault(enum.name(), {})[item] = html.unescape(
re.sub("<.*?>", "", line.strip().replace(item, ""))
)
describer = Describer()
describer.describe()
@@ -0,0 +1,43 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
#
# This file is part of BlenderBIM Add-on.
#
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# BlenderBIM Add-on is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy
import bmesh
import pprint
material_volumes = {}
for obj in bpy.context.selected_objects:
if not obj.data or not isinstance(obj.data, bpy.types.Mesh):
continue
material_polygons = {}
for polygon in obj.data.polygons:
material_polygons.setdefault(polygon.material_index, []).append(polygon.vertices)
verts = [v.co for v in obj.data.vertices]
for index, polygons in material_polygons.items():
mesh = bpy.data.meshes.new("Temporary Mesh")
mesh.from_pydata(verts, [], polygons)
bm = bmesh.new()
bm.from_mesh(mesh)
material_name = obj.data.materials[index].name
material_volumes.setdefault(material_name, 0)
material_volumes[material_name] += bm.calc_volume()
bm.free()
bpy.data.meshes.remove(mesh)
pprint.pprint(material_volumes)
+125
View File
@@ -0,0 +1,125 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# This file is part of BlenderBIM Add-on.
#
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# BlenderBIM Add-on is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
# This can be packaged with `pyinstaller --onefile --hidden-import numpy --collect-all ifcopenshell --clean obj2ifc.py`
import argparse
import pywavefront
import ifcopenshell
import ifcopenshell.api
import ifcopenshell.api.owner.settings
from pathlib import Path
class Obj2Ifc:
def __init__(self, path):
self.path = path
def execute(self):
self.basename = Path(self.path).stem
self.create_ifc_file()
self.scene = pywavefront.Wavefront(self.path, create_materials=True, collect_faces=True)
for mesh in self.scene.mesh_list:
ifc_faces = []
for face in mesh.faces:
ifc_faces.append(
self.file.createIfcFace(
[
self.file.createIfcFaceOuterBound(
self.file.createIfcPolyLoop(
[self.file.createIfcCartesianPoint(self.scene.vertices[index]) for index in face]
),
True,
)
]
)
)
representation = self.file.createIfcProductDefinitionShape(
None,
None,
[
self.file.createIfcShapeRepresentation(
self.context,
"Body",
"Brep",
[self.file.createIfcFacetedBrep(self.file.createIfcClosedShell(ifc_faces))],
)
],
)
product = self.file.create_entity(
"IfcBuildingElementProxy",
**{
"GlobalId": ifcopenshell.guid.new(),
"Name": mesh.name or self.basename,
"ObjectPlacement": self.placement,
"Representation": representation,
}
)
ifcopenshell.api.run("spatial.assign_container", self.file, product=product, relating_structure=self.storey)
self.file.write(self.path.replace(".obj", ".ifc"))
def create_ifc_file(self):
self.file = ifcopenshell.api.run("project.create_file", version="IFC2X3")
person = ifcopenshell.api.run("owner.add_person", self.file)
person.Id = person.GivenName = None
person.FamilyName = "user"
org = ifcopenshell.api.run("owner.add_organisation", self.file)
org.Id = None
org.Name = "template"
user = ifcopenshell.api.run("owner.add_person_and_organisation", self.file, person=person, organisation=org)
application = ifcopenshell.api.run("owner.add_application", self.file)
ifcopenshell.api.owner.settings.get_user = lambda ifc: user
ifcopenshell.api.owner.settings.get_application = lambda ifc: application
project = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcProject", name=self.basename)
lengthunit = ifcopenshell.api.run("unit.add_si_unit", self.file, unit_type="LENGTHUNIT", name="METRE")
ifcopenshell.api.run("unit.assign_unit", self.file, units=[lengthunit])
model = ifcopenshell.api.run("context.add_context", self.file, context_type="Model")
self.context = ifcopenshell.api.run(
"context.add_context",
self.file,
context_type="Model",
context_identifier="Body",
target_view="MODEL_VIEW",
parent=model,
)
site = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcSite", name="My Site")
building = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcBuilding", name="My Building")
self.storey = ifcopenshell.api.run(
"root.create_entity", self.file, ifc_class="IfcBuildingStorey", name="My Storey"
)
ifcopenshell.api.run("aggregate.assign_object", self.file, product=site, relating_object=project)
ifcopenshell.api.run("aggregate.assign_object", self.file, product=building, relating_object=site)
ifcopenshell.api.run("aggregate.assign_object", self.file, product=self.storey, relating_object=building)
self.origin = self.file.createIfcAxis2Placement3D(
self.file.createIfcCartesianPoint((0.0, 0.0, 0.0)),
self.file.createIfcDirection((0.0, 0.0, 1.0)),
self.file.createIfcDirection((1.0, 0.0, 0.0)),
)
self.placement = self.file.createIfcLocalPlacement(None, self.origin)
self.history = ifcopenshell.api.run("owner.create_owner_history", self.file)
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Converts an OBJ to an IFC")
parser.add_argument("obj", type=str, help="The OBJ file")
args = parser.parse_args()
obj2ifc = Obj2Ifc(args.obj)
obj2ifc.execute()
Binary file not shown.