mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-09 17:31:45 +00:00
Merge branch 'v0.7.0' into covering_tool
This commit is contained in:
@@ -58,6 +58,7 @@ option(BUILD_PACKAGE "" OFF)
|
||||
option(COLLADA_SUPPORT "Build IfcConvert with COLLADA support (requires OpenCOLLADA)." ON)
|
||||
option(GLTF_SUPPORT "Build IfcConvert with glTF support (requires json.hpp)." OFF)
|
||||
option(HDF5_SUPPORT "Enable HDF5 support (requires HDF5, zlib)" ON)
|
||||
option(WITH_PROJ "Enable output of Earth-Centered Earth-Fixed glTF output using the PROJ library" OFF)
|
||||
option(IFCXML_SUPPORT "Build IfcParse with ifcXML support (requires libxml2)." ON)
|
||||
option(USD_SUPPORT "Build IfcConvert with USD support (requires pixar's USD library)." OFF)
|
||||
|
||||
@@ -934,6 +935,17 @@ if(BUILD_CONVERT OR BUILD_IFCPYTHON)
|
||||
add_library(Serializers ${SERIALIZERS_FILES})
|
||||
set_target_properties(Serializers PROPERTIES COMPILE_FLAGS "-DIFC_GEOM_EXPORTS" VERSION "${PROJECT_VERSION}" SOVERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}")
|
||||
|
||||
message(STATUS "WITH_PROJ ${WITH_PROJ}")
|
||||
|
||||
if (WITH_PROJ)
|
||||
target_compile_definitions(Serializers PRIVATE "WITH_PROJ")
|
||||
if (PROJ_STATIC)
|
||||
target_compile_definitions(Serializers PRIVATE "PROJ_DLL=")
|
||||
endif()
|
||||
target_include_directories(Serializers PRIVATE ${PROJ_INCLUDE_DIR} ${SQLITE_INCLUDE_DIR})
|
||||
target_link_libraries(Serializers ${PROJ_LIBRARIES})
|
||||
endif()
|
||||
|
||||
target_link_libraries(Serializers ${SERIALIZER_SCHEMA_LIBRARIES} ${OPENCOLLADA_LIBRARIES} ${USD_LIBRARIES})
|
||||
|
||||
endif(BUILD_CONVERT OR BUILD_IFCPYTHON)
|
||||
|
||||
@@ -169,7 +169,7 @@ endif
|
||||
cp -r blenderbim/* dist/blenderbim/
|
||||
|
||||
# Provides IfcOpenShell Python functionality
|
||||
cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-$(PYNUMBER)-v0.7.0-9cc1f5f-$(PLATFORM)64.zip
|
||||
cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-$(PYNUMBER)-v0.7.0-f0e03c7-$(PLATFORM)64.zip
|
||||
cd dist/working && unzip ifcopenshell-python*
|
||||
cp -r dist/working/ifcopenshell dist/blenderbim/libs/site/packages/
|
||||
|
||||
|
||||
@@ -32,9 +32,16 @@ class SelectFMIfcFile(bpy.types.Operator):
|
||||
filename_ext = ".ifc"
|
||||
filter_glob: bpy.props.StringProperty(default="*.ifc;*.ifczip;*.ifcxml", options={"HIDDEN"})
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
files: bpy.props.CollectionProperty(name="File Path", type=bpy.types.OperatorFileListElement)
|
||||
|
||||
def execute(self, context):
|
||||
context.scene.BIMFMProperties.ifc_file = self.filepath
|
||||
props = context.scene.BIMFMProperties
|
||||
props.ifc_files.clear()
|
||||
dirname = os.path.dirname(self.filepath)
|
||||
for f in self.files:
|
||||
new = props.ifc_files.add()
|
||||
new.name = os.path.join(dirname, f.name)
|
||||
props.ifc_file = self.filepath
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
@@ -42,6 +49,7 @@ class SelectFMIfcFile(bpy.types.Operator):
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
|
||||
|
||||
class ExecuteIfcFM(bpy.types.Operator):
|
||||
bl_idname = "bim.execute_ifcfm"
|
||||
bl_label = "Execute IfcFM"
|
||||
@@ -66,17 +74,32 @@ class ExecuteIfcFM(bpy.types.Operator):
|
||||
|
||||
props = context.scene.BIMFMProperties
|
||||
ifc_file = tool.Ifc.get()
|
||||
filepaths = []
|
||||
if not (ifc_file and props.should_load_from_memory):
|
||||
ifc_file = ifcopenshell.open(props.ifc_file)
|
||||
if len(props.ifc_files):
|
||||
ifc_files = [ifcopenshell.open(f.name) for f in props.ifc_files]
|
||||
filepaths = [f.name for f in props.ifc_files]
|
||||
else:
|
||||
ifc_files = [ifcopenshell.open(props.ifc_file)]
|
||||
else:
|
||||
ifc_files = [ifc_file]
|
||||
|
||||
parser = ifcfm.Parser(preset=props.engine)
|
||||
parser.parse(ifc_file)
|
||||
writer = ifcfm.Writer(parser)
|
||||
writer.write()
|
||||
if props.format == "csv":
|
||||
writer.write_csv('tmp/')
|
||||
elif props.format == "ods":
|
||||
writer.write_ods(self.filepath)
|
||||
elif props.format == "xlsx":
|
||||
writer.write_xlsx(self.filepath)
|
||||
for i, ifc_file in enumerate(ifc_files):
|
||||
if filepaths:
|
||||
dirname = os.path.dirname(self.filepath)
|
||||
prefix, _ = os.path.splitext(os.path.basename(filepaths[i]))
|
||||
basename = os.path.basename(self.filepath)
|
||||
filepath = os.path.join(dirname, f"{prefix}-{basename}")
|
||||
else:
|
||||
filepath = self.filepath
|
||||
parser = ifcfm.Parser(preset=props.engine)
|
||||
parser.parse(ifc_file)
|
||||
writer = ifcfm.Writer(parser)
|
||||
writer.write()
|
||||
if props.format == "csv":
|
||||
writer.write_csv('tmp/')
|
||||
elif props.format == "ods":
|
||||
writer.write_ods(filepath)
|
||||
elif props.format == "xlsx":
|
||||
writer.write_xlsx(filepath)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
from blenderbim.bim.prop import StrProperty
|
||||
from bpy.types import PropertyGroup
|
||||
from bpy.props import (
|
||||
PointerProperty,
|
||||
@@ -32,6 +33,7 @@ from bpy.props import (
|
||||
|
||||
class BIMFMProperties(PropertyGroup):
|
||||
ifc_file: StringProperty(default="", name="IFC File")
|
||||
ifc_files: CollectionProperty(name="IFC Files", type=StrProperty)
|
||||
should_load_from_memory: BoolProperty(default=False, name="Load from Memory")
|
||||
engine: EnumProperty(
|
||||
items=[
|
||||
|
||||
@@ -42,7 +42,10 @@ class BIM_PT_fm(Panel):
|
||||
|
||||
if not IfcStore.get_file() or not props.should_load_from_memory:
|
||||
row = layout.row(align=True)
|
||||
row.prop(props, "ifc_file")
|
||||
if props.ifc_files:
|
||||
row.label(text=f"{len(props.ifc_files)} Files Selected")
|
||||
else:
|
||||
row.prop(props, "ifc_file")
|
||||
row.operator("bim.select_fm_ifc_file", icon="FILE_FOLDER", text="")
|
||||
|
||||
row = layout.row()
|
||||
|
||||
@@ -23,6 +23,7 @@ classes = (
|
||||
operator.AddRepresentation,
|
||||
operator.CopyRepresentation,
|
||||
operator.EditObjectPlacement,
|
||||
operator.FlipObject,
|
||||
operator.GetRepresentationIfcParameters,
|
||||
operator.OverrideDelete,
|
||||
operator.OverrideDuplicateMove,
|
||||
|
||||
@@ -1203,9 +1203,64 @@ class RefreshAggregate(bpy.types.Operator):
|
||||
else:
|
||||
parts = ifcopenshell.util.element.get_parts(instance_entity)
|
||||
for part in parts:
|
||||
pset = ifcopenshell.util.element.get_pset(part, "BBIM_Aggregate_Data")
|
||||
if part.is_a("IfcElementAssembly"):
|
||||
if not pset:
|
||||
data_children = {
|
||||
"children": [],
|
||||
"instance_of": [part.GlobalId],
|
||||
}
|
||||
data = [data_children]
|
||||
|
||||
pset = ifcopenshell.api.run(
|
||||
"pset.add_pset", tool.Ifc.get(), product=part, name="BBIM_Aggregate_Data"
|
||||
)
|
||||
ifcopenshell.api.run(
|
||||
"pset.edit_pset",
|
||||
tool.Ifc.get(),
|
||||
pset=pset,
|
||||
properties={"Parent": instance_entity.GlobalId, "Data": json.dumps(data)},
|
||||
)
|
||||
|
||||
part_obj = tool.Ifc.get_object(part)
|
||||
new_part = duplicate_objects(part_obj)
|
||||
|
||||
blenderbim.core.aggregate.assign_object(
|
||||
tool.Ifc,
|
||||
tool.Aggregate,
|
||||
tool.Collector,
|
||||
relating_obj=tool.Ifc.get_object(entity),
|
||||
related_obj=tool.Ifc.get_object(new_part),
|
||||
)
|
||||
duplicate_children(new_part)
|
||||
|
||||
for part in parts:
|
||||
pset = ifcopenshell.util.element.get_pset(part, "BBIM_Aggregate_Data")
|
||||
if part.is_a("IfcElementAssembly"):
|
||||
pass
|
||||
|
||||
else:
|
||||
if not pset:
|
||||
pset = ifcopenshell.api.run(
|
||||
"pset.add_pset", tool.Ifc.get(), product=part, name="BBIM_Aggregate_Data"
|
||||
)
|
||||
else:
|
||||
pset = ifcopenshell.util.element.get_pset(part, "BBIM_Aggregate_Data")
|
||||
pset = tool.Ifc.get().by_id(pset["id"])
|
||||
|
||||
data_children = {
|
||||
"children": [],
|
||||
"instance_of": [],
|
||||
}
|
||||
data = [data_children]
|
||||
|
||||
ifcopenshell.api.run(
|
||||
"pset.edit_pset",
|
||||
tool.Ifc.get(),
|
||||
pset=pset,
|
||||
properties={"Parent": instance_entity.GlobalId, "Data": json.dumps(data)},
|
||||
)
|
||||
|
||||
part_obj = tool.Ifc.get_object(part)
|
||||
new_part = duplicate_objects(part_obj)
|
||||
blenderbim.core.aggregate.assign_object(
|
||||
@@ -1226,6 +1281,10 @@ class RefreshAggregate(bpy.types.Operator):
|
||||
collection.objects.link(new_obj)
|
||||
obj.select_set(False)
|
||||
new_obj.select_set(True)
|
||||
|
||||
# This is needed to make sure the new object gets unlink from
|
||||
# the old object assembly collection
|
||||
new_obj.BIMObjectProperties.collection = None
|
||||
|
||||
# Copy the actual class
|
||||
new_entity = blenderbim.core.root.copy_class(
|
||||
@@ -1234,7 +1293,9 @@ class RefreshAggregate(bpy.types.Operator):
|
||||
|
||||
if new_entity:
|
||||
tool.Model.handle_array_on_copied_element(new_entity)
|
||||
blenderbim.core.aggregate.unassign_object(
|
||||
|
||||
if not new_entity.is_a("IfcElementAssembly"):
|
||||
blenderbim.core.aggregate.unassign_object(
|
||||
tool.Ifc,
|
||||
tool.Aggregate,
|
||||
tool.Collector,
|
||||
@@ -1662,3 +1723,19 @@ class OverrideModeSetObject(bpy.types.Operator):
|
||||
if self.edited_objs:
|
||||
return context.window_manager.invoke_props_dialog(self)
|
||||
return self.execute(context)
|
||||
|
||||
|
||||
class FlipObject(bpy.types.Operator):
|
||||
bl_idname = "bim.flip_object"
|
||||
bl_label = "Flip Object"
|
||||
bl_description = "Flip object's local axes, keep the position"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
flip_local_axes: bpy.props.EnumProperty(
|
||||
name="Flip Local Axes", items=(("XY", "XY", ""), ("YZ", "YZ", ""), ("XZ", "XZ", "")), default="XY"
|
||||
)
|
||||
|
||||
def execute(self, context):
|
||||
for obj in context.selected_objects:
|
||||
tool.Geometry.flip_object(obj, self.flip_local_axes)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -56,7 +56,12 @@ class GeoreferenceData:
|
||||
@classmethod
|
||||
def map_conversion(cls):
|
||||
if tool.Ifc.get_schema() == "IFC2X3":
|
||||
return {}
|
||||
project = tool.Ifc.get().by_type("IfcProject")[0]
|
||||
map_conversion = ifcopenshell.util.element.get_pset(project, "ePSet_MapConversion")
|
||||
if not map_conversion:
|
||||
return {}
|
||||
del map_conversion["id"]
|
||||
return map_conversion
|
||||
|
||||
for context in tool.Ifc.get().by_type("IfcGeometricRepresentationContext", include_subtypes=False):
|
||||
if context.HasCoordinateOperation:
|
||||
@@ -72,8 +77,8 @@ class GeoreferenceData:
|
||||
def map_derived_angle(cls):
|
||||
if (
|
||||
cls.data["map_conversion"]
|
||||
and cls.data["map_conversion"]["XAxisAbscissa"] is not None
|
||||
and cls.data["map_conversion"]["XAxisOrdinate"] is not None
|
||||
and cls.data["map_conversion"].get("XAxisAbscissa", None) is not None
|
||||
and cls.data["map_conversion"].get("XAxisOrdinate", None) is not None
|
||||
):
|
||||
return str(
|
||||
round(
|
||||
@@ -88,7 +93,12 @@ class GeoreferenceData:
|
||||
@classmethod
|
||||
def projected_crs(cls):
|
||||
if tool.Ifc.get_schema() == "IFC2X3":
|
||||
return {}
|
||||
project = tool.Ifc.get().by_type("IfcProject")[0]
|
||||
projected_crs = ifcopenshell.util.element.get_pset(project, "ePSet_ProjectedCRS")
|
||||
if not projected_crs:
|
||||
return {}
|
||||
del projected_crs["id"]
|
||||
return projected_crs
|
||||
|
||||
for context in tool.Ifc.get().by_type("IfcGeometricRepresentationContext", include_subtypes=False):
|
||||
if context.HasCoordinateOperation:
|
||||
|
||||
@@ -78,10 +78,14 @@ class BIM_PT_gis(Panel):
|
||||
def draw_ui(self, context):
|
||||
props = context.scene.BIMGeoreferenceProperties
|
||||
|
||||
if tool.Ifc.get_schema() == "IFC2X3":
|
||||
row = self.layout.row()
|
||||
row.label(text="IFC2X3 Fallback In Use", icon="ERROR")
|
||||
|
||||
if not GeoreferenceData.data["projected_crs"]:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Not Georeferenced")
|
||||
if tool.Ifc.get_schema != "IFC2X3":
|
||||
if tool.Ifc.get_schema() != "IFC2X3":
|
||||
row.operator("bim.add_georeferencing", icon="ADD", text="")
|
||||
|
||||
if props.has_blender_offset:
|
||||
@@ -110,8 +114,9 @@ class BIM_PT_gis(Panel):
|
||||
if GeoreferenceData.data["projected_crs"]:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Projected CRS", icon="WORLD")
|
||||
row.operator("bim.enable_editing_georeferencing", icon="GREASEPENCIL", text="")
|
||||
row.operator("bim.remove_georeferencing", icon="X", text="")
|
||||
if tool.Ifc.get_schema() != "IFC2X3":
|
||||
row.operator("bim.enable_editing_georeferencing", icon="GREASEPENCIL", text="")
|
||||
row.operator("bim.remove_georeferencing", icon="X", text="")
|
||||
|
||||
for key, value in GeoreferenceData.data["projected_crs"].items():
|
||||
if not value:
|
||||
@@ -184,4 +189,4 @@ class BIM_PT_gis_utilities(Panel):
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(props, "y_axis_abscissa_output", text="North Abscissa")
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(props, "y_axis_ordinate_output", text="North Ordinate")
|
||||
row.prop(props, "y_axis_ordinate_output", text="North Ordinate")
|
||||
|
||||
@@ -169,7 +169,6 @@ class RemoveArray(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
pset = ifcopenshell.util.element.get_pset(element, "BBIM_Array")
|
||||
data = json.loads(pset["Data"])
|
||||
data[self.item]["count"] = 1
|
||||
|
||||
if (self.keep_objs) & (self.item < (len(data) - 1)):
|
||||
self.report(
|
||||
@@ -188,6 +187,8 @@ class RemoveArray(bpy.types.Operator, tool.Ifc.Operator):
|
||||
tool.Blender.Modifier.Array.bake_children_transform(element, self.item)
|
||||
tool.Blender.Modifier.Array.set_children_lock_state(element, self.item, False)
|
||||
|
||||
if not self.keep_objs:
|
||||
data[self.item]["count"] = 1
|
||||
tool.Model.regenerate_array(parent, data, self.keep_objs)
|
||||
|
||||
pset = tool.Ifc.get().by_id(pset["id"])
|
||||
|
||||
@@ -393,18 +393,7 @@ class FlipFill(bpy.types.Operator, tool.Ifc.Operator):
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element or not element.FillsVoids:
|
||||
continue
|
||||
|
||||
flip_matrix = Matrix.Rotation(pi, 4, "Z")
|
||||
|
||||
bottom_left = obj.matrix_world @ Vector(obj.bound_box[0])
|
||||
top_right = obj.matrix_world @ Vector(obj.bound_box[6])
|
||||
center = obj.matrix_world.translation.copy()
|
||||
center_offset = center - bottom_left
|
||||
flipped_center = top_right - center_offset
|
||||
|
||||
obj.matrix_world = obj.matrix_world @ flip_matrix
|
||||
obj.matrix_world.translation.xy = flipped_center.xy
|
||||
bpy.context.view_layer.update()
|
||||
tool.Geometry.flip_object(obj, "XY")
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
|
||||
@@ -99,7 +99,7 @@ class DeleteSverchokGraph(bpy.types.Operator, tool.Ifc.Operator):
|
||||
return {"FINISHED"}
|
||||
|
||||
def draw(self, context):
|
||||
self.layout.label(text="WARNING. The graph will be removed permamently.")
|
||||
self.layout.label(text="WARNING. The graph will be removed permanently.")
|
||||
|
||||
def invoke(self, context, event):
|
||||
return context.window_manager.invoke_props_dialog(self)
|
||||
|
||||
@@ -1262,8 +1262,13 @@ class DumbWallJoiner:
|
||||
results["is_sloped"] = True
|
||||
results["height"] = (item.Depth * self.unit_scale) / (1 / cos(results["x_angle"]))
|
||||
break
|
||||
elif item.is_a("IfcBooleanClippingResult"):
|
||||
elif item.is_a("IfcBooleanClippingResult"): # should be before IfcBooleanResult check
|
||||
item = item.FirstOperand
|
||||
elif item.is_a("IfcBooleanResult"):
|
||||
if item.FirstOperand.is_a("IfcExtrudedAreaSolid") or item.FirstOperand.is_a("IfcBooleanResult"):
|
||||
item = item.FirstOperand
|
||||
else:
|
||||
item = item.SecondOperand
|
||||
else:
|
||||
break
|
||||
return results
|
||||
|
||||
@@ -358,6 +358,7 @@ class BimToolUI:
|
||||
cls.layout.operator("bim.mep_connect_elements")
|
||||
else:
|
||||
add_layout_hotkey_operator(cls.layout, "Edit Axis", "A_E", "")
|
||||
add_layout_hotkey_operator(cls.layout, "Flip", "S_F", bpy.ops.bim.flip_object.__doc__)
|
||||
add_layout_hotkey_operator(cls.layout, "Butt", "S_T", "")
|
||||
add_layout_hotkey_operator(cls.layout, "Mitre", "S_Y", "")
|
||||
add_layout_hotkey_operator(cls.layout, "Rotate 90", "S_R", bpy.ops.bim.rotate_90.__doc__)
|
||||
@@ -668,6 +669,8 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bpy.ops.bim.flip_wall()
|
||||
elif self.active_class in ("IfcWindow", "IfcWindowStandardCase", "IfcDoor", "IfcDoorStandardCase"):
|
||||
bpy.ops.bim.flip_fill()
|
||||
elif self.active_class in ("IfcBeam", "IfcColumn"):
|
||||
bpy.ops.bim.flip_object(flip_local_axes="XZ")
|
||||
elif self.active_class in ("IfcDuctSegment", "IfcPipeSegment", "IfcCableCarrierSegment", "IfcCableSegment"):
|
||||
bpy.ops.bim.fit_flow_segments()
|
||||
|
||||
|
||||
@@ -822,10 +822,13 @@ class SelectSimilar(Operator, tool.Ifc.Operator):
|
||||
props = context.scene.BIMSearchProperties
|
||||
obj = context.active_object
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
value = ifcopenshell.util.selector.get_element_value(element, props.element_key)
|
||||
key = props.element_key
|
||||
if props.element_key == "PredefinedType":
|
||||
key = "predefined_type"
|
||||
value = ifcopenshell.util.selector.get_element_value(element, key)
|
||||
for obj in context.visible_objects:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element:
|
||||
continue
|
||||
if ifcopenshell.util.selector.get_element_value(element, props.element_key) == value:
|
||||
if ifcopenshell.util.selector.get_element_value(element, key) == value:
|
||||
obj.select_set(True)
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
# 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 os
|
||||
import bpy
|
||||
import time
|
||||
import tempfile
|
||||
@@ -72,8 +72,10 @@ class ExecuteIfcTester(bpy.types.Operator, tool.Ifc.Operator):
|
||||
tool.Tester.report = report
|
||||
props.specifications.clear()
|
||||
for spec in report:
|
||||
print(spec)
|
||||
new_spec = props.specifications.add()
|
||||
new_spec.name = spec["name"]
|
||||
new_spec.description = spec["description"]
|
||||
new_spec.status = spec["status"]
|
||||
|
||||
blenderbim.bim.handler.refresh_ui_data()
|
||||
|
||||
@@ -37,6 +37,7 @@ def update_active_specification_index(self, context):
|
||||
|
||||
class Specification(PropertyGroup):
|
||||
name: StringProperty(name="Name")
|
||||
description: StringProperty(name="Description")
|
||||
status: BoolProperty(default=False, name="Status")
|
||||
|
||||
|
||||
|
||||
@@ -107,8 +107,9 @@ class BIM_PT_tester(Panel):
|
||||
class BIM_UL_tester_specifications(UIList):
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||
if item:
|
||||
row = layout.row(align=True)
|
||||
row = layout.split(factor=0.3, align=True)
|
||||
row.label(text=item.name, icon="CHECKMARK" if item.status else "CANCEL")
|
||||
row.label(text=item.description)
|
||||
|
||||
|
||||
class BIM_UL_tester_failed_entities(UIList):
|
||||
|
||||
@@ -237,7 +237,7 @@ class FileAssociate(bpy.types.Operator):
|
||||
# tried to do the regitsry change from powershell/cmd - but even admin rights are not enough
|
||||
# this is why we're using .reg
|
||||
reg_change_path = os.path.join(src_dir, "windows_bbim_association.reg")
|
||||
subprocess.run(["cmd", "/c", reg_change_path])
|
||||
subprocess.run(["cmd", "/c", "call", reg_change_path])
|
||||
|
||||
ps_script_path = os.path.join(src_dir, "windows_bbim_association.ps1")
|
||||
# NOTE: call powershell with RunAs to get admin rights from user
|
||||
|
||||
@@ -71,13 +71,13 @@ def set_cursor_location(georeference):
|
||||
|
||||
|
||||
def convert_local_to_global(georeference):
|
||||
coordinates = georeference.xyz2enh(georeference.get_coordinates("input"), georeference.get_map_conversion())
|
||||
coordinates = georeference.xyz2enh(georeference.get_coordinates("input"))
|
||||
georeference.set_coordinates("output", coordinates)
|
||||
georeference.set_cursor_location(coordinates)
|
||||
|
||||
|
||||
def convert_global_to_local(georeference):
|
||||
coordinates = georeference.enh2xyz(georeference.get_coordinates("input"), georeference.get_map_conversion())
|
||||
coordinates = georeference.enh2xyz(georeference.get_coordinates("input"))
|
||||
georeference.set_coordinates("output", coordinates)
|
||||
georeference.set_cursor_location(coordinates)
|
||||
|
||||
@@ -86,4 +86,4 @@ def convert_angle_to_coord(georeference, type):
|
||||
georeference.set_vector_coordinates(vector_coordinates,type)
|
||||
|
||||
def import_plot(georeference, filepath):
|
||||
georeference.import_plot(filepath, georeference.get_map_conversion())
|
||||
georeference.import_plot(filepath)
|
||||
|
||||
@@ -95,30 +95,37 @@ def select_similar_container(ifc, spatial, obj=None):
|
||||
def select_product(spatial, product):
|
||||
spatial.select_products([product])
|
||||
|
||||
|
||||
def load_container_manager(spatial):
|
||||
spatial.load_container_manager()
|
||||
|
||||
|
||||
def edit_container_attributes(spatial, entity=None):
|
||||
spatial.edit_container_attributes(entity)
|
||||
spatial.load_container_manager()
|
||||
|
||||
|
||||
def contract_container(spatial, container=None):
|
||||
spatial.contract_container(container)
|
||||
spatial.load_container_manager()
|
||||
|
||||
|
||||
def expand_container(spatial, container=None):
|
||||
spatial.expand_container(container)
|
||||
spatial.load_container_manager()
|
||||
|
||||
|
||||
def delete_container(ifc, spatial, geometry, container=None):
|
||||
geometry.delete_ifc_object(ifc.get_object(container))
|
||||
spatial.load_container_manager()
|
||||
|
||||
|
||||
def select_decomposed_elements(spatial):
|
||||
container = spatial.get_active_container()
|
||||
if container:
|
||||
spatial.select_products(spatial.get_decomposed_elements(container))
|
||||
|
||||
|
||||
#HERE STARTS SPATIAL TOOL
|
||||
def generate_space(ifc, spatial, model, Type):
|
||||
active_obj = spatial.get_active_obj()
|
||||
@@ -139,6 +146,7 @@ def generate_space(ifc, spatial, model, Type):
|
||||
else:
|
||||
x, y, z, h, mat = spatial.get_x_y_z_h_mat_from_cursor() ##mat
|
||||
|
||||
|
||||
space_polygon = spatial.get_space_polygon_from_context_visible_objects(x, y)
|
||||
|
||||
if not space_polygon:
|
||||
@@ -188,6 +196,7 @@ def generate_spaces_from_walls(ifc, spatial, collector):
|
||||
spatial.assign_ifcspace_class_to_obj(obj)
|
||||
|
||||
spatial.assign_container_to_obj(obj)
|
||||
|
||||
|
||||
def toggle_space_visibility(ifc, spatial):
|
||||
model = ifc.get()
|
||||
@@ -195,4 +204,3 @@ def toggle_space_visibility(ifc, spatial):
|
||||
if not spaces:
|
||||
return
|
||||
spatial.toggle_spaces_visibility_wired_and_textured(spaces)
|
||||
|
||||
|
||||
@@ -384,12 +384,11 @@ class Georeference:
|
||||
def angle2coords(cls, angle, type): pass
|
||||
def disable_editing(cls): pass
|
||||
def enable_editing(cls): pass
|
||||
def enh2xyz(cls, map_conversion, coordinates): pass
|
||||
def enh2xyz(cls, coordinates): pass
|
||||
def get_angle(cls, type): pass
|
||||
def get_coordinates(cls, io): pass
|
||||
def get_cursor_location(cls): pass
|
||||
def get_map_conversion_attributes(cls): pass
|
||||
def get_map_conversion(cls): pass
|
||||
def get_projected_crs_attributes(cls): pass
|
||||
def get_true_north_attributes(cls): pass
|
||||
def import_map_conversion(cls): pass
|
||||
@@ -402,7 +401,7 @@ class Georeference:
|
||||
def set_ifc_grid_north(cls): pass
|
||||
def set_ifc_true_north(cls): pass
|
||||
def set_vector_coordinates(cls, vector_coordinates, type): pass
|
||||
def xyz2enh(cls, map_conversion, coordinates): pass
|
||||
def xyz2enh(cls, coordinates): pass
|
||||
|
||||
|
||||
@interface
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
Windows Registry Editor Version 5.00
|
||||
|
||||
[-HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.ifc]
|
||||
[-]
|
||||
@@ -343,6 +343,8 @@ class Blender:
|
||||
"max_y": bound_box[6][1],
|
||||
"min_z": bound_box[0][2],
|
||||
"max_z": bound_box[6][2],
|
||||
"min_point": Vector(bound_box[0]),
|
||||
"max_point": Vector(bound_box[6]),
|
||||
"center": (Vector(bound_box[6]) + Vector(bound_box[0])) / 2,
|
||||
}
|
||||
return bbox_dict
|
||||
|
||||
@@ -28,7 +28,7 @@ import blenderbim.core.style
|
||||
import blenderbim.core.spatial
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.bim.import_ifc
|
||||
from math import radians
|
||||
from math import radians, pi
|
||||
from mathutils import Vector, Matrix
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
|
||||
@@ -641,3 +641,25 @@ class Geometry(blenderbim.core.tool.Geometry):
|
||||
@classmethod
|
||||
def get_model_representations(cls):
|
||||
return tool.Ifc.get().by_type("IfcShapeRepresentation")
|
||||
|
||||
@classmethod
|
||||
def flip_object(cls, obj, flip_local_axes):
|
||||
assert len(flip_local_axes) == 2, "flip_local_axes must be two axes to flip"
|
||||
rotation_axis = next(i for i in "XYZ" if i not in flip_local_axes)
|
||||
rotation_axis_i = "XYZ".index(rotation_axis)
|
||||
|
||||
bb_data = tool.Blender.get_object_bounding_box(obj)
|
||||
# min max points of rotated plane of origin based bounding box
|
||||
min_point = Vector([min(i, 0) for i in bb_data["min_point"]])
|
||||
max_point = Vector([max(i, 0) for i in bb_data["max_point"]])
|
||||
# keep it in rotated plane only
|
||||
max_point[rotation_axis_i] = min_point[rotation_axis_i]
|
||||
|
||||
# to compensate for flipped two axes
|
||||
# we adjust new max point to match previous min point (or vice versa)
|
||||
original_min_point = obj.matrix_world @ min_point
|
||||
obj.matrix_world = obj.matrix_world @ Matrix.Rotation(pi, 4, rotation_axis)
|
||||
new_max_point = obj.matrix_world @ max_point
|
||||
obj.matrix_world.translation += original_min_point - new_max_point
|
||||
|
||||
bpy.context.view_layer.update()
|
||||
|
||||
@@ -174,15 +174,7 @@ class Georeference(blenderbim.core.tool.Georeference):
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def get_map_conversion(cls):
|
||||
if tool.Ifc.get_schema() == "IFC2X3":
|
||||
return
|
||||
for context in tool.Ifc.get().by_type("IfcGeometricRepresentationContext", include_subtypes=False):
|
||||
if context.HasCoordinateOperation:
|
||||
return context.HasCoordinateOperation[0]
|
||||
|
||||
@classmethod
|
||||
def xyz2enh(cls, coordinates, map_conversion):
|
||||
def xyz2enh(cls, coordinates):
|
||||
props = bpy.context.scene.BIMGeoreferenceProperties
|
||||
if props.has_blender_offset:
|
||||
coordinates = ifcopenshell.util.geolocation.xyz2enh(
|
||||
@@ -196,53 +188,12 @@ class Georeference(blenderbim.core.tool.Georeference):
|
||||
float(props.blender_x_axis_ordinate),
|
||||
1.0,
|
||||
)
|
||||
if map_conversion:
|
||||
unit = map_conversion.TargetCRS.MapUnit
|
||||
e = map_conversion.Eastings
|
||||
n = map_conversion.Northings
|
||||
h = map_conversion.OrthogonalHeight
|
||||
if unit:
|
||||
scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
e = ifcopenshell.util.unit.convert(e, getattr(unit, "Prefix", None), unit.Name, None, None) / scale
|
||||
n = ifcopenshell.util.unit.convert(n, getattr(unit, "Prefix", None), unit.Name, None, None) / scale
|
||||
h = ifcopenshell.util.unit.convert(h, getattr(unit, "Prefix", None), unit.Name, None, None) / scale
|
||||
coordinates = ifcopenshell.util.geolocation.xyz2enh(
|
||||
coordinates[0],
|
||||
coordinates[1],
|
||||
coordinates[2],
|
||||
e,
|
||||
n,
|
||||
h,
|
||||
map_conversion.XAxisAbscissa or 1.0,
|
||||
map_conversion.XAxisOrdinate or 0.0,
|
||||
map_conversion.Scale or 1.0,
|
||||
)
|
||||
return coordinates
|
||||
return ifcopenshell.util.geolocation.auto_xyz2enh(tool.Ifc.get(), *coordinates)
|
||||
|
||||
@classmethod
|
||||
def enh2xyz(cls, coordinates, map_conversion):
|
||||
def enh2xyz(cls, coordinates):
|
||||
coordinates = ifcopenshell.util.geolocation.auto_enh2xyz(tool.Ifc.get(), *coordinates)
|
||||
props = bpy.context.scene.BIMGeoreferenceProperties
|
||||
if map_conversion:
|
||||
unit = map_conversion.TargetCRS.MapUnit
|
||||
e = map_conversion.Eastings
|
||||
n = map_conversion.Northings
|
||||
h = map_conversion.OrthogonalHeight
|
||||
if unit:
|
||||
scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
e = ifcopenshell.util.unit.convert(e, getattr(unit, "Prefix", None), unit.Name, None, None) / scale
|
||||
n = ifcopenshell.util.unit.convert(n, getattr(unit, "Prefix", None), unit.Name, None, None) / scale
|
||||
h = ifcopenshell.util.unit.convert(h, getattr(unit, "Prefix", None), unit.Name, None, None) / scale
|
||||
coordinates = ifcopenshell.util.geolocation.enh2xyz(
|
||||
coordinates[0],
|
||||
coordinates[1],
|
||||
coordinates[2],
|
||||
e,
|
||||
n,
|
||||
h,
|
||||
map_conversion.XAxisAbscissa or 1.0,
|
||||
map_conversion.XAxisOrdinate or 0.0,
|
||||
map_conversion.Scale or 1.0,
|
||||
)
|
||||
if props.has_blender_offset:
|
||||
coordinates = ifcopenshell.util.geolocation.enh2xyz(
|
||||
coordinates[0],
|
||||
@@ -316,7 +267,7 @@ class Georeference(blenderbim.core.tool.Georeference):
|
||||
rows = parse_csv(filepath)
|
||||
vertices = []
|
||||
for row in rows:
|
||||
coordinates = cls.enh2xyz([float(row[0]), float(row[1]), float(row[2])], map_conversion)
|
||||
coordinates = cls.enh2xyz([float(row[0]), float(row[1]), float(row[2])])
|
||||
vertices.append(coordinates)
|
||||
|
||||
mesh = bpy.data.meshes.new("mesh")
|
||||
|
||||
@@ -97,8 +97,7 @@ class TestSetCursorLocation:
|
||||
class TestConvertLocalToGlobal:
|
||||
def test_run(self, georeference):
|
||||
georeference.get_coordinates("input").should_be_called().will_return("coordinates")
|
||||
georeference.get_map_conversion().should_be_called().will_return("map_conversion")
|
||||
georeference.xyz2enh("coordinates", "map_conversion").should_be_called().will_return("enh")
|
||||
georeference.xyz2enh("coordinates").should_be_called().will_return("enh")
|
||||
georeference.set_coordinates("output", "enh").should_be_called()
|
||||
georeference.set_cursor_location("enh").should_be_called()
|
||||
subject.convert_local_to_global(georeference)
|
||||
@@ -107,8 +106,7 @@ class TestConvertLocalToGlobal:
|
||||
class TestConvertGlobalToLocal:
|
||||
def test_run(self, georeference):
|
||||
georeference.get_coordinates("input").should_be_called().will_return("coordinates")
|
||||
georeference.get_map_conversion().should_be_called().will_return("map_conversion")
|
||||
georeference.enh2xyz("coordinates", "map_conversion").should_be_called().will_return("xyz")
|
||||
georeference.enh2xyz("coordinates").should_be_called().will_return("xyz")
|
||||
georeference.set_coordinates("output", "xyz").should_be_called()
|
||||
georeference.set_cursor_location("xyz").should_be_called()
|
||||
subject.convert_global_to_local(georeference)
|
||||
|
||||
@@ -247,25 +247,21 @@ class TestSetBlenderTrueNorth(NewFile):
|
||||
assert round(math.degrees(bpy.context.scene.sun_pos_properties.north_offset)) == 45
|
||||
|
||||
|
||||
class TestGetMapConversion(NewFile):
|
||||
def test_run(self):
|
||||
ifc = ifcopenshell.file()
|
||||
tool.Ifc.set(ifc)
|
||||
ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcProject")
|
||||
ifcopenshell.api.run("context.add_context", ifc, context_type="Model")
|
||||
ifcopenshell.api.run("georeference.add_georeferencing", ifc)
|
||||
assert subject.get_map_conversion().is_a("IfcMapConversion")
|
||||
|
||||
|
||||
class TestXyz2Enh(NewFile):
|
||||
def test_run(self):
|
||||
assert subject.xyz2enh([0.0, 0.0, 0.0], None) == [0.0, 0.0, 0.0]
|
||||
ifc = ifcopenshell.file()
|
||||
ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcProject")
|
||||
tool.Ifc.set(ifc)
|
||||
assert subject.xyz2enh([0.0, 0.0, 0.0]) == (0.0, 0.0, 0.0)
|
||||
|
||||
def test_using_the_blender_offset(self):
|
||||
ifc = ifcopenshell.file()
|
||||
ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcProject")
|
||||
tool.Ifc.set(ifc)
|
||||
props = bpy.context.scene.BIMGeoreferenceProperties
|
||||
props.has_blender_offset = True
|
||||
props.blender_eastings = "1.0"
|
||||
assert subject.xyz2enh([0.0, 0.0, 0.0], None) == (1.0, 0.0, 0.0)
|
||||
assert subject.xyz2enh([0.0, 0.0, 0.0]) == (1.0, 0.0, 0.0)
|
||||
|
||||
def test_using_the_map_conversion(self):
|
||||
ifc = ifcopenshell.file()
|
||||
@@ -275,7 +271,7 @@ class TestXyz2Enh(NewFile):
|
||||
ifcopenshell.api.run("georeference.add_georeferencing", ifc)
|
||||
map_conversion = ifc.by_type("IfcMapConversion")[0]
|
||||
map_conversion.Eastings = 1.0
|
||||
assert subject.xyz2enh([0.0, 0.0, 0.0], map_conversion) == (1.0, 0.0, 0.0)
|
||||
assert subject.xyz2enh([0.0, 0.0, 0.0]) == (1.0, 0.0, 0.0)
|
||||
|
||||
def test_applying_both_blender_offset_and_map_conversion(self):
|
||||
props = bpy.context.scene.BIMGeoreferenceProperties
|
||||
@@ -288,18 +284,24 @@ class TestXyz2Enh(NewFile):
|
||||
ifcopenshell.api.run("georeference.add_georeferencing", ifc)
|
||||
map_conversion = ifc.by_type("IfcMapConversion")[0]
|
||||
map_conversion.Northings = 1.0
|
||||
assert subject.xyz2enh([0.0, 0.0, 0.0], map_conversion) == (1.0, 1.0, 0.0)
|
||||
assert subject.xyz2enh([0.0, 0.0, 0.0]) == (1.0, 1.0, 0.0)
|
||||
|
||||
|
||||
class TestEnh2Xyz(NewFile):
|
||||
def test_run(self):
|
||||
assert subject.enh2xyz([0.0, 0.0, 0.0], None) == [0.0, 0.0, 0.0]
|
||||
ifc = ifcopenshell.file()
|
||||
ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcProject")
|
||||
tool.Ifc.set(ifc)
|
||||
assert subject.enh2xyz([0.0, 0.0, 0.0]) == (0.0, 0.0, 0.0)
|
||||
|
||||
def test_using_the_blender_offset(self):
|
||||
ifc = ifcopenshell.file()
|
||||
ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcProject")
|
||||
tool.Ifc.set(ifc)
|
||||
props = bpy.context.scene.BIMGeoreferenceProperties
|
||||
props.has_blender_offset = True
|
||||
props.blender_eastings = "1.0"
|
||||
assert subject.enh2xyz([0.0, 0.0, 0.0], None) == (-1.0, 0.0, 0.0)
|
||||
assert subject.enh2xyz([0.0, 0.0, 0.0]) == (-1.0, 0.0, 0.0)
|
||||
|
||||
def test_using_the_map_conversion(self):
|
||||
ifc = ifcopenshell.file()
|
||||
@@ -309,7 +311,7 @@ class TestEnh2Xyz(NewFile):
|
||||
ifcopenshell.api.run("georeference.add_georeferencing", ifc)
|
||||
map_conversion = ifc.by_type("IfcMapConversion")[0]
|
||||
map_conversion.Eastings = 1.0
|
||||
assert subject.enh2xyz([0.0, 0.0, 0.0], map_conversion) == (-1.0, 0.0, 0.0)
|
||||
assert subject.enh2xyz([0.0, 0.0, 0.0]) == (-1.0, 0.0, 0.0)
|
||||
|
||||
def test_applying_both_blender_offset_and_map_conversion(self):
|
||||
props = bpy.context.scene.BIMGeoreferenceProperties
|
||||
@@ -322,7 +324,7 @@ class TestEnh2Xyz(NewFile):
|
||||
ifcopenshell.api.run("georeference.add_georeferencing", ifc)
|
||||
map_conversion = ifc.by_type("IfcMapConversion")[0]
|
||||
map_conversion.Northings = 1.0
|
||||
assert subject.enh2xyz([0.0, 0.0, 0.0], map_conversion) == (-1.0, -1.0, 0.0)
|
||||
assert subject.enh2xyz([0.0, 0.0, 0.0]) == (-1.0, -1.0, 0.0)
|
||||
|
||||
|
||||
class TestSetIfcGridNorth(NewFile):
|
||||
|
||||
@@ -52,12 +52,14 @@ class TestGenerateOccurrenceName(NewFile):
|
||||
bpy.context.scene.BIMModelProperties.occurrence_name_function = '"Foobar"'
|
||||
assert subject.generate_occurrence_name(element_type, "IfcWall") == "Foobar"
|
||||
|
||||
|
||||
class TestGetManualBooleans(NewFile):
|
||||
def test_run(self):
|
||||
assert isinstance(subject(), blenderbim.core.tool.Model)
|
||||
|
||||
def test_len_returned_boolean(self):
|
||||
def setup_profile_represntation(self, clippings=[]):
|
||||
ifc = ifcopenshell.file()
|
||||
self.ifc = ifc
|
||||
tool.Ifc.set(ifc)
|
||||
ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcProject")
|
||||
length = ifcopenshell.api.run("unit.add_si_unit", ifc, unit_type="LENGTHUNIT")
|
||||
@@ -65,17 +67,43 @@ class TestGetManualBooleans(NewFile):
|
||||
ifcopenshell.api.run("unit.assign_unit", ifc)
|
||||
element = ifc.createIfcColumn()
|
||||
hea100 = ifc.create_entity(
|
||||
"IfcIShapeProfileDef", ProfileName="HEA100", ProfileType="AREA",
|
||||
OverallWidth=100, OverallDepth=96, WebThickness=5, FlangeThickness=8, FilletRadius=12,
|
||||
"IfcIShapeProfileDef",
|
||||
ProfileName="HEA100",
|
||||
ProfileType="AREA",
|
||||
OverallWidth=100,
|
||||
OverallDepth=96,
|
||||
WebThickness=5,
|
||||
FlangeThickness=8,
|
||||
FilletRadius=12,
|
||||
)
|
||||
model3d = ifcopenshell.api.run("context.add_context", ifc, context_type="Model")
|
||||
body = ifcopenshell.api.run("context.add_context", ifc,context_type="Model", context_identifier="Body", target_view="MODEL_VIEW", parent=model3d)
|
||||
representation = ifcopenshell.api.run("geometry.add_profile_representation", ifc, context=body, profile=hea100, depth=5)
|
||||
body = ifcopenshell.api.run(
|
||||
"context.add_context",
|
||||
ifc,
|
||||
context_type="Model",
|
||||
context_identifier="Body",
|
||||
target_view="MODEL_VIEW",
|
||||
parent=model3d,
|
||||
)
|
||||
representation = ifcopenshell.api.run(
|
||||
"geometry.add_profile_representation", ifc, context=body, profile=hea100, depth=5, clippings=clippings
|
||||
)
|
||||
ifcopenshell.api.run("geometry.assign_representation", ifc, product=element, representation=representation)
|
||||
return element, representation
|
||||
|
||||
def test_manual_booleans(self):
|
||||
element, representation = self.setup_profile_represntation()
|
||||
matrix = np.eye(4)
|
||||
matrix = ifcopenshell.util.placement.rotation(45,"X") @ matrix
|
||||
matrix[:,3][0:3] = (0, 0, 3)
|
||||
matrix = matrix.tolist()
|
||||
ifcopenshell.api.run("geometry.add_boolean", ifc, representation = representation, type = "IfcHalfSpaceSolid", matrix = matrix)
|
||||
ifcopenshell.api.run(
|
||||
"geometry.add_boolean", self.ifc, representation=representation, type="IfcHalfSpaceSolid", matrix=matrix
|
||||
)
|
||||
assert len(subject.get_manual_booleans(element)) == 1
|
||||
|
||||
def test_automatic_booleans_ignored(self):
|
||||
clipping = {
|
||||
"type": "IfcBooleanClippingResult",
|
||||
"operand_type": "IfcHalfSpaceSolid",
|
||||
"matrix": np.eye(4).tolist(),
|
||||
}
|
||||
element, representation = self.setup_profile_represntation(clippings=[clipping])
|
||||
assert len(subject.get_manual_booleans(element)) == 0
|
||||
|
||||
@@ -461,6 +461,7 @@ int main(int argc, char** argv) {
|
||||
("space-name-transform", po::value<std::string>(),
|
||||
"Additional transform to the space labels in SVG")
|
||||
("edge-arrows", "Adds arrow heads to edge segments to signify edge direction")
|
||||
("ecef", "Write glTF in Earth-Centered Earth-Fixed coordinates. Requires PROJ")
|
||||
;
|
||||
|
||||
po::options_description cmdline_options;
|
||||
@@ -527,6 +528,7 @@ int main(int argc, char** argv) {
|
||||
const bool generate_uvs = vmap.count("generate-uvs") != 0;
|
||||
const bool validate = vmap.count("validate") != 0;
|
||||
const bool edge_arrows = vmap.count("edge-arrows") != 0;
|
||||
const bool write_gltf_ecef = vmap.count("ecef") != 0;
|
||||
const bool no_wire_intersection_check = vmap.count("no-wire-intersection-check") != 0;
|
||||
const bool no_wire_intersection_tolerance = vmap.count("no-wire-intersection-tolerance") != 0;
|
||||
const bool strict_tolerance = vmap.count("strict-tolerance") != 0;
|
||||
@@ -840,7 +842,8 @@ int main(int argc, char** argv) {
|
||||
settings.set(SerializerSettings::USE_MATERIAL_NAMES, use_material_names);
|
||||
settings.set(SerializerSettings::USE_ELEMENT_TYPES, use_element_types);
|
||||
settings.set(SerializerSettings::USE_ELEMENT_HIERARCHY, use_element_hierarchy);
|
||||
settings.set_deflection_tolerance(deflection_tolerance);
|
||||
settings.set(SerializerSettings::WRITE_GLTF_ECEF, write_gltf_ecef);
|
||||
settings.set_deflection_tolerance(deflection_tolerance);
|
||||
settings.set_angular_tolerance(angular_tolerance);
|
||||
settings.precision = precision;
|
||||
|
||||
|
||||
+56
-40
@@ -64,9 +64,10 @@ def get_facility_data(ifc_file, element):
|
||||
"Name": element.Name,
|
||||
"ProjectName": ifc_file.by_type("IfcProject")[0].Name,
|
||||
"SiteName": getattr(get_facility_parent(element, "IfcSite"), "Name", None),
|
||||
"Category": get_classification(element),
|
||||
"AuthorOrganizationName": get_owner_name(element),
|
||||
"AuthorDate": get_owner_creation_date(element),
|
||||
"ClassificationIdentification": get_classification_identification(element),
|
||||
"ClassificationName": get_classification_name(element),
|
||||
"OrganizationName": get_owner_name(element),
|
||||
"CreationDate": get_owner_creation_date(element),
|
||||
"ModelSoftware": get_owner_application(element),
|
||||
"ModelProjectID": ifc_file.by_type("IfcProject")[0].GlobalId,
|
||||
"ModelSiteID": getattr(get_facility_parent(element, "IfcSite"), "GlobalId", None),
|
||||
@@ -80,9 +81,10 @@ def get_facility_data(ifc_file, element):
|
||||
def get_storey_data(ifc_file, element):
|
||||
return {
|
||||
"Name": element.Name,
|
||||
"Category": "Level",
|
||||
"AuthorOrganizationName": get_owner_name(element),
|
||||
"AuthorDate": get_owner_creation_date(element),
|
||||
"ClassificationIdentification": "Level",
|
||||
"ClassificationName": get_classification_name(element),
|
||||
"OrganizationName": get_owner_name(element),
|
||||
"CreationDate": get_owner_creation_date(element),
|
||||
"ModelSoftware": get_owner_application(element),
|
||||
"ModelObject": element.is_a(),
|
||||
"ModelID": element.GlobalId,
|
||||
@@ -95,10 +97,11 @@ def get_space_data(ifc_file, element):
|
||||
return {
|
||||
"Name": element.Name,
|
||||
"Description": element.LongName,
|
||||
"Category": get_classification(element),
|
||||
"ClassificationIdentification": get_classification_identification(element),
|
||||
"ClassificationName": get_classification_name(element),
|
||||
"LevelName": getattr(get_facility_parent(element, "IfcBuildingStorey"), "Name", None),
|
||||
"AuthorOrganizationName": get_owner_name(element),
|
||||
"AuthorDate": get_owner_creation_date(element),
|
||||
"OrganizationName": get_owner_name(element),
|
||||
"CreationDate": get_owner_creation_date(element),
|
||||
"ModelSoftware": get_owner_application(element),
|
||||
"ModelID": element.GlobalId,
|
||||
"AreaGross": get_property(psets, "Qto_SpaceBaseQuantities", "GrossFloorArea", decimals=2),
|
||||
@@ -111,8 +114,8 @@ def get_zone_data(ifc_file, element):
|
||||
return {
|
||||
"Name": zone.Name,
|
||||
"SpaceName": space.Name,
|
||||
"AuthorOrganizationName": get_owner_name(zone),
|
||||
"AuthorDate": get_owner_creation_date(zone),
|
||||
"OrganizationName": get_owner_name(zone),
|
||||
"CreationDate": get_owner_creation_date(zone),
|
||||
"ModelSoftware": get_owner_application(zone),
|
||||
"ModelID": zone.GlobalId,
|
||||
}
|
||||
@@ -123,9 +126,10 @@ def get_element_type_data(ifc_file, element):
|
||||
return {
|
||||
"Name": element.Name,
|
||||
"Description": element.Description,
|
||||
"Category": get_classification(element),
|
||||
"AuthorOrganizationName": get_owner_name(element),
|
||||
"AuthorDate": get_owner_creation_date(element),
|
||||
"ClassificationIdentification": get_classification_identification(element),
|
||||
"ClassificationName": get_classification_name(element),
|
||||
"OrganizationName": get_owner_name(element),
|
||||
"CreationDate": get_owner_creation_date(element),
|
||||
"ModelSoftware": get_owner_application(element),
|
||||
"ModelObject": "{}[{}]".format(element.is_a(), ifcopenshell.util.element.get_predefined_type(element)),
|
||||
"ModelID": element.GlobalId,
|
||||
@@ -149,8 +153,8 @@ def get_element_data(ifc_file, element):
|
||||
"TypeName": ifcopenshell.util.element.get_type(element).Name,
|
||||
"SpaceName": space_name,
|
||||
"SystemName": system,
|
||||
"AuthorOrganizationName": get_owner_name(element),
|
||||
"AuthorDate": get_owner_creation_date(element),
|
||||
"OrganizationName": get_owner_name(element),
|
||||
"CreationDate": get_owner_creation_date(element),
|
||||
"ModelSoftware": get_owner_application(element),
|
||||
"ModelObject": "{}[{}]".format(element.is_a(), ifcopenshell.util.element.get_predefined_type(element)),
|
||||
"ModelID": element.GlobalId,
|
||||
@@ -169,9 +173,10 @@ def get_system_data(ifc_file, element):
|
||||
return {
|
||||
"Name": element.Name,
|
||||
"Description": element.Description,
|
||||
"Category": get_classification(element),
|
||||
"AuthorOrganizationName": get_owner_name(element),
|
||||
"AuthorDate": get_owner_creation_date(element),
|
||||
"ClassificationIdentification": get_classification_identification(element),
|
||||
"ClassificationName": get_classification_name(element),
|
||||
"OrganizationName": get_owner_name(element),
|
||||
"CreationDate": get_owner_creation_date(element),
|
||||
"ModelSoftware": get_owner_application(element),
|
||||
"ModelID": element.GlobalId,
|
||||
}
|
||||
@@ -205,12 +210,18 @@ def get_facility_parent(element, ifc_class):
|
||||
parent = ifcopenshell.util.element.get_aggregate(parent)
|
||||
|
||||
|
||||
def get_classification(element):
|
||||
def get_classification_identification(element):
|
||||
references = list(ifcopenshell.util.classification.get_references(element))
|
||||
if references:
|
||||
if hasattr(references[0], "Identification"):
|
||||
return "{}:{}".format(references[0].Identification, references[0].Name)
|
||||
return "{}:{}".format(references[0].ItemReference, references[0].Name)
|
||||
return references[0].Identification
|
||||
return references[0].ItemReference
|
||||
|
||||
|
||||
def get_classification_name(element):
|
||||
references = list(ifcopenshell.util.classification.get_references(element))
|
||||
if references:
|
||||
return references[0].Name
|
||||
|
||||
|
||||
def get_property(psets, pset_name, prop_name, decimals=None):
|
||||
@@ -239,9 +250,10 @@ config = {
|
||||
"Name",
|
||||
"ProjectName",
|
||||
"SiteName",
|
||||
"Category",
|
||||
"AuthorOrganizationName",
|
||||
"AuthorDate",
|
||||
"ClassificationIdentification",
|
||||
"ClassificationName",
|
||||
"OrganizationName",
|
||||
"CreationDate",
|
||||
"ModelSoftware",
|
||||
"ModelProjectID",
|
||||
"ModelSiteID",
|
||||
@@ -259,9 +271,10 @@ config = {
|
||||
"keys": ["Name"],
|
||||
"headers": [
|
||||
"Name",
|
||||
"Category",
|
||||
"AuthorOrganizationName",
|
||||
"AuthorDate",
|
||||
"ClassificationIdentification",
|
||||
"ClassificationName",
|
||||
"OrganizationName",
|
||||
"CreationDate",
|
||||
"ModelSoftware",
|
||||
"ModelObject",
|
||||
"ModelID",
|
||||
@@ -277,10 +290,11 @@ config = {
|
||||
"headers": [
|
||||
"Name",
|
||||
"Description",
|
||||
"Category",
|
||||
"ClassificationIdentification",
|
||||
"ClassificationName",
|
||||
"LevelName",
|
||||
"AuthorOrganizationName",
|
||||
"AuthorDate",
|
||||
"OrganizationName",
|
||||
"CreationDate",
|
||||
"ModelSoftware",
|
||||
"ModelID",
|
||||
"AreaGross",
|
||||
@@ -293,7 +307,7 @@ config = {
|
||||
},
|
||||
"Zones": {
|
||||
"keys": ["Name", "SpaceName"],
|
||||
"headers": ["Name", "SpaceName", "AuthorOrganizationName", "AuthorDate", "ModelSoftware", "ModelID"],
|
||||
"headers": ["Name", "SpaceName", "OrganizationName", "CreationDate", "ModelSoftware", "ModelID"],
|
||||
"colours": "prreee",
|
||||
"sort": [{"name": "Name", "order": "ASC"}],
|
||||
"get_category_elements": get_zones,
|
||||
@@ -304,9 +318,10 @@ config = {
|
||||
"headers": [
|
||||
"Name",
|
||||
"Description",
|
||||
"Category",
|
||||
"AuthorOrganizationName",
|
||||
"AuthorDate",
|
||||
"ClassificationIdentification",
|
||||
"ClassificationName",
|
||||
"OrganizationName",
|
||||
"CreationDate",
|
||||
"ModelSoftware",
|
||||
"ModelObject",
|
||||
"ModelID",
|
||||
@@ -329,8 +344,8 @@ config = {
|
||||
"TypeName",
|
||||
"SpaceName",
|
||||
"SystemName",
|
||||
"AuthorOrganizationName",
|
||||
"AuthorDate",
|
||||
"OrganizationName",
|
||||
"CreationDate",
|
||||
"ModelSoftware",
|
||||
"ModelObject",
|
||||
"ModelID",
|
||||
@@ -353,9 +368,10 @@ config = {
|
||||
"headers": [
|
||||
"Name",
|
||||
"Description",
|
||||
"Category",
|
||||
"AuthorOrganizationName",
|
||||
"AuthorDate",
|
||||
"ClassificationIdentification",
|
||||
"ClassificationName",
|
||||
"OrganizationName",
|
||||
"CreationDate",
|
||||
"ModelSoftware",
|
||||
"ModelID",
|
||||
],
|
||||
|
||||
+20
-53
@@ -320,27 +320,21 @@ def get_facility_data(ifc_file, element):
|
||||
site_name = None
|
||||
site_description = None
|
||||
if site:
|
||||
site_name = val(site.Name) or val(site.LongName) or site.GlobalId
|
||||
site_description = val(site.Description) or val(site.LongName) or val(site.Name)
|
||||
site_name = val(site.Name)
|
||||
site_description = val(site.Description)
|
||||
|
||||
project = None
|
||||
project_name = None
|
||||
project_description = None
|
||||
try:
|
||||
project = ifc_file.by_type("IfcProject")[0]
|
||||
project_name = val(project.Name) or val(project.LongName) or project.GlobalId
|
||||
project_description = val(project.Description) or val(project.LongName) or val(project.Name)
|
||||
project_name = val(project.Name)
|
||||
project_description = val(project.Description)
|
||||
except:
|
||||
pass
|
||||
|
||||
name = val(element.Name) or val(element.LongName)
|
||||
if not name:
|
||||
name = val(project.Name) or val(project.LongName)
|
||||
if not name and site:
|
||||
name = val(site.Name) or val(site.LongName)
|
||||
|
||||
return {
|
||||
"Name": name,
|
||||
"Name": val(element.Name),
|
||||
"CreatedBy": get_created_by(element),
|
||||
"CreatedOn": get_created_on(element),
|
||||
"Category": get_category(element),
|
||||
@@ -353,12 +347,12 @@ def get_facility_data(ifc_file, element):
|
||||
"AreaMeasurement": get_area_measurement(element),
|
||||
"ExternalSystem": get_external_system(element),
|
||||
"ExternalProjectObject": "IfcProject",
|
||||
"ExternalProjectIdentifier": project.GlobalId if project else ifcopenshell.guid.new(),
|
||||
"ExternalProjectIdentifier": project.GlobalId if project else None,
|
||||
"ExternalSiteObject": "IfcSite",
|
||||
"ExternalSiteIdentifier": site.GlobalId if site else ifcopenshell.guid.new(),
|
||||
"ExternalSiteIdentifier": site.GlobalId if site else None,
|
||||
"ExternalFacilityObject": "IfcBuilding",
|
||||
"ExternalFacilityIdentifier": element.GlobalId,
|
||||
"Description": val(element.Description) or val(element.LongName) or val(element.Name),
|
||||
"Description": val(element.Description),
|
||||
"ProjectDescription": project_description,
|
||||
"SiteDescription": site_description,
|
||||
"Phase": val(project.Phase) if project else None,
|
||||
@@ -366,10 +360,6 @@ def get_facility_data(ifc_file, element):
|
||||
|
||||
|
||||
def get_floor_data(ifc_file, element):
|
||||
external_object = element.is_a()
|
||||
if element.ObjectType and element.ObjectType.lower() in ("site", "ifcsite"):
|
||||
external_object = "IfcSite"
|
||||
|
||||
height_names = {
|
||||
"Height",
|
||||
"NetHeight",
|
||||
@@ -400,9 +390,9 @@ def get_floor_data(ifc_file, element):
|
||||
"CreatedOn": get_created_on(element),
|
||||
"Category": get_category(element),
|
||||
"ExternalSystem": get_external_system(element),
|
||||
"ExternalObject": external_object,
|
||||
"ExternalObject": element.is_a(),
|
||||
"ExternalIdentifier": element.GlobalId,
|
||||
"Description": val(element.Description) or val(element.LongName) or val(element.Name),
|
||||
"Description": val(element.Description),
|
||||
"Elevation": val(elevation),
|
||||
"Height": height,
|
||||
}
|
||||
@@ -439,7 +429,7 @@ def get_space_data(ifc_file, element):
|
||||
"CreatedOn": get_created_on(element),
|
||||
"Category": get_category(element),
|
||||
"FloorName": floor_name,
|
||||
"Description": val(element.Description) or val(element.LongName) or val(element.Name),
|
||||
"Description": val(element.Description),
|
||||
"ExternalSystem": get_external_system(element),
|
||||
"ExternalObject": element.is_a(),
|
||||
"ExternalIdentifier": element.GlobalId,
|
||||
@@ -976,6 +966,7 @@ def get_owner_name(element):
|
||||
def get_created_on(element):
|
||||
if getattr(element, "OwnerHistory", None):
|
||||
return ifcopenshell.util.date.ifc2datetime(element.OwnerHistory.CreationDate).isoformat()
|
||||
return "1900-12-31T23:59:59" # Yes, really
|
||||
|
||||
|
||||
def get_external_system(element):
|
||||
@@ -1009,13 +1000,20 @@ def get_area_measurement(element):
|
||||
if result:
|
||||
return result
|
||||
|
||||
psets = ifcopenshell.util.element.get_psets(element)
|
||||
if psets:
|
||||
for _, props in psets.items():
|
||||
for name, value in props.items():
|
||||
if name == "MethodOfMeasurement":
|
||||
return value
|
||||
|
||||
|
||||
def get_category(element):
|
||||
references = list(ifcopenshell.util.classification.get_references(element))
|
||||
results = []
|
||||
for reference in references:
|
||||
if reference.is_a("IfcClassification"):
|
||||
results.append(reference.Name)
|
||||
continue
|
||||
elif reference.is_a("IfcClassificationReference"):
|
||||
identification = val(getattr(reference, "Identification", getattr(reference, "ItemReference", None)))
|
||||
if val(reference.Name) and identification and val(reference.Name) != identification:
|
||||
@@ -1024,40 +1022,9 @@ def get_category(element):
|
||||
results.append(reference.Name)
|
||||
elif identification:
|
||||
results.append(identification)
|
||||
elif reference.ReferencedSource and val(reference.ReferencedSource.Name):
|
||||
results.append(reference.ReferencedSource.Name)
|
||||
elif val(reference.Location):
|
||||
results.append(reference.Location)
|
||||
if results:
|
||||
return ",".join(results)
|
||||
|
||||
category_props = [
|
||||
("Assembly Code", "Assembly Description"),
|
||||
("Category Code", "Category Description"),
|
||||
("Classification Code", "Classification Description"),
|
||||
("OmniClass Number", "OmniClass Title"),
|
||||
("Uniclass Code", "Uniclass Description"),
|
||||
]
|
||||
|
||||
psets = ifcopenshell.util.element.get_psets(element)
|
||||
properties = {}
|
||||
if psets:
|
||||
for _, props in psets.items():
|
||||
properties.update(props)
|
||||
|
||||
for code, description in category_props:
|
||||
code = val(properties.get(code, None))
|
||||
if code:
|
||||
description = val(properties.get(description, None))
|
||||
if code and description:
|
||||
results.append(code + " : " + description)
|
||||
else:
|
||||
results.append(code)
|
||||
if results:
|
||||
return ",".join(results)
|
||||
|
||||
return val(getattr(element, "ObjectType", None))
|
||||
|
||||
|
||||
def get_pao_address(person, organization, name):
|
||||
for actor in [organization, person]:
|
||||
|
||||
@@ -993,6 +993,7 @@ def get_owner_name(element):
|
||||
def get_created_on(element):
|
||||
if getattr(element, "OwnerHistory", None):
|
||||
return ifcopenshell.util.date.ifc2datetime(element.OwnerHistory.CreationDate).isoformat()
|
||||
return "1900-12-31T23:59:59" # Yes, really
|
||||
|
||||
|
||||
def get_external_system(element):
|
||||
|
||||
@@ -49,8 +49,11 @@ public:
|
||||
/// Use Y UP .
|
||||
/// Applicable for OBJ output.
|
||||
USE_Y_UP = 1ULL << (IfcGeom::IteratorSettings::NUM_SETTINGS + 7ULL),
|
||||
/// Write in ECEF coordinates.
|
||||
/// Applicable to glTF output
|
||||
WRITE_GLTF_ECEF = 1ULL << (IfcGeom::IteratorSettings::NUM_SETTINGS + 8ULL),
|
||||
/// Number of different setting flags.
|
||||
NUM_SETTINGS = 7
|
||||
NUM_SETTINGS = 8
|
||||
};
|
||||
|
||||
SerializerSettings()
|
||||
|
||||
@@ -103,7 +103,7 @@ endif
|
||||
mkdir -p dist/ifcopenshell
|
||||
cp -r ifcopenshell/* dist/ifcopenshell/
|
||||
|
||||
cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-$(PYNUMBER)-v0.7.0-9cc1f5f-$(PLATFORM)64.zip
|
||||
cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-$(PYNUMBER)-v0.7.0-f0e03c7-$(PLATFORM)64.zip
|
||||
cd dist/working && unzip ifcopenshell-python*
|
||||
cp -r dist/working/ifcopenshell/ifcopenshell_wrapper.py dist/ifcopenshell/
|
||||
ifeq ($(PLATFORM), win)
|
||||
|
||||
@@ -20,11 +20,11 @@ Pre-built packages
|
||||
| build-linux64_ | build-win32_ | build-win64_ | build-macos64_ | build-macosm164_ |
|
||||
+----------------+----------------+----------------+----------------+------------------+
|
||||
|
||||
.. _build-linux64: https://s3.amazonaws.com/ifcopenshell-builds/IfcConvert-v0.7.0-9cc1f5f-linux64.zip
|
||||
.. _build-win32: https://s3.amazonaws.com/ifcopenshell-builds/IfcConvert-v0.7.0-9cc1f5f-win32.zip
|
||||
.. _build-win64: https://s3.amazonaws.com/ifcopenshell-builds/IfcConvert-v0.7.0-9cc1f5f-win64.zip
|
||||
.. _build-macos64: https://s3.amazonaws.com/ifcopenshell-builds/IfcConvert-v0.7.0-9cc1f5f-macos64.zip
|
||||
.. _build-macosm164: https://s3.amazonaws.com/ifcopenshell-builds/IfcConvert-v0.7.0-9cc1f5f-macosm164.zip
|
||||
.. _build-linux64: https://s3.amazonaws.com/ifcopenshell-builds/IfcConvert-v0.7.0-f0e03c7-linux64.zip
|
||||
.. _build-win32: https://s3.amazonaws.com/ifcopenshell-builds/IfcConvert-v0.7.0-f0e03c7-win32.zip
|
||||
.. _build-win64: https://s3.amazonaws.com/ifcopenshell-builds/IfcConvert-v0.7.0-f0e03c7-win64.zip
|
||||
.. _build-macos64: https://s3.amazonaws.com/ifcopenshell-builds/IfcConvert-v0.7.0-f0e03c7-macos64.zip
|
||||
.. _build-macosm164: https://s3.amazonaws.com/ifcopenshell-builds/IfcConvert-v0.7.0-f0e03c7-macosm164.zip
|
||||
|
||||
2. Unzip the downloaded file and run IfcConvert using the command line.
|
||||
|
||||
|
||||
@@ -41,34 +41,34 @@ changes in the IfcOpenShell C++ core.
|
||||
| Python 3.11 | py311-linux64_ | py311-win32_ | py311-win64_ | N/A | py311-macosm164_ |
|
||||
+-------------+----------------+----------------+----------------+----------------+------------------+
|
||||
|
||||
.. _py36-linux64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-36-v0.7.0-9cc1f5f-linux64.zip
|
||||
.. _py37-linux64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-37-v0.7.0-9cc1f5f-linux64.zip
|
||||
.. _py38-linux64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-38-v0.7.0-9cc1f5f-linux64.zip
|
||||
.. _py39-linux64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-39-v0.7.0-9cc1f5f-linux64.zip
|
||||
.. _py310-linux64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-310-v0.7.0-9cc1f5f-linux64.zip
|
||||
.. _py311-linux64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-311-v0.7.0-9cc1f5f-linux64.zip
|
||||
.. _py36-win32: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-36-v0.7.0-9cc1f5f-win32.zip
|
||||
.. _py37-win32: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-37-v0.7.0-9cc1f5f-win32.zip
|
||||
.. _py38-win32: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-38-v0.7.0-9cc1f5f-win32.zip
|
||||
.. _py39-win32: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-39-v0.7.0-9cc1f5f-win32.zip
|
||||
.. _py310-win32: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-310-v0.7.0-9cc1f5f-win32.zip
|
||||
.. _py311-win32: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-311-v0.7.0-9cc1f5f-win32.zip
|
||||
.. _py36-win64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-36-v0.7.0-9cc1f5f-win64.zip
|
||||
.. _py37-win64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-37-v0.7.0-9cc1f5f-win64.zip
|
||||
.. _py38-win64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-38-v0.7.0-9cc1f5f-win64.zip
|
||||
.. _py39-win64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-39-v0.7.0-9cc1f5f-win64.zip
|
||||
.. _py310-win64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-310-v0.7.0-9cc1f5f-win64.zip
|
||||
.. _py311-win64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-311-v0.7.0-9cc1f5f-win64.zip
|
||||
.. _py36-macos64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-36-v0.7.0-9cc1f5f-macos64.zip
|
||||
.. _py37-macos64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-37-v0.7.0-9cc1f5f-macos64.zip
|
||||
.. _py38-macos64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-38-v0.7.0-9cc1f5f-macos64.zip
|
||||
.. _py39-macos64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-39-v0.7.0-9cc1f5f-macos64.zip
|
||||
.. _py310-macos64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-310-v0.7.0-9cc1f5f-macos64.zip
|
||||
.. _py37-macosm164: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-37-v0.7.0-9cc1f5f-macosm164.zip
|
||||
.. _py38-macosm164: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-38-v0.7.0-9cc1f5f-macosm164.zip
|
||||
.. _py39-macosm164: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-39-v0.7.0-9cc1f5f-macosm164.zip
|
||||
.. _py310-macosm164: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-310-v0.7.0-9cc1f5f-macosm164.zip
|
||||
.. _py311-macosm164: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-311-v0.7.0-9cc1f5f-macosm164.zip
|
||||
.. _py36-linux64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-36-v0.7.0-f0e03c7-linux64.zip
|
||||
.. _py37-linux64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-37-v0.7.0-f0e03c7-linux64.zip
|
||||
.. _py38-linux64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-38-v0.7.0-f0e03c7-linux64.zip
|
||||
.. _py39-linux64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-39-v0.7.0-f0e03c7-linux64.zip
|
||||
.. _py310-linux64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-310-v0.7.0-f0e03c7-linux64.zip
|
||||
.. _py311-linux64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-311-v0.7.0-f0e03c7-linux64.zip
|
||||
.. _py36-win32: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-36-v0.7.0-f0e03c7-win32.zip
|
||||
.. _py37-win32: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-37-v0.7.0-f0e03c7-win32.zip
|
||||
.. _py38-win32: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-38-v0.7.0-f0e03c7-win32.zip
|
||||
.. _py39-win32: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-39-v0.7.0-f0e03c7-win32.zip
|
||||
.. _py310-win32: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-310-v0.7.0-f0e03c7-win32.zip
|
||||
.. _py311-win32: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-311-v0.7.0-f0e03c7-win32.zip
|
||||
.. _py36-win64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-36-v0.7.0-f0e03c7-win64.zip
|
||||
.. _py37-win64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-37-v0.7.0-f0e03c7-win64.zip
|
||||
.. _py38-win64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-38-v0.7.0-f0e03c7-win64.zip
|
||||
.. _py39-win64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-39-v0.7.0-f0e03c7-win64.zip
|
||||
.. _py310-win64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-310-v0.7.0-f0e03c7-win64.zip
|
||||
.. _py311-win64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-311-v0.7.0-f0e03c7-win64.zip
|
||||
.. _py36-macos64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-36-v0.7.0-f0e03c7-macos64.zip
|
||||
.. _py37-macos64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-37-v0.7.0-f0e03c7-macos64.zip
|
||||
.. _py38-macos64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-38-v0.7.0-f0e03c7-macos64.zip
|
||||
.. _py39-macos64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-39-v0.7.0-f0e03c7-macos64.zip
|
||||
.. _py310-macos64: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-310-v0.7.0-f0e03c7-macos64.zip
|
||||
.. _py37-macosm164: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-37-v0.7.0-f0e03c7-macosm164.zip
|
||||
.. _py38-macosm164: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-38-v0.7.0-f0e03c7-macosm164.zip
|
||||
.. _py39-macosm164: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-39-v0.7.0-f0e03c7-macosm164.zip
|
||||
.. _py310-macosm164: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-310-v0.7.0-f0e03c7-macosm164.zip
|
||||
.. _py311-macosm164: https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-311-v0.7.0-f0e03c7-macosm164.zip
|
||||
|
||||
2. Unzip the downloaded file and copy the ``ifcopenshell`` directory into your
|
||||
Python path. If you're not sure where your Python path is, run the following
|
||||
|
||||
@@ -11,15 +11,15 @@ Packaged installation
|
||||
---------------------
|
||||
|
||||
IfcSverchok is packaged like a regular Blender add-on, so installation is the
|
||||
same as any other Blender add-on. You can download the package for installation
|
||||
at the `Get BlenderBIM <https://blenderbim.org/download.html>`__ website.
|
||||
same as any other Blender add-on. `Download IfcSverchok here
|
||||
<https://blenderbim.org/builds/ifcsverchok-230823.zip>`__.
|
||||
|
||||
Like all Blender add-ons, they can be installed using ``Edit > Preferences >
|
||||
Addons > Install > Choose Downloaded ZIP > Enable Add-on Checkbox``. You can
|
||||
enable add-ons permanently by using ``Save User Settings`` from the Addons menu.
|
||||
|
||||
Before installing, you will also need to `install the BlenderBIM Add-on
|
||||
<../blenderbim/installation>`__ and `install Sverchok
|
||||
<https://blenderbim.org/download.html>`__ and `install Sverchok
|
||||
<https://github.com/nortikin/sverchok#installation>`__.
|
||||
|
||||
If you downloaded Blender as a ``.zip`` file without running an installer, you
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import ifcopenshell.util.unit
|
||||
import numpy as np
|
||||
|
||||
|
||||
class Usecase:
|
||||
@@ -44,7 +45,6 @@ class Usecase:
|
||||
elif self.settings["type"] == "Mesh":
|
||||
if self.settings["blender_obj"]:
|
||||
result = self.create_blender_mesh()
|
||||
representation_type = "Clipping"
|
||||
items = []
|
||||
for item in self.settings["representation"].Items:
|
||||
# For now, we don't use IfcBooleanClippingResult.
|
||||
@@ -56,23 +56,13 @@ class Usecase:
|
||||
self.settings["representation"].Items = items
|
||||
|
||||
def create_half_space_solid(self):
|
||||
clipping = self.settings["matrix"]
|
||||
return self.file.createIfcHalfSpaceSolid(
|
||||
self.file.createIfcPlane(
|
||||
self.file.createIfcAxis2Placement3D(
|
||||
self.file.createIfcCartesianPoint(
|
||||
(
|
||||
self.convert_si_to_unit(clipping[0][3]),
|
||||
self.convert_si_to_unit(clipping[1][3]),
|
||||
self.convert_si_to_unit(clipping[2][3]),
|
||||
)
|
||||
),
|
||||
self.file.createIfcDirection((clipping[0][2], clipping[1][2], clipping[2][2])),
|
||||
self.file.createIfcDirection((clipping[0][0], clipping[1][0], clipping[2][0])),
|
||||
)
|
||||
),
|
||||
False,
|
||||
)
|
||||
clipping = np.array(self.settings["matrix"])[:3]
|
||||
local_z = self.file.createIfcDirection(clipping[:, 2].tolist())
|
||||
local_x = self.file.createIfcDirection(clipping[:, 0].tolist())
|
||||
point = self.file.createIfcCartesianPoint(self.convert_si_to_unit(clipping[:, 3]).tolist())
|
||||
placement = self.file.createIfcAxis2Placement3D(point, local_z, local_x)
|
||||
plane = self.file.createIfcPlane(placement)
|
||||
return self.file.createIfcHalfSpaceSolid(plane, AgreementFlag=False)
|
||||
|
||||
def create_blender_mesh(self):
|
||||
self.ifc_vertices = []
|
||||
|
||||
@@ -61,7 +61,7 @@ class Usecase:
|
||||
wall = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcWall")
|
||||
|
||||
# Let's construct that wall!
|
||||
ifcopenshell.api.run("sequence.assign_product", relating_product=wall, related_object=task)
|
||||
ifcopenshell.api.run("sequence.assign_product", model, relating_product=wall, related_object=task)
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {
|
||||
|
||||
@@ -23,11 +23,34 @@ import ifcopenshell.util.unit
|
||||
|
||||
|
||||
def dms2dd(degrees, minutes, seconds, ms=0):
|
||||
"""Convert degrees, minutes, and (milli)seconds to decimal degrees
|
||||
|
||||
:param degrees: The degrees component
|
||||
:type degrees: int
|
||||
:param minutes: The minutes component
|
||||
:type minutes: int
|
||||
:param seconds: The seconds component
|
||||
:type seconds: int
|
||||
:param ms: The milliseconds component
|
||||
:type ms: int
|
||||
:return: The angle in decimal degrees.
|
||||
:rtype: float
|
||||
"""
|
||||
dd = float(degrees) + float(minutes) / 60.0 + float(seconds) / (3600.0) + float(ms / 3600000000.0)
|
||||
return dd
|
||||
|
||||
|
||||
def dd2dms(dd, use_ms=False):
|
||||
"""Convert decimal degrees to degrees, minutes, and (milli)seconds format
|
||||
|
||||
:param dd: The decimal degrees
|
||||
:type dd: float
|
||||
:param use_ms: True if to include milliseconds and false otherwise. Defaults to false.
|
||||
:type use_ms: bool
|
||||
:return: The angle in a tuple of either 3 or 4 values, being degrees,
|
||||
minutes, seconds, and optionally milliseconds.
|
||||
:rtype: tuple[float]
|
||||
"""
|
||||
dd = float(dd)
|
||||
sign = 1 if dd >= 0 else -1
|
||||
dd = abs(dd)
|
||||
@@ -43,6 +66,43 @@ def dd2dms(dd, use_ms=False):
|
||||
|
||||
|
||||
def xyz2enh(x, y, z, eastings, northings, orthogonal_height, x_axis_abscissa, x_axis_ordinate, scale=None):
|
||||
"""Manually convert local XYZ coordinates to map eastings, northings, and height
|
||||
|
||||
This function is for advanced users as it allows you to specify your own
|
||||
helmert transformation parameters (i.e. those typically stored in
|
||||
IfcMapConversion). This manual approach is useful for tests or in case your
|
||||
are setting your helmert transformations in non-standard locations, or if
|
||||
you are applying your own temporary false origin (such as when federating
|
||||
models for digital twins of large cities).
|
||||
|
||||
No unit conversion is performed.
|
||||
|
||||
For most scenarios you should use ``auto_xyz2enh`` instead.
|
||||
|
||||
:param x: The X local engineering coordinate.
|
||||
:type x: float
|
||||
:param y: The Y local engineering coordinate.
|
||||
:type y: float
|
||||
:param z: The Z local engineering coordinate.
|
||||
:type z: float
|
||||
:param eastings: The eastings offset to apply.
|
||||
:type eastings: float
|
||||
:param northings: The northings offset to apply.
|
||||
:type northings: float
|
||||
:param orthogonal_height: The orthogonal height offset to apply.
|
||||
:type orthogonal_height: float
|
||||
:param x_axis_abscissa: The X axis abscissa (i.e. first coordinate) of the
|
||||
2D vector that points to the local X axis when in map coordinates.
|
||||
:type x_axis_abscissa: float
|
||||
:param x_axis_ordinate: The X axis ordinate (i.e. second coordinate) of the
|
||||
2D vector that points to the local X axis when in map coordinates.
|
||||
:type x_axis_ordinate: float
|
||||
:param scale: The combined scale factor to convert from local coordinates
|
||||
to map coordinates.
|
||||
:type scale: float
|
||||
:return: A tuple of three ordinates representing the easting, northing and height.
|
||||
:rtype: tuple[float]
|
||||
"""
|
||||
if scale is None:
|
||||
scale = 1.0
|
||||
rotation = math.atan2(x_axis_ordinate, x_axis_abscissa)
|
||||
@@ -76,20 +136,60 @@ def xyz2enh_ifc4x3(
|
||||
|
||||
|
||||
def auto_xyz2enh(ifc_file, x, y, z):
|
||||
"""Convert from local XYZ coordinates to global map coordinate eastings, northings, and heights
|
||||
|
||||
The necessary georeferencing map conversion is automatically detected from
|
||||
the IFC map conversion parameters present in the IFC model. If no map
|
||||
conversion is present, then the Z coordinate is returned unchanged.
|
||||
|
||||
For IFC2X3, the map conversion is detected from the IfcProject's
|
||||
ePSet_MapConversion. See the "User Guide for Geo-referencing in IFC":
|
||||
https://www.buildingsmart.org/standards/bsi-standards/standards-library/
|
||||
|
||||
:param ifc_file: The IFC file
|
||||
:type ifc_file: ifcopenshell.file.file
|
||||
:param x: The X local engineering coordinate provided in project length units.
|
||||
:type x: float
|
||||
:param y: The Y local engineering coordinate provided in project length units.
|
||||
:type y: float
|
||||
:param z: The Z local engineering coordinate provided in project length units.
|
||||
:type z: float
|
||||
:return: The global map coordinate eastings, northings, and height in map units.
|
||||
:rtype: tuple[float]
|
||||
"""
|
||||
conversion = None
|
||||
try:
|
||||
conversion = ifc_file.by_type("IfcMapConversion")
|
||||
except:
|
||||
return (x, y, z)
|
||||
if not conversion:
|
||||
return (x, y, z)
|
||||
conversion = conversion[0]
|
||||
e = conversion.Eastings or 0
|
||||
n = conversion.Northings or 0
|
||||
h = conversion.OrthogonalHeight or 0
|
||||
xaa = conversion.XAxisAbscissa or 0
|
||||
xao = conversion.XAxisOrdinate or 0
|
||||
scale = conversion.Scale or 0
|
||||
map_unit = conversion.TargetCRS.MapUnit
|
||||
pass
|
||||
|
||||
if conversion:
|
||||
conversion = conversion[0]
|
||||
e = conversion.Eastings or 0
|
||||
n = conversion.Northings or 0
|
||||
h = conversion.OrthogonalHeight or 0
|
||||
xaa = conversion.XAxisAbscissa or 0
|
||||
xao = conversion.XAxisOrdinate or 0
|
||||
scale = conversion.Scale or 1
|
||||
map_unit = conversion.TargetCRS.MapUnit
|
||||
else:
|
||||
project = ifc_file.by_type("IfcProject")[0]
|
||||
conversion = ifcopenshell.util.element.get_pset(project, "ePSet_MapConversion")
|
||||
if not conversion:
|
||||
return (x, y, z)
|
||||
|
||||
e = conversion.get("Eastings", None) or 0
|
||||
n = conversion.get("Northings", None) or 0
|
||||
h = conversion.get("OrthogonalHeight", None) or 0
|
||||
xaa = conversion.get("XAxisAbscissa", None) or 0
|
||||
xao = conversion.get("XAxisOrdinate", None) or 0
|
||||
scale = conversion.get("Scale", None) or 1
|
||||
map_unit = None
|
||||
|
||||
if not xaa and not xao:
|
||||
xaa = 1.0
|
||||
xao = 0.0
|
||||
|
||||
if map_unit:
|
||||
# Warning! This definition has changed in IFC4X3 such that map_unit no
|
||||
# longer affects unit conversion, only the Scale attribute affects unit
|
||||
@@ -103,6 +203,74 @@ def auto_xyz2enh(ifc_file, x, y, z):
|
||||
return xyz2enh(x, y, z, e, n, h, xaa, xao, scale)
|
||||
|
||||
|
||||
def auto_enh2xyz(ifc_file, easting, northing, height):
|
||||
"""Convert from global map coordinate eastings, northings, and heights to local XYZ coordinates
|
||||
|
||||
The necessary georeferencing map conversion is automatically detected from
|
||||
the IFC map conversion parameters present in the IFC model. If no map
|
||||
conversion is present, then the Z coordinate is returned unchanged.
|
||||
|
||||
For IFC2X3, the map conversion is detected from the IfcProject's
|
||||
ePSet_MapConversion. See the "User Guide for Geo-referencing in IFC":
|
||||
https://www.buildingsmart.org/standards/bsi-standards/standards-library/
|
||||
|
||||
:param ifc_file: The IFC file
|
||||
:type ifc_file: ifcopenshell.file.file
|
||||
:param easting: The global easting map coordinate provided in map units.
|
||||
:type easting: float
|
||||
:param northing: The global northing map coordinate provided in map units.
|
||||
:type northing: float
|
||||
:param height: The global height map coordinate provided in map units.
|
||||
:type height: float
|
||||
:return: The local engineering XYZ coordinates in project length units.
|
||||
:rtype: tuple[float]
|
||||
"""
|
||||
conversion = None
|
||||
try:
|
||||
conversion = ifc_file.by_type("IfcMapConversion")
|
||||
except:
|
||||
pass
|
||||
|
||||
if conversion:
|
||||
conversion = conversion[0]
|
||||
e = conversion.Eastings or 0
|
||||
n = conversion.Northings or 0
|
||||
h = conversion.OrthogonalHeight or 0
|
||||
xaa = conversion.XAxisAbscissa or 0
|
||||
xao = conversion.XAxisOrdinate or 0
|
||||
scale = conversion.Scale or 1
|
||||
map_unit = conversion.TargetCRS.MapUnit
|
||||
else:
|
||||
project = ifc_file.by_type("IfcProject")[0]
|
||||
conversion = ifcopenshell.util.element.get_pset(project, "ePSet_MapConversion")
|
||||
if not conversion:
|
||||
return (easting, northing, height)
|
||||
|
||||
e = conversion.get("Eastings", None) or 0
|
||||
n = conversion.get("Northings", None) or 0
|
||||
h = conversion.get("OrthogonalHeight", None) or 0
|
||||
xaa = conversion.get("XAxisAbscissa", None) or 0
|
||||
xao = conversion.get("XAxisOrdinate", None) or 0
|
||||
scale = conversion.get("Scale", None) or 1
|
||||
map_unit = None
|
||||
|
||||
if not xaa and not xao:
|
||||
xaa = 1.0
|
||||
xao = 0.0
|
||||
|
||||
if map_unit:
|
||||
# Warning! This definition has changed in IFC4X3 such that map_unit no
|
||||
# longer affects unit conversion, only the Scale attribute affects unit
|
||||
# conversion. TODO: consolidate once IFC4X3 confirmed.
|
||||
project_unit = ifcopenshell.util.unit.get_project_unit(ifc_file, "LENGTHUNIT")
|
||||
map_prefix = getattr(map_unit, "Prefix", None)
|
||||
project_prefix = getattr(project_unit, "Prefix", None)
|
||||
e = ifcopenshell.util.unit.convert(e, map_prefix, map_unit.Name, project_prefix, project_unit.Name)
|
||||
n = ifcopenshell.util.unit.convert(n, map_prefix, map_unit.Name, project_prefix, project_unit.Name)
|
||||
h = ifcopenshell.util.unit.convert(h, map_prefix, map_unit.Name, project_prefix, project_unit.Name)
|
||||
return enh2xyz(easting, northing, height, e, n, h, xaa, xao, scale)
|
||||
|
||||
|
||||
def auto_z2e(ifc_file, z):
|
||||
"""Convert a Z coordinate to an elevation using model georeferencing data
|
||||
|
||||
@@ -110,6 +278,10 @@ def auto_z2e(ifc_file, z):
|
||||
the IFC map conversion parameters present in the IFC model. If no map
|
||||
conversion is present, then the Z coordinate is returned unchanged.
|
||||
|
||||
For IFC2X3, the map conversion is detected from the IfcProject's
|
||||
ePSet_MapConversion. See the "User Guide for Geo-referencing in IFC":
|
||||
https://www.buildingsmart.org/standards/bsi-standards/standards-library/
|
||||
|
||||
:param ifc_file: The IFC file
|
||||
:type ifc_file: ifcopenshell.file.file
|
||||
:param z: The Z local engineering coordinate provided in project length units.
|
||||
@@ -117,15 +289,25 @@ def auto_z2e(ifc_file, z):
|
||||
:return: The elevation in project length units.
|
||||
:rtype: float
|
||||
"""
|
||||
conversion = None
|
||||
try:
|
||||
conversion = ifc_file.by_type("IfcMapConversion")
|
||||
except:
|
||||
return z
|
||||
if not conversion or not conversion[0].OrthogonalHeight:
|
||||
return z
|
||||
conversion = conversion[0]
|
||||
h = conversion.OrthogonalHeight
|
||||
map_unit = conversion.TargetCRS.MapUnit
|
||||
pass
|
||||
|
||||
if conversion and not conversion[0].OrthogonalHeight:
|
||||
conversion = conversion[0]
|
||||
h = conversion.OrthogonalHeight
|
||||
map_unit = conversion.TargetCRS.MapUnit
|
||||
else:
|
||||
project = ifc_file.by_type("IfcProject")[0]
|
||||
conversion = ifcopenshell.util.element.get_pset(project, "ePSet_MapConversion")
|
||||
if not conversion:
|
||||
return z
|
||||
|
||||
h = conversion.get("OrthogonalHeight", None) or 0
|
||||
map_unit = None
|
||||
|
||||
if map_unit:
|
||||
# Warning! This definition has changed in IFC4X3 such that map_unit no
|
||||
# longer affects unit conversion, only the Scale attribute affects unit
|
||||
@@ -142,10 +324,61 @@ def auto_z2e(ifc_file, z):
|
||||
|
||||
|
||||
def z2e(z, h):
|
||||
"""Manually convert a Z coordinate to an elevation
|
||||
|
||||
This function is for advanced users as it allows you to specify your own
|
||||
orthogonal height offset.
|
||||
|
||||
For most scenarios you should use ``auto_z2e`` instead.
|
||||
|
||||
:param z: The Z local engineering coordinate provided in project length units.
|
||||
:type z: float
|
||||
:param h: The orthogonal height offset in project length units.
|
||||
:type h: float
|
||||
:return: The elevation in project length units.
|
||||
:rtype: float
|
||||
"""
|
||||
return z + h
|
||||
|
||||
|
||||
def enh2xyz(e, n, h, eastings, northings, orthogonal_height, x_axis_abscissa, x_axis_ordinate, scale=None):
|
||||
"""Manually convert map eastings, northings, and height to local XYZ coordinates
|
||||
|
||||
This function is for advanced users as it allows you to specify your own
|
||||
helmert transformation parameters (i.e. those typically stored in
|
||||
IfcMapConversion). This manual approach is useful for tests or in case your
|
||||
are setting your helmert transformations in non-standard locations, or if
|
||||
you are applying your own temporary false origin (such as when federating
|
||||
models for digital twins of large cities).
|
||||
|
||||
No unit conversion is performed.
|
||||
|
||||
For most scenarios you should use ``auto_enh2xyz`` instead.
|
||||
|
||||
:param e: The global easting map coordinate.
|
||||
:type e: float
|
||||
:param n: The global northing map coordinate.
|
||||
:type n: float
|
||||
:param h: The global height map coordinate.
|
||||
:type h: float
|
||||
:param eastings: The eastings offset to apply.
|
||||
:type eastings: float
|
||||
:param northings: The northings offset to apply.
|
||||
:type northings: float
|
||||
:param orthogonal_height: The orthogonal height offset to apply.
|
||||
:type orthogonal_height: float
|
||||
:param x_axis_abscissa: The X axis abscissa (i.e. first coordinate) of the
|
||||
2D vector that points to the local X axis when in map coordinates.
|
||||
:type x_axis_abscissa: float
|
||||
:param x_axis_ordinate: The X axis ordinate (i.e. second coordinate) of the
|
||||
2D vector that points to the local X axis when in map coordinates.
|
||||
:type x_axis_ordinate: float
|
||||
:param scale: The combined scale factor to convert from local coordinates
|
||||
to map coordinates.
|
||||
:type scale: float
|
||||
:return: A tuple of three ordinates representing XYZ.
|
||||
:rtype: tuple[float]
|
||||
"""
|
||||
if scale is None:
|
||||
scale = 1.0
|
||||
rotation = math.atan2(x_axis_ordinate, x_axis_abscissa)
|
||||
@@ -158,6 +391,37 @@ def enh2xyz(e, n, h, eastings, northings, orthogonal_height, x_axis_abscissa, x_
|
||||
|
||||
|
||||
def local2global(matrix, eastings, northings, orthogonal_height, x_axis_abscissa, x_axis_ordinate, scale=None):
|
||||
"""Manually convert a 4x4 matrix from local to global coordinates
|
||||
|
||||
This function is for advanced users as it allows you to specify your own
|
||||
helmert transformation parameters (i.e. those typically stored in
|
||||
IfcMapConversion). This manual approach is useful for tests or in case your
|
||||
are setting your helmert transformations in non-standard locations, or if
|
||||
you are applying your own temporary false origin (such as when federating
|
||||
models for digital twins of large cities).
|
||||
|
||||
No unit conversion is performed.
|
||||
|
||||
:param matrix: A 4x4 numpy matrix representing local coordinates.
|
||||
:type matrix: np.array
|
||||
:param eastings: The eastings offset to apply.
|
||||
:type eastings: float
|
||||
:param northings: The northings offset to apply.
|
||||
:type northings: float
|
||||
:param orthogonal_height: The orthogonal height offset to apply.
|
||||
:type orthogonal_height: float
|
||||
:param x_axis_abscissa: The X axis abscissa (i.e. first coordinate) of the
|
||||
2D vector that points to the local X axis when in map coordinates.
|
||||
:type x_axis_abscissa: float
|
||||
:param x_axis_ordinate: The X axis ordinate (i.e. second coordinate) of the
|
||||
2D vector that points to the local X axis when in map coordinates.
|
||||
:type x_axis_ordinate: float
|
||||
:param scale: The combined scale factor to convert from local coordinates
|
||||
to map coordinates.
|
||||
:type scale: float
|
||||
:return: A numpy 4x4 array matrix representing global coordinates.
|
||||
:rtype: np.array
|
||||
"""
|
||||
if scale is None:
|
||||
scale = 1.0
|
||||
x = np.array([x_axis_abscissa, x_axis_ordinate, 0])
|
||||
@@ -221,6 +485,37 @@ def local2global_ifc4x3(
|
||||
|
||||
|
||||
def global2local(matrix, eastings, northings, orthogonal_height, x_axis_abscissa, x_axis_ordinate, scale=None):
|
||||
"""Manually convert a 4x4 matrix from global to local coordinates
|
||||
|
||||
This function is for advanced users as it allows you to specify your own
|
||||
helmert transformation parameters (i.e. those typically stored in
|
||||
IfcMapConversion). This manual approach is useful for tests or in case your
|
||||
are setting your helmert transformations in non-standard locations, or if
|
||||
you are applying your own temporary false origin (such as when federating
|
||||
models for digital twins of large cities).
|
||||
|
||||
No unit conversion is performed.
|
||||
|
||||
:param matrix: A 4x4 numpy matrix representing global coordinates.
|
||||
:type matrix: np.array
|
||||
:param eastings: The eastings offset to apply.
|
||||
:type eastings: float
|
||||
:param northings: The northings offset to apply.
|
||||
:type northings: float
|
||||
:param orthogonal_height: The orthogonal height offset to apply.
|
||||
:type orthogonal_height: float
|
||||
:param x_axis_abscissa: The X axis abscissa (i.e. first coordinate) of the
|
||||
2D vector that points to the local X axis when in map coordinates.
|
||||
:type x_axis_abscissa: float
|
||||
:param x_axis_ordinate: The X axis ordinate (i.e. second coordinate) of the
|
||||
2D vector that points to the local X axis when in map coordinates.
|
||||
:type x_axis_ordinate: float
|
||||
:param scale: The combined scale factor to convert from local coordinates
|
||||
to map coordinates.
|
||||
:type scale: float
|
||||
:return: A numpy 4x4 array matrix representing local coordinates.
|
||||
:rtype: np.array
|
||||
"""
|
||||
if scale is None:
|
||||
scale = 1.0
|
||||
x = np.array([x_axis_abscissa, x_axis_ordinate, 0])
|
||||
@@ -245,13 +540,31 @@ def global2local(matrix, eastings, northings, orthogonal_height, x_axis_abscissa
|
||||
)
|
||||
|
||||
|
||||
# Used for converting the X and Y vectors of the X Axis in IFC grid north geolocation
|
||||
def xaxis2angle(x, y):
|
||||
"""Converts X axis abscissa and ordinates to an angle in decimal degrees
|
||||
|
||||
:param x: The X axis abscissa
|
||||
:type x: float
|
||||
:param y: The X axis ordinate
|
||||
:type y: float
|
||||
:return: The equivalent angle in decimal degrees from the X axis
|
||||
:rtype: float
|
||||
"""
|
||||
return math.degrees(math.atan2(y, x)) * -1
|
||||
|
||||
|
||||
# Used for converting the X and Y vectors of the Y Axis in IFC true north geolocation
|
||||
def yaxis2angle(x, y):
|
||||
"""Converts Y axis abscissa and ordinates to an angle in decimal degrees
|
||||
|
||||
The Y axis abscissa and ordinate is how IFC stores true north.
|
||||
|
||||
:param x: The Y axis abscissa
|
||||
:type x: float
|
||||
:param y: The Y axis ordinate
|
||||
:type y: float
|
||||
:return: The equivalent angle in decimal degrees from the Y axis
|
||||
:rtype: float
|
||||
"""
|
||||
angle = math.degrees(math.atan2(y, x)) - 90
|
||||
if angle < -180:
|
||||
angle += 360
|
||||
@@ -261,36 +574,94 @@ def yaxis2angle(x, y):
|
||||
|
||||
|
||||
def get_grid_north(ifc_file):
|
||||
"""Get an angle pointing to map grid north
|
||||
|
||||
Anticlockwise is positive.
|
||||
|
||||
The necessary georeferencing map conversion is automatically detected from
|
||||
the IFC map conversion parameters present in the IFC model. If no map
|
||||
conversion is present, then the Z coordinate is returned unchanged.
|
||||
|
||||
For IFC2X3, the map conversion is detected from the IfcProject's
|
||||
ePSet_MapConversion. See the "User Guide for Geo-referencing in IFC":
|
||||
https://www.buildingsmart.org/standards/bsi-standards/standards-library/
|
||||
|
||||
:param ifc_file: The IFC file
|
||||
:type ifc_file: ifcopenshell.file.file
|
||||
:return: An angle to grid north in decimal degrees
|
||||
:rtype: float
|
||||
"""
|
||||
conversion = None
|
||||
try:
|
||||
conversion = ifc_file.by_type("IfcMapConversion")[0]
|
||||
except:
|
||||
return 0
|
||||
if not conversion.XAxisAbscissa or not conversion.XAxisOrdinate:
|
||||
return 0
|
||||
return xaxis2angle(conversion.XAxisAbscissa, conversion.XAxisOrdinate)
|
||||
pass
|
||||
if conversion:
|
||||
if not conversion.XAxisAbscissa or not conversion.XAxisOrdinate:
|
||||
return 0
|
||||
xaa = conversion.XAxisAbscissa
|
||||
xao = conversion.XAxisOrdinate
|
||||
else:
|
||||
project = ifc_file.by_type("IfcProject")[0]
|
||||
conversion = ifcopenshell.util.element.get_pset(project, "ePSet_MapConversion")
|
||||
if not conversion:
|
||||
return 0
|
||||
xaa = conversion.get("XAxisAbscissa", None) or 0
|
||||
xao = conversion.get("XAxisOrdinate", None) or 0
|
||||
return xaxis2angle(xaa, xao)
|
||||
|
||||
|
||||
def get_true_north(ifc_file):
|
||||
"""Get an angle pointing to global true north
|
||||
|
||||
Anticlockwise is positive.
|
||||
|
||||
Always remember that true north is not a constant! (Unless you are working
|
||||
in polar coordinates) This true north is only a reference value useful for
|
||||
things like solar analysis on small sites (<1km). If you're after the north
|
||||
that your surveyor is using, you're probably after ``get_grid_north``
|
||||
instead.
|
||||
|
||||
:param ifc_file: The IFC file
|
||||
:type ifc_file: ifcopenshell.file.file
|
||||
:return: An angle to true north in decimal degrees
|
||||
:rtype: float
|
||||
"""
|
||||
try:
|
||||
for context in ifc_file.by_type("IfcGeometricRepresentationContext", include_subtypes=False):
|
||||
if not context.TrueNorth:
|
||||
continue
|
||||
return yaxis2angle(*context.TrueNorth.DirectionRatios[0:2])
|
||||
if context.TrueNorth:
|
||||
return yaxis2angle(*context.TrueNorth.DirectionRatios[0:2])
|
||||
except:
|
||||
return 0
|
||||
return 0
|
||||
|
||||
|
||||
# Used for converting an angle in degrees to return the X and Y vectors of the X Axis in IFC grid north geolocation:
|
||||
def angle2xaxis(angle):
|
||||
"""Converts an angle into an X axis abscissa and ordinate
|
||||
|
||||
The inverse of ``xaxis2angle``.
|
||||
|
||||
:param angle: The angle in decimal degrees where anticlockwise is positive.
|
||||
:type angle: float
|
||||
:return: A tuple of X axis abscissa and ordinate
|
||||
:rtype: tuple[float]
|
||||
"""
|
||||
angle_rad = math.radians(angle)
|
||||
x = math.cos(angle_rad)
|
||||
y = -math.sin(angle_rad)
|
||||
return x, y
|
||||
|
||||
|
||||
# Used for converting True North angle as seen in CAD (relative to +Y)
|
||||
def angle2yaxis(angle):
|
||||
"""Converts an angle into an Y axis abscissa and ordinate
|
||||
|
||||
The inverse of ``yaxis2angle``.
|
||||
|
||||
:param angle: The angle in decimal degrees where anticlockwise is positive.
|
||||
:type angle: float
|
||||
:return: A tuple of Y axis abscissa and ordinate
|
||||
:rtype: tuple[float]
|
||||
"""
|
||||
angle_rad = math.radians(angle)
|
||||
x = -math.sin(angle_rad)
|
||||
y = math.cos(angle_rad)
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2023 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell 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 Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import test.bootstrap
|
||||
import ifcopenshell.api
|
||||
import numpy as np
|
||||
from ifcopenshell.util.shape_builder import ShapeBuilder
|
||||
|
||||
|
||||
class TestAddBoolean(test.bootstrap.IFC4):
|
||||
def test_returning_ifc_boolean_result(self):
|
||||
ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcProject")
|
||||
model = ifcopenshell.api.run("context.add_context", self.file, context_type="Model")
|
||||
body = ifcopenshell.api.run(
|
||||
"context.add_context",
|
||||
self.file,
|
||||
context_type="Model",
|
||||
context_identifier="Body",
|
||||
target_view="MODEL_VIEW",
|
||||
parent=model,
|
||||
)
|
||||
wall = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
|
||||
|
||||
builder = ShapeBuilder(self.file)
|
||||
extrusion = builder.extrude(builder.rectangle())
|
||||
rep = builder.get_representation(body, extrusion)
|
||||
ifcopenshell.api.run("geometry.assign_representation", self.file, product=wall, representation=rep)
|
||||
|
||||
ifcopenshell.api.run("geometry.add_boolean", self.file, representation=rep, matrix=np.eye(4))
|
||||
assert rep.Items[0].is_a() == "IfcBooleanResult"
|
||||
assert rep.RepresentationType == "CSG"
|
||||
@@ -0,0 +1,51 @@
|
||||
# IfcPatch - IFC patching utiliy
|
||||
# Copyright (C) 2023 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of IfcPatch.
|
||||
#
|
||||
# IfcPatch is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcPatch 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 Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcPatch. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.element
|
||||
|
||||
|
||||
class Patcher:
|
||||
def __init__(self, src, file, logger):
|
||||
"""Reassigns occurrence classifications to types
|
||||
|
||||
Revit has a bug (see https://github.com/Autodesk/revit-ifc/issues/691)
|
||||
where it assigns classification codes to occurrences instead of types.
|
||||
Almost always, this is not what you want. This patch reassigns all
|
||||
classifications to their respective types.
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
ifcpatch.execute({"input": model, "recipe": "FixRevitClassificationCodeTypes"})
|
||||
"""
|
||||
self.src = src
|
||||
self.file = file
|
||||
self.logger = logger
|
||||
|
||||
def patch(self):
|
||||
for rel in self.file.by_type("IfcRelAssociatesClassification"):
|
||||
related_types = set()
|
||||
for related_object in rel.RelatedObjects:
|
||||
relating_type = ifcopenshell.util.element.get_type(related_object)
|
||||
if relating_type:
|
||||
related_types.add(relating_type)
|
||||
else:
|
||||
related_types.add(related_object)
|
||||
rel.RelatedObjects = list(related_types)
|
||||
@@ -0,0 +1,60 @@
|
||||
# IfcPatch - IFC patching utiliy
|
||||
# Copyright (C) 2023 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of IfcPatch.
|
||||
#
|
||||
# IfcPatch is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcPatch 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 Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcPatch. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
class Patcher:
|
||||
def __init__(self, src, file, logger):
|
||||
"""Removes the built-in Revit Uniformat classification.
|
||||
|
||||
Revit has a bug (see https://github.com/Autodesk/revit-ifc/issues/486)
|
||||
where it always inserts a Uniformat classification regardless if your
|
||||
project needs it or not. This patch removes it.
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
ifcpatch.execute({"input": model, "recipe": "RemoveRevitUniformatClassification"})
|
||||
"""
|
||||
self.src = src
|
||||
self.file = file
|
||||
self.logger = logger
|
||||
|
||||
def patch(self):
|
||||
for classification in self.file.by_type("IfcClassification"):
|
||||
if classification.Name != "Uniformat":
|
||||
continue
|
||||
references = self.get_references(classification)
|
||||
for reference in references:
|
||||
self.file.remove(reference)
|
||||
self.file.remove(classification)
|
||||
for rel in self.file.by_type("IfcRelAssociatesClassification"):
|
||||
if not rel.RelatingClassification:
|
||||
self.file.remove(rel)
|
||||
for rel in self.file.by_type("IfcExternalReferenceRelationship"):
|
||||
if not rel.RelatingReference:
|
||||
self.file.remove(rel)
|
||||
|
||||
def get_references(self, classification):
|
||||
results = []
|
||||
if not classification.HasReferences:
|
||||
return results
|
||||
for reference in classification.HasReferences:
|
||||
results.append(reference)
|
||||
results.extend(self.get_references(reference))
|
||||
return results
|
||||
@@ -48,7 +48,7 @@ def get_pset(element, pset):
|
||||
|
||||
|
||||
@lru_cache
|
||||
def get_psets(element, pset):
|
||||
def get_psets(element):
|
||||
return ifcopenshell.util.element.get_psets(element)
|
||||
|
||||
|
||||
|
||||
@@ -24,4 +24,4 @@ dependencies = [
|
||||
include = ["ifctester"]
|
||||
exclude = ["test*"]
|
||||
[tool.setuptools.package-data]
|
||||
ifctester = ["*.xsd"]
|
||||
ifctester = ["*.xsd", "templates/*.html"]
|
||||
|
||||
@@ -23,6 +23,10 @@
|
||||
|
||||
#include "../ifcparse/utils.h"
|
||||
|
||||
#ifdef WITH_PROJ
|
||||
#include <proj.h>
|
||||
#endif
|
||||
|
||||
#include <iterator>
|
||||
|
||||
static const uint32_t GLTF = 0x46546C67U;
|
||||
@@ -177,13 +181,23 @@ void GltfSerializer::write(const IfcGeom::TriangulationElement* o) {
|
||||
node_array_.push_back(json_["nodes"].size());
|
||||
|
||||
const std::vector<double>& m = o->transformation().matrix().data();
|
||||
// nb: note that this contains the Y-UP transform as well.
|
||||
const std::array<double, 16> matrix_flat = {
|
||||
m[0], m[ 2], -m[ 1], 0,
|
||||
m[3], m[ 5], -m[ 4], 0,
|
||||
m[6], m[ 8], -m[ 7], 0,
|
||||
m[9], m[11], -m[10], 1
|
||||
};
|
||||
std::array<double, 16> matrix_flat;
|
||||
if (settings_.get(SerializerSettings::WRITE_GLTF_ECEF)) {
|
||||
matrix_flat = {
|
||||
m[0], m[1], m[2], 0,
|
||||
m[3], m[4], m[5], 0,
|
||||
m[6], m[7], m[8], 0,
|
||||
m[9], m[10], m[11], 1
|
||||
};
|
||||
} else {
|
||||
// nb: note that this contains the Y-UP transform as well.
|
||||
matrix_flat = {
|
||||
m[0], m[2], -m[1], 0,
|
||||
m[3], m[5], -m[4], 0,
|
||||
m[6], m[8], -m[7], 0,
|
||||
m[9], m[11], -m[10], 1
|
||||
};
|
||||
}
|
||||
static const std::array<double, 16> identity_matrix = {1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1};
|
||||
|
||||
json node;
|
||||
@@ -317,6 +331,22 @@ void write_block(std::ostream& fs, It begin, It end) {
|
||||
}
|
||||
|
||||
void GltfSerializer::finalize() {
|
||||
if (north_rotation_) {
|
||||
(*north_rotation_)["children"] = json::array();
|
||||
for (int i = 0; i < json_["nodes"].size(); ++i) {
|
||||
(*north_rotation_)["children"].push_back(i);
|
||||
}
|
||||
json_["nodes"].push_back(*north_rotation_);
|
||||
}
|
||||
|
||||
if (ecef_transform_) {
|
||||
(*ecef_transform_)["children"] = json::array();
|
||||
for (int i = 0; i < json_["nodes"].size(); ++i) {
|
||||
(*ecef_transform_)["children"].push_back(i);
|
||||
}
|
||||
json_["nodes"].push_back(*ecef_transform_);
|
||||
}
|
||||
|
||||
tmp_fstream1_.close();
|
||||
tmp_fstream2_.close();
|
||||
|
||||
@@ -335,7 +365,11 @@ void GltfSerializer::finalize() {
|
||||
}
|
||||
|
||||
json scene_0;
|
||||
scene_0["nodes"] = node_array_;
|
||||
if (north_rotation_ || ecef_transform_) {
|
||||
scene_0["nodes"] = std::array<size_t, 1>{json_["nodes"].size() - 1};
|
||||
} else {
|
||||
scene_0["nodes"] = node_array_;
|
||||
}
|
||||
json_["scenes"].push_back(scene_0);
|
||||
|
||||
//The generated glb file will contain the indices buffer followed by the vertices buffer.
|
||||
@@ -375,4 +409,255 @@ void GltfSerializer::finalize() {
|
||||
write_padding<BIN>(fstream_, binary_length);
|
||||
}
|
||||
|
||||
namespace {
|
||||
void normalize(std::array<double, 3>& v) {
|
||||
auto l = std::sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
|
||||
|
||||
v[0] /= l;
|
||||
v[1] /= l;
|
||||
v[2] /= l;
|
||||
}
|
||||
|
||||
void cross(const std::array<double, 3>& v1, const std::array<double, 3>& v2, std::array<double, 3>& result) {
|
||||
result[0] = v1[1] * v2[2] - v1[2] * v2[1];
|
||||
result[1] = v1[2] * v2[0] - v1[0] * v2[2];
|
||||
result[2] = v1[0] * v2[1] - v1[1] * v2[0];
|
||||
}
|
||||
|
||||
void proj_log(void *, int, const char* c) {
|
||||
Logger::Error("PROJ: " + std::string(c));
|
||||
}
|
||||
}
|
||||
|
||||
void GltfSerializer::setFile(IfcParse::IfcFile* f) {
|
||||
if (!settings_.get(SerializerSettings::WRITE_GLTF_ECEF)) {
|
||||
return;
|
||||
}
|
||||
|
||||
boost::optional<std::string> crs_epsg;
|
||||
boost::optional<std::array<double, 3>> crs_x_axis;
|
||||
boost::optional<std::array<double, 3>> eastings_northings_elevation;
|
||||
|
||||
aggregate_of_instance::ptr coordops;
|
||||
try {
|
||||
coordops = f->instances_by_type("IfcCoordinateOperation");
|
||||
} catch (IfcParse::IfcException&) {
|
||||
// Ignored. Schema likely doesn't support IfcCoordinateOperation.
|
||||
}
|
||||
if (coordops) {
|
||||
for (auto& coordop : *coordops) {
|
||||
IfcUtil::IfcBaseClass* source_crs = *coordop->as<IfcUtil::IfcBaseEntity>()->get("SourceCRS");
|
||||
if (source_crs->declaration().is("IfcGeometricRepresentationContext")) {
|
||||
IfcUtil::IfcBaseClass* target_crs = *coordop->as<IfcUtil::IfcBaseEntity>()->get("TargetCRS");
|
||||
auto name_attr = target_crs->as<IfcUtil::IfcBaseEntity>()->get("Name");
|
||||
if (coordop->declaration().is("IfcMapConversion")) {
|
||||
|
||||
if (!name_attr->isNull()) {
|
||||
std::string epsg_code = *name_attr;
|
||||
crs_epsg = epsg_code;
|
||||
|
||||
// @todo in which unit are these?
|
||||
double eastings = *coordop->as<IfcUtil::IfcBaseEntity>()->get("Eastings");
|
||||
double northings = *coordop->as<IfcUtil::IfcBaseEntity>()->get("Northings");
|
||||
double height = *coordop->as<IfcUtil::IfcBaseEntity>()->get("OrthogonalHeight");
|
||||
height = 0.;
|
||||
|
||||
eastings_northings_elevation = { { eastings, northings, height} };
|
||||
|
||||
auto xaxis_attr = coordop->as<IfcUtil::IfcBaseEntity>()->get("XAxisAbscissa");
|
||||
auto yaxis_attr = coordop->as<IfcUtil::IfcBaseEntity>()->get("XAxisOrdinate");
|
||||
if (!xaxis_attr->isNull() && !yaxis_attr->isNull()) {
|
||||
double xaxis = *xaxis_attr;
|
||||
double yaxis = *yaxis_attr;
|
||||
|
||||
crs_x_axis = { { xaxis, yaxis, 0. } };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!crs_epsg) {
|
||||
auto sites = f->instances_by_type("IfcSite");
|
||||
|
||||
if (sites && sites->size() == 1) {
|
||||
auto lat_attr = (*sites->begin())->as<IfcUtil::IfcBaseEntity>()->get("RefLatitude");
|
||||
auto lon_attr = (*sites->begin())->as<IfcUtil::IfcBaseEntity>()->get("RefLongitude");
|
||||
|
||||
if (!lat_attr->isNull() && !lon_attr->isNull()) {
|
||||
std::vector<int> lat_dms = *lat_attr;
|
||||
std::vector<int> lon_dms = *lon_attr;
|
||||
|
||||
auto to_decimal = [](const std::vector<int>& dms) {
|
||||
double val = dms[0] + dms[1] / 60. + dms[2] / 3600.;
|
||||
if (dms.size() == 4) {
|
||||
val += dms[3] / 3600.e6;
|
||||
}
|
||||
return val;
|
||||
};
|
||||
|
||||
auto lat = to_decimal(lat_dms);
|
||||
auto lon = to_decimal(lon_dms);
|
||||
double elev = 0.;
|
||||
|
||||
/*
|
||||
auto elev_attr = (*sites->begin())->as<IfcUtil::IfcBaseEntity>()->get("RefElevation");
|
||||
if (!elev_attr->isNull()) {
|
||||
elev = *elev_attr;
|
||||
}
|
||||
*/
|
||||
|
||||
crs_epsg.reset("EPSG:4326");
|
||||
eastings_northings_elevation = { { lat, lon, elev } };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto contexts = f->instances_by_type_excl_subtypes("IfcGeometricRepresentationContext");
|
||||
|
||||
if (contexts && contexts->size() > 0) {
|
||||
auto context = (*contexts->begin())->as<IfcUtil::IfcBaseEntity>();
|
||||
auto north_attr = context->get("TrueNorth");
|
||||
if (!north_attr->isNull()) {
|
||||
IfcUtil::IfcBaseClass* north = *north_attr;
|
||||
if (north->declaration().is("IfcDirection")) {
|
||||
std::vector<double> ratios = *north->as<IfcUtil::IfcBaseEntity>()->get("DirectionRatios");
|
||||
crs_x_axis = { { ratios[1], -ratios[0], 0. } };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef WITH_PROJ
|
||||
|
||||
if (crs_epsg) {
|
||||
PJ_COORD wgs84_point;
|
||||
|
||||
auto C = proj_context_create();
|
||||
proj_log_func(C, nullptr, proj_log);
|
||||
|
||||
// @todo a bit ugly we assume a proj.db in current working directory.
|
||||
// a very simplistic but at least portable solution.
|
||||
proj_context_set_database_path(C, "proj.db", nullptr, nullptr);
|
||||
|
||||
if (*crs_epsg == "EPSG:4326") {
|
||||
wgs84_point = proj_coord(
|
||||
(*eastings_northings_elevation)[0],
|
||||
(*eastings_northings_elevation)[1],
|
||||
(*eastings_northings_elevation)[2],
|
||||
0);
|
||||
} else {
|
||||
// @todo a bit ugly we assume a proj.db in current working directory.
|
||||
// a very simplistic but at least portable solution.
|
||||
proj_context_set_database_path(C, "proj.db", nullptr, nullptr);
|
||||
|
||||
auto P = proj_create_crs_to_crs(
|
||||
C, crs_epsg->c_str(), "EPSG:4326",
|
||||
NULL);
|
||||
|
||||
if (!P) {
|
||||
Logger::Error("Failed to create PROJ transformation object");
|
||||
return;
|
||||
}
|
||||
|
||||
auto a = proj_coord(
|
||||
(*eastings_northings_elevation)[0],
|
||||
(*eastings_northings_elevation)[1],
|
||||
(*eastings_northings_elevation)[2],
|
||||
0);
|
||||
|
||||
wgs84_point = proj_trans(P, PJ_FWD, a);
|
||||
|
||||
Logger::Notice("Calculated latitude: " + std::to_string(wgs84_point.lp.lam) + " longitude: " + std::to_string(wgs84_point.lp.phi));
|
||||
}
|
||||
|
||||
std::swap(wgs84_point.lp.phi, wgs84_point.lp.lam);
|
||||
|
||||
const char *input_crs = "+proj=latlong +datum=WGS84";
|
||||
const char *output_crs = "+proj=geocent +datum=WGS84 +units=m";
|
||||
|
||||
// Create a transformation object
|
||||
PJ *transform = proj_create_crs_to_crs(C, input_crs, output_crs, NULL);
|
||||
|
||||
// Perform the transformation
|
||||
PJ_COORD output_point = proj_trans(transform, PJ_FWD, wgs84_point);
|
||||
|
||||
// Extract the ECEF coordinates
|
||||
double x = output_point.xyz.x;
|
||||
double y = output_point.xyz.y;
|
||||
double z = output_point.xyz.z;
|
||||
|
||||
const char *ellipsoid_def = "WGS84";
|
||||
|
||||
// Create a CRS object representing the ellipsoid
|
||||
PJ *ellipsoid_crs = proj_create(C, ellipsoid_def);
|
||||
|
||||
if (!ellipsoid_crs) {
|
||||
Logger::Error("Failed to create ellipsoid CRS");
|
||||
return;
|
||||
}
|
||||
|
||||
auto ellipse = proj_get_ellipsoid(C, ellipsoid_crs);
|
||||
|
||||
|
||||
int _;
|
||||
double semi_major, semi_minor, __;
|
||||
proj_ellipsoid_get_parameters(C, ellipse, &semi_major, &semi_minor, &_, &__);
|
||||
|
||||
std::array<double, 3> dxyz = { {
|
||||
x * (1. / (semi_major * semi_major)),
|
||||
y * (1. / (semi_major * semi_major)),
|
||||
z * (1. / (semi_minor * semi_minor))
|
||||
} };
|
||||
normalize(dxyz);
|
||||
|
||||
// Oblate spheroid, so X and Y axis are equal, so rotation around Z yields east axis.
|
||||
std::array<double, 3> east_xyz = { {
|
||||
-y,
|
||||
x,
|
||||
0.
|
||||
} };
|
||||
normalize(east_xyz);
|
||||
|
||||
std::array<double, 3> north;
|
||||
cross(dxyz, east_xyz, north);
|
||||
|
||||
std::array<double, 16> matrix = {
|
||||
east_xyz[0], east_xyz[1], east_xyz[2], 0,
|
||||
north[0], north[1], north[2], 0.,
|
||||
dxyz[0], dxyz[1], dxyz[2], 0,
|
||||
0,0,0,1
|
||||
};
|
||||
|
||||
ecef_transform_ = json::object({
|
||||
{"matrix", matrix }
|
||||
});
|
||||
|
||||
json_["extensions"]["CESIUM_RTC"]["center"] = std::array<double, 3>{ {x, y, z} };
|
||||
json_["extensionsUsed"].push_back("CESIUM_RTC");
|
||||
|
||||
// Clean up
|
||||
proj_destroy(ellipsoid_crs);
|
||||
proj_destroy(transform);
|
||||
proj_context_destroy(C);
|
||||
}
|
||||
|
||||
if (crs_x_axis) {
|
||||
normalize(*crs_x_axis);
|
||||
|
||||
auto phi = std::atan2((*crs_x_axis)[1], (*crs_x_axis)[0]);
|
||||
|
||||
north_rotation_ = json::object({
|
||||
{"matrix", std::array<double, 16>{
|
||||
+std::cos(-phi), -std::sin(-phi), 0., 0.,
|
||||
+std::sin(-phi), +std::cos(-phi), 0., 0.,
|
||||
0., 0., 1., 0.,
|
||||
0., 0., 0., 1.
|
||||
}}
|
||||
});
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
@@ -36,9 +36,9 @@ private:
|
||||
std::ofstream fstream_, tmp_fstream1_, tmp_fstream2_;
|
||||
std::map<std::string, int> materials_, meshes_;
|
||||
json json_, node_array_;
|
||||
boost::optional<json> ecef_transform_, north_rotation_;
|
||||
int bufferViewId;
|
||||
|
||||
|
||||
int writeMaterial(const IfcGeom::Material& style);
|
||||
public:
|
||||
GltfSerializer(const std::string& filename, const SerializerSettings& settings);
|
||||
@@ -50,7 +50,7 @@ public:
|
||||
void finalize();
|
||||
bool isTesselated() const { return true; }
|
||||
void setUnitNameAndMagnitude(const std::string& /*name*/, float /*magnitude*/) {}
|
||||
void setFile(IfcParse::IfcFile*) {}
|
||||
void setFile(IfcParse::IfcFile*);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -193,6 +193,41 @@ IF "%IFCOS_INSTALL_PYTHON%"=="TRUE" (
|
||||
echo PYTHONHOME=%PYTHONHOME%>>"%~dp0\%BUILD_DEPS_CACHE_PATH%"
|
||||
)
|
||||
|
||||
|
||||
:proj
|
||||
|
||||
set DEPENDENCY_NAME=sqlite3
|
||||
md %INSTALL_DIR%\sqlite3\lib %INSTALL_DIR%\sqlite3\bin %INSTALL_DIR%\sqlite3\include
|
||||
call :DownloadFile https://www.sqlite.org/2023/sqlite-amalgamation-3430100.zip "%DEPS_DIR%" sqlite-amalgamation-3430100.zip
|
||||
IF NOT %ERRORLEVEL%==0 GOTO :Error
|
||||
call :ExtractArchive sqlite-amalgamation-3430100.zip "%DEPS_DIR%" "%DEPS_DIR%\sqlite-amalgamation-3430100"
|
||||
IF NOT %ERRORLEVEL%==0 GOTO :Error
|
||||
pushd "%DEPS_DIR%\sqlite-amalgamation-3430100"
|
||||
cl /c sqlite3.c
|
||||
lib /OUT:%INSTALL_DIR%\sqlite3\lib\sqlite3.lib sqlite3.obj
|
||||
cl sqlite3.c shell.c /link /out:%INSTALL_DIR%\sqlite3\bin\sqlite3.exe
|
||||
copy sqlite3.h %INSTALL_DIR%\sqlite3\include
|
||||
popd
|
||||
|
||||
set DEPENDENCY_NAME=proj
|
||||
set DEPENDENCY_DIR=%DEPS_DIR%\proj-9.2.1
|
||||
call :DownloadFile https://download.osgeo.org/proj/proj-9.2.1.zip "%DEPS_DIR%" proj-9.2.1.zip
|
||||
IF NOT %ERRORLEVEL%==0 GOTO :Error
|
||||
call :ExtractArchive proj-9.2.1.zip "%DEPS_DIR%" "%DEPS_DIR%\proj-9.2.1"
|
||||
IF NOT %ERRORLEVEL%==0 GOTO :Error
|
||||
cd "%DEPENDENCY_DIR%"
|
||||
call :RunCMake -DCMAKE_INSTALL_PREFIX="%INSTALL_DIR%\proj-9.2.1" ^
|
||||
-DSQLITE3_INCLUDE_DIR=%INSTALL_DIR%\sqlite3\include -DSQLITE3_LIBRARY=%INSTALL_DIR%\sqlite3\lib\sqlite3.lib ^
|
||||
-DENABLE_TIFF=Off -DENABLE_CURL=Off -DBUILD_PROJSYNC=Off ^
|
||||
-DBUILD_SHARED_LIBS=Off
|
||||
IF NOT %ERRORLEVEL%==0 GOTO :Error
|
||||
call :BuildSolution "%DEPENDENCY_DIR%\%BUILD_DIR%\PROJ.sln" %BUILD_CFG%
|
||||
IF NOT %ERRORLEVEL%==0 GOTO :Error
|
||||
call :InstallCMakeProject "%DEPENDENCY_DIR%\%BUILD_DIR%" %BUILD_CFG%
|
||||
IF NOT %ERRORLEVEL%==0 GOTO :Error
|
||||
|
||||
goto :Successful
|
||||
|
||||
:mpir
|
||||
set DEPENDENCY_NAME=mpir
|
||||
set DEPENDENCY_DIR=%DEPS_DIR%\mpir
|
||||
|
||||
Reference in New Issue
Block a user