#1153. Section references are now auto generated when activating a drawing.

This commit is contained in:
Dion Moult
2022-03-29 17:04:50 +11:00
parent a60dfd8dd5
commit 2c6cb2fbb2
6 changed files with 228 additions and 110 deletions
@@ -40,7 +40,7 @@ class BaseDecorator:
DEF_GLSL = """
#define PI 3.141592653589793
#define MAX_POINTS 32
#define MAX_POINTS 64
#define CIRCLE_SEGS 12
"""
@@ -1162,15 +1162,13 @@ class ElevationDecorator(LevelDecorator):
vec4 p0w = CLIP2WIN(p0), p1w = CLIP2WIN(p1);
vec4 edge = p1w - p0w, dir = normalize(edge);
// vec4 dir = normalize(vec4(1, 0, 0, 0));
vec4 gap = dir * viewportDrawingScale * TRIANGLE_L * .5;
vec4 side = vec4(cross(vec3(dir.xy, 0), vec3(0, 0, 1)).xy, 0, 0);
vec4 p;
vec4 head[CIRCLE_SEGS];
circle_head(viewportDrawingScale * CIRCLE_SIZE, head);
vec4 head3[5];
vec4 head5[5];
// start edge circle
for(int i=0; i<CIRCLE_SEGS; i++) {
@@ -1183,21 +1181,21 @@ class ElevationDecorator(LevelDecorator):
EmitVertex();
EndPrimitive();
// start edge triangle
triangle_head(side, dir, viewportDrawingScale * TRIANGLE_L, viewportDrawingScale * TRIANGLE_W, viewportDrawingScale * CIRCLE_SIZE, head3);
p = p0w + head3[0];
// edge triangle
triangle_head(side, dir, viewportDrawingScale * TRIANGLE_L, viewportDrawingScale * TRIANGLE_W, viewportDrawingScale * CIRCLE_SIZE, head5);
p = p0w + head5[0];
gl_Position = WIN2CLIP(p);
EmitVertex();
p = p0w + head3[1];
p = p0w + head5[1];
gl_Position = WIN2CLIP(p);
EmitVertex();
p = p0w + head3[2];
p = p0w + head5[2];
gl_Position = WIN2CLIP(p);
EmitVertex();
p = p0w + head3[3];
p = p0w + head5[3];
gl_Position = WIN2CLIP(p);
EmitVertex();
p = p0w + head3[4];
p = p0w + head5[4];
gl_Position = WIN2CLIP(p);
EmitVertex();
EndPrimitive();
@@ -1220,15 +1218,15 @@ class ElevationDecorator(LevelDecorator):
self.draw_lines(context, obj, [v1, v2], [(0, 1)])
class SectionViewDecorator(LevelDecorator):
class SectionDecorator(LevelDecorator):
objecttype = "SECTION"
DEF_GLSL = (
BaseDecorator.DEF_GLSL
+ """
#define CIRCLE_SIZE 8.0
#define TRIANGLE_L 32.0
#define TRIANGLE_W 16.0
#define TRIANGLE_L 22.63
#define TRIANGLE_W 11.31
"""
)
@@ -1239,12 +1237,14 @@ class SectionViewDecorator(LevelDecorator):
layout(lines) in;
layout(line_strip, max_vertices=MAX_POINTS) out;
void triangle_head(in vec4 dir, in vec4 side, in float length, in float width, out vec4 head[3]) {
vec4 nose = dir * length;
vec4 ear = side * width;
head[0] = vec4(0);
head[1] = nose * .5 + ear;
head[2] = nose;
void triangle_head(in vec4 side, in vec4 dir, in float length, in float width, in float radius, out vec4 head[5]) {
vec4 nose = side * length;
vec4 ear = dir * width;
head[0] = side * -radius;
head[1] = nose * -.5;
head[2] = vec4(0) + ear;
head[3] = nose * .5;
head[4] = side * radius;
}
void main() {
@@ -1254,76 +1254,114 @@ class SectionViewDecorator(LevelDecorator):
vec4 p0 = gl_in[0].gl_Position, p1 = gl_in[1].gl_Position;
vec4 p0w = CLIP2WIN(p0), p1w = CLIP2WIN(p1);
vec4 edge = p1w - p0w, dir = normalize(edge);
vec4 gap = dir * viewportDrawingScale * TRIANGLE_L * .5;
vec4 side = vec4(cross(vec3(dir.xy, 0), vec3(0, 0, 1)).xy, 0, 0);
vec4 edge = p1w - p0w, side = normalize(edge);
vec4 gap = side * viewportDrawingScale * CIRCLE_SIZE;
vec4 dir = vec4(cross(vec3(side.xy, 0), vec3(0, 0, 1)).xy, 0, 0);
vec4 p;
vec4 head[CIRCLE_SEGS];
circle_head(viewportDrawingScale * CIRCLE_SIZE, head);
vec4 head3[3];
vec4 head5[5];
// start edge circle
for(int i=0; i<CIRCLE_SEGS; i++) {
p = p0w + gap + head[i];
p = p0w + head[i];
gl_Position = WIN2CLIP(p);
EmitVertex();
}
p = p0w + gap + head[0];
p = p0w + head[0];
gl_Position = WIN2CLIP(p);
EmitVertex();
EndPrimitive();
// start edge triangle
triangle_head(dir, -side, viewportDrawingScale * TRIANGLE_L, viewportDrawingScale * TRIANGLE_W, head3);
p = p0w + head3[0];
triangle_head(side, dir, viewportDrawingScale * TRIANGLE_L, viewportDrawingScale * TRIANGLE_W, viewportDrawingScale * CIRCLE_SIZE, head5);
p = p0w + head5[0];
gl_Position = WIN2CLIP(p);
EmitVertex();
p = p0w + head3[1];
p = p0w + head5[1];
gl_Position = WIN2CLIP(p);
EmitVertex();
p = p0w + head3[2];
p = p0w + head5[2];
gl_Position = WIN2CLIP(p);
EmitVertex();
p = p0w + head5[3];
gl_Position = WIN2CLIP(p);
EmitVertex();
p = p0w + head5[4];
gl_Position = WIN2CLIP(p);
EmitVertex();
EndPrimitive();
// start reference divider
p = p0w + normalize(vec4(-1, 0, 0, 0)) * viewportDrawingScale * CIRCLE_SIZE;
gl_Position = WIN2CLIP(p);
EmitVertex();
p = p0w + normalize(vec4(1, 0, 0, 0)) * viewportDrawingScale * CIRCLE_SIZE;
gl_Position = WIN2CLIP(p);
EmitVertex();
EndPrimitive();
// end edge circle
for(int i=0; i<CIRCLE_SEGS; i++) {
p = p1w - gap + head[i];
p = p1w - head[i];
gl_Position = WIN2CLIP(p);
EmitVertex();
}
p = p1w - gap + head[0];
p = p1w - head[0];
gl_Position = WIN2CLIP(p);
EmitVertex();
EndPrimitive();
// end edge triangle
triangle_head(-dir, -side, viewportDrawingScale * TRIANGLE_L, viewportDrawingScale * TRIANGLE_W, head3);
p = p1w + head3[0];
triangle_head(side, dir, viewportDrawingScale * TRIANGLE_L, viewportDrawingScale * TRIANGLE_W, viewportDrawingScale * CIRCLE_SIZE, head5);
p = p1w + head5[0];
gl_Position = WIN2CLIP(p);
EmitVertex();
p = p1w + head3[1];
p = p1w + head5[1];
gl_Position = WIN2CLIP(p);
EmitVertex();
p = p1w + head3[2];
p = p1w + head5[2];
gl_Position = WIN2CLIP(p);
EmitVertex();
p = p1w + head5[3];
gl_Position = WIN2CLIP(p);
EmitVertex();
p = p1w + head5[4];
gl_Position = WIN2CLIP(p);
EmitVertex();
EndPrimitive();
// end reference divider
p = p1w + normalize(vec4(-1, 0, 0, 0)) * viewportDrawingScale * CIRCLE_SIZE;
gl_Position = WIN2CLIP(p);
EmitVertex();
p = p1w + normalize(vec4(1, 0, 0, 0)) * viewportDrawingScale * CIRCLE_SIZE;
gl_Position = WIN2CLIP(p);
EmitVertex();
EndPrimitive();
// stem
gl_Position = p0;
p = p0w + gap;
gl_Position = WIN2CLIP(p);
EmitVertex();
gl_Position = p1;
p = p1w - gap;
gl_Position = WIN2CLIP(p);
EmitVertex();
EndPrimitive();
}
"""
def decorate(self, context, obj):
verts, _, _ = self.get_path_geom(obj, topo=False)
self.draw_lines(context, obj, verts, [(0, 1)])
if obj.data.is_editmode:
verts, idxs = self.get_editmesh_geom(obj)
else:
verts, idxs = self.get_mesh_geom(obj)
vert_map = {v.freeze(): (obj.matrix_world.inverted() @ v).x for v in verts}
verts = sorted(verts, key=lambda v: vert_map[v], reverse=True)
self.draw_lines(context, obj, verts, idxs)
class TextDecorator(BaseDecorator):
@@ -1422,7 +1460,7 @@ class DecorationsHandler:
SectionLevelDecorator,
StairDecorator,
BreakDecorator,
SectionViewDecorator,
SectionDecorator,
ElevationDecorator,
TextDecorator,
]
@@ -260,14 +260,11 @@ def format_distance(value, isArea=False, hide_units=True):
def get_active_drawing(scene):
"""Get active drawing collection and camera"""
props = scene.DocProperties
if props.active_drawing_index is None or len(props.drawings) == 0:
return None, None
try:
drawing = props.active_drawing
camera = tool.Ifc.get_object(tool.Ifc.get().by_id(drawing.ifc_definition_id))
camera = tool.Ifc.get_object(tool.Ifc.get().by_id(props.active_drawing_id))
return camera.users_collection[0], camera
except (KeyError, IndexError):
raise RuntimeError("missing drawing collection")
except:
return None, None
def get_project_collection(scene):
@@ -82,6 +82,11 @@ class AddDrawing(bpy.types.Operator, Operator):
target_view=self.props.target_view,
location_hint=hint,
)
try:
drawing = tool.Ifc.get().by_id(self.props.active_drawing_id)
core.sync_references(tool.Ifc, tool.Collector, tool.Drawing, drawing=drawing)
except:
pass
class CreateDrawing(bpy.types.Operator):
@@ -777,12 +782,10 @@ class RemoveDrawing(bpy.types.Operator, Operator):
bl_idname = "bim.remove_drawing"
bl_label = "Remove Drawing"
bl_options = {"REGISTER", "UNDO"}
index: bpy.props.IntProperty()
drawing: bpy.props.IntProperty()
def _execute(self, context):
props = context.scene.DocProperties
drawing = tool.Ifc.get().by_id(props.drawings[self.index].ifc_definition_id)
core.remove_drawing(tool.Ifc, tool.Drawing, drawing=drawing)
core.remove_drawing(tool.Ifc, tool.Drawing, drawing=tool.Ifc.get().by_id(self.drawing))
class AddDrawingStyle(bpy.types.Operator):
@@ -293,6 +293,7 @@ class DocProperties(PropertyGroup):
)
location_hint: EnumProperty(items=get_location_hint, name="Location Hint")
drawings: CollectionProperty(name="Drawings", type=Drawing)
active_drawing_id: IntProperty(name="Active Drawing Id")
active_drawing_index: IntProperty(name="Active Drawing Index")
current_drawing_index: IntProperty(name="Current Drawing Index")
schedules: CollectionProperty(name="Schedules", type=Schedule)
@@ -312,10 +313,6 @@ class DocProperties(PropertyGroup):
def active_schedule(self):
return self.schedules[self.active_schedule_index]
@property
def active_drawing(self):
return self.drawings[self.active_drawing_index]
@property
def active_sheet(self):
return self.sheets[self.active_sheet_index]
@@ -172,14 +172,15 @@ class BIM_PT_drawings(Panel):
if self.props.drawings:
if self.props.active_drawing_index < len(self.props.drawings):
active_drawing = self.props.drawings[self.props.active_drawing_index]
row = self.layout.row(align=True)
row.alignment = "RIGHT"
op = row.operator("bim.open_view", icon="URL", text="")
op.view = self.props.active_drawing.name
op.view = active_drawing.name
op = row.operator("bim.activate_view", icon="OUTLINER_OB_CAMERA", text="")
op.drawing = self.props.active_drawing.ifc_definition_id
op.drawing = active_drawing.ifc_definition_id
row.operator("bim.create_drawing", text="", icon="OUTPUT")
row.operator("bim.remove_drawing", icon="X", text="").index = self.props.active_drawing_index
row.operator("bim.remove_drawing", icon="X", text="").drawing = active_drawing.ifc_definition_id
self.layout.template_list(
"BIM_UL_drawinglist", "", self.props, "drawings", self.props, "active_drawing_index"
)
+134 -52
View File
@@ -413,60 +413,15 @@ class Drawing(blenderbim.core.tool.Drawing):
target_view = psets.get("EPset_Drawing", {}).get("TargetView", None)
if target_view == "ELEVATION_VIEW":
return cls.generate_elevation_reference_annotation(drawing, reference_element, context)
elif target_view == "SECTION_VIEW":
return cls.generate_section_reference_annotation(drawing, reference_element, context)
@classmethod
def generate_elevation_reference_annotation(cls, drawing, reference_element, context):
def generate_section_reference_annotation(cls, drawing, reference_element, context):
reference_obj = tool.Ifc.get_object(reference_element)
reference_obj.matrix_world
camera = tool.Ifc.get_object(drawing)
def is_perpendicular(a, b):
axes = [mathutils.Vector((1, 0, 0)), mathutils.Vector((0, 1, 0)), mathutils.Vector((0, 0, 1))]
a_quaternion = a.matrix_world.to_quaternion()
b_quaternion = b.matrix_world.to_quaternion()
for axis in axes:
if abs((a_quaternion @ axis).angle((b_quaternion @ axis)) - (math.pi / 2)) < 1e-5:
return True
return False
def get_camera_block(obj):
raster_x = obj.data.BIMCameraProperties.raster_x
raster_y = obj.data.BIMCameraProperties.raster_y
if raster_x > raster_y:
width = obj.data.ortho_scale
height = width / raster_x * raster_y
else:
height = obj.data.ortho_scale
width = height / raster_y * raster_x
depth = obj.data.clip_end
verts = (
obj.matrix_world @ mathutils.Vector((-width / 2, -height /2, -depth)),
obj.matrix_world @ mathutils.Vector((-width / 2, -height /2, 0)),
obj.matrix_world @ mathutils.Vector((-width / 2, height /2, -depth)),
obj.matrix_world @ mathutils.Vector((-width / 2, height /2, 0)),
obj.matrix_world @ mathutils.Vector((width / 2, -height /2, -depth)),
obj.matrix_world @ mathutils.Vector((width / 2, -height /2, 0)),
obj.matrix_world @ mathutils.Vector((width / 2, height /2, -depth)),
obj.matrix_world @ mathutils.Vector((width / 2, height /2, 0))
)
faces = [
[0, 1, 3, 2],
[2, 3, 7, 6],
[6, 7, 5, 4],
[4, 5, 1, 0],
[2, 6, 4, 0],
[7, 3, 1, 5],
]
return {"verts": verts, "faces": faces}
def is_intersecting(a, b):
a_block = get_camera_block(a)
a_tree = mathutils.bvhtree.BVHTree.FromPolygons(a_block["verts"], a_block["faces"])
b_block = get_camera_block(b)
b_tree = mathutils.bvhtree.BVHTree.FromPolygons(b_block["verts"], b_block["faces"])
return bool(a_tree.overlap(b_tree))
bounds = helper.ortho_view_frame(camera.data) if camera.data.type == "ORTHO" else None
def to_camera_coords(camera, reference_obj):
mat = reference_obj.matrix_world.copy()
@@ -476,14 +431,90 @@ class Drawing(blenderbim.core.tool.Drawing):
mat[0][3] = xyz[0]
mat[1][3] = xyz[1]
mat[2][3] = xyz[2]
annotation_offset = mathutils.Vector((0, 0, -camera.data.clip_start))
annotation_offset = mathutils.Vector((0, 0, -camera.data.clip_start - 0.05))
annotation_offset = camera.matrix_world.to_quaternion() @ annotation_offset
mat[0][3] += annotation_offset[0]
mat[1][3] += annotation_offset[1]
mat[2][3] += annotation_offset[2]
return mat
if is_perpendicular(camera, reference_obj) and is_intersecting(camera, reference_obj):
def clip_to_camera_boundary(mesh, bounds):
mesh.verts.ensure_lookup_table()
points = [v.co for v in mesh.verts[0:2]]
points = helper.clip_segment(bounds, points)
if points is None:
return None
mesh.verts[0].co = points[0]
mesh.verts[1].co = points[1]
return mesh
if cls.is_perpendicular(camera, reference_obj) and cls.is_intersecting(camera, reference_obj):
reference_mesh = cls.get_camera_block(reference_obj)
obj_matrix = to_camera_coords(camera, reference_obj)
# The reference mesh vertices represent a view cube. To convert this into a section line we:
# 1. Select the 4 +Z vertices local to the reference element. This is the cutting plane.
verts_local_to_reference = [reference_obj.matrix_world.inverted() @ v for v in reference_mesh["verts"]]
cutting_plane_verts = sorted(verts_local_to_reference, key=lambda x: x.z)[-4:]
# Filter verts with the same XY coords
#set([v.xy for v in cutting_plane_verts])
global_cutting_plane_verts = [reference_obj.matrix_world @ v for v in cutting_plane_verts]
# 2. Project the cutting plane onto our viewing camera.
verts_local_to_camera = [camera.matrix_world.inverted() @ v for v in global_cutting_plane_verts]
# 3. Collapse verts with the same XY coords, and set Z to be just below the clip_start so it's visible
collapsed_verts = []
for vert in verts_local_to_camera:
if not [True for v in collapsed_verts if (vert.xy - v.xy).length < 1e-2]:
collapsed_verts.append(mathutils.Vector((vert.x, vert.y, -camera.data.clip_start - 0.05)))
# 4. The first two vertices is the section line
section_line = collapsed_verts[0:2]
global_section_line = [camera.matrix_world @ v for v in section_line]
local_section_line = [obj_matrix.inverted() @ v for v in global_section_line]
mesh = bpy.data.meshes.new(name="Annotation")
mesh.from_pydata(local_section_line, [(0, 1)], [])
bm = bmesh.new()
bm.from_mesh(mesh)
bm = clip_to_camera_boundary(bm, bounds)
bm.to_mesh(mesh)
bm.free()
obj = bpy.data.objects.new(reference_obj.name, mesh)
obj.matrix_world = obj_matrix
element = cls.run_root_assign_class(
obj=obj,
ifc_class="IfcAnnotation",
predefined_type="SECTION",
should_add_representation=True,
context=context,
ifc_representation_class=None,
)
return element
@classmethod
def generate_elevation_reference_annotation(cls, drawing, reference_element, context):
reference_obj = tool.Ifc.get_object(reference_element)
reference_obj.matrix_world
camera = tool.Ifc.get_object(drawing)
def to_camera_coords(camera, reference_obj):
mat = reference_obj.matrix_world.copy()
xyz = camera.matrix_world.inverted() @ reference_obj.matrix_world.translation
xyz[2] = 0
xyz = camera.matrix_world @ xyz
mat[0][3] = xyz[0]
mat[1][3] = xyz[1]
mat[2][3] = xyz[2]
annotation_offset = mathutils.Vector((0, 0, -camera.data.clip_start - 0.05))
annotation_offset = camera.matrix_world.to_quaternion() @ annotation_offset
mat[0][3] += annotation_offset[0]
mat[1][3] += annotation_offset[1]
mat[2][3] += annotation_offset[2]
return mat
if cls.is_perpendicular(camera, reference_obj) and cls.is_intersecting(camera, reference_obj):
obj = bpy.data.objects.new(reference_obj.name, None)
obj.empty_display_size = 0.1
obj.matrix_world = to_camera_coords(camera, reference_obj)
@@ -530,7 +561,7 @@ class Drawing(blenderbim.core.tool.Drawing):
def to_camera_coords(obj, mesh):
mesh.transform(camera.matrix_world.inverted() @ obj.matrix_world)
obj.matrix_world = camera.matrix_world
annotation_offset = mathutils.Vector((0, 0, -camera.data.clip_start))
annotation_offset = mathutils.Vector((0, 0, -camera.data.clip_start - 0.05))
annotation_offset = camera.matrix_world.to_quaternion() @ annotation_offset
obj.matrix_world[0][3] += annotation_offset[0]
obj.matrix_world[1][3] += annotation_offset[1]
@@ -585,6 +616,57 @@ class Drawing(blenderbim.core.tool.Drawing):
)
return element
@classmethod
def is_perpendicular(cls, a, b):
axes = [mathutils.Vector((1, 0, 0)), mathutils.Vector((0, 1, 0)), mathutils.Vector((0, 0, 1))]
a_quaternion = a.matrix_world.to_quaternion()
b_quaternion = b.matrix_world.to_quaternion()
for axis in axes:
if abs((a_quaternion @ axis).angle((b_quaternion @ axis)) - (math.pi / 2)) < 1e-5:
return True
return False
@classmethod
def get_camera_block(cls, obj):
raster_x = obj.data.BIMCameraProperties.raster_x
raster_y = obj.data.BIMCameraProperties.raster_y
if raster_x > raster_y:
width = obj.data.ortho_scale
height = width / raster_x * raster_y
else:
height = obj.data.ortho_scale
width = height / raster_y * raster_x
depth = obj.data.clip_end
verts = (
obj.matrix_world @ mathutils.Vector((-width / 2, -height /2, -depth)),
obj.matrix_world @ mathutils.Vector((-width / 2, -height /2, 0)),
obj.matrix_world @ mathutils.Vector((-width / 2, height /2, -depth)),
obj.matrix_world @ mathutils.Vector((-width / 2, height /2, 0)),
obj.matrix_world @ mathutils.Vector((width / 2, -height /2, -depth)),
obj.matrix_world @ mathutils.Vector((width / 2, -height /2, 0)),
obj.matrix_world @ mathutils.Vector((width / 2, height /2, -depth)),
obj.matrix_world @ mathutils.Vector((width / 2, height /2, 0))
)
faces = [
[0, 1, 3, 2],
[2, 3, 7, 6],
[6, 7, 5, 4],
[4, 5, 1, 0],
[2, 6, 4, 0],
[7, 3, 1, 5],
]
return {"verts": verts, "faces": faces}
@classmethod
def is_intersecting(cls, a, b):
a_block = cls.get_camera_block(a)
a_tree = mathutils.bvhtree.BVHTree.FromPolygons(a_block["verts"], a_block["faces"])
b_block = cls.get_camera_block(b)
b_tree = mathutils.bvhtree.BVHTree.FromPolygons(b_block["verts"], b_block["faces"])
return bool(a_tree.overlap(b_tree))
@classmethod
def sync_object_representation(cls, obj):
bpy.ops.bim.update_representation(obj=obj.name)