mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-23 15:16:23 +00:00
Move product preview logic to its own decorator.
This commit is contained in:
@@ -323,9 +323,6 @@ class PolylineDecorator:
|
|||||||
cls.handlers.append(SpaceView3D.draw_handler_add(handler.draw_snap_point, (context,), "WINDOW", "POST_PIXEL"))
|
cls.handlers.append(SpaceView3D.draw_handler_add(handler.draw_snap_point, (context,), "WINDOW", "POST_PIXEL"))
|
||||||
cls.handlers.append(SpaceView3D.draw_handler_add(handler.draw_measurements, (context,), "WINDOW", "POST_PIXEL"))
|
cls.handlers.append(SpaceView3D.draw_handler_add(handler.draw_measurements, (context,), "WINDOW", "POST_PIXEL"))
|
||||||
cls.handlers.append(SpaceView3D.draw_handler_add(handler.draw_input_ui, (context,), "WINDOW", "POST_PIXEL"))
|
cls.handlers.append(SpaceView3D.draw_handler_add(handler.draw_input_ui, (context,), "WINDOW", "POST_PIXEL"))
|
||||||
cls.handlers.append(
|
|
||||||
SpaceView3D.draw_handler_add(handler.draw_product_preview, (context,), "WINDOW", "POST_VIEW")
|
|
||||||
)
|
|
||||||
cls.handlers.append(SpaceView3D.draw_handler_add(handler, (context,), "WINDOW", "POST_VIEW"))
|
cls.handlers.append(SpaceView3D.draw_handler_add(handler, (context,), "WINDOW", "POST_VIEW"))
|
||||||
cls.is_installed = True
|
cls.is_installed = True
|
||||||
|
|
||||||
@@ -402,358 +399,6 @@ class PolylineDecorator:
|
|||||||
shader.uniform_float("color", color)
|
shader.uniform_float("color", color)
|
||||||
batch.draw(shader)
|
batch.draw(shader)
|
||||||
|
|
||||||
def get_wall_preview_data(cls, context, relating_type):
|
|
||||||
def create_bmesh_from_vertices(vertices):
|
|
||||||
bm = bmesh.new()
|
|
||||||
|
|
||||||
new_verts = [bm.verts.new(v) for v in polyline_vertices]
|
|
||||||
if is_closed:
|
|
||||||
new_edges = [bm.edges.new((new_verts[i], new_verts[i + 1])) for i in range(len(new_verts) - 1)]
|
|
||||||
new_edges.append(
|
|
||||||
bm.edges.new((new_verts[-1], new_verts[0]))
|
|
||||||
) # Add an edge between the last an first point to make it closed.
|
|
||||||
else:
|
|
||||||
new_edges = [bm.edges.new((new_verts[i], new_verts[i + 1])) for i in range(len(new_verts) - 1)]
|
|
||||||
|
|
||||||
bm.verts.index_update()
|
|
||||||
bm.edges.index_update()
|
|
||||||
return bm
|
|
||||||
|
|
||||||
# Get properties from object type
|
|
||||||
layers = tool.Model.get_material_layer_parameters(relating_type)
|
|
||||||
if not layers["thickness"]:
|
|
||||||
return
|
|
||||||
thickness = layers["thickness"]
|
|
||||||
model_props = context.scene.BIMModelProperties
|
|
||||||
direction_sense = model_props.direction_sense
|
|
||||||
direction = 1
|
|
||||||
if direction_sense == "NEGATIVE":
|
|
||||||
direction = -1
|
|
||||||
offset_type = model_props.offset_type
|
|
||||||
offset = 0
|
|
||||||
if offset_type == "CENTER":
|
|
||||||
offset = -thickness / 2
|
|
||||||
elif offset_type == "INTERIOR":
|
|
||||||
offset = -thickness
|
|
||||||
|
|
||||||
unit_system = tool.Drawing.get_unit_system()
|
|
||||||
factor = 1
|
|
||||||
if unit_system == "IMPERIAL":
|
|
||||||
factor = 3.048
|
|
||||||
if unit_system == "METRIC":
|
|
||||||
unit_length = context.scene.unit_settings.length_unit
|
|
||||||
if unit_length == "MILLIMETERS":
|
|
||||||
factor = 1000
|
|
||||||
|
|
||||||
# For the model properties, the offset value should just be converted
|
|
||||||
# However, for the wall preview logic that follows, offset and thickness must change direction
|
|
||||||
model_props.offset = offset * factor
|
|
||||||
thickness *= direction
|
|
||||||
offset *= direction
|
|
||||||
|
|
||||||
height = float(model_props.extrusion_depth)
|
|
||||||
rl = float(model_props.rl1)
|
|
||||||
x_angle = float(model_props.x_angle)
|
|
||||||
angle_distortion = height * tan(x_angle)
|
|
||||||
|
|
||||||
wall_preview_data = {}
|
|
||||||
wall_preview_data["verts"] = []
|
|
||||||
|
|
||||||
# Verts
|
|
||||||
polyline_vertices = []
|
|
||||||
polyline_data = context.scene.BIMPolylineProperties.insertion_polyline
|
|
||||||
polyline_points = polyline_data[0].polyline_points if polyline_data else []
|
|
||||||
if len(polyline_points) < 2:
|
|
||||||
wall_preview_data = []
|
|
||||||
return
|
|
||||||
for point in polyline_points:
|
|
||||||
polyline_vertices.append(Vector((point.x, point.y, point.z)))
|
|
||||||
|
|
||||||
is_closed = False
|
|
||||||
if (
|
|
||||||
polyline_vertices[0].x == polyline_vertices[-1].x
|
|
||||||
and polyline_vertices[0].y == polyline_vertices[-1].y
|
|
||||||
and polyline_vertices[0].z == polyline_vertices[-1].z
|
|
||||||
):
|
|
||||||
is_closed = True
|
|
||||||
polyline_vertices.pop(-1) # Remove the last point. The edges are going to inform that the shape is closed.
|
|
||||||
|
|
||||||
bm_base = create_bmesh_from_vertices(polyline_vertices)
|
|
||||||
base_vertices = tool.Cad.offset_edges(bm_base, offset)
|
|
||||||
offset_base_verts = tool.Cad.offset_edges(bm_base, thickness + offset)
|
|
||||||
top_vertices = tool.Cad.offset_edges(bm_base, angle_distortion + offset)
|
|
||||||
offset_top_verts = tool.Cad.offset_edges(bm_base, angle_distortion + thickness + offset)
|
|
||||||
if is_closed:
|
|
||||||
base_vertices.append(base_vertices[0])
|
|
||||||
offset_base_verts.append(offset_base_verts[0])
|
|
||||||
top_vertices.append(top_vertices[0])
|
|
||||||
offset_top_verts.append(offset_top_verts[0])
|
|
||||||
|
|
||||||
if offset_base_verts is not None:
|
|
||||||
for v in base_vertices:
|
|
||||||
wall_preview_data["verts"].append((v.co.x, v.co.y, v.co.z + rl))
|
|
||||||
|
|
||||||
for v in offset_base_verts[::-1]:
|
|
||||||
wall_preview_data["verts"].append((v.co.x, v.co.y, v.co.z + rl))
|
|
||||||
|
|
||||||
for v in top_vertices:
|
|
||||||
wall_preview_data["verts"].append((v.co.x, v.co.y, v.co.z + rl + height))
|
|
||||||
|
|
||||||
for v in offset_top_verts[::-1]:
|
|
||||||
wall_preview_data["verts"].append((v.co.x, v.co.y, v.co.z + rl + height))
|
|
||||||
|
|
||||||
bm_base.free()
|
|
||||||
|
|
||||||
# Edges and Tris
|
|
||||||
points = []
|
|
||||||
side_edges_1 = []
|
|
||||||
side_edges_2 = []
|
|
||||||
base_edges = []
|
|
||||||
|
|
||||||
for i in range(len(wall_preview_data["verts"])):
|
|
||||||
points.append(Vector(wall_preview_data["verts"][i]))
|
|
||||||
|
|
||||||
n = len(points) // 2
|
|
||||||
bottom_side_1 = [[i, (i + 1) % (n)] for i in range((n - 1) // 2)]
|
|
||||||
bottom_side_2 = [[i, (i + 1) % (n)] for i in range(n // 2, n - 1)]
|
|
||||||
bottom_connections = [[i, n - i - 1] for i in range(n // 2)]
|
|
||||||
bottom_loop = bottom_connections + bottom_side_1 + bottom_side_2
|
|
||||||
side_edges_1.extend(bottom_side_1)
|
|
||||||
side_edges_2.extend(bottom_side_2)
|
|
||||||
base_edges.extend(bottom_loop)
|
|
||||||
|
|
||||||
upper_side_1 = [[i + n for i in edges] for edges in bottom_side_1]
|
|
||||||
upper_side_2 = [[i + n for i in edges] for edges in bottom_side_2]
|
|
||||||
upper_loop = [[i + n for i in edges] for edges in bottom_loop]
|
|
||||||
side_edges_1.extend(upper_side_1)
|
|
||||||
side_edges_2.extend(upper_side_2)
|
|
||||||
base_edges.extend(upper_loop)
|
|
||||||
|
|
||||||
loops = [side_edges_1, side_edges_2, base_edges]
|
|
||||||
|
|
||||||
wall_preview_data["edges"] = []
|
|
||||||
wall_preview_data["tris"] = []
|
|
||||||
for i, group in enumerate(loops):
|
|
||||||
bm = bmesh.new()
|
|
||||||
|
|
||||||
new_verts = [bm.verts.new(v) for v in points]
|
|
||||||
new_edges = [bm.edges.new((new_verts[e[0]], new_verts[e[1]])) for e in group]
|
|
||||||
|
|
||||||
bm.verts.index_update()
|
|
||||||
bm.edges.index_update()
|
|
||||||
|
|
||||||
if i == 2:
|
|
||||||
new_faces = bmesh.ops.contextual_create(bm, geom=bm.edges)
|
|
||||||
new_faces = bmesh.ops.bridge_loops(bm, edges=bm.edges, use_pairs=True, use_cyclic=True)
|
|
||||||
|
|
||||||
bm.verts.index_update()
|
|
||||||
bm.edges.index_update()
|
|
||||||
edges = [[v.index for v in e.verts] for e in bm.edges]
|
|
||||||
tris = [[l.vert.index for l in loop] for loop in bm.calc_loop_triangles()]
|
|
||||||
wall_preview_data["edges"].extend(edges)
|
|
||||||
wall_preview_data["tris"].extend(tris)
|
|
||||||
|
|
||||||
wall_preview_data["edges"] = list(set(tuple(e) for e in wall_preview_data["edges"]))
|
|
||||||
wall_preview_data["tris"] = list(set(tuple(t) for t in wall_preview_data["tris"]))
|
|
||||||
|
|
||||||
return wall_preview_data
|
|
||||||
|
|
||||||
def get_product_preview_data(cls, context, relating_type):
|
|
||||||
model_props = context.scene.BIMModelProperties
|
|
||||||
if relating_type.is_a("IfcDoorType"):
|
|
||||||
rl = float(model_props.rl1)
|
|
||||||
elif relating_type.is_a("IfcWindowType"):
|
|
||||||
rl = float(model_props.rl2)
|
|
||||||
else:
|
|
||||||
rl = 0
|
|
||||||
snap_prop = context.scene.BIMPolylineProperties.snap_mouse_point[0]
|
|
||||||
mouse_point = Vector((snap_prop.x, snap_prop.y, snap_prop.z))
|
|
||||||
snap_obj = bpy.data.objects.get(snap_prop.snap_object)
|
|
||||||
snap_element = tool.Ifc.get_entity(snap_obj)
|
|
||||||
rot_mat = Quaternion()
|
|
||||||
if snap_element and snap_element.is_a("IfcWall"):
|
|
||||||
rot_mat = snap_obj.matrix_world.to_quaternion()
|
|
||||||
|
|
||||||
obj_type = tool.Ifc.get_object(relating_type)
|
|
||||||
if obj_type.data:
|
|
||||||
data = ItemDecorator.get_obj_data(obj_type)
|
|
||||||
data["verts"] = [tuple(rot_mat @ (Vector((v[0], v[1], (v[2] + rl)))) + mouse_point) for v in data["verts"]]
|
|
||||||
return data
|
|
||||||
|
|
||||||
def get_profile_preview_data(self, context, relating_type):
|
|
||||||
material = ifcopenshell.util.element.get_material(relating_type)
|
|
||||||
profile = material.MaterialProfiles[0].Profile
|
|
||||||
model_props = context.scene.BIMModelProperties
|
|
||||||
extrusion_depth = model_props.extrusion_depth
|
|
||||||
cardinal_point = model_props.cardinal_point
|
|
||||||
rot_mat = Quaternion()
|
|
||||||
if relating_type.is_a("IfcBeamType"):
|
|
||||||
y_rot = Quaternion((0.0, 1.0, 0.0), radians(90))
|
|
||||||
z_rot = Quaternion((0.0, 0.0, 1.0), radians(90))
|
|
||||||
rot_mat = y_rot @ z_rot
|
|
||||||
# Get profile data
|
|
||||||
settings = ifcopenshell.geom.settings()
|
|
||||||
settings.set("dimensionality", ifcopenshell.ifcopenshell_wrapper.CURVES_SURFACES_AND_SOLIDS)
|
|
||||||
shape = ifcopenshell.geom.create_shape(settings, profile)
|
|
||||||
|
|
||||||
verts = shape.verts
|
|
||||||
if not verts:
|
|
||||||
raise RuntimeError("Profile shape has no vertices, it probably is invalid.")
|
|
||||||
|
|
||||||
edges = shape.edges
|
|
||||||
|
|
||||||
grouped_verts = [[verts[i], verts[i + 1], 0] for i in range(0, len(verts), 3)]
|
|
||||||
grouped_edges = [[edges[i], edges[i + 1]] for i in range(0, len(edges), 2)]
|
|
||||||
|
|
||||||
# Create offsets based on cardinal point
|
|
||||||
min_x = min(v[0] for v in grouped_verts)
|
|
||||||
max_x = max(v[0] for v in grouped_verts)
|
|
||||||
min_y = min(v[1] for v in grouped_verts)
|
|
||||||
max_y = max(v[1] for v in grouped_verts)
|
|
||||||
|
|
||||||
x_offset = (max_x - min_x) / 2
|
|
||||||
y_offset = (max_y - min_y) / 2
|
|
||||||
|
|
||||||
match cardinal_point:
|
|
||||||
case "1":
|
|
||||||
grouped_verts = [(v[0] - x_offset, v[1] + y_offset, v[2]) for v in grouped_verts]
|
|
||||||
case "2":
|
|
||||||
grouped_verts = [(v[0], v[1] + y_offset, v[2]) for v in grouped_verts]
|
|
||||||
case "3":
|
|
||||||
grouped_verts = [(v[0] + x_offset, v[1] + y_offset, v[2]) for v in grouped_verts]
|
|
||||||
case "4":
|
|
||||||
grouped_verts = [(v[0] - x_offset, v[1], v[2]) for v in grouped_verts]
|
|
||||||
case "5":
|
|
||||||
grouped_verts = [(v[0], v[1], v[2]) for v in grouped_verts]
|
|
||||||
case "6":
|
|
||||||
grouped_verts = [(v[0] + x_offset, v[1], v[2]) for v in grouped_verts]
|
|
||||||
case "7":
|
|
||||||
grouped_verts = [(v[0] - x_offset, v[1] - y_offset, v[2]) for v in grouped_verts]
|
|
||||||
case "8":
|
|
||||||
grouped_verts = [(v[0], v[1] - y_offset, v[2]) for v in grouped_verts]
|
|
||||||
case "9":
|
|
||||||
grouped_verts = [(v[0] + x_offset, v[1] - y_offset, v[2]) for v in grouped_verts]
|
|
||||||
|
|
||||||
# Create extrusion bmesh
|
|
||||||
bm = bmesh.new()
|
|
||||||
|
|
||||||
new_verts = [bm.verts.new(v) for v in grouped_verts]
|
|
||||||
new_edges = [bm.edges.new((new_verts[i], new_verts[i + 1])) for i in range(len(grouped_verts) - 1)]
|
|
||||||
|
|
||||||
bm.verts.index_update()
|
|
||||||
bm.edges.index_update()
|
|
||||||
|
|
||||||
new_faces = bmesh.ops.contextual_create(bm, geom=bm.edges)
|
|
||||||
|
|
||||||
new_faces = bmesh.ops.extrude_face_region(bm, geom=bm.faces)
|
|
||||||
new_verts = [e for e in new_faces["geom"] if isinstance(e, bmesh.types.BMVert)]
|
|
||||||
new_faces = bmesh.ops.translate(bm, verts=new_verts, vec=(0.0, 0.0, extrusion_depth))
|
|
||||||
|
|
||||||
bm.verts.index_update()
|
|
||||||
bm.edges.index_update()
|
|
||||||
tris = [[loop.vert.index for loop in triangles] for triangles in bm.calc_loop_triangles()]
|
|
||||||
|
|
||||||
# Create bounding box
|
|
||||||
verts = bm.verts
|
|
||||||
i = len(verts)
|
|
||||||
|
|
||||||
min_x = min(v.co.x for v in verts)
|
|
||||||
max_x = max(v.co.x for v in verts)
|
|
||||||
min_y = min(v.co.y for v in verts)
|
|
||||||
max_y = max(v.co.y for v in verts)
|
|
||||||
min_z = min(v.co.z for v in verts)
|
|
||||||
max_z = max(v.co.z for v in verts)
|
|
||||||
|
|
||||||
bbox_verts = [
|
|
||||||
(min_x, min_y, min_z),
|
|
||||||
(max_x, min_y, min_z),
|
|
||||||
(max_x, max_y, min_z),
|
|
||||||
(min_x, max_y, min_z),
|
|
||||||
(min_x, min_y, max_z),
|
|
||||||
(max_x, min_y, max_z),
|
|
||||||
(max_x, max_y, max_z),
|
|
||||||
(min_x, max_y, max_z),
|
|
||||||
]
|
|
||||||
|
|
||||||
bbox_edges = [
|
|
||||||
(0 + i, 3 + i),
|
|
||||||
(3 + i, 7 + i),
|
|
||||||
(7 + i, 4 + i),
|
|
||||||
(4 + i, 0 + i),
|
|
||||||
(0 + i, 1 + i),
|
|
||||||
(3 + i, 2 + i),
|
|
||||||
(7 + i, 6 + i),
|
|
||||||
(4 + i, 5 + i),
|
|
||||||
(1 + i, 2 + i),
|
|
||||||
(2 + i, 6 + i),
|
|
||||||
(6 + i, 5 + i),
|
|
||||||
(5 + i, 1 + i),
|
|
||||||
]
|
|
||||||
|
|
||||||
# Calculate rotation, mouse position, angle and cardinal point
|
|
||||||
# TODO Angle
|
|
||||||
snap_prop = context.scene.BIMPolylineProperties.snap_mouse_point[0]
|
|
||||||
mouse_point = Vector((snap_prop.x, snap_prop.y, snap_prop.z))
|
|
||||||
data = {}
|
|
||||||
|
|
||||||
verts = [tuple(v.co) for v in verts]
|
|
||||||
verts.extend(bbox_verts)
|
|
||||||
verts = [tuple(rot_mat @ Vector(v)) for v in verts]
|
|
||||||
verts = [tuple(Vector(v) + mouse_point) for v in verts]
|
|
||||||
data["verts"] = verts
|
|
||||||
data["edges"] = bbox_edges
|
|
||||||
# data["edges"] = [(edge.verts[0].index, edge.verts[1].index) for edge in bm.edges]
|
|
||||||
data["tris"] = tris
|
|
||||||
|
|
||||||
bm.free()
|
|
||||||
return data
|
|
||||||
|
|
||||||
def draw_product_preview(self, context):
|
|
||||||
def transparent_color(color, alpha=0.1):
|
|
||||||
color = [i for i in color]
|
|
||||||
color[3] = alpha
|
|
||||||
return color
|
|
||||||
|
|
||||||
self.addon_prefs = tool.Blender.get_addon_preferences()
|
|
||||||
self.line_shader = gpu.shader.from_builtin("POLYLINE_UNIFORM_COLOR")
|
|
||||||
self.line_shader.bind() # required to be able to change uniforms of the shader
|
|
||||||
self.line_shader.uniform_float("viewportSize", (context.region.width, context.region.height))
|
|
||||||
self.shader = gpu.shader.from_builtin("UNIFORM_COLOR")
|
|
||||||
self.line_shader.uniform_float("lineWidth", 2.0)
|
|
||||||
decorator_color = self.addon_prefs.decorator_color_special
|
|
||||||
polyline_data = context.scene.BIMPolylineProperties.insertion_polyline
|
|
||||||
polyline_points = polyline_data[0].polyline_points if polyline_data else []
|
|
||||||
|
|
||||||
self.relating_type = None
|
|
||||||
props = context.scene.BIMModelProperties
|
|
||||||
relating_type_id = props.relating_type_id
|
|
||||||
if relating_type_id:
|
|
||||||
self.relating_type = tool.Ifc.get().by_id(int(relating_type_id))
|
|
||||||
|
|
||||||
# Wall
|
|
||||||
wall_preview_data = self.get_wall_preview_data(context, self.relating_type)
|
|
||||||
if wall_preview_data:
|
|
||||||
self.draw_batch("LINES", wall_preview_data["verts"], decorator_color, wall_preview_data["edges"])
|
|
||||||
self.draw_batch(
|
|
||||||
"TRIS", wall_preview_data["verts"], transparent_color(decorator_color), wall_preview_data["tris"]
|
|
||||||
)
|
|
||||||
|
|
||||||
# Mesh type products
|
|
||||||
product_preview_data = self.get_product_preview_data(context, self.relating_type)
|
|
||||||
if product_preview_data:
|
|
||||||
self.draw_batch("LINES", product_preview_data["verts"], decorator_color, product_preview_data["edges"])
|
|
||||||
self.draw_batch(
|
|
||||||
"TRIS", product_preview_data["verts"], transparent_color(decorator_color), product_preview_data["tris"]
|
|
||||||
)
|
|
||||||
|
|
||||||
# Profile type products
|
|
||||||
product_preview_data = self.get_profile_preview_data(context, self.relating_type)
|
|
||||||
if product_preview_data:
|
|
||||||
self.draw_batch("LINES", product_preview_data["verts"], decorator_color, product_preview_data["edges"])
|
|
||||||
self.draw_batch(
|
|
||||||
"TRIS", product_preview_data["verts"], transparent_color(decorator_color), product_preview_data["tris"]
|
|
||||||
)
|
|
||||||
|
|
||||||
def draw_input_ui(self, context):
|
def draw_input_ui(self, context):
|
||||||
texts = {
|
texts = {
|
||||||
"D": "Distance: ",
|
"D": "Distance: ",
|
||||||
@@ -1064,3 +709,390 @@ class PolylineDecorator:
|
|||||||
self.draw_batch("POINTS", polyline_verts, decorator_color_unselected)
|
self.draw_batch("POINTS", polyline_verts, decorator_color_unselected)
|
||||||
if len(polyline_verts) > 1:
|
if len(polyline_verts) > 1:
|
||||||
self.draw_batch("LINES", polyline_verts, decorator_color_unselected, polyline_edges)
|
self.draw_batch("LINES", polyline_verts, decorator_color_unselected, polyline_edges)
|
||||||
|
|
||||||
|
|
||||||
|
class ProductDecorator:
|
||||||
|
is_installed = False
|
||||||
|
handlers = []
|
||||||
|
relating_type = None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def install(cls, context):
|
||||||
|
if cls.is_installed:
|
||||||
|
cls.uninstall()
|
||||||
|
handler = cls()
|
||||||
|
cls.handlers.append(
|
||||||
|
SpaceView3D.draw_handler_add(handler.draw_product_preview, (context,), "WINDOW", "POST_VIEW")
|
||||||
|
)
|
||||||
|
cls.is_installed = True
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def uninstall(cls):
|
||||||
|
for handler in cls.handlers:
|
||||||
|
try:
|
||||||
|
SpaceView3D.draw_handler_remove(handler, "WINDOW")
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
cls.is_installed = False
|
||||||
|
|
||||||
|
def draw_batch(self, shader_type, content_pos, color, indices=None):
|
||||||
|
shader = self.line_shader if shader_type == "LINES" else self.shader
|
||||||
|
batch = batch_for_shader(shader, shader_type, {"pos": content_pos}, indices=indices)
|
||||||
|
shader.uniform_float("color", color)
|
||||||
|
batch.draw(shader)
|
||||||
|
|
||||||
|
def get_wall_preview_data(cls, context, relating_type):
|
||||||
|
def create_bmesh_from_vertices(vertices):
|
||||||
|
bm = bmesh.new()
|
||||||
|
|
||||||
|
new_verts = [bm.verts.new(v) for v in polyline_vertices]
|
||||||
|
if is_closed:
|
||||||
|
new_edges = [bm.edges.new((new_verts[i], new_verts[i + 1])) for i in range(len(new_verts) - 1)]
|
||||||
|
new_edges.append(
|
||||||
|
bm.edges.new((new_verts[-1], new_verts[0]))
|
||||||
|
) # Add an edge between the last an first point to make it closed.
|
||||||
|
else:
|
||||||
|
new_edges = [bm.edges.new((new_verts[i], new_verts[i + 1])) for i in range(len(new_verts) - 1)]
|
||||||
|
|
||||||
|
bm.verts.index_update()
|
||||||
|
bm.edges.index_update()
|
||||||
|
return bm
|
||||||
|
|
||||||
|
# Get properties from object type
|
||||||
|
layers = tool.Model.get_material_layer_parameters(relating_type)
|
||||||
|
if not layers["thickness"]:
|
||||||
|
return
|
||||||
|
thickness = layers["thickness"]
|
||||||
|
model_props = context.scene.BIMModelProperties
|
||||||
|
direction_sense = model_props.direction_sense
|
||||||
|
direction = 1
|
||||||
|
if direction_sense == "NEGATIVE":
|
||||||
|
direction = -1
|
||||||
|
offset_type = model_props.offset_type
|
||||||
|
offset = 0
|
||||||
|
if offset_type == "CENTER":
|
||||||
|
offset = -thickness / 2
|
||||||
|
elif offset_type == "INTERIOR":
|
||||||
|
offset = -thickness
|
||||||
|
|
||||||
|
unit_system = tool.Drawing.get_unit_system()
|
||||||
|
factor = 1
|
||||||
|
if unit_system == "IMPERIAL":
|
||||||
|
factor = 3.048
|
||||||
|
if unit_system == "METRIC":
|
||||||
|
unit_length = context.scene.unit_settings.length_unit
|
||||||
|
if unit_length == "MILLIMETERS":
|
||||||
|
factor = 1000
|
||||||
|
|
||||||
|
# For the model properties, the offset value should just be converted
|
||||||
|
# However, for the wall preview logic that follows, offset and thickness must change direction
|
||||||
|
model_props.offset = offset * factor
|
||||||
|
thickness *= direction
|
||||||
|
offset *= direction
|
||||||
|
|
||||||
|
height = float(model_props.extrusion_depth)
|
||||||
|
rl = float(model_props.rl1)
|
||||||
|
x_angle = float(model_props.x_angle)
|
||||||
|
angle_distortion = height * tan(x_angle)
|
||||||
|
|
||||||
|
wall_preview_data = {}
|
||||||
|
wall_preview_data["verts"] = []
|
||||||
|
|
||||||
|
# Verts
|
||||||
|
polyline_vertices = []
|
||||||
|
polyline_data = context.scene.BIMPolylineProperties.insertion_polyline
|
||||||
|
polyline_points = polyline_data[0].polyline_points if polyline_data else []
|
||||||
|
if len(polyline_points) < 2:
|
||||||
|
wall_preview_data = []
|
||||||
|
return
|
||||||
|
for point in polyline_points:
|
||||||
|
polyline_vertices.append(Vector((point.x, point.y, point.z)))
|
||||||
|
|
||||||
|
is_closed = False
|
||||||
|
if (
|
||||||
|
polyline_vertices[0].x == polyline_vertices[-1].x
|
||||||
|
and polyline_vertices[0].y == polyline_vertices[-1].y
|
||||||
|
and polyline_vertices[0].z == polyline_vertices[-1].z
|
||||||
|
):
|
||||||
|
is_closed = True
|
||||||
|
polyline_vertices.pop(-1) # Remove the last point. The edges are going to inform that the shape is closed.
|
||||||
|
|
||||||
|
bm_base = create_bmesh_from_vertices(polyline_vertices)
|
||||||
|
base_vertices = tool.Cad.offset_edges(bm_base, offset)
|
||||||
|
offset_base_verts = tool.Cad.offset_edges(bm_base, thickness + offset)
|
||||||
|
top_vertices = tool.Cad.offset_edges(bm_base, angle_distortion + offset)
|
||||||
|
offset_top_verts = tool.Cad.offset_edges(bm_base, angle_distortion + thickness + offset)
|
||||||
|
if is_closed:
|
||||||
|
base_vertices.append(base_vertices[0])
|
||||||
|
offset_base_verts.append(offset_base_verts[0])
|
||||||
|
top_vertices.append(top_vertices[0])
|
||||||
|
offset_top_verts.append(offset_top_verts[0])
|
||||||
|
|
||||||
|
if offset_base_verts is not None:
|
||||||
|
for v in base_vertices:
|
||||||
|
wall_preview_data["verts"].append((v.co.x, v.co.y, v.co.z + rl))
|
||||||
|
|
||||||
|
for v in offset_base_verts[::-1]:
|
||||||
|
wall_preview_data["verts"].append((v.co.x, v.co.y, v.co.z + rl))
|
||||||
|
|
||||||
|
for v in top_vertices:
|
||||||
|
wall_preview_data["verts"].append((v.co.x, v.co.y, v.co.z + rl + height))
|
||||||
|
|
||||||
|
for v in offset_top_verts[::-1]:
|
||||||
|
wall_preview_data["verts"].append((v.co.x, v.co.y, v.co.z + rl + height))
|
||||||
|
|
||||||
|
bm_base.free()
|
||||||
|
|
||||||
|
# Edges and Tris
|
||||||
|
points = []
|
||||||
|
side_edges_1 = []
|
||||||
|
side_edges_2 = []
|
||||||
|
base_edges = []
|
||||||
|
|
||||||
|
for i in range(len(wall_preview_data["verts"])):
|
||||||
|
points.append(Vector(wall_preview_data["verts"][i]))
|
||||||
|
|
||||||
|
n = len(points) // 2
|
||||||
|
bottom_side_1 = [[i, (i + 1) % (n)] for i in range((n - 1) // 2)]
|
||||||
|
bottom_side_2 = [[i, (i + 1) % (n)] for i in range(n // 2, n - 1)]
|
||||||
|
bottom_connections = [[i, n - i - 1] for i in range(n // 2)]
|
||||||
|
bottom_loop = bottom_connections + bottom_side_1 + bottom_side_2
|
||||||
|
side_edges_1.extend(bottom_side_1)
|
||||||
|
side_edges_2.extend(bottom_side_2)
|
||||||
|
base_edges.extend(bottom_loop)
|
||||||
|
|
||||||
|
upper_side_1 = [[i + n for i in edges] for edges in bottom_side_1]
|
||||||
|
upper_side_2 = [[i + n for i in edges] for edges in bottom_side_2]
|
||||||
|
upper_loop = [[i + n for i in edges] for edges in bottom_loop]
|
||||||
|
side_edges_1.extend(upper_side_1)
|
||||||
|
side_edges_2.extend(upper_side_2)
|
||||||
|
base_edges.extend(upper_loop)
|
||||||
|
|
||||||
|
loops = [side_edges_1, side_edges_2, base_edges]
|
||||||
|
|
||||||
|
wall_preview_data["edges"] = []
|
||||||
|
wall_preview_data["tris"] = []
|
||||||
|
for i, group in enumerate(loops):
|
||||||
|
bm = bmesh.new()
|
||||||
|
|
||||||
|
new_verts = [bm.verts.new(v) for v in points]
|
||||||
|
new_edges = [bm.edges.new((new_verts[e[0]], new_verts[e[1]])) for e in group]
|
||||||
|
|
||||||
|
bm.verts.index_update()
|
||||||
|
bm.edges.index_update()
|
||||||
|
|
||||||
|
if i == 2:
|
||||||
|
new_faces = bmesh.ops.contextual_create(bm, geom=bm.edges)
|
||||||
|
new_faces = bmesh.ops.bridge_loops(bm, edges=bm.edges, use_pairs=True, use_cyclic=True)
|
||||||
|
|
||||||
|
bm.verts.index_update()
|
||||||
|
bm.edges.index_update()
|
||||||
|
edges = [[v.index for v in e.verts] for e in bm.edges]
|
||||||
|
tris = [[l.vert.index for l in loop] for loop in bm.calc_loop_triangles()]
|
||||||
|
wall_preview_data["edges"].extend(edges)
|
||||||
|
wall_preview_data["tris"].extend(tris)
|
||||||
|
|
||||||
|
wall_preview_data["edges"] = list(set(tuple(e) for e in wall_preview_data["edges"]))
|
||||||
|
wall_preview_data["tris"] = list(set(tuple(t) for t in wall_preview_data["tris"]))
|
||||||
|
|
||||||
|
return wall_preview_data
|
||||||
|
|
||||||
|
def get_product_preview_data(cls, context, relating_type):
|
||||||
|
model_props = context.scene.BIMModelProperties
|
||||||
|
if relating_type.is_a("IfcDoorType"):
|
||||||
|
rl = float(model_props.rl1)
|
||||||
|
elif relating_type.is_a("IfcWindowType"):
|
||||||
|
rl = float(model_props.rl2)
|
||||||
|
else:
|
||||||
|
rl = 0
|
||||||
|
snap_prop = context.scene.BIMPolylineProperties.snap_mouse_point[0]
|
||||||
|
mouse_point = Vector((snap_prop.x, snap_prop.y, snap_prop.z))
|
||||||
|
snap_obj = bpy.data.objects.get(snap_prop.snap_object)
|
||||||
|
snap_element = tool.Ifc.get_entity(snap_obj)
|
||||||
|
rot_mat = Quaternion()
|
||||||
|
if snap_element and snap_element.is_a("IfcWall"):
|
||||||
|
rot_mat = snap_obj.matrix_world.to_quaternion()
|
||||||
|
|
||||||
|
obj_type = tool.Ifc.get_object(relating_type)
|
||||||
|
if obj_type.data:
|
||||||
|
data = ItemDecorator.get_obj_data(obj_type)
|
||||||
|
data["verts"] = [tuple(rot_mat @ (Vector((v[0], v[1], (v[2] + rl)))) + mouse_point) for v in data["verts"]]
|
||||||
|
return data
|
||||||
|
|
||||||
|
def get_profile_preview_data(self, context, relating_type):
|
||||||
|
material = ifcopenshell.util.element.get_material(relating_type)
|
||||||
|
try:
|
||||||
|
profile = material.MaterialProfiles[0].Profile
|
||||||
|
except:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
model_props = context.scene.BIMModelProperties
|
||||||
|
extrusion_depth = model_props.extrusion_depth
|
||||||
|
cardinal_point = model_props.cardinal_point
|
||||||
|
rot_mat = Quaternion()
|
||||||
|
if relating_type.is_a("IfcBeamType"):
|
||||||
|
y_rot = Quaternion((0.0, 1.0, 0.0), radians(90))
|
||||||
|
z_rot = Quaternion((0.0, 0.0, 1.0), radians(90))
|
||||||
|
rot_mat = y_rot @ z_rot
|
||||||
|
# Get profile data
|
||||||
|
settings = ifcopenshell.geom.settings()
|
||||||
|
settings.set("dimensionality", ifcopenshell.ifcopenshell_wrapper.CURVES_SURFACES_AND_SOLIDS)
|
||||||
|
shape = ifcopenshell.geom.create_shape(settings, profile)
|
||||||
|
|
||||||
|
verts = shape.verts
|
||||||
|
if not verts:
|
||||||
|
raise RuntimeError("Profile shape has no vertices, it probably is invalid.")
|
||||||
|
|
||||||
|
edges = shape.edges
|
||||||
|
|
||||||
|
grouped_verts = [[verts[i], verts[i + 1], 0] for i in range(0, len(verts), 3)]
|
||||||
|
grouped_edges = [[edges[i], edges[i + 1]] for i in range(0, len(edges), 2)]
|
||||||
|
|
||||||
|
# Create offsets based on cardinal point
|
||||||
|
min_x = min(v[0] for v in grouped_verts)
|
||||||
|
max_x = max(v[0] for v in grouped_verts)
|
||||||
|
min_y = min(v[1] for v in grouped_verts)
|
||||||
|
max_y = max(v[1] for v in grouped_verts)
|
||||||
|
|
||||||
|
x_offset = (max_x - min_x) / 2
|
||||||
|
y_offset = (max_y - min_y) / 2
|
||||||
|
|
||||||
|
match cardinal_point:
|
||||||
|
case "1":
|
||||||
|
grouped_verts = [(v[0] - x_offset, v[1] + y_offset, v[2]) for v in grouped_verts]
|
||||||
|
case "2":
|
||||||
|
grouped_verts = [(v[0], v[1] + y_offset, v[2]) for v in grouped_verts]
|
||||||
|
case "3":
|
||||||
|
grouped_verts = [(v[0] + x_offset, v[1] + y_offset, v[2]) for v in grouped_verts]
|
||||||
|
case "4":
|
||||||
|
grouped_verts = [(v[0] - x_offset, v[1], v[2]) for v in grouped_verts]
|
||||||
|
case "5":
|
||||||
|
grouped_verts = [(v[0], v[1], v[2]) for v in grouped_verts]
|
||||||
|
case "6":
|
||||||
|
grouped_verts = [(v[0] + x_offset, v[1], v[2]) for v in grouped_verts]
|
||||||
|
case "7":
|
||||||
|
grouped_verts = [(v[0] - x_offset, v[1] - y_offset, v[2]) for v in grouped_verts]
|
||||||
|
case "8":
|
||||||
|
grouped_verts = [(v[0], v[1] - y_offset, v[2]) for v in grouped_verts]
|
||||||
|
case "9":
|
||||||
|
grouped_verts = [(v[0] + x_offset, v[1] - y_offset, v[2]) for v in grouped_verts]
|
||||||
|
|
||||||
|
# Create extrusion bmesh
|
||||||
|
bm = bmesh.new()
|
||||||
|
|
||||||
|
new_verts = [bm.verts.new(v) for v in grouped_verts]
|
||||||
|
new_edges = [bm.edges.new((new_verts[i], new_verts[i + 1])) for i in range(len(grouped_verts) - 1)]
|
||||||
|
|
||||||
|
bm.verts.index_update()
|
||||||
|
bm.edges.index_update()
|
||||||
|
|
||||||
|
new_faces = bmesh.ops.contextual_create(bm, geom=bm.edges)
|
||||||
|
|
||||||
|
new_faces = bmesh.ops.extrude_face_region(bm, geom=bm.faces)
|
||||||
|
new_verts = [e for e in new_faces["geom"] if isinstance(e, bmesh.types.BMVert)]
|
||||||
|
new_faces = bmesh.ops.translate(bm, verts=new_verts, vec=(0.0, 0.0, extrusion_depth))
|
||||||
|
|
||||||
|
bm.verts.index_update()
|
||||||
|
bm.edges.index_update()
|
||||||
|
tris = [[loop.vert.index for loop in triangles] for triangles in bm.calc_loop_triangles()]
|
||||||
|
|
||||||
|
# Create bounding box
|
||||||
|
verts = bm.verts
|
||||||
|
i = len(verts)
|
||||||
|
|
||||||
|
min_x = min(v.co.x for v in verts)
|
||||||
|
max_x = max(v.co.x for v in verts)
|
||||||
|
min_y = min(v.co.y for v in verts)
|
||||||
|
max_y = max(v.co.y for v in verts)
|
||||||
|
min_z = min(v.co.z for v in verts)
|
||||||
|
max_z = max(v.co.z for v in verts)
|
||||||
|
|
||||||
|
bbox_verts = [
|
||||||
|
(min_x, min_y, min_z),
|
||||||
|
(max_x, min_y, min_z),
|
||||||
|
(max_x, max_y, min_z),
|
||||||
|
(min_x, max_y, min_z),
|
||||||
|
(min_x, min_y, max_z),
|
||||||
|
(max_x, min_y, max_z),
|
||||||
|
(max_x, max_y, max_z),
|
||||||
|
(min_x, max_y, max_z),
|
||||||
|
]
|
||||||
|
|
||||||
|
bbox_edges = [
|
||||||
|
(0 + i, 3 + i),
|
||||||
|
(3 + i, 7 + i),
|
||||||
|
(7 + i, 4 + i),
|
||||||
|
(4 + i, 0 + i),
|
||||||
|
(0 + i, 1 + i),
|
||||||
|
(3 + i, 2 + i),
|
||||||
|
(7 + i, 6 + i),
|
||||||
|
(4 + i, 5 + i),
|
||||||
|
(1 + i, 2 + i),
|
||||||
|
(2 + i, 6 + i),
|
||||||
|
(6 + i, 5 + i),
|
||||||
|
(5 + i, 1 + i),
|
||||||
|
]
|
||||||
|
|
||||||
|
# Calculate rotation, mouse position, angle and cardinal point
|
||||||
|
# TODO Angle
|
||||||
|
snap_prop = context.scene.BIMPolylineProperties.snap_mouse_point[0]
|
||||||
|
mouse_point = Vector((snap_prop.x, snap_prop.y, snap_prop.z))
|
||||||
|
data = {}
|
||||||
|
|
||||||
|
verts = [tuple(v.co) for v in verts]
|
||||||
|
verts.extend(bbox_verts)
|
||||||
|
verts = [tuple(rot_mat @ Vector(v)) for v in verts]
|
||||||
|
verts = [tuple(Vector(v) + mouse_point) for v in verts]
|
||||||
|
data["verts"] = verts
|
||||||
|
data["edges"] = bbox_edges
|
||||||
|
# data["edges"] = [(edge.verts[0].index, edge.verts[1].index) for edge in bm.edges]
|
||||||
|
data["tris"] = tris
|
||||||
|
|
||||||
|
bm.free()
|
||||||
|
return data
|
||||||
|
|
||||||
|
def draw_product_preview(self, context):
|
||||||
|
def transparent_color(color, alpha=0.1):
|
||||||
|
color = [i for i in color]
|
||||||
|
color[3] = alpha
|
||||||
|
return color
|
||||||
|
|
||||||
|
self.addon_prefs = tool.Blender.get_addon_preferences()
|
||||||
|
self.line_shader = gpu.shader.from_builtin("POLYLINE_UNIFORM_COLOR")
|
||||||
|
self.line_shader.bind() # required to be able to change uniforms of the shader
|
||||||
|
self.line_shader.uniform_float("viewportSize", (context.region.width, context.region.height))
|
||||||
|
self.shader = gpu.shader.from_builtin("UNIFORM_COLOR")
|
||||||
|
self.line_shader.uniform_float("lineWidth", 2.0)
|
||||||
|
decorator_color = self.addon_prefs.decorator_color_special
|
||||||
|
polyline_data = context.scene.BIMPolylineProperties.insertion_polyline
|
||||||
|
polyline_points = polyline_data[0].polyline_points if polyline_data else []
|
||||||
|
|
||||||
|
self.relating_type = None
|
||||||
|
props = context.scene.BIMModelProperties
|
||||||
|
relating_type_id = props.relating_type_id
|
||||||
|
if relating_type_id:
|
||||||
|
self.relating_type = tool.Ifc.get().by_id(int(relating_type_id))
|
||||||
|
|
||||||
|
# Wall
|
||||||
|
wall_preview_data = self.get_wall_preview_data(context, self.relating_type)
|
||||||
|
if wall_preview_data:
|
||||||
|
self.draw_batch("LINES", wall_preview_data["verts"], decorator_color, wall_preview_data["edges"])
|
||||||
|
self.draw_batch(
|
||||||
|
"TRIS", wall_preview_data["verts"], transparent_color(decorator_color), wall_preview_data["tris"]
|
||||||
|
)
|
||||||
|
|
||||||
|
# Mesh type products
|
||||||
|
product_preview_data = self.get_product_preview_data(context, self.relating_type)
|
||||||
|
if product_preview_data:
|
||||||
|
self.draw_batch("LINES", product_preview_data["verts"], decorator_color, product_preview_data["edges"])
|
||||||
|
self.draw_batch(
|
||||||
|
"TRIS", product_preview_data["verts"], transparent_color(decorator_color), product_preview_data["tris"]
|
||||||
|
)
|
||||||
|
|
||||||
|
# Profile type products
|
||||||
|
product_preview_data = self.get_profile_preview_data(context, self.relating_type)
|
||||||
|
if product_preview_data:
|
||||||
|
self.draw_batch("LINES", product_preview_data["verts"], decorator_color, product_preview_data["edges"])
|
||||||
|
self.draw_batch(
|
||||||
|
"TRIS", product_preview_data["verts"], transparent_color(decorator_color), product_preview_data["tris"]
|
||||||
|
)
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ from bonsai.bim.ifc import IfcStore
|
|||||||
from bonsai.bim.helper import get_enum_items
|
from bonsai.bim.helper import get_enum_items
|
||||||
from bonsai.bim.module.model.data import AuthoringData
|
from bonsai.bim.module.model.data import AuthoringData
|
||||||
from bonsai.bim.module.model.polyline import PolylineOperator
|
from bonsai.bim.module.model.polyline import PolylineOperator
|
||||||
from bonsai.bim.module.model.decorator import PolylineDecorator
|
from bonsai.bim.module.model.decorator import PolylineDecorator, ProductDecorator
|
||||||
from mathutils import Vector, Matrix
|
from mathutils import Vector, Matrix
|
||||||
from bpy_extras.object_utils import AddObjectHelper
|
from bpy_extras.object_utils import AddObjectHelper
|
||||||
import json
|
import json
|
||||||
@@ -187,12 +187,14 @@ class AddOccurrence(bpy.types.Operator, PolylineOperator):
|
|||||||
|
|
||||||
cancel = self.handle_cancelation(context, event)
|
cancel = self.handle_cancelation(context, event)
|
||||||
if cancel is not None:
|
if cancel is not None:
|
||||||
|
ProductDecorator.uninstall()
|
||||||
return cancel
|
return cancel
|
||||||
|
|
||||||
return {"RUNNING_MODAL"}
|
return {"RUNNING_MODAL"}
|
||||||
|
|
||||||
def invoke(self, context, event):
|
def invoke(self, context, event):
|
||||||
super().invoke(context, event)
|
super().invoke(context, event)
|
||||||
|
ProductDecorator.install(context)
|
||||||
self.tool_state.use_default_container = True
|
self.tool_state.use_default_container = True
|
||||||
self.tool_state.plane_method = "XY"
|
self.tool_state.plane_method = "XY"
|
||||||
return {"RUNNING_MODAL"}
|
return {"RUNNING_MODAL"}
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ from bonsai.bim.ifc import IfcStore
|
|||||||
from math import pi, sin, cos, degrees
|
from math import pi, sin, cos, degrees
|
||||||
from mathutils import Vector, Matrix
|
from mathutils import Vector, Matrix
|
||||||
from bonsai.bim.module.model.opening import FilledOpeningGenerator
|
from bonsai.bim.module.model.opening import FilledOpeningGenerator
|
||||||
from bonsai.bim.module.model.decorator import PolylineDecorator
|
from bonsai.bim.module.model.decorator import PolylineDecorator, ProductDecorator
|
||||||
from bonsai.bim.module.model.polyline import PolylineOperator
|
from bonsai.bim.module.model.polyline import PolylineOperator
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
from lark import Lark, Transformer
|
from lark import Lark, Transformer
|
||||||
@@ -383,6 +383,7 @@ class DrawPolylineWall(bpy.types.Operator, PolylineOperator):
|
|||||||
):
|
):
|
||||||
self.create_walls_from_polyline(context)
|
self.create_walls_from_polyline(context)
|
||||||
context.workspace.status_text_set(text=None)
|
context.workspace.status_text_set(text=None)
|
||||||
|
ProductDecorator.uninstall()
|
||||||
PolylineDecorator.uninstall()
|
PolylineDecorator.uninstall()
|
||||||
tool.Polyline.clear_polyline()
|
tool.Polyline.clear_polyline()
|
||||||
tool.Blender.update_viewport()
|
tool.Blender.update_viewport()
|
||||||
@@ -394,12 +395,14 @@ class DrawPolylineWall(bpy.types.Operator, PolylineOperator):
|
|||||||
|
|
||||||
cancel = self.handle_cancelation(context, event)
|
cancel = self.handle_cancelation(context, event)
|
||||||
if cancel is not None:
|
if cancel is not None:
|
||||||
|
ProductDecorator.uninstall()
|
||||||
return cancel
|
return cancel
|
||||||
|
|
||||||
return {"RUNNING_MODAL"}
|
return {"RUNNING_MODAL"}
|
||||||
|
|
||||||
def invoke(self, context, event):
|
def invoke(self, context, event):
|
||||||
super().invoke(context, event)
|
super().invoke(context, event)
|
||||||
|
ProductDecorator.install(context)
|
||||||
self.tool_state.use_default_container = True
|
self.tool_state.use_default_container = True
|
||||||
self.tool_state.plane_method = "XY"
|
self.tool_state.plane_method = "XY"
|
||||||
return {"RUNNING_MODAL"}
|
return {"RUNNING_MODAL"}
|
||||||
|
|||||||
Reference in New Issue
Block a user