mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-10 17:58:20 +00:00
Merge remote-tracking branch 'origin/v0.7.0' into v0.8.0
This commit is contained in:
@@ -88,7 +88,7 @@ jobs:
|
||||
sudo /usr/bin/python -m pip install lark
|
||||
sudo /usr/bin/python -m pip install networkx
|
||||
sudo /usr/bin/python -m pip install tabulate
|
||||
sudo /usr/bin/python -m pip install dateutil
|
||||
sudo /usr/bin/python -m pip install python-dateutil
|
||||
|
||||
- name: Test
|
||||
run: |
|
||||
|
||||
@@ -114,7 +114,7 @@ jobs:
|
||||
sudo /usr/bin/python -m pip install networkx
|
||||
sudo /usr/bin/python -m pip install https://github.com/Andrej730/aud/archive/refs/heads/master-reduced-size.zip
|
||||
sudo /usr/bin/python -m pip install tabulate
|
||||
sudo /usr/bin/python -m pip install dateutil
|
||||
sudo /usr/bin/python -m pip install python-dateutil
|
||||
|
||||
- name: Test
|
||||
run: |
|
||||
|
||||
@@ -115,6 +115,7 @@ class DecoratorData:
|
||||
# stores 1 type of data per object
|
||||
data = {}
|
||||
|
||||
# used by Ifc Annotations with ObjectType = "BATTING"
|
||||
@classmethod
|
||||
def get_batting_thickness(cls, obj):
|
||||
result = cls.data.get(obj.name, None)
|
||||
@@ -128,6 +129,7 @@ class DecoratorData:
|
||||
cls.data[obj.name] = thickness
|
||||
return thickness
|
||||
|
||||
# used by Ifc Annotations with ObjectType = "SECTION"
|
||||
@classmethod
|
||||
def get_section_markers_display_data(cls, obj):
|
||||
result = cls.data.get(obj.name, None)
|
||||
@@ -169,6 +171,7 @@ class DecoratorData:
|
||||
cls.data[obj.name] = display_data
|
||||
return display_data
|
||||
|
||||
# used by Ifc Annotations with ObjectType = "TEXT"
|
||||
@classmethod
|
||||
def get_ifc_text_data(cls, obj):
|
||||
"""returns font size in mm for current ifc text object"""
|
||||
@@ -214,3 +217,22 @@ class DecoratorData:
|
||||
text_data = {"Literals": literals_data, "FontSize": font_size}
|
||||
cls.data[obj.name] = text_data
|
||||
return text_data
|
||||
|
||||
# used by Ifc Annotations with ObjectType = "DIMENSION"
|
||||
@classmethod
|
||||
def get_dimension_style(cls, obj):
|
||||
result = cls.data.get(obj.name, None)
|
||||
if result is not None:
|
||||
return result
|
||||
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element or not element.is_a("IfcAnnotation") or element.ObjectType != "DIMENSION":
|
||||
return None
|
||||
|
||||
dimension_style = "arrow"
|
||||
classes = ifcopenshell.util.element.get_pset(element, "EPset_Annotation", "Classes")
|
||||
if classes and "oblique" in classes.lower().split():
|
||||
dimension_style = "oblique"
|
||||
|
||||
cls.data[obj.name] = dimension_style
|
||||
return dimension_style
|
||||
|
||||
@@ -442,7 +442,7 @@ class BaseDecorator:
|
||||
box_alignment_offset += Vector((0, h))
|
||||
else:
|
||||
# middle / center
|
||||
box_alignment_offset += Vector((0, h / 2))
|
||||
box_alignment_offset += Vector((0, h / 2))
|
||||
|
||||
if "left" in box_alignment:
|
||||
pass
|
||||
@@ -451,7 +451,7 @@ class BaseDecorator:
|
||||
else:
|
||||
# middle / center
|
||||
box_alignment_offset += Vector((w / 2, 0))
|
||||
|
||||
|
||||
pos -= rotation_matrix.transposed() @ box_alignment_offset
|
||||
else:
|
||||
if center:
|
||||
@@ -506,6 +506,10 @@ class DimensionDecorator(BaseDecorator):
|
||||
DEF_GLSL = (
|
||||
BaseDecorator.DEF_GLSL
|
||||
+ """
|
||||
#define OBLIQUE_SYMBOL_ANGLE PI / 4.0
|
||||
#define OLBIQUE_SYMBOL_SIZE 10.0
|
||||
#define OBLIQUE_HEAD_VERTS 7
|
||||
|
||||
#define ARROW_ANGLE PI / 12.0
|
||||
#define ARROW_SIZE 16.0
|
||||
"""
|
||||
@@ -514,10 +518,23 @@ class DimensionDecorator(BaseDecorator):
|
||||
GEOM_GLSL = """
|
||||
uniform vec2 winsize;
|
||||
uniform float viewportDrawingScale;
|
||||
uniform float use_oblique_style;
|
||||
|
||||
layout(lines) in;
|
||||
layout(line_strip, max_vertices=MAX_POINTS) out;
|
||||
|
||||
void oblique_dimension_head(in vec4 dir, in float size, in float angle, out vec4 head[OBLIQUE_HEAD_VERTS]) {
|
||||
float c = cos(angle), s = sin(angle);
|
||||
vec4 ortho = vec4(-dir.y, dir.x, 0, 0);
|
||||
head[0] = -dir * size*0.66;
|
||||
head[1] = vec4(0);
|
||||
head[2] = ortho * size;
|
||||
head[3] = -ortho * size;
|
||||
head[4] = vec4(0);
|
||||
head[5] = vec4((mat2(c, +s, -s, c) * dir.xy) * size * 0.47, 0, 0);
|
||||
head[6] = -head[5];
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec4 clip2win = matCLIP2WIN();
|
||||
vec4 win2clip = matWIN2CLIP();
|
||||
@@ -529,49 +546,78 @@ class DimensionDecorator(BaseDecorator):
|
||||
|
||||
vec4 p;
|
||||
|
||||
vec4 head[3];
|
||||
arrow_head(dir, viewportDrawingScale * ARROW_SIZE, ARROW_ANGLE, head);
|
||||
if (use_oblique_style > 0) {
|
||||
vec4 head[OBLIQUE_HEAD_VERTS];
|
||||
oblique_dimension_head(dir, viewportDrawingScale * OLBIQUE_SYMBOL_SIZE, OBLIQUE_SYMBOL_ANGLE, head);
|
||||
|
||||
// start edge arrow
|
||||
gl_Position = p0;
|
||||
EmitVertex();
|
||||
p = p0w + head[1];
|
||||
gl_Position = WIN2CLIP(p);
|
||||
EmitVertex();
|
||||
p = p0w + head[2];
|
||||
gl_Position = WIN2CLIP(p);
|
||||
EmitVertex();
|
||||
gl_Position = p0;
|
||||
EmitVertex();
|
||||
EndPrimitive();
|
||||
// start edge arrow
|
||||
for (int i = 0; i < OBLIQUE_HEAD_VERTS; i++) {
|
||||
gl_Position = WIN2CLIP((p0w + head[i]));
|
||||
EmitVertex();
|
||||
}
|
||||
EndPrimitive();
|
||||
|
||||
// end edge arrow
|
||||
gl_Position = p1;
|
||||
EmitVertex();
|
||||
p = p1w - head[1];
|
||||
gl_Position = WIN2CLIP(p);
|
||||
EmitVertex();
|
||||
p = p1w - head[2];
|
||||
gl_Position = WIN2CLIP(p);
|
||||
EmitVertex();
|
||||
gl_Position = p1;
|
||||
EmitVertex();
|
||||
EndPrimitive();
|
||||
// end edge arrow
|
||||
for (int i = 0; i < OBLIQUE_HEAD_VERTS; i++) {
|
||||
gl_Position = WIN2CLIP((p1w - head[i]));
|
||||
EmitVertex();
|
||||
}
|
||||
EndPrimitive();
|
||||
|
||||
// stem, with gaps for arrows
|
||||
p = p0w + head[0];
|
||||
gl_Position = WIN2CLIP(p);
|
||||
EmitVertex();
|
||||
p = p1w - head[0];
|
||||
gl_Position = WIN2CLIP(p);
|
||||
EmitVertex();
|
||||
EndPrimitive();
|
||||
// stem
|
||||
gl_Position = p0;
|
||||
EmitVertex();
|
||||
gl_Position = p1;
|
||||
EmitVertex();
|
||||
EndPrimitive();
|
||||
} else {
|
||||
vec4 head[3];
|
||||
arrow_head(dir, viewportDrawingScale * ARROW_SIZE, ARROW_ANGLE, head);
|
||||
|
||||
// start edge arrow
|
||||
gl_Position = p0;
|
||||
EmitVertex();
|
||||
p = p0w + head[1];
|
||||
gl_Position = WIN2CLIP(p);
|
||||
EmitVertex();
|
||||
p = p0w + head[2];
|
||||
gl_Position = WIN2CLIP(p);
|
||||
EmitVertex();
|
||||
gl_Position = p0;
|
||||
EmitVertex();
|
||||
EndPrimitive();
|
||||
|
||||
// end edge arrow
|
||||
gl_Position = p1;
|
||||
EmitVertex();
|
||||
p = p1w - head[1];
|
||||
gl_Position = WIN2CLIP(p);
|
||||
EmitVertex();
|
||||
p = p1w - head[2];
|
||||
gl_Position = WIN2CLIP(p);
|
||||
EmitVertex();
|
||||
gl_Position = p1;
|
||||
EmitVertex();
|
||||
EndPrimitive();
|
||||
|
||||
// stem, with gaps for arrows
|
||||
p = p0w + head[0];
|
||||
gl_Position = WIN2CLIP(p);
|
||||
EmitVertex();
|
||||
p = p1w - head[0];
|
||||
gl_Position = WIN2CLIP(p);
|
||||
EmitVertex();
|
||||
EndPrimitive();
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
def decorate(self, context, obj):
|
||||
verts, idxs, _ = self.get_path_geom(obj, topo=False)
|
||||
self.draw_lines(context, obj, verts, idxs)
|
||||
dimension_style = DecoratorData.get_dimension_style(obj)
|
||||
self.draw_lines(
|
||||
context, obj, verts, idxs, extra_float_kwargs={"use_oblique_style": float(dimension_style == "oblique")}
|
||||
)
|
||||
self.draw_labels(context, obj, verts, idxs)
|
||||
|
||||
def draw_labels(self, context, obj, vertices, indices):
|
||||
@@ -806,7 +852,7 @@ class LeaderDecorator(BaseDecorator):
|
||||
|
||||
props = obj.BIMTextProperties
|
||||
text_data = props.get_text_edited_data() if props.is_editing else DecoratorData.get_ifc_text_data(obj)
|
||||
text = text_data["Literals"][0]['CurrentValue']
|
||||
text = text_data["Literals"][0]["CurrentValue"]
|
||||
|
||||
self.draw_label(context, text, pos, dir, gap=0, center=False, vcenter=False)
|
||||
|
||||
@@ -1332,18 +1378,22 @@ class PlanLevelDecorator(LevelDecorator):
|
||||
region = context.region
|
||||
region3d = context.region_data
|
||||
for verts in splines:
|
||||
v0 = verts[0]
|
||||
v1 = verts[1]
|
||||
p0 = location_3d_to_region_2d(region, region3d, v0)
|
||||
p1 = location_3d_to_region_2d(region, region3d, v1)
|
||||
p0, p1 = [location_3d_to_region_2d(region, region3d, v) for v in verts[:2]]
|
||||
dir = p1 - p0
|
||||
if dir.length < 1:
|
||||
continue
|
||||
|
||||
if dir.x > 0:
|
||||
box_alignment = "bottom-left"
|
||||
else:
|
||||
box_alignment = "bottom-right"
|
||||
dir *= -1
|
||||
|
||||
z = verts[-1].z / unit_scale
|
||||
z = ifcopenshell.util.geolocation.auto_z2e(tool.Ifc.get(), z)
|
||||
z *= unit_scale
|
||||
text = "RL " + self.format_value(context, z)
|
||||
self.draw_label(context, text, p0, dir, gap=8, center=False)
|
||||
self.draw_label(context, text, p0, dir, gap=8, center=False, box_alignment=box_alignment)
|
||||
|
||||
|
||||
class SectionLevelDecorator(LevelDecorator):
|
||||
@@ -2094,7 +2144,7 @@ class TextDecorator(BaseDecorator):
|
||||
def matrix_only_rotation(m):
|
||||
return m.to_3x3().normalized().to_4x4()
|
||||
|
||||
text_dir = Vector((1,0))
|
||||
text_dir = Vector((1, 0))
|
||||
text_dir_world = matrix_only_rotation(region3d.perspective_matrix.inverted()) @ text_dir.to_3d()
|
||||
text_dir_world_rotated = matrix_only_rotation(obj.matrix_world) @ text_dir_world
|
||||
text_dir = (matrix_only_rotation(region3d.perspective_matrix) @ text_dir_world_rotated).to_2d().normalized()
|
||||
|
||||
@@ -562,6 +562,7 @@ def disable_editing_extrusion_profile(context):
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
body = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
|
||||
|
||||
profile_mesh = obj.data
|
||||
blenderbim.core.geometry.switch_representation(
|
||||
tool.Ifc,
|
||||
tool.Geometry,
|
||||
@@ -571,6 +572,7 @@ def disable_editing_extrusion_profile(context):
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
tool.Geometry.delete_data(profile_mesh)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -663,6 +665,7 @@ class EditExtrusionProfile(bpy.types.Operator, tool.Ifc.Operator):
|
||||
ifcopenshell.util.element.replace_attribute(inverse, old_profile, profile)
|
||||
ifcopenshell.util.element.remove_deep2(tool.Ifc.get(), old_profile)
|
||||
|
||||
profile_mesh = obj.data
|
||||
blenderbim.core.geometry.switch_representation(
|
||||
tool.Ifc,
|
||||
tool.Geometry,
|
||||
@@ -672,6 +675,7 @@ class EditExtrusionProfile(bpy.types.Operator, tool.Ifc.Operator):
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
bpy.data.meshes.remove(profile_mesh)
|
||||
|
||||
footprint_context = ifcopenshell.util.representation.get_context(
|
||||
tool.Ifc.get(), "Plan", "FootPrint", "SKETCH_VIEW"
|
||||
@@ -734,15 +738,20 @@ class SetArcIndex(bpy.types.Operator):
|
||||
return bool(obj) and obj.type == "MESH"
|
||||
|
||||
def cancel_message(self, msg):
|
||||
self.report({"WARNING"}, msg)
|
||||
self.report({"ERROR"}, msg)
|
||||
return {"CANCELLED"}
|
||||
|
||||
def execute(self, context):
|
||||
# NOTE: undo won't remove new verex group
|
||||
# because of jumping between modes
|
||||
obj = context.active_object
|
||||
|
||||
bm = tool.Blender.get_bmesh_for_mesh(obj.data)
|
||||
selected_vertices = [v.index for v in bm.verts if v.select]
|
||||
if len(selected_vertices) != 3:
|
||||
return self.cancel_message("Select 3 vertices.")
|
||||
|
||||
bpy.ops.object.mode_set(mode="OBJECT")
|
||||
selected_vertices = [v.index for v in obj.data.vertices if v.select]
|
||||
in_group = any([bool(obj.data.vertices[v].groups) for v in selected_vertices])
|
||||
for group in obj.vertex_groups:
|
||||
group.remove(selected_vertices)
|
||||
|
||||
@@ -148,7 +148,9 @@ def disable_editing_arbitrary_profile(context):
|
||||
if obj and obj.data and obj.data.BIMMeshProperties.subshape_type == "PROFILE":
|
||||
ProfileDecorator.uninstall()
|
||||
bpy.ops.object.mode_set(mode="OBJECT")
|
||||
profile_mesh = obj.data
|
||||
bpy.data.objects.remove(obj)
|
||||
bpy.data.meshes.remove(profile_mesh)
|
||||
|
||||
# need to update profile manager ui
|
||||
# if this was called from decorator
|
||||
@@ -191,7 +193,10 @@ class EditArbitraryProfile(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bpy.ops.object.mode_set(mode="EDIT")
|
||||
return
|
||||
|
||||
profile_mesh = obj.data
|
||||
bpy.data.objects.remove(obj)
|
||||
bpy.data.meshes.remove(profile_mesh)
|
||||
|
||||
profile.ProfileType = old_profile.ProfileType
|
||||
profile.ProfileName = old_profile.ProfileName
|
||||
for inverse in tool.Ifc.get().get_inverse(old_profile):
|
||||
|
||||
@@ -176,6 +176,11 @@ class Model(blenderbim.core.tool.Model):
|
||||
|
||||
@classmethod
|
||||
def import_profile(cls, profile, obj=None, position=None):
|
||||
"""Creates new profile mesh and assigns it to `obj`,
|
||||
if `obj` is `None` then new "Profile" object will be created.
|
||||
|
||||
Need to make sure to remove temporary mesh/object after use to avoid orphan data.
|
||||
"""
|
||||
cls.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
|
||||
if position is None:
|
||||
|
||||
Reference in New Issue
Block a user