Refactor create_space operator and better UI for covering tool

This commit is contained in:
Massimo Fabbro
2023-10-02 22:12:11 +02:00
parent 09a9753ca7
commit cda61bff5b
7 changed files with 251 additions and 174 deletions
@@ -42,13 +42,14 @@ class CoveringTool(WorkSpaceTool):
bl_description = "Create and edit coverings"
bl_icon = os.path.join(os.path.dirname(__file__), "ops.authoring.covering")
bl_widget = None
ifc_element_type = "IfcCoveringType"
bl_keymap = tool.Blender.get_default_selection_keypmap() + (
("bim.covering_hotkey", {"type": "A", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_A")]}),
)
@classmethod
def draw_settings(cls, context, layout, ws_tool):
CoveringToolUI.draw(context, layout, ifc_element_type="IfcCoveringType")
CoveringToolUI.draw(context, layout, ifc_element_type=cls.ifc_element_type)
def add_layout_hotkey(layout, text, hotkey, description):
args = ["covering", layout, text, hotkey, description]
@@ -71,7 +72,6 @@ class CoveringToolUI:
elif AuthoringData.data["ifc_element_type"] != ifc_element_type:
AuthoringData.load(ifc_element_type)
if context.region.type == "TOOL_HEADER":
cls.draw_header_interface()
elif context.region.type in ("UI", "WINDOW"):
@@ -97,6 +97,7 @@ class CoveringToolUI:
if AuthoringData.data["predefined_type"] == "FLOORING":
if element and bpy.context.selected_objects and element.is_a("IfcWall"):
op = row.operator("bim.add_instance_flooring_coverings_from_walls")
# PLEASE KEEP COMMENTS AS A REMINDER
# elif element and bpy.context.selected_objects and element.is_a("IfcSpace"):
# op. = row.operator("bim.add_istance_flooring_from_spaces"):
else:
@@ -183,8 +184,11 @@ class Hotkey(bpy.types.Operator, Operator):
def hotkey_S_A(self):
active_obj = bpy.context.active_object
element = tool.Ifc.get_entity(active_obj)
if element and bpy.context.selected_objects and element.is_a("IfcWall"):
bpy.ops.bim.add_instance_flooring_coverings_from_walls()
if AuthoringData.data["predefined_type"] == "FLOORING":
if element and bpy.context.selected_objects and element.is_a("IfcWall"):
bpy.ops.bim.add_instance_flooring_coverings_from_walls()
else:
bpy.ops.bim.add_constr_type_instance()
else:
bpy.ops.bim.add_constr_type_instance()
@@ -25,7 +25,7 @@ import blenderbim.core.covering as core
class AddInstanceFlooringCoveringsFromWalls(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.add_instance_flooring_coverings_from_walls"
bl_label = "Add Covering From Walls"
bl_label = "Add Flooring From Walls"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Add instance flooring coverings from selected walls. The active object must be a wall and layered vertically"
@@ -79,6 +79,7 @@ def update_type_class(self, context):
def update_relating_type_id(self, context):
AuthoringData.data["relating_type_id"] = AuthoringData.relating_type_id()
AuthoringData.data["type_thumbnail"] = AuthoringData.type_thumbnail()
AuthoringData.data["predefined_type"] = AuthoringData.predefined_type()
def update_type_page(self, context):
@@ -50,13 +50,7 @@ class GenerateSpace(bpy.types.Operator, tool.Ifc.Operator):
def msg(self, context):
self.layout.label(text="NO ACTIVE STOREY")
props = context.scene.BIMModelProperties
relating_type_id = props.relating_type_id
relating_type = None
if relating_type_id:
relating_type = tool.Ifc.get().by_id(int(relating_type_id))
if not relating_type.is_a("IfcSpaceType"):
relating_type = None
# props = context.scene.BIMModelProperties
collection = context.view_layer.active_layer_collection.collection
collection_obj = collection.BIMCollectionProperties.obj
@@ -68,158 +62,7 @@ class GenerateSpace(bpy.types.Operator, tool.Ifc.Operator):
bpy.context.window_manager.popup_menu(msg, title="Error", icon="ERROR")
return
# core.generate_space(tool.Ifc, tool.Spatial)
active_obj = bpy.context.active_object
element = None
if bpy.context.selected_objects and active_obj:
element = tool.Ifc.get_entity(active_obj)
mat = active_obj.matrix_world
local_bbox_center = 0.125 * sum((Vector(b) for b in active_obj.bound_box), Vector())
global_bbox_center = mat @ local_bbox_center
x = global_bbox_center.x
y = global_bbox_center.y
z = (mat @ Vector(active_obj.bound_box[0])).z
h = active_obj.dimensions.z
else:
x, y = context.scene.cursor.location.xy
z = collection_obj.matrix_world.translation.z
mat = Matrix()
mat.translation = (x, y, z)
h = 3
calculation_rl = context.scene.BIMModelProperties.rl3
self.cut_point = collection_obj.matrix_world.translation.copy() + Vector((0, 0, calculation_rl))
self.cut_normal = Vector((0, 0, 1))
boundary_lines = []
gross_settings = ifcopenshell.geom.settings()
gross_settings.set(gross_settings.DISABLE_OPENING_SUBTRACTIONS, True)
for obj in bpy.context.visible_objects:
visible_element = tool.Ifc.get_entity(obj)
if (
not visible_element
or obj.type != "MESH"
or not self.is_bounding_class(visible_element)
or not tool.Drawing.is_intersecting_plane(obj, self.cut_point, self.cut_normal)
):
continue
old_mesh = None
if visible_element.HasOpenings:
new_mesh = self.create_mesh(ifcopenshell.geom.create_shape(gross_settings, visible_element))
old_mesh = obj.data
obj.data = new_mesh
local_cut_point = obj.matrix_world.inverted() @ self.cut_point
local_cut_normal = obj.matrix_world.inverted().to_quaternion() @ self.cut_normal
verts, edges = tool.Drawing.bisect_mesh_with_plane(obj, local_cut_point, local_cut_normal)
if old_mesh:
obj.data = old_mesh
bpy.data.meshes.remove(new_mesh)
for edge in edges or []:
boundary_lines.append(
shapely.LineString(
[Vector((round(x, 3) for x in verts[edge[0]])), Vector((round(x, 3) for x in verts[edge[1]]))]
)
)
unioned_boundaries = shapely.union_all(shapely.GeometryCollection(boundary_lines))
closed_polygons = shapely.polygonize(unioned_boundaries.geoms)
space_polygon = None
for polygon in closed_polygons.geoms:
if shapely.contains_xy(polygon, x, y):
space_polygon = shapely.force_3d(polygon)
if not space_polygon:
return
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], z])) for v in space_polygon.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]))
for interior in space_polygon.interiors:
new_verts = [bm.verts.new(mat_invert @ Vector([v[0], v[1], z])) for v in interior.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)
mesh = bpy.data.meshes.new(name="Space")
bm.to_mesh(mesh)
bm.free()
if element and element.is_a("IfcSpace"):
mesh.name = active_obj.data.name
mesh.BIMMeshProperties.ifc_definition_id = active_obj.data.BIMMeshProperties.ifc_definition_id
tool.Geometry.change_object_data(active_obj, mesh, is_global=True)
tool.Ifc.edit(active_obj)
else:
if relating_type:
name = tool.Model.generate_occurrence_name(relating_type, "IfcSpace")
else:
name = "Space"
obj = bpy.data.objects.new("Space", mesh)
obj.matrix_world = mat
collection.objects.link(obj)
bpy.ops.bim.assign_class(obj=obj.name, ifc_class="IfcSpace")
element = tool.Ifc.get_entity(obj)
if relating_type:
blenderbim.core.type.assign_type(tool.Ifc, tool.Type, element=element, type=relating_type)
def is_bounding_class(self, element):
for ifc_class in ["IfcWall", "IfcColumn", "IfcMember", "IfcVirtualElement", "IfcPlate"]:
if element.is_a(ifc_class):
return True
return False
def create_mesh(self, shape):
geometry = shape.geometry
mesh = bpy.data.meshes.new("tmp")
verts = geometry.verts
if geometry.faces:
num_vertices = len(verts) // 3
total_faces = len(geometry.faces)
loop_start = range(0, total_faces, 3)
num_loops = total_faces // 3
loop_total = [3] * num_loops
num_vertex_indices = len(geometry.faces)
mesh.vertices.add(num_vertices)
mesh.vertices.foreach_set("co", verts)
mesh.loops.add(num_vertex_indices)
mesh.loops.foreach_set("vertex_index", geometry.faces)
mesh.polygons.add(num_loops)
mesh.polygons.foreach_set("loop_start", loop_start)
mesh.polygons.foreach_set("loop_total", loop_total)
mesh.polygons.foreach_set("use_smooth", [0] * total_faces)
mesh.update()
return mesh
core.generate_space(tool.Ifc, tool.Spatial, tool.Model)
class GenerateSpacesFromWalls(bpy.types.Operator, tool.Ifc.Operator):
+46 -5
View File
@@ -16,9 +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 blenderbim.core
import bpy
import blenderbim.core.type
def reference_structure(ifc, spatial, structure=None, element=None):
if spatial.can_reference(structure, element):
@@ -123,8 +121,51 @@ def select_decomposed_elements(spatial):
spatial.select_products(spatial.get_decomposed_elements(container))
#HERE STARTS SPATIAL TOOL
def generate_space(ifc, spatial):
pass
def generate_space(ifc, spatial, model):
active_obj = spatial.get_active_obj()
selected_objects = spatial.get_selected_objects()
element = None
relating_type_id = spatial.get_relating_type_id()
relating_type = None
if relating_type_id:
relating_type = ifc.get().by_id(int(relating_type_id))
if not relating_type.is_a("IfcSpaceType"):
relating_type = None
if selected_objects and active_obj:
x, y, z, h, mat = spatial.get_x_y_z_h_mat_from_active_obj(active_obj) ##mat
element = ifc.get_entity(active_obj)
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:
return
bm = spatial.get_bmesh_from_polygon(space_polygon, h=h) ##mat
mesh = spatial.get_named_mesh_from_bmesh(name = "Space", bmesh = bm)
if element and element.is_a("IfcSpace"):
spatial.edit_active_space_obj_from_mesh(mesh)
else:
if relating_type:
name = model.generate_occurrence_name(relating_type, "IfcSpace")
else:
name = "Space"
obj = spatial.get_named_obj_from_mesh(name, mesh) ##mat
spatial.set_obj_origin_to_cursor_position(obj)
spatial.link_obj_to_active_collection(obj)
spatial.assign_ifcspace_class_to_obj(obj)
element = ifc.get_entity(obj)
if relating_type:
blenderbim.core.type.assign_type(tool.Ifc, tool.Type, element=element, type=relating_type)
def generate_spaces_from_walls(ifc, spatial, collector):
+14
View File
@@ -810,6 +810,13 @@ class Spatial:
def set_relative_object_matrix(cls, target_obj, relative_to_obj, matrix): pass
def show_scene_objects(cls): pass
#HERE STARTS SPATIAL TOOL
def is_bounding_class(cls, visible_element): pass
def get_space_polygon_from_context_visible_objects(cls, x, y): pass
def get_boundary_lines_from_context_visible_objects(cls, visible_objects): pass
def get_gross_mesh_from_element(cls, visible_element): pass
def create_mesh_from_shape(cls, shape): pass
def get_x_y_z_h_mat_from_active_obj(cls, active_obj): pass
def get_x_y_z_h_mat_from_cursor(cls): pass
def get_union_shape_from_selected_objects(cls): pass
def get_boundary_elements(cls, selected_objects): pass
def get_polygons(cls, boundary_elements): pass
@@ -820,9 +827,16 @@ class Spatial:
def get_buffered_poly_from_linear_ring(cls, linear_ring): pass
def get_bmesh_from_polygon(cls, poly, h): pass
def get_named_obj_from_bmesh(cls, name, bmesh): pass
def get_named_obj_from_mesh(cls, name, mesh): pass
def get_named_mesh_from_bmesh(cls, name, bmesh): pass
def edit_active_space_obj_from_mesh(cls, mesh): pass
def set_obj_origin_to_bboxcenter(cls, obj): pass
def set_obj_origin_to_cursor_position(cls, obj): pass
def get_selected_objects(cls): pass
def get_active_obj(cls): pass
def get_active_obj_z(cls): pass
def get_active_obj_height(cls): pass
def get_relating_type_id(cls): pass
def traslate_obj_to_z_location(cls, obj): pass
def link_obj_to_active_collection(cls, obj): pass
def get_2d_vertices_from_obj(cls, obj): pass
+179 -5
View File
@@ -270,6 +270,123 @@ class Spatial(blenderbim.core.tool.Spatial):
#HERE STARTS SPATIAL TOOL
@classmethod
def is_bounding_class(cls, visible_element):
for ifc_class in ["IfcWall", "IfcColumn", "IfcMember", "IfcVirtualElement", "IfcPlate"]:
if visible_element.is_a(ifc_class):
return True
return False
@classmethod
def get_space_polygon_from_context_visible_objects(cls, x, y):
boundary_lines = cls.get_boundary_lines_from_context_visible_objects()
unioned_boundaries = shapely.union_all(shapely.GeometryCollection(boundary_lines))
closed_polygons = shapely.polygonize(unioned_boundaries.geoms)
space_polygon = None
for polygon in closed_polygons.geoms:
if shapely.contains_xy(polygon, x, y):
space_polygon = shapely.force_3d(polygon)
return space_polygon
@classmethod
def get_boundary_lines_from_context_visible_objects(cls):
calculation_rl = bpy.context.scene.BIMModelProperties.rl3
collection = bpy.context.view_layer.active_layer_collection.collection
collection_obj = collection.BIMCollectionProperties.obj
cut_point = collection_obj.matrix_world.translation.copy() + Vector((0, 0, calculation_rl))
cut_normal = Vector((0, 0, 1))
boundary_lines = []
for obj in bpy.context.visible_objects:
visible_element = tool.Ifc.get_entity(obj)
if (
not visible_element
or obj.type != "MESH"
or not cls.is_bounding_class(visible_element)
or not tool.Drawing.is_intersecting_plane(obj, cut_point, cut_normal)
):
continue
old_mesh = None
if visible_element.HasOpenings:
new_mesh = cls.get_gross_mesh_from_element(visible_element)
old_mesh = obj.data
obj.data = new_mesh
local_cut_point = obj.matrix_world.inverted() @ cut_point
local_cut_normal = obj.matrix_world.inverted().to_quaternion() @ cut_normal
verts, edges = tool.Drawing.bisect_mesh_with_plane(obj, local_cut_point, local_cut_normal)
if old_mesh:
obj.data = old_mesh
bpy.data.meshes.remove(new_mesh)
for edge in edges or []:
boundary_lines.append(
shapely.LineString(
[Vector((round(x, 3) for x in verts[edge[0]])), Vector((round(x, 3) for x in verts[edge[1]]))]
)
)
return boundary_lines
@classmethod
def get_gross_mesh_from_element(cls, visible_element):
gross_settings = ifcopenshell.geom.settings()
gross_settings.set(gross_settings.DISABLE_OPENING_SUBTRACTIONS, True)
new_mesh = cls.create_mesh_from_shape(ifcopenshell.geom.create_shape(gross_settings, visible_element))
return new_mesh
@classmethod
def create_mesh_from_shape(cls, shape):
geometry = shape.geometry
mesh = bpy.data.meshes.new("tmp")
verts = geometry.verts
if geometry.faces:
num_vertices = len(verts) // 3
total_faces = len(geometry.faces)
loop_start = range(0, total_faces, 3)
num_loops = total_faces // 3
loop_total = [3] * num_loops
num_vertex_indices = len(geometry.faces)
mesh.vertices.add(num_vertices)
mesh.vertices.foreach_set("co", verts)
mesh.loops.add(num_vertex_indices)
mesh.loops.foreach_set("vertex_index", geometry.faces)
mesh.polygons.add(num_loops)
mesh.polygons.foreach_set("loop_start", loop_start)
mesh.polygons.foreach_set("loop_total", loop_total)
mesh.polygons.foreach_set("use_smooth", [0] * total_faces)
mesh.update()
return mesh
@classmethod
def get_x_y_z_h_mat_from_active_obj(cls, active_obj):
element = tool.Ifc.get_entity(active_obj)
mat = active_obj.matrix_world
local_bbox_center = 0.125 * sum((Vector(b) for b in active_obj.bound_box), Vector())
global_bbox_center = mat @ local_bbox_center
x = global_bbox_center.x
y = global_bbox_center.y
z = (mat @ Vector(active_obj.bound_box[0])).z
h = active_obj.dimensions.z
return x, y, z, h, mat
@classmethod
def get_x_y_z_h_mat_from_cursor(cls):
collection = bpy.context.view_layer.active_layer_collection.collection
collection_obj = collection.BIMCollectionProperties.obj
x, y = bpy.context.scene.cursor.location.xy
z = collection_obj.matrix_world.translation.z
mat = Matrix()
h = 3
return x, y, z, h, mat
@classmethod
def get_union_shape_from_selected_objects(cls):
selected_objects = bpy.context.selected_objects
@@ -369,7 +486,11 @@ class Spatial(blenderbim.core.tool.Spatial):
@classmethod
def get_bmesh_from_polygon(cls, poly, h):
mat = bpy.context.active_object.matrix_world
if bpy.context.selected_objects and bpy.context.active_object:
mat = bpy.context.active_object.matrix_world
else:
mat = Matrix()
bm = bmesh.new()
bm.verts.index_update()
bm.edges.index_update()
@@ -398,13 +519,32 @@ class Spatial(blenderbim.core.tool.Spatial):
@classmethod
def get_named_obj_from_bmesh(cls, name, bmesh):
mesh = cls.get_named_mesh_from_bmesh(name, bmesh)
obj = cls.get_named_obj_from_mesh(name, mesh)
return obj
@classmethod
def get_named_obj_from_mesh(cls, name, mesh):
obj = bpy.data.objects.new(name, mesh)
if bpy.context.active_object:
mat = bpy.context.active_object.matrix_world
obj.matrix_world = mat
return obj
@classmethod
def get_named_mesh_from_bmesh(cls, name, bmesh):
mesh = bpy.data.meshes.new(name=name)
bmesh.to_mesh(mesh)
bmesh.free()
obj = bpy.data.objects.new(name, mesh)
mat = bpy.context.active_object.matrix_world
obj.matrix_world = mat
return obj
return mesh
@classmethod
def edit_active_space_obj_from_mesh(cls, mesh):
active_obj = bpy.context.active_object
mesh.name = active_obj.data.name
mesh.BIMMeshProperties.ifc_definition_id = active_obj.data.BIMMeshProperties.ifc_definition_id
tool.Geometry.change_object_data(active_obj, mesh, is_global=True)
tool.Ifc.edit(active_obj)
@classmethod
def set_obj_origin_to_bboxcenter(cls, obj):
@@ -422,6 +562,34 @@ class Spatial(blenderbim.core.tool.Spatial):
vert.co = inverted @ aux_vector
obj.location = newLoc
@classmethod
def set_obj_origin_to_cursor_position(cls, obj):
mat = obj.matrix_world
inverted = mat.inverted()
collection = bpy.context.view_layer.active_layer_collection.collection
collection_obj = collection.BIMCollectionProperties.obj
x, y = bpy.context.scene.cursor.location.xy
z = collection_obj.matrix_world.translation.z
oldLoc = obj.location
newLoc = Vector((x,y,z))
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 get_selected_objects(cls):
return bpy.context.selected_objects
@classmethod
def get_active_obj(cls):
return bpy.context.active_object
@classmethod
def get_active_obj_z(cls):
x, y, z = bpy.context.active_object.matrix_world.translation.xyz
@@ -432,6 +600,12 @@ class Spatial(blenderbim.core.tool.Spatial):
height = bpy.context.active_object.dimensions.z
return height
@classmethod
def get_relating_type_id(cls):
props = bpy.context.scene.BIMModelProperties
relating_type_id = props.relating_type_id
return relating_type_id
@classmethod
def traslate_obj_to_z_location(cls, obj, z):
if z != 0: