mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-18 03:19:53 +00:00
Minor housekeeping
This commit is contained in:
Binary file not shown.
@@ -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()
|
||||
@@ -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))
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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.
Reference in New Issue
Block a user