mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-18 22:33:33 +00:00
Partial spatial tool refactor
This commit is contained in:
@@ -106,6 +106,7 @@ classes = (
|
|||||||
slab.SetArcIndex,
|
slab.SetArcIndex,
|
||||||
space.GenerateSpace,
|
space.GenerateSpace,
|
||||||
space.GenerateSpacesFromWalls,
|
space.GenerateSpacesFromWalls,
|
||||||
|
space.GenerateFlooringCoveringsFromWalls,
|
||||||
space.ToggleSpaceVisibility,
|
space.ToggleSpaceVisibility,
|
||||||
mep.FitFlowSegments,
|
mep.FitFlowSegments,
|
||||||
mep.RegenerateDistributionElement,
|
mep.RegenerateDistributionElement,
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import shapely
|
|||||||
import ifcopenshell
|
import ifcopenshell
|
||||||
import ifcopenshell.util.element
|
import ifcopenshell.util.element
|
||||||
import blenderbim.tool as tool
|
import blenderbim.tool as tool
|
||||||
|
import blenderbim.core.spatial as core
|
||||||
import blenderbim.core.type
|
import blenderbim.core.type
|
||||||
from math import pi
|
from math import pi
|
||||||
from mathutils import Vector, Matrix
|
from mathutils import Vector, Matrix
|
||||||
@@ -230,182 +231,29 @@ class GenerateSpacesFromWalls(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
def _execute(self, context):
|
def _execute(self, context):
|
||||||
# This only works based on a 2D plan only considering the standard
|
# This only works based on a 2D plan only considering the standard
|
||||||
# walls (i.e. prismatic) in the active object storey.
|
# walls (i.e. prismatic) in the active object storey.
|
||||||
# In order to run, the active objct must be a wall and
|
# In order to run, the active object must be a wall and
|
||||||
# have to be selected walls
|
# there must be selected walls
|
||||||
props = context.scene.BIMModelProperties
|
core.generate_spaces_from_walls(tool.Ifc, tool.Spatial, tool.Collector)
|
||||||
|
|
||||||
|
class GenerateFlooringCoveringsFromWalls(bpy.types.Operator, tool.Ifc.Operator):
|
||||||
|
bl_idname = "bim.generate_flooring_coverings_from_walls"
|
||||||
|
bl_label = "Generate Flooring Coverings From Walls"
|
||||||
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
bl_description = "Generate flooring coverings from selected walls. The active object must be a wall"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def poll(cls, context):
|
||||||
active_obj = bpy.context.active_object
|
active_obj = bpy.context.active_object
|
||||||
|
|
||||||
if not active_obj:
|
|
||||||
self.report({"ERROR"}, "No active object. Please select a wall")
|
|
||||||
return
|
|
||||||
|
|
||||||
element = tool.Ifc.get_entity(active_obj)
|
element = tool.Ifc.get_entity(active_obj)
|
||||||
if element and not element.is_a("IfcWall"):
|
if element:
|
||||||
return self.report({"ERROR"}, "The active object is not a wall. Please select a wall.")
|
return context.selected_objects and element.is_a("IfcWall")
|
||||||
|
|
||||||
container = ifcopenshell.util.element.get_container(element)
|
|
||||||
if not container:
|
|
||||||
self.report({"ERROR"}, "The wall is not contained.")
|
|
||||||
|
|
||||||
if not bpy.context.selected_objects:
|
|
||||||
self.report({"ERROR"}, "No selected objects found. Please select walls.")
|
|
||||||
return
|
|
||||||
|
|
||||||
x, y, z = active_obj.matrix_world.translation.xyz
|
|
||||||
mat = active_obj.matrix_world
|
|
||||||
h = active_obj.dimensions.z
|
|
||||||
selected_objects = bpy.context.selected_objects
|
|
||||||
|
|
||||||
boundary_elements = self.get_boundary_elements(selected_objects)
|
|
||||||
|
|
||||||
polys = self.get_polygons(boundary_elements)
|
|
||||||
|
|
||||||
converted_tolerance = self.get_converted_tolerance(tolerance=0.03)
|
|
||||||
|
|
||||||
union = shapely.ops.unary_union(polys).buffer(converted_tolerance, cap_style=2, join_style=2)
|
|
||||||
|
|
||||||
union = self.get_purged_inner_holes_poly(union_geom=union, min_area=self.get_converted_tolerance(tolerance=3))
|
|
||||||
|
|
||||||
for i, linear_ring in enumerate(union.interiors):
|
|
||||||
poly = Polygon(linear_ring)
|
|
||||||
poly = poly.buffer(converted_tolerance, single_sided=True, cap_style=2, join_style=2)
|
|
||||||
|
|
||||||
bm = self.get_bmesh_from_polygon(poly, mat, h)
|
|
||||||
|
|
||||||
name = "Space" + str(i)
|
|
||||||
mesh = bpy.data.meshes.new(name=name)
|
|
||||||
bm.to_mesh(mesh)
|
|
||||||
bm.free()
|
|
||||||
|
|
||||||
obj = bpy.data.objects.new(name, mesh)
|
|
||||||
obj.matrix_world = mat
|
|
||||||
|
|
||||||
self.set_obj_origin_to_bboxcenter(obj)
|
|
||||||
|
|
||||||
if z != 0:
|
|
||||||
obj.location = obj.location + Vector((0, 0, z))
|
|
||||||
|
|
||||||
context.view_layer.active_layer_collection.collection.objects.link(obj)
|
|
||||||
bpy.ops.bim.assign_class(obj=obj.name, ifc_class="IfcSpace")
|
|
||||||
container_obj = tool.Ifc.get_object(container)
|
|
||||||
blenderbim.core.spatial.assign_container(
|
|
||||||
tool.Ifc, tool.Collector, tool.Spatial, structure_obj=container_obj, element_obj=obj
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_boundary_elements(self, selected_objects):
|
|
||||||
boundary_elements = []
|
|
||||||
for obj in selected_objects:
|
|
||||||
subelement = tool.Ifc.get_entity(obj)
|
|
||||||
if subelement.is_a("IfcWall") or subelement.is_a("IfcColumn"):
|
|
||||||
boundary_elements.append(subelement)
|
|
||||||
return boundary_elements
|
|
||||||
|
|
||||||
def get_polygons(self, boundary_elements):
|
|
||||||
polys = []
|
|
||||||
for boundary_element in boundary_elements:
|
|
||||||
obj = tool.Ifc.get_object(boundary_element)
|
|
||||||
if not obj:
|
|
||||||
continue
|
|
||||||
points = []
|
|
||||||
base = self.get_obj_base_points(obj)
|
|
||||||
for index in ["low_left", "low_right", "high_right", "high_left"]:
|
|
||||||
point = base[index]
|
|
||||||
points.append(point)
|
|
||||||
|
|
||||||
polys.append(Polygon(points))
|
|
||||||
return polys
|
|
||||||
|
|
||||||
def get_obj_base_points(self, obj):
|
|
||||||
x_values = [(obj.matrix_world @ Vector(v)).x for v in obj.bound_box]
|
|
||||||
y_values = [(obj.matrix_world @ Vector(v)).y for v in obj.bound_box]
|
|
||||||
return {
|
|
||||||
"low_left": (x_values[0], y_values[0]),
|
|
||||||
"high_left": (x_values[3], y_values[3]),
|
|
||||||
"low_right": (x_values[4], y_values[4]),
|
|
||||||
"high_right": (x_values[7], y_values[7]),
|
|
||||||
}
|
|
||||||
|
|
||||||
def get_converted_tolerance(self, tolerance):
|
|
||||||
model = tool.Ifc.get()
|
|
||||||
project_unit = ifcopenshell.util.unit.get_project_unit(model, "LENGTHUNIT")
|
|
||||||
prefix = getattr(project_unit, "Prefix", None)
|
|
||||||
|
|
||||||
converted_tolerance = ifcopenshell.util.unit.convert(
|
|
||||||
value=tolerance,
|
|
||||||
from_prefix=None,
|
|
||||||
from_unit="METRE",
|
|
||||||
to_prefix=prefix,
|
|
||||||
to_unit=project_unit.Name,
|
|
||||||
)
|
|
||||||
return tolerance
|
|
||||||
|
|
||||||
def get_purged_inner_holes_poly(self, union_geom, min_area):
|
|
||||||
interiors_list = []
|
|
||||||
|
|
||||||
if union_geom.geom_type == "MultiPolygon":
|
|
||||||
for poly in union_geom.geoms:
|
|
||||||
interiors_list = self.get_poly_valid_interior_list(
|
|
||||||
poly=poly, min_area=min_area, interiors_list=interiors_list
|
|
||||||
)
|
|
||||||
|
|
||||||
new_poly = Polygon(poly.exterior.coords, holes=interiors_list)
|
|
||||||
|
|
||||||
if union_geom.geom_type == "Polygon":
|
|
||||||
interiors_list = self.get_poly_valid_interior_list(
|
|
||||||
poly=union_geom, min_area=min_area, interiors_list=interiors_list
|
|
||||||
)
|
|
||||||
new_poly = Polygon(union_geom.exterior.coords, holes=interiors_list)
|
|
||||||
|
|
||||||
return new_poly
|
|
||||||
|
|
||||||
def get_poly_valid_interior_list(self, poly, min_area, interiors_list):
|
|
||||||
for interior in poly.interiors:
|
|
||||||
p = Polygon(interior)
|
|
||||||
if p.area >= min_area:
|
|
||||||
interiors_list.append(interior)
|
|
||||||
return interiors_list
|
|
||||||
|
|
||||||
def get_bmesh_from_polygon(self, poly, mat, h):
|
|
||||||
bm = bmesh.new()
|
|
||||||
bm.verts.index_update()
|
|
||||||
bm.edges.index_update()
|
|
||||||
|
|
||||||
mat_invert = mat.inverted()
|
|
||||||
|
|
||||||
new_verts = [bm.verts.new(mat_invert @ Vector([v[0], v[1], 0])) for v in poly.exterior.coords[0:-1]]
|
|
||||||
[bm.edges.new((new_verts[i], new_verts[i + 1])) for i in range(len(new_verts) - 1)]
|
|
||||||
bm.edges.new((new_verts[len(new_verts) - 1], new_verts[0]))
|
|
||||||
|
|
||||||
bm.verts.index_update()
|
|
||||||
bm.edges.index_update()
|
|
||||||
|
|
||||||
bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=1e-5)
|
|
||||||
bmesh.ops.triangle_fill(bm, edges=bm.edges)
|
|
||||||
bmesh.ops.dissolve_limit(bm, angle_limit=pi / 180 * 5, verts=bm.verts, edges=bm.edges)
|
|
||||||
|
|
||||||
extrusion = bmesh.ops.extrude_face_region(bm, geom=bm.faces)
|
|
||||||
extruded_verts = [g for g in extrusion["geom"] if isinstance(g, bmesh.types.BMVert)]
|
|
||||||
bmesh.ops.translate(bm, vec=[0.0, 0.0, h], verts=extruded_verts)
|
|
||||||
|
|
||||||
bmesh.ops.recalc_face_normals(bm, faces=bm.faces)
|
|
||||||
|
|
||||||
return bm
|
|
||||||
|
|
||||||
def set_obj_origin_to_bboxcenter(self, obj):
|
|
||||||
mat = obj.matrix_world
|
|
||||||
inverted = mat.inverted()
|
|
||||||
local_bbox_center = 0.125 * sum((Vector(b) for b in obj.bound_box), Vector())
|
|
||||||
global_bbox_center = mat @ local_bbox_center
|
|
||||||
|
|
||||||
oldLoc = obj.location
|
|
||||||
newLoc = global_bbox_center
|
|
||||||
diff = newLoc - oldLoc
|
|
||||||
for vert in obj.data.vertices:
|
|
||||||
aux_vector = mat @ vert.co
|
|
||||||
aux_vector = aux_vector - diff
|
|
||||||
vert.co = inverted @ aux_vector
|
|
||||||
obj.location = newLoc
|
|
||||||
|
|
||||||
|
def _execute(self, context):
|
||||||
|
# This only works based on a 2D plan only considering the standard
|
||||||
|
# walls (i.e. prismatic) in the active object storey.
|
||||||
|
# In order to run, the active object must be a wall and
|
||||||
|
# there must be selected walls
|
||||||
|
core.generate_flooring_coverings_from_walls(tool.Ifc, tool.Spatial, tool.Collector)
|
||||||
|
|
||||||
class ToggleSpaceVisibility(bpy.types.Operator, tool.Ifc.Operator):
|
class ToggleSpaceVisibility(bpy.types.Operator, tool.Ifc.Operator):
|
||||||
bl_idname = "bim.toggle_space_visibility"
|
bl_idname = "bim.toggle_space_visibility"
|
||||||
@@ -414,26 +262,6 @@ class ToggleSpaceVisibility(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
bl_description = "Change the space visibility"
|
bl_description = "Change the space visibility"
|
||||||
|
|
||||||
def execute(cls, context):
|
def execute(cls, context):
|
||||||
model = tool.Ifc.get()
|
core.toggle_space_visibility(tool.Ifc, tool.Spatial)
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
spaces = model.by_type("IfcSpace")
|
|
||||||
|
|
||||||
if not spaces:
|
|
||||||
print(spaces)
|
|
||||||
return {"FINISHED"}
|
|
||||||
|
|
||||||
first_obj = tool.Ifc.get_object(spaces[0])
|
|
||||||
|
|
||||||
if bpy.data.objects[first_obj.name].display_type == "TEXTURED":
|
|
||||||
for space in spaces:
|
|
||||||
obj = tool.Ifc.get_object(space)
|
|
||||||
bpy.data.objects[obj.name].show_wire = True
|
|
||||||
bpy.data.objects[obj.name].display_type = "WIRE"
|
|
||||||
return {"FINISHED"}
|
|
||||||
|
|
||||||
elif bpy.data.objects[first_obj.name].display_type == "WIRE":
|
|
||||||
for space in spaces:
|
|
||||||
obj = tool.Ifc.get_object(space)
|
|
||||||
bpy.data.objects[obj.name].show_wire = False
|
|
||||||
bpy.data.objects[obj.name].display_type = "TEXTURED"
|
|
||||||
return {"FINISHED"}
|
|
||||||
|
|||||||
@@ -17,6 +17,8 @@
|
|||||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import blenderbim.core
|
import blenderbim.core
|
||||||
|
import bpy
|
||||||
|
import ifcopenshell
|
||||||
|
|
||||||
|
|
||||||
def reference_structure(ifc, spatial, structure=None, element=None):
|
def reference_structure(ifc, spatial, structure=None, element=None):
|
||||||
@@ -120,3 +122,67 @@ def select_decomposed_elements(spatial):
|
|||||||
container = spatial.get_active_container()
|
container = spatial.get_active_container()
|
||||||
if container:
|
if container:
|
||||||
spatial.select_products(spatial.get_decomposed_elements(container))
|
spatial.select_products(spatial.get_decomposed_elements(container))
|
||||||
|
|
||||||
|
#HERE STARTS SPATIAL TOOL
|
||||||
|
|
||||||
|
def generate_spaces_from_walls(ifc, spatial, collector):
|
||||||
|
#props = bpy.context.scene.BIMModelProperties
|
||||||
|
container, active_obj = spatial.get_container_and_active_obj()
|
||||||
|
|
||||||
|
if not active_obj:
|
||||||
|
self.report({"ERROR"}, "No active object. Please select a wall")
|
||||||
|
return
|
||||||
|
|
||||||
|
element = ifc.get_entity(active_obj)
|
||||||
|
if element and not element.is_a("IfcWall"):
|
||||||
|
return self.report({"ERROR"}, "The active object is not a wall. Please select a wall.")
|
||||||
|
|
||||||
|
if not container:
|
||||||
|
self.report({"ERROR"}, "The wall is not contained.")
|
||||||
|
|
||||||
|
if not bpy.context.selected_objects:
|
||||||
|
self.report({"ERROR"}, "No selected objects found. Please select walls.")
|
||||||
|
return
|
||||||
|
|
||||||
|
x, y, z = active_obj.matrix_world.translation.xyz
|
||||||
|
mat = active_obj.matrix_world
|
||||||
|
h = active_obj.dimensions.z
|
||||||
|
selected_objects = bpy.context.selected_objects
|
||||||
|
|
||||||
|
union = spatial.get_union_shape_from_selected_objects(selected_objects)
|
||||||
|
|
||||||
|
for i, linear_ring in enumerate(union.interiors):
|
||||||
|
poly = spatial.get_buffered_poly_from_linear_ring(linear_ring)
|
||||||
|
|
||||||
|
bm = spatial.get_bmesh_from_polygon(poly, mat, h)
|
||||||
|
|
||||||
|
name = "Space" + str(i)
|
||||||
|
mesh = bpy.data.meshes.new(name=name)
|
||||||
|
bm.to_mesh(mesh)
|
||||||
|
bm.free()
|
||||||
|
|
||||||
|
obj = bpy.data.objects.new(name, mesh)
|
||||||
|
obj.matrix_world = mat
|
||||||
|
|
||||||
|
spatial.set_obj_origin_to_bboxcenter(obj)
|
||||||
|
|
||||||
|
if z != 0:
|
||||||
|
obj.location = obj.location + Vector((0, 0, z))
|
||||||
|
|
||||||
|
bpy.context.view_layer.active_layer_collection.collection.objects.link(obj)
|
||||||
|
bpy.ops.bim.assign_class(obj=obj.name, ifc_class="IfcSpace")
|
||||||
|
container_obj = ifc.get_object(container)
|
||||||
|
blenderbim.core.spatial.assign_container(
|
||||||
|
ifc, collector, spatial, structure_obj=container_obj, element_obj=obj
|
||||||
|
)
|
||||||
|
|
||||||
|
def generate_flooring_coverings_from_walls(ifc, spatial, collector):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def toggle_space_visibility(ifc, spatial):
|
||||||
|
model = ifc.get()
|
||||||
|
spaces = model.by_type("IfcSpace")
|
||||||
|
if not spaces:
|
||||||
|
return
|
||||||
|
spatial.toggle_spaces_visibility_wired_and_textured(spaces)
|
||||||
|
|
||||||
|
|||||||
@@ -810,6 +810,19 @@ class Spatial:
|
|||||||
def set_active_object(cls, obj): pass
|
def set_active_object(cls, obj): pass
|
||||||
def set_relative_object_matrix(cls, target_obj, relative_to_obj, matrix): pass
|
def set_relative_object_matrix(cls, target_obj, relative_to_obj, matrix): pass
|
||||||
def show_scene_objects(cls): pass
|
def show_scene_objects(cls): pass
|
||||||
|
#HERE STARTS SPATIAL TOOL
|
||||||
|
def get_container_and_active_obj(cls): pass
|
||||||
|
def get_union_shape_from_selected_objects(cls, selected_objects): pass
|
||||||
|
def get_boundary_elements(cls, selected_objects): pass
|
||||||
|
def get_polygons(cls, boundary_elements): pass
|
||||||
|
def get_obj_base_points(cls, obj): pass
|
||||||
|
def get_converted_tolerance(cls, tolerance): pass
|
||||||
|
def get_purged_inner_holes_poly(cls, union_geom, min_area): pass
|
||||||
|
def get_poly_valid_interior_list(cls, poly, min_area, interiors_list): pass
|
||||||
|
def get_buffered_poly_from_linear_ring(cls, linear_ring): pass
|
||||||
|
def get_bmesh_from_polygon(cls, poly, mat, h): pass
|
||||||
|
def set_obj_origin_to_bboxcenter(cls, obj): pass
|
||||||
|
def toggle_spaces_visibility_wired_and_textured(cls, spaces): pass
|
||||||
|
|
||||||
|
|
||||||
@interface
|
@interface
|
||||||
|
|||||||
@@ -17,13 +17,17 @@
|
|||||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
|
import bmesh
|
||||||
|
import shapely
|
||||||
import ifcopenshell
|
import ifcopenshell
|
||||||
import blenderbim.core.tool
|
import blenderbim.core.tool
|
||||||
import blenderbim.core.root
|
import blenderbim.core.root
|
||||||
import blenderbim.core.spatial
|
import blenderbim.core.spatial
|
||||||
import blenderbim.tool as tool
|
import blenderbim.tool as tool
|
||||||
import json
|
import json
|
||||||
|
from math import pi
|
||||||
|
from mathutils import Vector, Matrix
|
||||||
|
from shapely import Polygon, MultiPolygon
|
||||||
|
|
||||||
class Spatial(blenderbim.core.tool.Spatial):
|
class Spatial(blenderbim.core.tool.Spatial):
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -262,3 +266,168 @@ class Spatial(blenderbim.core.tool.Spatial):
|
|||||||
contracted_containers = json.loads(props.contracted_containers)
|
contracted_containers = json.loads(props.contracted_containers)
|
||||||
contracted_containers.remove(container.id())
|
contracted_containers.remove(container.id())
|
||||||
props.contracted_containers = json.dumps(contracted_containers)
|
props.contracted_containers = json.dumps(contracted_containers)
|
||||||
|
|
||||||
|
#HERE STARTS SPATIAL TOOL
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_container_and_active_obj(cls):
|
||||||
|
active_obj = bpy.context.active_object
|
||||||
|
element = tool.Ifc.get_entity(active_obj)
|
||||||
|
container = ifcopenshell.util.element.get_container(element)
|
||||||
|
return container, active_obj
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_union_shape_from_selected_objects(cls, selected_objects):
|
||||||
|
boundary_elements = cls.get_boundary_elements(selected_objects)
|
||||||
|
polys = cls.get_polygons(boundary_elements)
|
||||||
|
converted_tolerance = cls.get_converted_tolerance(tolerance=0.03)
|
||||||
|
union = shapely.ops.unary_union(polys).buffer(converted_tolerance, cap_style=2, join_style=2)
|
||||||
|
union = cls.get_purged_inner_holes_poly(union_geom=union, min_area=cls.get_converted_tolerance(tolerance=3))
|
||||||
|
|
||||||
|
return union
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_boundary_elements(cls, selected_objects):
|
||||||
|
boundary_elements = []
|
||||||
|
for obj in selected_objects:
|
||||||
|
subelement = tool.Ifc.get_entity(obj)
|
||||||
|
if subelement.is_a("IfcWall") or subelement.is_a("IfcColumn"):
|
||||||
|
boundary_elements.append(subelement)
|
||||||
|
return boundary_elements
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_polygons(cls, boundary_elements):
|
||||||
|
polys = []
|
||||||
|
for boundary_element in boundary_elements:
|
||||||
|
obj = tool.Ifc.get_object(boundary_element)
|
||||||
|
if not obj:
|
||||||
|
continue
|
||||||
|
points = []
|
||||||
|
base = cls.get_obj_base_points(obj)
|
||||||
|
for index in ["low_left", "low_right", "high_right", "high_left"]:
|
||||||
|
point = base[index]
|
||||||
|
points.append(point)
|
||||||
|
|
||||||
|
polys.append(Polygon(points))
|
||||||
|
return polys
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_obj_base_points(cls, obj):
|
||||||
|
x_values = [(obj.matrix_world @ Vector(v)).x for v in obj.bound_box]
|
||||||
|
y_values = [(obj.matrix_world @ Vector(v)).y for v in obj.bound_box]
|
||||||
|
return {
|
||||||
|
"low_left": (x_values[0], y_values[0]),
|
||||||
|
"high_left": (x_values[3], y_values[3]),
|
||||||
|
"low_right": (x_values[4], y_values[4]),
|
||||||
|
"high_right": (x_values[7], y_values[7]),
|
||||||
|
}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_converted_tolerance(cls, tolerance):
|
||||||
|
model = tool.Ifc.get()
|
||||||
|
project_unit = ifcopenshell.util.unit.get_project_unit(model, "LENGTHUNIT")
|
||||||
|
prefix = getattr(project_unit, "Prefix", None)
|
||||||
|
|
||||||
|
converted_tolerance = ifcopenshell.util.unit.convert(
|
||||||
|
value=tolerance,
|
||||||
|
from_prefix=None,
|
||||||
|
from_unit="METRE",
|
||||||
|
to_prefix=prefix,
|
||||||
|
to_unit=project_unit.Name,
|
||||||
|
)
|
||||||
|
return tolerance
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_purged_inner_holes_poly(cls, union_geom, min_area):
|
||||||
|
interiors_list = []
|
||||||
|
|
||||||
|
if union_geom.geom_type == "MultiPolygon":
|
||||||
|
for poly in union_geom.geoms:
|
||||||
|
interiors_list = cls.get_poly_valid_interior_list(
|
||||||
|
poly=poly, min_area=min_area, interiors_list=interiors_list
|
||||||
|
)
|
||||||
|
|
||||||
|
new_poly = Polygon(poly.exterior.coords, holes=interiors_list)
|
||||||
|
|
||||||
|
if union_geom.geom_type == "Polygon":
|
||||||
|
interiors_list = cls.get_poly_valid_interior_list(
|
||||||
|
poly=union_geom, min_area=min_area, interiors_list=interiors_list
|
||||||
|
)
|
||||||
|
new_poly = Polygon(union_geom.exterior.coords, holes=interiors_list)
|
||||||
|
|
||||||
|
return new_poly
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_poly_valid_interior_list(cls, poly, min_area, interiors_list):
|
||||||
|
for interior in poly.interiors:
|
||||||
|
p = Polygon(interior)
|
||||||
|
if p.area >= min_area:
|
||||||
|
interiors_list.append(interior)
|
||||||
|
return interiors_list
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_buffered_poly_from_linear_ring(cls, linear_ring):
|
||||||
|
poly = Polygon(linear_ring)
|
||||||
|
converted_tolerance = cls.get_converted_tolerance(tolerance=0.03)
|
||||||
|
poly = poly.buffer(converted_tolerance, single_sided=True, cap_style=2, join_style=2)
|
||||||
|
return poly
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_bmesh_from_polygon(cls, poly, mat, h):
|
||||||
|
bm = bmesh.new()
|
||||||
|
bm.verts.index_update()
|
||||||
|
bm.edges.index_update()
|
||||||
|
|
||||||
|
mat_invert = mat.inverted()
|
||||||
|
|
||||||
|
new_verts = [bm.verts.new(mat_invert @ Vector([v[0], v[1], 0])) for v in poly.exterior.coords[0:-1]]
|
||||||
|
[bm.edges.new((new_verts[i], new_verts[i + 1])) for i in range(len(new_verts) - 1)]
|
||||||
|
bm.edges.new((new_verts[len(new_verts) - 1], new_verts[0]))
|
||||||
|
|
||||||
|
bm.verts.index_update()
|
||||||
|
bm.edges.index_update()
|
||||||
|
|
||||||
|
bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=1e-5)
|
||||||
|
bmesh.ops.triangle_fill(bm, edges=bm.edges)
|
||||||
|
bmesh.ops.dissolve_limit(bm, angle_limit=pi / 180 * 5, verts=bm.verts, edges=bm.edges)
|
||||||
|
|
||||||
|
extrusion = bmesh.ops.extrude_face_region(bm, geom=bm.faces)
|
||||||
|
extruded_verts = [g for g in extrusion["geom"] if isinstance(g, bmesh.types.BMVert)]
|
||||||
|
bmesh.ops.translate(bm, vec=[0.0, 0.0, h], verts=extruded_verts)
|
||||||
|
|
||||||
|
bmesh.ops.recalc_face_normals(bm, faces=bm.faces)
|
||||||
|
|
||||||
|
return bm
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def set_obj_origin_to_bboxcenter(cls, obj):
|
||||||
|
mat = obj.matrix_world
|
||||||
|
inverted = mat.inverted()
|
||||||
|
local_bbox_center = 0.125 * sum((Vector(b) for b in obj.bound_box), Vector())
|
||||||
|
global_bbox_center = mat @ local_bbox_center
|
||||||
|
|
||||||
|
oldLoc = obj.location
|
||||||
|
newLoc = global_bbox_center
|
||||||
|
diff = newLoc - oldLoc
|
||||||
|
for vert in obj.data.vertices:
|
||||||
|
aux_vector = mat @ vert.co
|
||||||
|
aux_vector = aux_vector - diff
|
||||||
|
vert.co = inverted @ aux_vector
|
||||||
|
obj.location = newLoc
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def toggle_spaces_visibility_wired_and_textured(cls, spaces):
|
||||||
|
first_obj = tool.Ifc.get_object(spaces[0])
|
||||||
|
if bpy.data.objects[first_obj.name].display_type == "TEXTURED":
|
||||||
|
for space in spaces:
|
||||||
|
obj = tool.Ifc.get_object(space)
|
||||||
|
bpy.data.objects[obj.name].show_wire = True
|
||||||
|
bpy.data.objects[obj.name].display_type = "WIRE"
|
||||||
|
return
|
||||||
|
|
||||||
|
elif bpy.data.objects[first_obj.name].display_type == "WIRE":
|
||||||
|
for space in spaces:
|
||||||
|
obj = tool.Ifc.get_object(space)
|
||||||
|
bpy.data.objects[obj.name].show_wire = False
|
||||||
|
bpy.data.objects[obj.name].display_type = "TEXTURED"
|
||||||
|
return
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
@spatial
|
@spatial
|
||||||
Feature: Spatial
|
Feature: Spatial
|
||||||
Covers spatial containment management.
|
Covers spatial containment management and spatial tool.
|
||||||
|
|
||||||
Scenario: Enable editing container
|
Scenario: Enable editing container
|
||||||
Given an empty IFC project
|
Given an empty IFC project
|
||||||
@@ -21,8 +21,7 @@ Scenario: Assign container
|
|||||||
Given an empty IFC project
|
Given an empty IFC project
|
||||||
And I add a cube
|
And I add a cube
|
||||||
And the object "Cube" is selected
|
And the object "Cube" is selected
|
||||||
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
|
And I press "bim.assign_class(ifc_class='IfcWall', predefined_type='SOLIDWALL')"
|
||||||
And I press "bim.assign_class"
|
|
||||||
And the object "IfcWall/Cube" is selected
|
And the object "IfcWall/Cube" is selected
|
||||||
And I press "bim.enable_editing_container"
|
And I press "bim.enable_editing_container"
|
||||||
And the variable "site" is "tool.Ifc.get().by_type('IfcSite')[0].id()"
|
And the variable "site" is "tool.Ifc.get().by_type('IfcSite')[0].id()"
|
||||||
@@ -33,8 +32,7 @@ Scenario: Copy to container
|
|||||||
Given an empty IFC project
|
Given an empty IFC project
|
||||||
And I add a cube
|
And I add a cube
|
||||||
And the object "Cube" is selected
|
And the object "Cube" is selected
|
||||||
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
|
And I press "bim.assign_class(ifc_class='IfcWall', predefined_type='SOLIDWALL')"
|
||||||
And I press "bim.assign_class"
|
|
||||||
And the object "IfcWall/Cube" is selected
|
And the object "IfcWall/Cube" is selected
|
||||||
And I press "bim.enable_editing_container"
|
And I press "bim.enable_editing_container"
|
||||||
When I set "scene.BIMSpatialProperties.containers[0].is_selected" to "True"
|
When I set "scene.BIMSpatialProperties.containers[0].is_selected" to "True"
|
||||||
@@ -45,8 +43,7 @@ Scenario: Reference structure
|
|||||||
Given an empty IFC project
|
Given an empty IFC project
|
||||||
And I add a cube
|
And I add a cube
|
||||||
And the object "Cube" is selected
|
And the object "Cube" is selected
|
||||||
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
|
And I press "bim.assign_class(ifc_class='IfcWall', predefined_type='SOLIDWALL')"
|
||||||
And I press "bim.assign_class"
|
|
||||||
And the object "IfcWall/Cube" is selected
|
And the object "IfcWall/Cube" is selected
|
||||||
And I press "bim.enable_editing_container"
|
And I press "bim.enable_editing_container"
|
||||||
When I set "scene.BIMSpatialProperties.containers[0].is_selected" to "True"
|
When I set "scene.BIMSpatialProperties.containers[0].is_selected" to "True"
|
||||||
@@ -57,8 +54,7 @@ Scenario: Dereference structure
|
|||||||
Given an empty IFC project
|
Given an empty IFC project
|
||||||
And I add a cube
|
And I add a cube
|
||||||
And the object "Cube" is selected
|
And the object "Cube" is selected
|
||||||
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
|
And I press "bim.assign_class(ifc_class='IfcWall', predefined_type='SOLIDWALL')"
|
||||||
And I press "bim.assign_class"
|
|
||||||
And the object "IfcWall/Cube" is selected
|
And the object "IfcWall/Cube" is selected
|
||||||
And I press "bim.enable_editing_container"
|
And I press "bim.enable_editing_container"
|
||||||
When I set "scene.BIMSpatialProperties.containers[0].is_selected" to "True"
|
When I set "scene.BIMSpatialProperties.containers[0].is_selected" to "True"
|
||||||
@@ -70,8 +66,7 @@ Scenario: Select container
|
|||||||
Given an empty IFC project
|
Given an empty IFC project
|
||||||
And I add a cube
|
And I add a cube
|
||||||
And the object "Cube" is selected
|
And the object "Cube" is selected
|
||||||
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
|
And I press "bim.assign_class(ifc_class='IfcWall', predefined_type='SOLIDWALL')"
|
||||||
And I press "bim.assign_class"
|
|
||||||
And the object "IfcWall/Cube" is selected
|
And the object "IfcWall/Cube" is selected
|
||||||
And I press "bim.enable_editing_container"
|
And I press "bim.enable_editing_container"
|
||||||
And the variable "site" is "tool.Ifc.get().by_type('IfcSite')[0].id()"
|
And the variable "site" is "tool.Ifc.get().by_type('IfcSite')[0].id()"
|
||||||
@@ -83,11 +78,47 @@ Scenario: Select similar container
|
|||||||
Given an empty IFC project
|
Given an empty IFC project
|
||||||
And I add a cube
|
And I add a cube
|
||||||
And the object "Cube" is selected
|
And the object "Cube" is selected
|
||||||
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
|
And I press "bim.assign_class(ifc_class='IfcWall', predefined_type='SOLIDWALL')"
|
||||||
And I press "bim.assign_class"
|
|
||||||
And the object "IfcWall/Cube" is selected
|
And the object "IfcWall/Cube" is selected
|
||||||
And I press "bim.enable_editing_container"
|
And I press "bim.enable_editing_container"
|
||||||
And the variable "site" is "tool.Ifc.get().by_type('IfcSite')[0].id()"
|
And the variable "site" is "tool.Ifc.get().by_type('IfcSite')[0].id()"
|
||||||
And I press "bim.assign_container(structure={site})"
|
And I press "bim.assign_container(structure={site})"
|
||||||
When I press "bim.select_similar_container"
|
When I press "bim.select_similar_container"
|
||||||
Then nothing happens
|
Then nothing happens
|
||||||
|
|
||||||
|
#HERE STARTS TESTS FOR SPATIAL TOOL
|
||||||
|
|
||||||
|
Scenario: Execute generate space from cursor position
|
||||||
|
Given an empty IFC project
|
||||||
|
When I press "bim.generate_space"
|
||||||
|
Then nothing happens
|
||||||
|
|
||||||
|
Scenario: Execute generate spaces from walls
|
||||||
|
Given an empty IFC project
|
||||||
|
And I load the demo construction library
|
||||||
|
And I set "scene.BIMModelProperties.ifc_class" to "IfcWallType"
|
||||||
|
And the variable "element_type" is "[e for e in {ifc}.by_type('IfcWallType') if e.Name == 'WAL100'][0].id()"
|
||||||
|
And I set "scene.BIMModelProperties.relating_type_id" to "{element_type}"
|
||||||
|
And I press "bim.add_constr_type_instance"
|
||||||
|
And the object "IfcWall/Wall" is selected
|
||||||
|
When I press "bim.generate_spaces_from_walls"
|
||||||
|
Then nothing happens
|
||||||
|
|
||||||
|
Scenario: Execute generate flooring coverings from walls
|
||||||
|
Given an empty IFC project
|
||||||
|
And I load the demo construction library
|
||||||
|
And I set "scene.BIMModelProperties.ifc_class" to "IfcWallType"
|
||||||
|
And the variable "element_type" is "[e for e in {ifc}.by_type('IfcWallType') if e.Name == 'WAL100'][0].id()"
|
||||||
|
And I set "scene.BIMModelProperties.relating_type_id" to "{element_type}"
|
||||||
|
And I press "bim.add_constr_type_instance"
|
||||||
|
And the object "IfcWall/Wall" is selected
|
||||||
|
When I press "bim.generate_flooring_coverings_from_walls"
|
||||||
|
Then nothing happens
|
||||||
|
|
||||||
|
Scenario: Execute toggle space visibility
|
||||||
|
Given an empty IFC project
|
||||||
|
And I add a cube
|
||||||
|
And the object "Cube" is selected
|
||||||
|
And I press "bim.assign_class(ifc_class='IfcSpace', predefined_type='SPACE')"
|
||||||
|
When I press "bim.toggle_space_visibility"
|
||||||
|
Then nothing happens
|
||||||
|
|||||||
Reference in New Issue
Block a user