Merge branch 'v0.7.0' into elevation_tweaks

This commit is contained in:
Dion Moult
2023-05-30 09:53:19 +10:00
committed by GitHub
75 changed files with 2107 additions and 662 deletions
@@ -1,125 +1,755 @@
{
"Technical": {
"render_type": "VIEWPORT",
"raster_style": {
"bpy.data.worlds[0].color": [
1,
1,
1
1.0,
1.0,
1.0
],
"scene.render.bake_bias": 0.0010000000474974513,
"scene.render.bake_margin": 16,
"scene.render.bake_margin_type": "ADJACENT_FACES",
"scene.render.bake_samples": 256,
"scene.render.bake_type": "NORMALS",
"scene.render.bake_user_scale": 0.0,
"scene.render.border_max_x": 1.0,
"scene.render.border_max_y": 1.0,
"scene.render.border_min_x": 0.0,
"scene.render.border_min_y": 0.0,
"scene.render.dither_intensity": 1.0,
"scene.render.engine": "BLENDER_WORKBENCH",
"scene.render.film_transparent": false,
"scene.display.shading.show_object_outline": true,
"scene.display.shading.show_cavity": false,
"scene.display.shading.cavity_type": "BOTH",
"scene.display.shading.curvature_ridge_factor": 1,
"scene.display.shading.curvature_valley_factor": 1,
"scene.render.filter_size": 1.5,
"scene.render.fps": 24,
"scene.render.fps_base": 1.0,
"scene.render.frame_map_new": 100,
"scene.render.frame_map_old": 100,
"scene.render.hair_subdiv": 0,
"scene.render.hair_type": "STRAND",
"scene.render.line_thickness": 1.0,
"scene.render.line_thickness_mode": "ABSOLUTE",
"scene.render.metadata_input": "SCENE",
"scene.render.motion_blur_shutter": 0.5,
"scene.render.pixel_aspect_x": 1.0,
"scene.render.pixel_aspect_y": 1.0,
"scene.render.preview_pixel_size": "AUTO",
"scene.render.resolution_percentage": 100,
"scene.render.resolution_x": 1000,
"scene.render.resolution_y": 1000,
"scene.render.sequencer_gl_preview": "SOLID",
"scene.render.simplify_child_particles": 1.0,
"scene.render.simplify_child_particles_render": 1.0,
"scene.render.simplify_gpencil": false,
"scene.render.simplify_gpencil_antialiasing": true,
"scene.render.simplify_gpencil_modifier": true,
"scene.render.simplify_gpencil_onplay": false,
"scene.render.simplify_gpencil_shader_fx": true,
"scene.render.simplify_gpencil_tint": true,
"scene.render.simplify_gpencil_view_fill": true,
"scene.render.simplify_shadows": 1.0,
"scene.render.simplify_shadows_render": 1.0,
"scene.render.simplify_subdivision": 6,
"scene.render.simplify_subdivision_render": 6,
"scene.render.simplify_volumes": 1.0,
"scene.render.stamp_font_size": 12,
"scene.render.stamp_note_text": "",
"scene.render.threads": 20,
"scene.render.threads_mode": "AUTO",
"scene.render.use_bake_clear": true,
"scene.render.use_bake_lores_mesh": false,
"scene.render.use_bake_multires": false,
"scene.render.use_bake_selected_to_active": false,
"scene.render.use_bake_user_scale": false,
"scene.render.use_border": false,
"scene.render.use_compositing": true,
"scene.render.use_crop_to_border": false,
"scene.render.use_file_extension": true,
"scene.render.use_freestyle": false,
"scene.render.use_high_quality_normals": false,
"scene.render.use_lock_interface": false,
"scene.render.use_motion_blur": false,
"scene.render.use_multiview": false,
"scene.render.use_overwrite": true,
"scene.render.use_persistent_data": false,
"scene.render.use_placeholder": false,
"scene.render.use_render_cache": false,
"scene.render.use_sequencer": true,
"scene.render.use_sequencer_override_scene_strip": false,
"scene.render.use_simplify": false,
"scene.render.use_single_layer": false,
"scene.render.use_stamp": false,
"scene.render.use_stamp_camera": true,
"scene.render.use_stamp_date": true,
"scene.render.use_stamp_filename": true,
"scene.render.use_stamp_frame": true,
"scene.render.use_stamp_frame_range": false,
"scene.render.use_stamp_hostname": false,
"scene.render.use_stamp_labels": true,
"scene.render.use_stamp_lens": false,
"scene.render.use_stamp_marker": false,
"scene.render.use_stamp_memory": false,
"scene.render.use_stamp_note": false,
"scene.render.use_stamp_render_time": true,
"scene.render.use_stamp_scene": true,
"scene.render.use_stamp_sequencer_strip": false,
"scene.render.use_stamp_time": true,
"scene.render.views_format": "STEREO_3D",
"scene.view_settings.exposure": 0.0,
"scene.view_settings.gamma": 1.0,
"scene.view_settings.look": "None",
"scene.view_settings.use_curve_mapping": false,
"scene.view_settings.view_transform": "Standard",
"scene.display.shading.light": "FLAT",
"scene.display.shading.color_type": "SINGLE",
"scene.display.shading.single_color": [
1,
1,
1
"scene.display.shading.aov_name": "",
"scene.display.shading.background_color": [
0.0,
0.0,
0.0
],
"scene.display.shading.show_shadows": false,
"scene.display.shading.background_type": "THEME",
"scene.display.shading.cavity_ridge_factor": 1.0,
"scene.display.shading.cavity_type": "BOTH",
"scene.display.shading.cavity_valley_factor": 1.0,
"scene.display.shading.color_type": "SINGLE",
"scene.display.shading.curvature_ridge_factor": 1.0,
"scene.display.shading.curvature_valley_factor": 1.0,
"scene.display.shading.light": "FLAT",
"scene.display.shading.object_outline_color": [
0.0,
0.0,
0.0
],
"scene.display.shading.render_pass": "COMBINED",
"scene.display.shading.shadow_intensity": 0.5,
"scene.display.shading.show_backface_culling": false,
"scene.display.shading.show_cavity": false,
"scene.display.shading.show_object_outline": true,
"scene.display.shading.show_shadows": false,
"scene.display.shading.show_specular_highlight": true,
"scene.display.shading.show_xray": false,
"scene.display.shading.show_xray_wireframe": false,
"scene.display.shading.single_color": [
1.0,
1.0,
1.0
],
"scene.display.shading.studio_light": "Default",
"scene.display.shading.studiolight_background_alpha": 0.0,
"scene.display.shading.studiolight_background_blur": 0.0,
"scene.display.shading.studiolight_intensity": 0.0,
"scene.display.shading.studiolight_rotate_z": 0.0,
"scene.display.shading.type": "SOLID",
"scene.display.shading.use_compositor": "DISABLED",
"scene.display.shading.use_dof": false,
"scene.display.shading.use_scene_lights": false,
"scene.display.shading.use_scene_lights_render": false,
"scene.display.shading.use_scene_world": false,
"scene.display.shading.use_scene_world_render": false,
"scene.display.shading.use_studiolight_view_rotation": true,
"scene.display.shading.use_world_space_lighting": false,
"scene.display.shading.wireframe_color_type": "MATERIAL",
"scene.display.shading.xray_alpha": 0.5,
"scene.display.shading.xray_alpha_wireframe": 0.0,
"scene.display.light_direction": [
0.5,
0.5,
0.5
],
"scene.view_settings.use_curve_mapping": false,
"space.overlay.show_wireframes": true,
"space.overlay.wireframe_threshold": 0,
"space.overlay.show_floor": false,
"scene.display.matcap_ssao_attenuation": 1.0,
"scene.display.matcap_ssao_distance": 0.20000000298023224,
"scene.display.matcap_ssao_samples": 16,
"scene.display.render_aa": "8",
"scene.display.shadow_focus": 0.0,
"scene.display.shadow_shift": 0.10000000149011612,
"scene.display.viewport_aa": "FXAA",
"space.overlay.backwire_opacity": 0.5,
"space.overlay.bone_wire_alpha": 1.0,
"space.overlay.display_handle": "SELECTED",
"space.overlay.fade_inactive_alpha": 0.4000000059604645,
"space.overlay.gpencil_fade_layer": 0.5,
"space.overlay.gpencil_fade_objects": 0.5,
"space.overlay.gpencil_grid_opacity": 0.5,
"space.overlay.gpencil_vertex_paint_opacity": 1.0,
"space.overlay.grid_lines": 16,
"space.overlay.grid_scale": 1.0,
"space.overlay.grid_subdivisions": 10,
"space.overlay.normals_constant_screen_size": 7.0,
"space.overlay.normals_length": 0.10000000149011612,
"space.overlay.sculpt_curves_cage_opacity": 0.5,
"space.overlay.sculpt_mode_face_sets_opacity": 1.0,
"space.overlay.sculpt_mode_mask_opacity": 0.75,
"space.overlay.show_annotation": true,
"space.overlay.show_axis_x": false,
"space.overlay.show_axis_y": false,
"space.overlay.show_axis_z": false,
"space.overlay.show_bones": true,
"space.overlay.show_cursor": true,
"space.overlay.show_curve_normals": false,
"space.overlay.show_edge_bevel_weight": true,
"space.overlay.show_edge_crease": true,
"space.overlay.show_edge_seams": true,
"space.overlay.show_edge_sharp": true,
"space.overlay.show_edges": false,
"space.overlay.show_extra_edge_angle": false,
"space.overlay.show_extra_edge_length": false,
"space.overlay.show_extra_face_angle": false,
"space.overlay.show_extra_face_area": false,
"space.overlay.show_extra_indices": false,
"space.overlay.show_extras": true,
"space.overlay.show_face_center": false,
"space.overlay.show_face_normals": false,
"space.overlay.show_face_orientation": false,
"space.overlay.show_faces": true,
"space.overlay.show_fade_inactive": false,
"space.overlay.show_floor": false,
"space.overlay.show_freestyle_edge_marks": true,
"space.overlay.show_freestyle_face_marks": true,
"space.overlay.show_look_dev": false,
"space.overlay.show_motion_paths": true,
"space.overlay.show_object_origins": false,
"space.overlay.show_relationship_lines": false
},
"render_type": "VIEWPORT"
"space.overlay.show_object_origins_all": false,
"space.overlay.show_occlude_wire": false,
"space.overlay.show_onion_skins": false,
"space.overlay.show_ortho_grid": true,
"space.overlay.show_outline_selected": true,
"space.overlay.show_overlays": true,
"space.overlay.show_paint_wire": false,
"space.overlay.show_relationship_lines": false,
"space.overlay.show_sculpt_curves_cage": true,
"space.overlay.show_sculpt_face_sets": true,
"space.overlay.show_sculpt_mask": true,
"space.overlay.show_split_normals": false,
"space.overlay.show_stats": false,
"space.overlay.show_statvis": false,
"space.overlay.show_text": true,
"space.overlay.show_vertex_normals": false,
"space.overlay.show_viewer_attribute": true,
"space.overlay.show_weight": false,
"space.overlay.show_wireframes": true,
"space.overlay.show_wpaint_contours": false,
"space.overlay.show_xray_bone": false,
"space.overlay.texture_paint_mode_opacity": 1.0,
"space.overlay.use_debug_freeze_view_culling": false,
"space.overlay.use_gpencil_canvas_xray": false,
"space.overlay.use_gpencil_edit_lines": true,
"space.overlay.use_gpencil_fade_gp_objects": false,
"space.overlay.use_gpencil_fade_layers": false,
"space.overlay.use_gpencil_fade_objects": false,
"space.overlay.use_gpencil_grid": false,
"space.overlay.use_gpencil_multiedit_line_only": false,
"space.overlay.use_gpencil_onion_skin": false,
"space.overlay.use_gpencil_show_directions": false,
"space.overlay.use_gpencil_show_material_name": false,
"space.overlay.use_normals_constant_screen_size": false,
"space.overlay.vertex_opacity": 1.0,
"space.overlay.vertex_paint_mode_opacity": 1.0,
"space.overlay.viewer_attribute_opacity": 1.0,
"space.overlay.weight_paint_mode_opacity": 1.0,
"space.overlay.wireframe_opacity": 1.0,
"space.overlay.wireframe_threshold": 0.0,
"space.overlay.xray_alpha_bone": 0.0
}
},
"Shaded": {
"render_type": "VIEWPORT",
"raster_style": {
"bpy.data.worlds[0].color": [
1,
1,
1
1.0,
1.0,
1.0
],
"scene.render.bake_bias": 0.0010000000474974513,
"scene.render.bake_margin": 16,
"scene.render.bake_margin_type": "ADJACENT_FACES",
"scene.render.bake_samples": 256,
"scene.render.bake_type": "NORMALS",
"scene.render.bake_user_scale": 0.0,
"scene.render.border_max_x": 1.0,
"scene.render.border_max_y": 1.0,
"scene.render.border_min_x": 0.0,
"scene.render.border_min_y": 0.0,
"scene.render.dither_intensity": 1.0,
"scene.render.engine": "BLENDER_WORKBENCH",
"scene.render.film_transparent": false,
"scene.display.shading.show_object_outline": true,
"scene.display.shading.show_cavity": true,
"scene.display.shading.cavity_type": "BOTH",
"scene.display.shading.curvature_ridge_factor": 1,
"scene.display.shading.curvature_valley_factor": 1,
"scene.render.filter_size": 1.5,
"scene.render.fps": 24,
"scene.render.fps_base": 1.0,
"scene.render.frame_map_new": 100,
"scene.render.frame_map_old": 100,
"scene.render.hair_subdiv": 0,
"scene.render.hair_type": "STRAND",
"scene.render.line_thickness": 1.0,
"scene.render.line_thickness_mode": "ABSOLUTE",
"scene.render.metadata_input": "SCENE",
"scene.render.motion_blur_shutter": 0.5,
"scene.render.pixel_aspect_x": 1.0,
"scene.render.pixel_aspect_y": 1.0,
"scene.render.preview_pixel_size": "AUTO",
"scene.render.resolution_percentage": 100,
"scene.render.resolution_x": 1000,
"scene.render.resolution_y": 1000,
"scene.render.sequencer_gl_preview": "SOLID",
"scene.render.simplify_child_particles": 1.0,
"scene.render.simplify_child_particles_render": 1.0,
"scene.render.simplify_gpencil": false,
"scene.render.simplify_gpencil_antialiasing": true,
"scene.render.simplify_gpencil_modifier": true,
"scene.render.simplify_gpencil_onplay": false,
"scene.render.simplify_gpencil_shader_fx": true,
"scene.render.simplify_gpencil_tint": true,
"scene.render.simplify_gpencil_view_fill": true,
"scene.render.simplify_shadows": 1.0,
"scene.render.simplify_shadows_render": 1.0,
"scene.render.simplify_subdivision": 6,
"scene.render.simplify_subdivision_render": 6,
"scene.render.simplify_volumes": 1.0,
"scene.render.stamp_font_size": 12,
"scene.render.stamp_note_text": "",
"scene.render.threads": 20,
"scene.render.threads_mode": "AUTO",
"scene.render.use_bake_clear": true,
"scene.render.use_bake_lores_mesh": false,
"scene.render.use_bake_multires": false,
"scene.render.use_bake_selected_to_active": false,
"scene.render.use_bake_user_scale": false,
"scene.render.use_border": false,
"scene.render.use_compositing": true,
"scene.render.use_crop_to_border": false,
"scene.render.use_file_extension": true,
"scene.render.use_freestyle": false,
"scene.render.use_high_quality_normals": false,
"scene.render.use_lock_interface": false,
"scene.render.use_motion_blur": false,
"scene.render.use_multiview": false,
"scene.render.use_overwrite": true,
"scene.render.use_persistent_data": false,
"scene.render.use_placeholder": false,
"scene.render.use_render_cache": false,
"scene.render.use_sequencer": true,
"scene.render.use_sequencer_override_scene_strip": false,
"scene.render.use_simplify": false,
"scene.render.use_single_layer": false,
"scene.render.use_stamp": false,
"scene.render.use_stamp_camera": true,
"scene.render.use_stamp_date": true,
"scene.render.use_stamp_filename": true,
"scene.render.use_stamp_frame": true,
"scene.render.use_stamp_frame_range": false,
"scene.render.use_stamp_hostname": false,
"scene.render.use_stamp_labels": true,
"scene.render.use_stamp_lens": false,
"scene.render.use_stamp_marker": false,
"scene.render.use_stamp_memory": false,
"scene.render.use_stamp_note": false,
"scene.render.use_stamp_render_time": true,
"scene.render.use_stamp_scene": true,
"scene.render.use_stamp_sequencer_strip": false,
"scene.render.use_stamp_time": true,
"scene.render.views_format": "STEREO_3D",
"scene.view_settings.exposure": 0.0,
"scene.view_settings.gamma": 1.0,
"scene.view_settings.look": "None",
"scene.view_settings.use_curve_mapping": false,
"scene.view_settings.view_transform": "Standard",
"scene.display.shading.light": "STUDIO",
"scene.display.shading.color_type": "MATERIAL",
"scene.display.shading.single_color": [
1,
1,
1
"scene.display.shading.aov_name": "",
"scene.display.shading.background_color": [
0.0,
0.0,
0.0
],
"scene.display.shading.show_shadows": true,
"scene.display.shading.background_type": "THEME",
"scene.display.shading.cavity_ridge_factor": 1.0,
"scene.display.shading.cavity_type": "BOTH",
"scene.display.shading.cavity_valley_factor": 1.0,
"scene.display.shading.color_type": "MATERIAL",
"scene.display.shading.curvature_ridge_factor": 1.0,
"scene.display.shading.curvature_valley_factor": 1.0,
"scene.display.shading.light": "STUDIO",
"scene.display.shading.object_outline_color": [
0.0,
0.0,
0.0
],
"scene.display.shading.render_pass": "COMBINED",
"scene.display.shading.shadow_intensity": 0.5,
"scene.display.shading.show_backface_culling": false,
"scene.display.shading.show_cavity": true,
"scene.display.shading.show_object_outline": true,
"scene.display.shading.show_shadows": true,
"scene.display.shading.show_specular_highlight": true,
"scene.display.shading.show_xray": false,
"scene.display.shading.show_xray_wireframe": false,
"scene.display.shading.single_color": [
1.0,
1.0,
1.0
],
"scene.display.shading.studio_light": "Default",
"scene.display.shading.studiolight_background_alpha": 0.0,
"scene.display.shading.studiolight_background_blur": 0.0,
"scene.display.shading.studiolight_intensity": 0.0,
"scene.display.shading.studiolight_rotate_z": 0.0,
"scene.display.shading.type": "SOLID",
"scene.display.shading.use_compositor": "DISABLED",
"scene.display.shading.use_dof": false,
"scene.display.shading.use_scene_lights": false,
"scene.display.shading.use_scene_lights_render": false,
"scene.display.shading.use_scene_world": false,
"scene.display.shading.use_scene_world_render": false,
"scene.display.shading.use_studiolight_view_rotation": true,
"scene.display.shading.use_world_space_lighting": false,
"scene.display.shading.wireframe_color_type": "MATERIAL",
"scene.display.shading.xray_alpha": 0.5,
"scene.display.shading.xray_alpha_wireframe": 0.0,
"scene.display.light_direction": [
0.5,
0.5,
0.5
],
"scene.view_settings.use_curve_mapping": false,
"space.overlay.show_wireframes": false,
"space.overlay.wireframe_threshold": 0,
"space.overlay.show_floor": false,
"scene.display.matcap_ssao_attenuation": 1.0,
"scene.display.matcap_ssao_distance": 0.20000000298023224,
"scene.display.matcap_ssao_samples": 16,
"scene.display.render_aa": "8",
"scene.display.shadow_focus": 0.0,
"scene.display.shadow_shift": 0.10000000149011612,
"scene.display.viewport_aa": "FXAA",
"space.overlay.backwire_opacity": 0.5,
"space.overlay.bone_wire_alpha": 1.0,
"space.overlay.display_handle": "SELECTED",
"space.overlay.fade_inactive_alpha": 0.4000000059604645,
"space.overlay.gpencil_fade_layer": 0.5,
"space.overlay.gpencil_fade_objects": 0.5,
"space.overlay.gpencil_grid_opacity": 0.5,
"space.overlay.gpencil_vertex_paint_opacity": 1.0,
"space.overlay.grid_lines": 16,
"space.overlay.grid_scale": 1.0,
"space.overlay.grid_subdivisions": 10,
"space.overlay.normals_constant_screen_size": 7.0,
"space.overlay.normals_length": 0.10000000149011612,
"space.overlay.sculpt_curves_cage_opacity": 0.5,
"space.overlay.sculpt_mode_face_sets_opacity": 1.0,
"space.overlay.sculpt_mode_mask_opacity": 0.75,
"space.overlay.show_annotation": true,
"space.overlay.show_axis_x": false,
"space.overlay.show_axis_y": false,
"space.overlay.show_axis_z": false,
"space.overlay.show_bones": true,
"space.overlay.show_cursor": true,
"space.overlay.show_curve_normals": false,
"space.overlay.show_edge_bevel_weight": true,
"space.overlay.show_edge_crease": true,
"space.overlay.show_edge_seams": true,
"space.overlay.show_edge_sharp": true,
"space.overlay.show_edges": false,
"space.overlay.show_extra_edge_angle": false,
"space.overlay.show_extra_edge_length": false,
"space.overlay.show_extra_face_angle": false,
"space.overlay.show_extra_face_area": false,
"space.overlay.show_extra_indices": false,
"space.overlay.show_extras": true,
"space.overlay.show_face_center": false,
"space.overlay.show_face_normals": false,
"space.overlay.show_face_orientation": false,
"space.overlay.show_faces": true,
"space.overlay.show_fade_inactive": false,
"space.overlay.show_floor": false,
"space.overlay.show_freestyle_edge_marks": true,
"space.overlay.show_freestyle_face_marks": true,
"space.overlay.show_look_dev": false,
"space.overlay.show_motion_paths": true,
"space.overlay.show_object_origins": false,
"space.overlay.show_relationship_lines": false
},
"render_type": "VIEWPORT"
"space.overlay.show_object_origins_all": false,
"space.overlay.show_occlude_wire": false,
"space.overlay.show_onion_skins": false,
"space.overlay.show_ortho_grid": true,
"space.overlay.show_outline_selected": true,
"space.overlay.show_overlays": true,
"space.overlay.show_paint_wire": false,
"space.overlay.show_relationship_lines": false,
"space.overlay.show_sculpt_curves_cage": true,
"space.overlay.show_sculpt_face_sets": true,
"space.overlay.show_sculpt_mask": true,
"space.overlay.show_split_normals": false,
"space.overlay.show_stats": false,
"space.overlay.show_statvis": false,
"space.overlay.show_text": true,
"space.overlay.show_vertex_normals": false,
"space.overlay.show_viewer_attribute": true,
"space.overlay.show_weight": false,
"space.overlay.show_wireframes": false,
"space.overlay.show_wpaint_contours": false,
"space.overlay.show_xray_bone": false,
"space.overlay.texture_paint_mode_opacity": 1.0,
"space.overlay.use_debug_freeze_view_culling": false,
"space.overlay.use_gpencil_canvas_xray": false,
"space.overlay.use_gpencil_edit_lines": true,
"space.overlay.use_gpencil_fade_gp_objects": false,
"space.overlay.use_gpencil_fade_layers": false,
"space.overlay.use_gpencil_fade_objects": false,
"space.overlay.use_gpencil_grid": false,
"space.overlay.use_gpencil_multiedit_line_only": false,
"space.overlay.use_gpencil_onion_skin": false,
"space.overlay.use_gpencil_show_directions": false,
"space.overlay.use_gpencil_show_material_name": false,
"space.overlay.use_normals_constant_screen_size": false,
"space.overlay.vertex_opacity": 1.0,
"space.overlay.vertex_paint_mode_opacity": 1.0,
"space.overlay.viewer_attribute_opacity": 1.0,
"space.overlay.weight_paint_mode_opacity": 1.0,
"space.overlay.wireframe_opacity": 1.0,
"space.overlay.wireframe_threshold": 0.0,
"space.overlay.xray_alpha_bone": 0.0
}
},
"Blender Default": {
"render_type": "VIEWPORT",
"raster_style": {
"bpy.data.worlds[0].color": [
0.05087608844041824,
0.05087608844041824,
0.05087608844041824
],
"scene.render.bake_bias": 0.0010000000474974513,
"scene.render.bake_margin": 16,
"scene.render.bake_margin_type": "ADJACENT_FACES",
"scene.render.bake_samples": 256,
"scene.render.bake_type": "NORMALS",
"scene.render.bake_user_scale": 0.0,
"scene.render.border_max_x": 1.0,
"scene.render.border_max_y": 1.0,
"scene.render.border_min_x": 0.0,
"scene.render.border_min_y": 0.0,
"scene.render.dither_intensity": 1.0,
"scene.render.engine": "BLENDER_EEVEE",
"scene.render.film_transparent": false,
"scene.render.filter_size": 1.5,
"scene.render.fps": 24,
"scene.render.fps_base": 1.0,
"scene.render.frame_map_new": 100,
"scene.render.frame_map_old": 100,
"scene.render.hair_subdiv": 0,
"scene.render.hair_type": "STRAND",
"scene.render.line_thickness": 1.0,
"scene.render.line_thickness_mode": "ABSOLUTE",
"scene.render.metadata_input": "SCENE",
"scene.render.motion_blur_shutter": 0.5,
"scene.render.pixel_aspect_x": 1.0,
"scene.render.pixel_aspect_y": 1.0,
"scene.render.preview_pixel_size": "AUTO",
"scene.render.resolution_percentage": 100,
"scene.render.resolution_x": 1000,
"scene.render.resolution_y": 1000,
"scene.render.sequencer_gl_preview": "SOLID",
"scene.render.simplify_child_particles": 1.0,
"scene.render.simplify_child_particles_render": 1.0,
"scene.render.simplify_gpencil": false,
"scene.render.simplify_gpencil_antialiasing": true,
"scene.render.simplify_gpencil_modifier": true,
"scene.render.simplify_gpencil_onplay": false,
"scene.render.simplify_gpencil_shader_fx": true,
"scene.render.simplify_gpencil_tint": true,
"scene.render.simplify_gpencil_view_fill": true,
"scene.render.simplify_shadows": 1.0,
"scene.render.simplify_shadows_render": 1.0,
"scene.render.simplify_subdivision": 6,
"scene.render.simplify_subdivision_render": 6,
"scene.render.simplify_volumes": 1.0,
"scene.render.stamp_font_size": 12,
"scene.render.stamp_note_text": "",
"scene.render.threads": 20,
"scene.render.threads_mode": "AUTO",
"scene.render.use_bake_clear": true,
"scene.render.use_bake_lores_mesh": false,
"scene.render.use_bake_multires": false,
"scene.render.use_bake_selected_to_active": false,
"scene.render.use_bake_user_scale": false,
"scene.render.use_border": false,
"scene.render.use_compositing": true,
"scene.render.use_crop_to_border": false,
"scene.render.use_file_extension": true,
"scene.render.use_freestyle": false,
"scene.render.use_high_quality_normals": false,
"scene.render.use_lock_interface": false,
"scene.render.use_motion_blur": false,
"scene.render.use_multiview": false,
"scene.render.use_overwrite": true,
"scene.render.use_persistent_data": false,
"scene.render.use_placeholder": false,
"scene.render.use_render_cache": false,
"scene.render.use_sequencer": true,
"scene.render.use_sequencer_override_scene_strip": false,
"scene.render.use_simplify": false,
"scene.render.use_single_layer": false,
"scene.render.use_stamp": false,
"scene.render.use_stamp_camera": true,
"scene.render.use_stamp_date": true,
"scene.render.use_stamp_filename": true,
"scene.render.use_stamp_frame": true,
"scene.render.use_stamp_frame_range": false,
"scene.render.use_stamp_hostname": false,
"scene.render.use_stamp_labels": true,
"scene.render.use_stamp_lens": false,
"scene.render.use_stamp_marker": false,
"scene.render.use_stamp_memory": false,
"scene.render.use_stamp_note": false,
"scene.render.use_stamp_render_time": true,
"scene.render.use_stamp_scene": true,
"scene.render.use_stamp_sequencer_strip": false,
"scene.render.use_stamp_time": true,
"scene.render.views_format": "STEREO_3D",
"scene.view_settings.exposure": 0.0,
"scene.view_settings.gamma": 1.0,
"scene.view_settings.look": "None",
"scene.view_settings.use_curve_mapping": false,
"scene.view_settings.view_transform": "Filmic",
"scene.display.shading.show_object_outline": false,
"scene.display.shading.show_cavity": false,
"scene.display.shading.aov_name": "",
"scene.display.shading.background_color": [
0.0,
0.0,
0.0
],
"scene.display.shading.background_type": "THEME",
"scene.display.shading.cavity_ridge_factor": 1.0,
"scene.display.shading.cavity_type": "WORLD",
"scene.display.shading.cavity_valley_factor": 1.0,
"scene.display.shading.color_type": "MATERIAL",
"scene.display.shading.curvature_ridge_factor": 0.0,
"scene.display.shading.curvature_valley_factor": 0.0,
"scene.display.shading.light": "STUDIO",
"scene.display.shading.color_type": "MATERIAL",
"scene.display.shading.object_outline_color": [
0.0,
0.0,
0.0
],
"scene.display.shading.render_pass": "COMBINED",
"scene.display.shading.shadow_intensity": 0.5,
"scene.display.shading.show_backface_culling": false,
"scene.display.shading.show_cavity": false,
"scene.display.shading.show_object_outline": false,
"scene.display.shading.show_shadows": false,
"scene.display.shading.show_specular_highlight": true,
"scene.display.shading.show_xray": false,
"scene.display.shading.show_xray_wireframe": false,
"scene.display.shading.single_color": [
0.800000011920929,
0.800000011920929,
0.800000011920929
],
"scene.display.shading.show_shadows": false,
"scene.display.shading.shadow_intensity": 0.5,
"scene.display.shading.studio_light": "Default",
"scene.display.shading.studiolight_background_alpha": 0.0,
"scene.display.shading.studiolight_background_blur": 0.0,
"scene.display.shading.studiolight_intensity": 0.0,
"scene.display.shading.studiolight_rotate_z": 0.0,
"scene.display.shading.type": "SOLID",
"scene.display.shading.use_compositor": "DISABLED",
"scene.display.shading.use_dof": false,
"scene.display.shading.use_scene_lights": false,
"scene.display.shading.use_scene_lights_render": false,
"scene.display.shading.use_scene_world": false,
"scene.display.shading.use_scene_world_render": false,
"scene.display.shading.use_studiolight_view_rotation": true,
"scene.display.shading.use_world_space_lighting": false,
"scene.display.shading.wireframe_color_type": "MATERIAL",
"scene.display.shading.xray_alpha": 0.5,
"scene.display.shading.xray_alpha_wireframe": 0.0,
"scene.display.light_direction": [
0.5773502588272095,
0.5773502588272095,
0.5773502588272095
],
"scene.view_settings.use_curve_mapping": false,
"space.overlay.show_wireframes": false,
"space.overlay.wireframe_threshold": 1.0,
"space.overlay.show_floor": true,
"scene.display.matcap_ssao_attenuation": 1.0,
"scene.display.matcap_ssao_distance": 0.20000000298023224,
"scene.display.matcap_ssao_samples": 16,
"scene.display.render_aa": "8",
"scene.display.shadow_focus": 0.0,
"scene.display.shadow_shift": 0.10000000149011612,
"scene.display.viewport_aa": "FXAA",
"space.overlay.backwire_opacity": 0.5,
"space.overlay.bone_wire_alpha": 1.0,
"space.overlay.display_handle": "SELECTED",
"space.overlay.fade_inactive_alpha": 0.4000000059604645,
"space.overlay.gpencil_fade_layer": 0.5,
"space.overlay.gpencil_fade_objects": 0.5,
"space.overlay.gpencil_grid_opacity": 0.5,
"space.overlay.gpencil_vertex_paint_opacity": 1.0,
"space.overlay.grid_lines": 16,
"space.overlay.grid_scale": 1.0,
"space.overlay.grid_subdivisions": 10,
"space.overlay.normals_constant_screen_size": 7.0,
"space.overlay.normals_length": 0.10000000149011612,
"space.overlay.sculpt_curves_cage_opacity": 0.5,
"space.overlay.sculpt_mode_face_sets_opacity": 1.0,
"space.overlay.sculpt_mode_mask_opacity": 0.75,
"space.overlay.show_annotation": true,
"space.overlay.show_axis_x": true,
"space.overlay.show_axis_y": true,
"space.overlay.show_axis_z": false,
"space.overlay.show_bones": true,
"space.overlay.show_cursor": true,
"space.overlay.show_curve_normals": false,
"space.overlay.show_edge_bevel_weight": true,
"space.overlay.show_edge_crease": true,
"space.overlay.show_edge_seams": true,
"space.overlay.show_edge_sharp": true,
"space.overlay.show_edges": false,
"space.overlay.show_extra_edge_angle": false,
"space.overlay.show_extra_edge_length": false,
"space.overlay.show_extra_face_angle": false,
"space.overlay.show_extra_face_area": false,
"space.overlay.show_extra_indices": false,
"space.overlay.show_extras": true,
"space.overlay.show_face_center": false,
"space.overlay.show_face_normals": false,
"space.overlay.show_face_orientation": false,
"space.overlay.show_faces": true,
"space.overlay.show_fade_inactive": false,
"space.overlay.show_floor": true,
"space.overlay.show_freestyle_edge_marks": true,
"space.overlay.show_freestyle_face_marks": true,
"space.overlay.show_look_dev": false,
"space.overlay.show_motion_paths": true,
"space.overlay.show_object_origins": true,
"space.overlay.show_relationship_lines": true
},
"render_type": "VIEWPORT"
"space.overlay.show_object_origins_all": false,
"space.overlay.show_occlude_wire": false,
"space.overlay.show_onion_skins": false,
"space.overlay.show_ortho_grid": true,
"space.overlay.show_outline_selected": true,
"space.overlay.show_overlays": true,
"space.overlay.show_paint_wire": false,
"space.overlay.show_relationship_lines": true,
"space.overlay.show_sculpt_curves_cage": true,
"space.overlay.show_sculpt_face_sets": true,
"space.overlay.show_sculpt_mask": true,
"space.overlay.show_split_normals": false,
"space.overlay.show_stats": false,
"space.overlay.show_statvis": false,
"space.overlay.show_text": true,
"space.overlay.show_vertex_normals": false,
"space.overlay.show_viewer_attribute": true,
"space.overlay.show_weight": false,
"space.overlay.show_wireframes": false,
"space.overlay.show_wpaint_contours": false,
"space.overlay.show_xray_bone": false,
"space.overlay.texture_paint_mode_opacity": 1.0,
"space.overlay.use_debug_freeze_view_culling": false,
"space.overlay.use_gpencil_canvas_xray": false,
"space.overlay.use_gpencil_edit_lines": true,
"space.overlay.use_gpencil_fade_gp_objects": false,
"space.overlay.use_gpencil_fade_layers": false,
"space.overlay.use_gpencil_fade_objects": false,
"space.overlay.use_gpencil_grid": false,
"space.overlay.use_gpencil_multiedit_line_only": false,
"space.overlay.use_gpencil_onion_skin": false,
"space.overlay.use_gpencil_show_directions": false,
"space.overlay.use_gpencil_show_material_name": false,
"space.overlay.use_normals_constant_screen_size": false,
"space.overlay.vertex_opacity": 1.0,
"space.overlay.vertex_paint_mode_opacity": 1.0,
"space.overlay.viewer_attribute_opacity": 1.0,
"space.overlay.weight_paint_mode_opacity": 1.0,
"space.overlay.wireframe_opacity": 1.0,
"space.overlay.wireframe_threshold": 1.0,
"space.overlay.xray_alpha_bone": 0.0
}
}
}
-1
View File
@@ -22,7 +22,6 @@ import addon_utils
import ifcopenshell.api.owner.settings
import blenderbim.tool as tool
import blenderbim.core.owner as core_owner
from blenderbim.bim.module.drawing.prop import RasterStyleProperty
from bpy.app.handlers import persistent
from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.module.owner.prop import get_user_person, get_user_organisation
+6 -2
View File
@@ -381,7 +381,7 @@ class IfcImporter:
representations = self.get_transformed_body_representations(element.Representation.Representations)
# Single swept disk solids (e.g. rebar) are better natively represented as beveled curves
if self.is_native_swept_disk_solid(representations):
if self.is_native_swept_disk_solid(element, representations):
self.native_data[element.GlobalId] = {
"representations": representations,
"representation": self.get_body_representation(element.Representation.Representations),
@@ -409,7 +409,11 @@ class IfcImporter:
}
return True
def is_native_swept_disk_solid(self, representations):
def is_native_swept_disk_solid(self, element, representations):
# detect BBIM Railings to represent them with meshes and not curves
if tool.Pset.get_element_pset(element, "BBIM_Railing"):
return False
for representation in representations:
items = representation["raw"].Items or [] # Be forgiving of invalid IFCs because Revit :(
if len(items) == 1 and items[0].is_a("IfcSweptDiskSolid"):
@@ -43,13 +43,29 @@ class BIM_OT_assign_object(bpy.types.Operator, Operator):
related_object: bpy.props.IntProperty()
def _execute(self, context):
core.assign_object(
tool.Ifc,
tool.Aggregate,
tool.Collector,
relating_obj=tool.Ifc.get_object(tool.Ifc.get().by_id(self.relating_object)),
related_obj=tool.Ifc.get_object(tool.Ifc.get().by_id(self.related_object)),
)
for obj in bpy.context.selected_objects:
element = tool.Ifc.get_entity(obj)
if not element:
continue
if self.relating_object:
relating_obj = tool.Ifc.get_object(tool.Ifc.get().by_id(self.relating_object))
else:
if obj == context.active_object:
continue
relating_obj = context.active_object
if not relating_obj:
continue
result = core.assign_object(
tool.Ifc,
tool.Aggregate,
tool.Collector,
relating_obj=relating_obj,
related_obj=obj,
)
if not result:
self.report({"ERROR"}, f" Cannot aggregate {obj.name} to {relating_obj.name}")
return {"FINISHED"}
class BIM_OT_unassign_object(bpy.types.Operator, Operator):
@@ -76,6 +76,7 @@ classes = (
operator.LoadCostItemTaskQuantities,
operator.LoadCostItemResourceQuantities,
operator.ChangeParentCostItem,
operator.CopyCostItem,
prop.CostItem,
prop.CostItemQuantity,
prop.CostItemType,
@@ -97,10 +97,9 @@ class AddSummaryCostItem(bpy.types.Operator, tool.Ifc.Operator):
bl_label = "Add Cost Item"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Add a summary cost item"
cost_schedule: bpy.props.IntProperty()
def _execute(self, context):
core.add_summary_cost_item(tool.Ifc, tool.Cost, cost_schedule=tool.Ifc.get().by_id(self.cost_schedule))
core.add_summary_cost_item(tool.Ifc, tool.Cost, cost_schedule=tool.Cost.get_active_cost_schedule())
class AddCostItem(bpy.types.Operator, tool.Ifc.Operator):
@@ -114,6 +113,16 @@ class AddCostItem(bpy.types.Operator, tool.Ifc.Operator):
core.add_cost_item(tool.Ifc, tool.Cost, cost_item=tool.Ifc.get().by_id(self.cost_item))
class CopyCostItem(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.copy_cost_item"
bl_label = "Copy Cost Item"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Copy a cost item"
def _execute(self, context):
core.copy_cost_item(tool.Ifc, tool.Cost)
class ExpandCostItem(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.expand_cost_item"
bl_label = "Expand Cost Item"
@@ -214,8 +223,9 @@ class UnassignCostItemType(bpy.types.Operator, tool.Ifc.Operator):
core.unassign_cost_item_type(
tool.Ifc,
tool.Cost,
self.cost_item,
products=[tool.Ifc.get().by_id(self.related_object)] if self.related_object else [],
tool.Spatial,
cost_item=tool.Ifc.get().by_id(self.cost_item),
product_types=[tool.Ifc.get().by_id(self.related_object)] if self.related_object else [],
)
return {"FINISHED"}
@@ -491,7 +501,7 @@ class AddCostColumn(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
name: bpy.props.StringProperty()
def _execute(self, context):
def execute(self, context):
core.add_cost_column(tool.Cost, self.name)
return {"FINISHED"}
@@ -619,10 +629,12 @@ class ExportCostSchedules(bpy.types.Operator):
bl_label = "Export Cost Schedule"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Export a cost schedule to a CSV, XSLX OR ODS file"
cost_schedule: bpy.props.IntProperty()
format: bpy.props.EnumProperty("Format", items=(("CSV", "CSV", ""), ("XLSX", "XLSX", ""), ("ODS", "ODS", "")))
def execute(self, context):
core.export_cost_schedules(tool.Cost, format=self.format)
cost_schedule = tool.Ifc.get().by_id(self.cost_schedule) if self.cost_schedule else None
core.export_cost_schedules(tool.Cost, format=self.format, cost_schedule=cost_schedule)
return {"FINISHED"}
def invoke(self, context, event):
@@ -667,7 +679,9 @@ class LoadProductCostItems(bpy.types.Operator):
return True
def execute(self, context):
core.load_product_cost_items(tool.Cost, product=tool.Ifc.get().by_id(context.active_object.BIMObjectProperties.ifc_definition_id))
core.load_product_cost_items(
tool.Cost, product=tool.Ifc.get().by_id(context.active_object.BIMObjectProperties.ifc_definition_id)
)
return {"FINISHED"}
@@ -213,3 +213,4 @@ class BIMCostProperties(PropertyGroup):
name="Show Nested Tasks", default=False, update=update_active_cost_item_resources
)
change_cost_item_parent: BoolProperty(name="Change Cost Item Parent", default=False, update=update_cost_item_parent)
show_cost_item_operators: BoolProperty(name="Show Cost Item Operators", default=False)
+61 -43
View File
@@ -43,43 +43,54 @@ class BIM_PT_cost_schedules(Panel):
self.props = context.scene.BIMCostProperties
row = self.layout.row()
if CostSchedulesData.data["total_cost_schedules"]:
row.label(text=f"{CostSchedulesData.data['total_cost_schedules']} Cost Schedules Found", icon="TEXT")
row.operator("bim.export_cost_schedules", text="Export as spreadsheet", icon="EXPORT")
else:
row.label(text="No Cost Schedules Found found.", icon="COMMUNITY")
row = self.layout.row()
row.prop(self.props, "cost_schedule_predefined_types")
row.operator("bim.add_cost_schedule", icon="ADD", text="Add")
if not self.props.active_cost_schedule_id:
if CostSchedulesData.data["total_cost_schedules"]:
row.label(text=f"{CostSchedulesData.data['total_cost_schedules']} Cost Schedules Found", icon="TEXT")
row.operator("bim.export_cost_schedules", text="Export as spreadsheet", icon="EXPORT")
else:
row.label(text="No Cost Schedules Found found.", icon="COMMUNITY")
row = self.layout.row()
row.prop(self.props, "cost_schedule_predefined_types")
row.operator("bim.add_cost_schedule", icon="ADD", text="Add")
for schedule in CostSchedulesData.data["schedules"]:
self.draw_cost_schedule_ui(schedule)
def draw_cost_schedule_ui(self, cost_schedule):
row = self.layout.row(align=True)
row.label(text=cost_schedule["name"], icon="LINENUMBERS_ON")
if self.props.active_cost_schedule_id and self.props.active_cost_schedule_id == cost_schedule["id"]:
op = row.operator("bim.select_cost_schedule_products", icon="RESTRICT_SELECT_OFF", text="Assigned")
row.label(text="Currently editing: {}".format(cost_schedule["name"]), icon="LINENUMBERS_ON")
grid = self.layout.grid_flow(columns=2, even_columns=True)
col = grid.column()
row1 = col.row(align=True)
row1.alignment = "LEFT"
row1.label(text="Schedule tools")
row1 = col.row(align=True)
row1.alignment = "RIGHT"
row1.operator("bim.export_cost_schedules", text="Export", icon="EXPORT").cost_schedule = cost_schedule["id"]
row2 = col.row(align=True)
row2.alignment = "RIGHT"
op = row2.operator("bim.select_cost_schedule_products", icon="RESTRICT_SELECT_OFF", text="Assigned")
op.cost_schedule = cost_schedule["id"]
row.operator("bim.select_unassigned_products", icon="RESTRICT_SELECT_OFF", text="Unassigned")
row2.operator("bim.select_unassigned_products", icon="RESTRICT_SELECT_OFF", text="Unassigned")
row.prop(self.props, "should_show_column_ui", text="", icon="SHORTDISPLAY")
col = grid.column()
row1 = col.row(align=True)
row1.alignment = "LEFT"
row1.label(text="Settings")
row1 = col.row(align=True)
row1.alignment = "RIGHT"
row1.prop(self.props, "should_show_column_ui", text="Schedule Columns", icon="SHORTDISPLAY")
if self.props.is_editing == "COST_SCHEDULE_ATTRIBUTES":
row.operator("bim.edit_cost_schedule", text="", icon="CHECKMARK")
elif self.props.is_editing == "COST_ITEMS":
row.operator("bim.add_summary_cost_item", text="", icon="ADD").cost_schedule = cost_schedule["id"]
row.operator("bim.disable_editing_cost_schedule", text="", icon="CANCEL")
elif self.props.active_cost_schedule_id:
row.operator("bim.remove_cost_schedule", text="", icon="X").cost_schedule = cost_schedule["id"]
row.operator("bim.disable_editing_cost_schedule", text="Disable Editing", icon="CANCEL")
else:
row.label(text=cost_schedule["name"], icon="LINENUMBERS_ON")
row.operator("bim.enable_editing_cost_items", text="", icon="OUTLINER").cost_schedule = cost_schedule["id"]
row.operator(
"bim.enable_editing_cost_schedule_attributes", text="", icon="GREASEPENCIL"
).cost_schedule = cost_schedule["id"]
row.operator("bim.remove_cost_schedule", text="", icon="X").cost_schedule = cost_schedule["id"]
if self.props.active_cost_schedule_id == cost_schedule["id"]:
if self.props.is_editing == "COST_SCHEDULE_ATTRIBUTES":
self.draw_editable_cost_schedule_ui()
@@ -101,30 +112,39 @@ class BIM_PT_cost_schedules(Panel):
row = self.layout.row(align=True)
row.alignment = "RIGHT"
ifc_definition_id = None
row = self.layout.row(align=True)
row.label(text="Cost Item Tools")
row = self.layout.row(align=True)
row.alignment = "RIGHT"
row.operator("bim.add_summary_cost_item", text="Add Summary Cost", icon="ADD")
row.operator("bim.expand_all_tasks", text="Expand All")
row.operator("bim.contract_all_tasks", text="Contract All")
row = self.layout.row(align=True)
row.alignment = "RIGHT"
if self.props.cost_items and self.props.active_cost_item_index < len(self.props.cost_items):
ifc_definition_id = self.props.cost_items[self.props.active_cost_item_index].ifc_definition_id
if ifc_definition_id:
row.prop(self.props, "change_cost_item_parent", text="", icon="LINKED")
row.prop(self.props, "enable_reorder", text="", icon="SORTALPHA")
if not CostSchedulesData.data["is_editing_rates"]:
op = row.operator("bim.enable_editing_cost_item_quantities", text="", icon="PROPERTIES")
row.prop(self.props, "show_cost_item_operators", text="Edit", icon="DOWNARROW_HLT")
row.operator("bim.add_cost_item", text="Add", icon="ADD").cost_item = ifc_definition_id
row.operator("bim.copy_cost_item", text="Copy", icon="ADD")
row.operator("bim.remove_cost_item", text="Delete", icon="X").cost_item = ifc_definition_id
if self.props.show_cost_item_operators:
row = self.layout.row(align=True)
row.alignment = "RIGHT"
row.prop(self.props, "change_cost_item_parent", text="", icon="LINKED")
row.prop(self.props, "enable_reorder", text="", icon="SORTALPHA")
if not CostSchedulesData.data["is_editing_rates"]:
op = row.operator("bim.enable_editing_cost_item_quantities", text="", icon="PROPERTIES")
op.cost_item = ifc_definition_id
op = row.operator("bim.enable_editing_cost_item_values", text="", icon="DISC")
op.cost_item = ifc_definition_id
op = row.operator("bim.enable_editing_cost_item_values", text="", icon="DISC")
op.cost_item = ifc_definition_id
row.operator("bim.add_cost_item", text="", icon="ADD").cost_item = ifc_definition_id
if self.props.active_cost_item_id == ifc_definition_id:
if self.props.cost_item_editing_type == "ATTRIBUTES":
row.operator("bim.edit_cost_item", text="", icon="CHECKMARK")
row.operator("bim.disable_editing_cost_item", text="", icon="CANCEL")
else:
op = row.operator("bim.enable_editing_cost_item_attributes", text="", icon="GREASEPENCIL")
op.cost_item = ifc_definition_id
row.operator("bim.remove_cost_item", text="", icon="X").cost_item = ifc_definition_id
if self.props.active_cost_item_id == ifc_definition_id:
if self.props.cost_item_editing_type == "ATTRIBUTES":
row.operator("bim.edit_cost_item", text="", icon="CHECKMARK")
row.operator("bim.disable_editing_cost_item", text="", icon="CANCEL")
else:
op = row.operator("bim.enable_editing_cost_item_attributes", text="", icon="GREASEPENCIL")
op.cost_item = ifc_definition_id
self.layout.template_list(
"BIM_UL_cost_items",
"",
@@ -698,9 +718,7 @@ class BIM_PT_Costing_Tools(Panel):
def draw(self, context):
self.props = context.scene.BIMCostProperties
row = self.layout.row()
row.operator(
"bim.load_product_cost_items", icon="FILE_REFRESH"
)
row.operator("bim.load_product_cost_items", icon="FILE_REFRESH")
row = self.layout.row()
row.template_list(
"BIM_UL_product_cost_items",
@@ -20,6 +20,7 @@ import bpy
import os
import blenderbim.tool as tool
from mathutils import Vector
import bmesh
class Annotator:
@@ -94,7 +95,7 @@ class Annotator:
return obj
@staticmethod
def add_plane_to_annotation(obj):
def add_plane_to_annotation(obj, remove_face=False):
# default order = bot left, top left, bot right, top right
# therefore we redefine the order
face_verts = [0, 2, 3, 1]
@@ -104,7 +105,9 @@ class Annotator:
bm = tool.Blender.get_bmesh_for_mesh(obj.data, clean=True)
new_verts = [bm.verts.new(v) for v in verts_local]
bm.faces.new([new_verts[i] for i in face_verts])
face = bm.faces.new([new_verts[i] for i in face_verts])
if remove_face:
bmesh.ops.delete(bm, geom=[face], context="FACES_ONLY")
tool.Blender.apply_bmesh(obj.data, bm, obj)
return obj
@@ -32,13 +32,38 @@ from bpy.types import SpaceView3D
from mathutils import Vector, Matrix
from bpy_extras.view3d_utils import location_3d_to_region_2d
from gpu_extras.batch import batch_for_shader
from blenderbim.bim.module.drawing.data import DecoratorData
from blenderbim.bim.module.drawing.shaders import BASE_LIB_GLSL, BASE_DEF_GLSL, add_verts_sequence, add_offsets
from blenderbim.bim.module.drawing.data import DecoratorData, DrawingsData
from blenderbim.bim.module.drawing.shaders import add_verts_sequence, add_offsets
from blenderbim.bim.module.drawing.helper import format_distance
from timeit import default_timer as timer
from functools import lru_cache
UNSPECIAL_ELEMENT_COLOR = (0.2, 0.2, 0.2, 1) # GREY # TODO: move back to 0.2
class profile_consequential:
start_time = None
lines = []
@classmethod
def __init__(cls, test_name):
cls.log()
cls.start_time = timer()
cls.test_name = test_name
@classmethod
def log(cls):
if cls.start_time is not None:
cls.lines.append(f"{cls.test_name}\t{timer() - cls.start_time:.10f}")
@classmethod
def stop(cls):
cls.log()
cls.start_time = None
print('\n'.join(cls.lines))
cls.lines = []
def ccw(A, B, C):
"""whether a-b-c located in counter-clockwise order in 2d space"""
return (C.y - A.y) * (B.x - A.x) > (B.y - A.y) * (C.x - A.x)
@@ -167,56 +192,6 @@ class BaseDecorator:
def camera_zoom_to_factor(self, zoom):
return math.pow(((zoom / 50) + math.sqrt(2)) / 2, 2)
def get_objects(self, collection):
"""find relevant objects
using class.objecttype
returns: iterable of blender objects
"""
results = []
# NOTE: if the ObjectType is not on the list
# it will be also drawn with MiscDecorator
decoration_presets = (
"DIMENSION",
"TEXT_LEADER",
"STAIR_ARROW",
"HIDDEN_LINE",
"PLAN_LEVEL",
"SECTION_LEVEL",
"BREAKLINE",
"GRID",
"ELEVATION",
"SECTION",
"TEXT",
"BATTING",
)
for obj in collection.all_objects:
if obj.hide_get():
continue
element = tool.Ifc.get_entity(obj)
if not element:
continue
if element.is_a("IfcAnnotation"):
if element.ObjectType == self.objecttype:
results.append(obj)
elif (
self.objecttype == "MISC"
and element.ObjectType not in decoration_presets
and isinstance(obj.data, bpy.types.Mesh)
):
results.append(obj)
elif self.objecttype == "FALL" and element.ObjectType in (
"SLOPE_ANGLE",
"SLOPE_FRACTION",
"SLOPE_PERCENT",
):
results.append(obj)
return results
def get_splines(self, obj):
"""Iterates through splines
Args:
@@ -343,7 +318,7 @@ class BaseDecorator:
shader.uniform_float("color", color)
batch.draw(shader)
def draw_lines(self, context, obj, vertices, indices, color=None):
def draw_lines(self, context, obj, vertices, indices, color=None, line_width=1.0):
"""`verts` should be in winspace with `(0,0,0)` in the screen left bottom corner, not in the center"""
region = context.region
if not color:
@@ -352,7 +327,7 @@ class BaseDecorator:
self.line_shader.bind()
# POLYLINE_UNIFORM_COLOR specific uniforms
self.line_shader.uniform_float("viewportSize", (region.width, region.height))
self.line_shader.uniform_float("lineWidth", 1.0)
self.line_shader.uniform_float("lineWidth", line_width)
gpu.state.blend_set("ALPHA")
self.draw_batch("LINES", vertices, color, indices)
@@ -486,14 +461,15 @@ class BaseDecorator:
blf.draw(font_id, text)
blf.disable(font_id, blf.ROTATION)
@lru_cache(maxsize=None)
def format_value(self, context, value):
return bpy.utils.units.to_string(
context.scene.unit_settings.system,
"LENGTH",
value,
precision=4,
split_unit=context.scene.unit_settings.system == "IMPERIAL",
)
drawing_pset_data = DrawingsData.data["active_drawing_pset_data"]
precision = drawing_pset_data.get("MetricPrecision", None)
if not precision:
precision = drawing_pset_data.get("ImperialPrecision", None)
decimal_places = drawing_pset_data.get("DecimalPlaces", None)
return format_distance(value, precision=precision, decimal_places=decimal_places)
def draw_asterisk(self, context, obj):
# gather geometry data and convert to winspace
@@ -520,6 +496,7 @@ class BaseDecorator:
def draw_text(self, context, obj, text_world_position=None, reverse_lines_order=False):
"""if `text_world_position` is not provided, the object's location will be used"""
if not text_world_position:
text_world_position = obj.location
@@ -570,6 +547,7 @@ class BaseDecorator:
line_i += 1 if not reverse_lines_order else -1
class DimensionDecorator(BaseDecorator):
"""Decorator for dimension objects
- each edge of a segment with arrow
@@ -657,6 +635,7 @@ class DimensionDecorator(BaseDecorator):
text_offset_value = viewportDrawingScale * 3
for i0, i1 in indices:
v0 = Vector(vertices[i0])
v1 = Vector(vertices[i1])
p0 = location_3d_to_region_2d(region, region3d, v0)
@@ -669,8 +648,6 @@ class DimensionDecorator(BaseDecorator):
if not show_description_only:
length = (v1 - v0).length
# TODO: same distance format function as in svg?
# requires storing drawing precision and decimal_places from pset to data.py
text = self.format_value(context, length)
text = text_prefix + text + text_suffix
@@ -982,15 +959,13 @@ class StairDecorator(BaseDecorator):
# circle head on first vert
if edge[0] == 0:
circle_head = get_circle_head(circle_size)
add_verts_sequence([v + v0 for v in circle_head], start_i, **out_kwargs, closed=True)
start_i += 12
start_i = add_verts_sequence([v + v0 for v in circle_head], start_i, **out_kwargs, closed=True)
# arrow head on last vert
if edge[1] == last_vert:
edge_dir = (v1 - v0).normalized()
arrow_head = get_arrow_head(edge_dir, arrow_size, rot_matrix_cw, rot_matrix_ccw)
add_verts_sequence([v1 - arrow_head[1], v1, v1 - arrow_head[2]], start_i, **out_kwargs)
start_i += 3
start_i = add_verts_sequence([v1 - arrow_head[1], v1, v1 - arrow_head[2]], start_i, **out_kwargs)
# stem with gaps for arrows
add_verts_sequence([v0, v1], start_i, **out_kwargs)
@@ -1010,6 +985,19 @@ class MiscDecorator(BaseDecorator):
self.draw_lines(context, obj, winspace_verts, idxs)
class RevisionCloudDecorator(BaseDecorator):
objecttype = "REVISION_CLOUD"
def decorate(self, context, obj):
if obj.data.is_editmode:
verts, idxs = self.get_editmesh_geom(obj)
else:
verts, idxs = self.get_mesh_geom(obj)
winspace_verts = worldspace_to_winspace(verts, context)
# TODO: draw revision clouds inside viewport?
self.draw_lines(context, obj, winspace_verts, idxs, color=(1, 0, 0, 1), line_width=2)
# TODO: custom frag shader to support dashed lines?
class HiddenDecorator(BaseDecorator):
objecttype = "HIDDEN_LINE"
@@ -1883,6 +1871,7 @@ class DecorationsHandler:
PlanLevelDecorator,
SectionLevelDecorator,
StairDecorator,
RevisionCloudDecorator,
BreakDecorator,
SectionDecorator,
ElevationDecorator,
@@ -1898,7 +1887,7 @@ class DecorationsHandler:
if cls.installed:
cls.uninstall()
handler = cls()
# NOTE: that we USE POST_PIXEL here so that we can draw use both 3D_POLYLINE_UNIFORM_COLOR
# NOTE: we USE POST_PIXEL here so that we can draw use both 3D_POLYLINE_UNIFORM_COLOR
# and drawing text in the same handler. BUT this means that we supply coordinates in WINSPACE
cls.installed = SpaceView3D.draw_handler_add(handler, (context,), "WINDOW", "POST_PIXEL")
@@ -1911,13 +1900,44 @@ class DecorationsHandler:
cls.installed = None
def __init__(self):
self.decorators = [cls() for cls in self.decorators_classes]
self.decorators = {cls.objecttype: cls() for cls in self.decorators_classes}
for object_type in ("SLOPE_ANGLE", "SLOPE_FRACTION", "SLOPE_PERCENT"):
self.decorators[object_type] = self.decorators["FALL"]
def get_objects_and_decorators(self, collection):
results = []
for obj in collection.all_objects:
if obj.hide_get():
continue
element = tool.Ifc.get_entity(obj)
if not element:
continue
if not element.is_a("IfcAnnotation"):
continue
object_type = element.ObjectType
if object_type == "DRAWING":
continue
if (dec := self.decorators.get(object_type, None)):
results.append((obj, dec))
elif isinstance(obj.data, bpy.types.Mesh):
results.append((obj, self.decorators["MISC"]))
return results
def __call__(self, context):
collection, _ = helper.get_active_drawing(context.scene)
if collection is None:
return
for decorator in self.decorators:
for obj in decorator.get_objects(collection):
decorator.decorate(context, obj)
if not DrawingsData.is_loaded:
DrawingsData.load()
object_decorators = self.get_objects_and_decorators(collection)
for obj, decorator in object_decorators:
decorator.decorate(context, obj)
@@ -154,13 +154,14 @@ def format_distance(
# Separate ft and inches
# Unless Inches are the specified Length Unit
if unit_length != "INCHES":
feet = math.floor(decInches / inPerFoot)
feet = int(decInches / inPerFoot) # remove decimal
decInches -= feet * inPerFoot
else:
feet = 0
# Separate Fractional Inches
inches = math.floor(decInches)
decInches = abs(decInches) # ignore the sign for inches
inches = math.floor(decInches) # remove decimal
if inches != 0:
frac = round(base * (decInches - inches))
else:
@@ -44,9 +44,9 @@ from blenderbim.bim.module.drawing.decoration import CutDecorator
from blenderbim.bim.module.drawing.data import DecoratorData, DrawingsData
import blenderbim.bim.export_ifc
from lxml import etree
from mathutils import Vector
from mathutils import Vector, Color
from timeit import default_timer as timer
from blenderbim.bim.module.drawing.prop import RasterStyleProperty, Literal
from blenderbim.bim.module.drawing.prop import RasterStyleProperty, Literal, RASTER_STYLE_PROPERTIES_EXCLUDE
from blenderbim.bim.ifc import IfcStore
from pathlib import Path
@@ -481,6 +481,14 @@ class CreateDrawing(bpy.types.Operator):
tree.add_element(elem)
elements -= processed
if self.camera_element not in drawing_elements:
with profile("Camera element"):
# The camera must always be included, regardless of any include/exclude filters.
geom_settings = ifcopenshell.geom.settings(DISABLE_TRIANGULATION=True, STRICT_TOLERANCE=True)
it = ifcopenshell.geom.iterator(geom_settings, ifc, include=[self.camera_element])
for elem in self.yield_from_iterator(it):
self.serialiser.write(elem)
with profile("Finalizing"):
self.serialiser.finalize()
results = self.svg_buffer.get_value()
@@ -836,15 +844,15 @@ class CreateDrawing(bpy.types.Operator):
ifc = tool.Ifc.get()
for el in root.findall(".//{http://www.w3.org/2000/svg}g[@{http://www.ifcopenshell.org/ns}guid]"):
element = ifc.by_guid(el.get("{http://www.ifcopenshell.org/ns}guid"))
# Architectural convention only merges these objects. E.g. pipe segments and fittings shouldn't merge.
if not element.is_a("IfcWall") and not element.is_a("IfcSlab"):
continue
classes = self.get_svg_classes(element)
classes.append("cut")
el.set("class", " ".join(classes))
# Architectural convention only merges these objects. E.g. pipe segments and fittings shouldn't merge.
if not element.is_a("IfcWall") and not element.is_a("IfcSlab"):
continue
keys = []
for query in join_criteria:
key = ifcopenshell.util.selector.get_element_value(element, query)
@@ -1055,7 +1063,7 @@ class AddSheet(bpy.types.Operator, Operator):
class OpenSheet(bpy.types.Operator, Operator):
bl_idname = "bim.open_sheet"
bl_label = "Open Sheet"
bl_label = "Open Sheet Layout"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
@@ -1138,6 +1146,7 @@ class RemoveDrawingFromSheet(bpy.types.Operator, Operator):
class CreateSheets(bpy.types.Operator, Operator):
bl_idname = "bim.create_sheets"
bl_label = "Create Sheets"
bl_description = "Build a sheet from the sheet layout"
bl_options = {"REGISTER", "UNDO"}
@classmethod
@@ -1174,7 +1183,7 @@ class CreateSheets(bpy.types.Operator, Operator):
has_sheet_reference = True
elif reference.Description == "RASTER":
if reference.Location in raster_references:
del raster_references[reference.Location]
raster_references.remove(reference.Location)
else:
tool.Ifc.run("document.remove_reference", reference=reference)
@@ -1298,9 +1307,13 @@ class ActivateModel(bpy.types.Operator):
bl_description = "Activates the model view"
def execute(self, context):
dprops = bpy.context.scene.DocProperties
dprops.active_drawing_id = 0
CutDecorator.uninstall()
bpy.ops.object.hide_view_clear()
if not bpy.app.background:
bpy.ops.object.hide_view_clear()
subcontext = ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Model", "Body", "MODEL_VIEW")
@@ -1341,6 +1354,8 @@ class ActivateDrawing(bpy.types.Operator):
dprops = bpy.context.scene.DocProperties
core.activate_drawing_view(tool.Ifc, tool.Drawing, drawing=drawing)
dprops.active_drawing_id = self.drawing
# reset DrawingsData to reload_drawing_styles work correctly
DrawingsData.is_loaded = False
dprops.drawing_styles.clear()
if ifcopenshell.util.element.get_pset(drawing, "EPset_Drawing", "HasUnderlay"):
bpy.ops.bim.reload_drawing_styles()
@@ -1408,7 +1423,7 @@ class RemoveDrawing(bpy.types.Operator, Operator):
else:
drawings = [tool.Ifc.get().by_id(self.drawing)]
print("Removing drawings: {}".format([d for d in drawings]))
removed_drawings = [drawing.id() for drawing in drawings]
for drawing in drawings:
core.remove_drawing(tool.Ifc, tool.Drawing, drawing=drawing)
@@ -1418,6 +1433,7 @@ class ReloadDrawingStyles(bpy.types.Operator):
bl_idname = "bim.reload_drawing_styles"
bl_label = "Reload Drawing Styles"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Reload drawing styles for the active camera"
def execute(self, context):
if not DrawingsData.is_loaded:
@@ -1514,14 +1530,38 @@ class SaveDrawingStyle(bpy.types.Operator, Operator):
space = self.get_view_3d(context) # Do not remove. It is used later in eval
scene = context.scene
style = {}
for prop in RasterStyleProperty:
value = eval(prop.value)
eval_namespace = {"context": context, "scene": scene, "space": space}
def add_prop_to_style(prop_path, context, scene, space):
value = eval(prop_path)
if not isinstance(value, str):
try:
value = tuple(value)
except TypeError:
pass
style[prop.value] = value
style[prop_path] = value
for prop in RasterStyleProperty:
if prop.name.startswith("EVAL_PROP"):
prop_path = prop.value
add_prop_to_style(prop_path, **eval_namespace)
else:
props_source_path = prop.value
props_source = eval(props_source_path)
for prop_name in dir(props_source):
if prop_name.startswith("__"):
continue
prop_path = f"{props_source_path}.{prop_name}"
prop_value = eval(prop_path)
if (
not isinstance(prop_value, (int, float, bool, str, Color, Vector))
or props_source.is_property_readonly(prop_name)
or prop_path in RASTER_STYLE_PROPERTIES_EXCLUDE
):
continue
add_prop_to_style(prop_path, **eval_namespace)
if self.index:
index = int(self.index)
@@ -1606,7 +1646,8 @@ class ActivateDrawingStyle(bpy.types.Operator, Operator):
active_drawing_style_index = scene.camera.data.BIMCameraProperties.active_drawing_style_index
if active_drawing_style_index >= len(scene.DocProperties.drawing_styles):
return
self.report({"ERROR"}, "Could not find active drawing style")
return {"CANCELLED"}
self.drawing_style = scene.DocProperties.drawing_styles[active_drawing_style_index]
@@ -178,6 +178,10 @@ def update_schedule_name(self, context):
def update_has_underlay(self, context):
update_layer(self, context, "HasUnderlay", self.has_underlay)
# making sure that camera is active
if self.has_underlay and (context.active_object and context.active_object.data == self.id_data):
bpy.ops.bim.reload_drawing_styles()
bpy.ops.bim.activate_drawing_style()
def update_has_linework(self, context):
@@ -271,30 +275,20 @@ class DrawingStyle(PropertyGroup):
class RasterStyleProperty(enum.Enum):
WORLD_COLOR = "bpy.data.worlds[0].color"
RENDER_ENGINE = "scene.render.engine"
RENDER_TRANSPARENT = "scene.render.film_transparent"
VIEW_TRANSFORM = "scene.view_settings.view_transform"
SHADING_SHOW_OBJECT_OUTLINE = "scene.display.shading.show_object_outline"
SHADING_SHOW_CAVITY = "scene.display.shading.show_cavity"
SHADING_CAVITY_TYPE = "scene.display.shading.cavity_type"
SHADING_CURVATURE_RIDGE_FACTOR = "scene.display.shading.curvature_ridge_factor"
SHADING_CURVATURE_VALLEY_FACTOR = "scene.display.shading.curvature_valley_factor"
SHADING_LIGHT = "scene.display.shading.light"
SHADING_COLOR_TYPE = "scene.display.shading.color_type"
SHADING_SINGLE_COLOR = "scene.display.shading.single_color"
SHADING_SHOW_SHADOWS = "scene.display.shading.show_shadows"
SHADING_SHADOW_INTENSITY = "scene.display.shading.shadow_intensity"
DISPLAY_LIGHT_DIRECTION = "scene.display.light_direction"
VIEW_USE_CURVE_MAPPING = "scene.view_settings.use_curve_mapping"
OVERLAY_SHOW_WIREFRAMES = "space.overlay.show_wireframes"
OVERLAY_WIREFRAME_THRESHOLD = "space.overlay.wireframe_threshold"
OVERLAY_SHOW_FLOOR = "space.overlay.show_floor"
OVERLAY_SHOW_AXIS_X = "space.overlay.show_axis_x"
OVERLAY_SHOW_AXIS_Y = "space.overlay.show_axis_y"
OVERLAY_SHOW_AXIS_Z = "space.overlay.show_axis_z"
OVERLAY_SHOW_OBJECT_ORIGINS = "space.overlay.show_object_origins"
OVERLAY_SHOW_RELATIONSHIP_LINES = "space.overlay.show_relationship_lines"
# EVAL_PROP_ props will be evaluated explicitly
EVAL_PROP_WORLD_COLOR = "bpy.data.worlds[0].color"
# those props attributes used as a source for shading style properties
RENDER = "scene.render"
VIEW_SETTINGS = "scene.view_settings"
SHADING = "scene.display.shading"
DISPLAY = "scene.display"
OVERLAY = "space.overlay"
RASTER_STYLE_PROPERTIES_EXCLUDE = (
"scene.render.filepath",
)
class DocProperties(PropertyGroup):
@@ -492,6 +486,7 @@ ANNOTATION_TYPES_DATA = {
"BREAKLINE": ("Breakline", "", "FCURVE", "mesh"),
"LINEWORK": ("Line", "", "MESH_MONKEY", "mesh"),
"BATTING": ("Batting", "Add batting annotation.\nThickness could be changed through Thickness property of BBIM_Batting property set", "FORCE_FORCE", "mesh"),
"REVISION_CLOUD":("Revision Cloud", "Add revision cloud", "VOLUME_DATA", "mesh"),
"FILL_AREA": ("Fill Area", "", "NODE_TEXTURE", "mesh"),
"FALL": ("Fall", "", "SORT_ASC", "curve"),
}
@@ -514,6 +509,8 @@ def update_annotation_object_type(self, context):
# changing enum doesn't trigger refresh by itself
AnnotationData.is_loaded = False
def update_sheet_data(self, context):
SheetsData.is_loaded = False
class BIMAnnotationProperties(PropertyGroup):
object_type: bpy.props.EnumProperty(
@@ -225,17 +225,24 @@ class SvgWriter:
)
)
# TODO: allow metric to be configurable
rl = (matrix_world @ points[0].co.xyz).z
rl_value = (matrix_world @ points[0].co.xyz).z
if bpy.context.scene.unit_settings.system == "IMPERIAL":
rl = helper.format_distance(rl, precision=self.precision, decimal_places=self.decimal_places)
rl = helper.format_distance(rl_value, precision=self.precision, decimal_places=self.decimal_places)
else:
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
rl /= unit_scale
rl = rl_value / unit_scale
rl = ifcopenshell.util.geolocation.auto_z2e(tool.Ifc.get(), rl)
rl *= unit_scale
rl = "{:.3f}m".format(rl)
text_style = SvgWriter.get_box_alignment_parameters("bottom-left")
self.svg.add(self.svg.text(f"RL +{rl}", insert=tuple(text_position), class_="SECTIONLEVEL", **text_style))
self.svg.add(
self.svg.text(
"RL {}{}".format("" if rl_value < 0 else "+", rl),
insert=tuple(text_position),
class_="SECTIONLEVEL",
**text_style,
)
)
if tag:
self.svg.add(self.svg.text(tag, insert=(text_position[0], text_position[1] - 5), **text_style))
@@ -396,8 +403,7 @@ class SvgWriter:
self.svg.line(start=start * self.svg_scale, end=end * self.svg_scale, class_=" ".join(classes))
)
# BATTING ANNOTATIONS
if predefined_type == "BATTING":
def draw_batting_annotation():
v0_global = matrix_world @ obj.data.vertices[0].co.xyz
v1_global = matrix_world @ obj.data.vertices[1].co.xyz
v0 = self.project_point_onto_camera(v0_global)
@@ -463,7 +469,94 @@ class SvgWriter:
)
self.svg.add(self.svg.polyline(points=points, class_=" ".join(classes), style=polyline_style))
elif predefined_type == "SECTION":
def draw_revision_cloud_annotation():
segment_width = 15.0
base_height = 1
width = 5
def get_svg_half_circle(height, width):
cp0 = f"0,-{height}"
cp1 = f"{width},-{height}"
end_point = f"{width},0"
circle = f"c{cp0} {cp1} {end_point}"
return circle
def get_revision_pattern(base_offset):
pattern = f"m{base_offset.x},{base_offset.y}"
pattern += " " + get_svg_half_circle(2 * base_height, width)
pattern += " " + get_svg_half_circle(2.5 * base_height, width)
pattern += " " + get_svg_half_circle(1.5 * base_height, width)
return pattern
def get_scale(size, direction):
vector = direction * size
shrinked_vector = size // segment_width * segment_width * direction
scale = [1 if vector[i] == 0 else vector[i] / shrinked_vector[i] for i in range(2)]
return "scale(%f, %f)" % (scale[0], scale[1])
def poly_to_edges(poly):
edges = []
n_verts = len(poly)
lats_index = n_verts - 1
for i in range(len(poly)):
edge = [poly[i], (poly[i + 1]) if i != lats_index else poly[0]]
edges.append(edge)
return edges
element = tool.Ifc.get_entity(obj)
safe_offset_x = 2.0
marker_width = segment_width + safe_offset_x * 2
market_height = 15.0
ref_y = 5.0
revision_pattern = get_revision_pattern(Vector([safe_offset_x, ref_y]))
bm = tool.Blender.get_bmesh_for_mesh(obj.data).copy()
bmesh.ops.contextual_create(bm, geom=bm.edges[:])
faces = bm.faces[:]
assert len(faces) == 1, "Revision cloud edges must form just 1 polygon"
# ensure clockwise order of polygon verts
# given default blender counter-clockwise order
polygon = faces[0]
if polygon.normal.z > 0:
polygon.normal_flip()
marker_id = f"revision-cloud-{element.GlobalId}"
svg_path = self.svg.path(style="fill: none; stroke:red; stroke-width:0.20", d=revision_pattern)
marker = self.svg.marker(
markerUnits="userSpaceOnUse",
insert=(safe_offset_x, ref_y),
size=(marker_width, market_height),
orient="auto",
id=marker_id,
)
marker.add(svg_path)
self.svg.add(marker)
for v0, v1 in poly_to_edges(polygon.verts):
v0_global = matrix_world @ v0.co.xyz
v1_global = matrix_world @ v1.co.xyz
v0 = self.project_point_onto_camera(v0_global)
v1 = self.project_point_onto_camera(v1_global)
start_svg = Vector(((x_offset + v0.x), (y_offset - v0.y))) * self.svg_scale
end_svg = Vector(((x_offset + v1.x), (y_offset - v1.y))) * self.svg_scale
pattern_edge = end_svg - start_svg
pattern_dir = pattern_edge.normalized()
pattern_length = pattern_edge.length
segments = int(pattern_length // segment_width)
pattern_dir_step = pattern_dir * segment_width
points = [pattern_dir_step * i for i in range(segments)]
polyline_style = f"marker: url(#{marker_id}); stroke: none;"
polyline_transform = f"translate({start_svg.x}, {start_svg.y}) {get_scale(pattern_length, pattern_dir)}"
polyline = self.svg.polyline(
points=points, class_=" ".join(classes), style=polyline_style, transform=polyline_transform
)
self.svg.add(polyline)
def draw_section_annotation():
display_data = DecoratorData.get_section_markers_display_data(obj)
connect_markers = display_data["connect_markers"]
@@ -504,6 +597,12 @@ class SvgWriter:
)
)
if predefined_type == "BATTING":
draw_batting_annotation()
elif predefined_type == "REVISIONCLOUD":
draw_revision_cloud_annotation()
elif predefined_type == "SECTION":
draw_section_annotation()
else:
for edge in obj.data.edges:
draw_simple_edge_annotation(*edge.vertices[:])
@@ -787,12 +886,12 @@ class SvgWriter:
)
)
# TODO: allow metric to be configurable
rl = (matrix_world @ points[0].co).z
rl_value = (matrix_world @ points[0].co).z
if bpy.context.scene.unit_settings.system == "IMPERIAL":
rl = helper.format_distance(rl, precision=self.precision, decimal_places=self.decimal_places)
rl = helper.format_distance(rl_value, precision=self.precision, decimal_places=self.decimal_places)
else:
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
rl /= unit_scale
rl = rl_value / unit_scale
rl = ifcopenshell.util.geolocation.auto_z2e(tool.Ifc.get(), rl)
rl *= unit_scale
rl = "{:.3f}m".format(rl)
@@ -801,7 +900,7 @@ class SvgWriter:
text_style = SvgWriter.get_box_alignment_parameters(box_alignment)
self.svg.add(
self.svg.text(
"{}".format(rl),
"{}{}".format("" if rl_value < 0 else "+", rl),
insert=tuple(text_position),
class_="PLANLEVEL",
**text_style,
@@ -27,6 +27,7 @@ from blenderbim.bim.module.drawing.data import (
DrawingsData,
DecoratorData,
)
from blenderbim.bim.module.drawing.prop import ANNOTATION_TYPES_DATA
class BIM_PT_camera(Panel):
@@ -471,55 +472,55 @@ class BIM_PT_annotation_utilities(Panel):
row = layout.row(align=True)
op = row.operator("bim.add_annotation", text="Dimension", icon="FIXED_SIZE")
op.object_type = "DIMENSION"
op.data_type = "curve"
op.data_type = tool.Drawing.get_annotation_data_type(op.object_type)
op = row.operator("bim.add_annotation", text="Angle", icon="DRIVER_ROTATIONAL_DIFFERENCE")
op.object_type = "ANGLE"
op.data_type = "curve"
op.data_type = tool.Drawing.get_annotation_data_type(op.object_type)
row = layout.row(align=True)
op = row.operator("bim.add_annotation", text="Radius", icon="FORWARD")
op.object_type = "RADIUS"
op.data_type = "curve"
op.data_type = tool.Drawing.get_annotation_data_type(op.object_type)
op = row.operator("bim.add_annotation", text="Diameter", icon="ARROW_LEFTRIGHT")
op.object_type = "DIAMETER"
op.data_type = "curve"
op.data_type = tool.Drawing.get_annotation_data_type(op.object_type)
row = layout.row(align=True)
op = row.operator("bim.add_annotation", text="Text", icon="SMALL_CAPS")
op.object_type = "TEXT"
op.data_type = "empty"
op.data_type = tool.Drawing.get_annotation_data_type(op.object_type)
op = row.operator("bim.add_annotation", text="Leader", icon="TRACKING_BACKWARDS")
op.object_type = "TEXT_LEADER"
op.data_type = "curve"
op.data_type = tool.Drawing.get_annotation_data_type(op.object_type)
row = layout.row(align=True)
op = row.operator("bim.add_annotation", text="Stair Arrow", icon="SCREEN_BACK")
op.object_type = "STAIR_ARROW"
op.data_type = "curve"
op.data_type = tool.Drawing.get_annotation_data_type(op.object_type)
op = row.operator("bim.add_annotation", text="Hidden", icon="CON_TRACKTO")
op.object_type = "HIDDEN_LINE"
op.data_type = "mesh"
op.data_type = tool.Drawing.get_annotation_data_type(op.object_type)
row = layout.row(align=True)
op = row.operator("bim.add_annotation", text="Level (Plan)", icon="SORTBYEXT")
op.object_type = "PLAN_LEVEL"
op.data_type = "curve"
op.data_type = tool.Drawing.get_annotation_data_type(op.object_type)
op = row.operator("bim.add_annotation", text="Level (Section)", icon="TRIA_DOWN")
op.object_type = "SECTION_LEVEL"
op.data_type = "curve"
op.data_type = tool.Drawing.get_annotation_data_type(op.object_type)
row = layout.row(align=True)
op = row.operator("bim.add_annotation", text="Breakline", icon="FCURVE")
op.object_type = "BREAKLINE"
op.data_type = "mesh"
op.data_type = tool.Drawing.get_annotation_data_type(op.object_type)
op = row.operator("bim.add_annotation", text="Line", icon="MESH_MONKEY")
op.object_type = "LINEWORK"
op.data_type = "mesh"
op.data_type = tool.Drawing.get_annotation_data_type(op.object_type)
row = layout.row(align=True)
op = row.operator("bim.add_annotation", text="Batting", icon="FORCE_FORCE")
op.object_type = "BATTING"
op.data_type = "mesh"
op.data_type = tool.Drawing.get_annotation_data_type(op.object_type)
op.description = "Add batting annotation.\nThickness could be changed through Thickness property of BBIM_Batting property set"
op = row.operator("bim.add_annotation", text="Fill Area", icon="NODE_TEXTURE")
op.object_type = "FILL_AREA"
@@ -527,7 +528,10 @@ class BIM_PT_annotation_utilities(Panel):
row = layout.row(align=True)
op = row.operator("bim.add_annotation", text="Fall", icon="SORT_ASC")
op.object_type = "FALL"
op.data_type = "curve"
op.data_type = tool.Drawing.get_annotation_data_type(op.object_type)
op = row.operator("bim.add_annotation", text="Revision", icon="VOLUME_DATA")
op.object_type = "REVISION_CLOUD"
op.data_type = tool.Drawing.get_annotation_data_type(op.object_type)
row = layout.row(align=True)
row.prop(self.props, "should_draw_decorations", text="Viewport Annotations")
@@ -28,7 +28,9 @@ classes = (
operator.DiscardUncommitted,
operator.DisplayRevision,
operator.DisplayUncommitted,
operator.Fetch,
operator.Merge,
operator.Push,
operator.RefreshGit,
operator.SwitchRevision,
prop.IfcGitTag,
@@ -19,7 +19,10 @@ class IfcGitData:
def load(cls):
cls.data = {
"repo": cls.repo(),
"remotes": cls.remotes(),
"branch_names": cls.branch_names(),
"remote_names": cls.remote_names(),
"remote_urls": cls.remote_urls(),
"path_ifc": cls.path_ifc(),
"branches_by_hexsha": cls.branches_by_hexsha(),
"tags_by_hexsha": cls.tags_by_hexsha(),
@@ -42,10 +45,28 @@ class IfcGitData:
return tool.IfcGit.repo_from_path(path_ifc)
return None
@classmethod
def remotes(cls):
if cls.repo():
return cls.repo().remotes
return None
@classmethod
def branch_names(cls):
return []
@classmethod
def remote_names(cls):
return []
@classmethod
def remote_urls(cls):
result = {}
if cls.repo():
for remote in cls.repo().remotes:
result[remote.name] = remote.url
return result
@classmethod
def path_ifc(cls):
path_ifc = tool.Ifc.get_path()
@@ -137,7 +137,7 @@ class CommitChanges(bpy.types.Operator):
class AddTag(bpy.types.Operator):
"""Tag selected revision"""
bl_label = "Add tag"
bl_label = "Tag selected revision"
bl_idname = "ifcgit.add_tag"
bl_options = {"REGISTER"}
@@ -246,3 +246,35 @@ class Merge(bpy.types.Operator):
return {"FINISHED"}
else:
return {"CANCELLED"}
class Push(bpy.types.Operator):
"""Pushes the working branch to selected remote"""
bl_label = "Push working branch"
bl_idname = "ifcgit.push"
bl_options = {"REGISTER"}
def execute(self, context):
props = context.scene.IfcGitProperties
repo = IfcGitData.data["repo"]
remote = repo.remotes[props.select_remote]
remote.push(refspec=IfcGitData.data["repo"].active_branch.name)
return {"FINISHED"}
class Fetch(bpy.types.Operator):
"""Fetches from the selected remote"""
bl_label = "Fetch from remote"
bl_idname = "ifcgit.fetch"
bl_options = {"REGISTER"}
def execute(self, context):
props = context.scene.IfcGitProperties
repo = IfcGitData.data["repo"]
remote = repo.remotes[props.select_remote]
remote.fetch()
return {"FINISHED"}
@@ -21,9 +21,25 @@ def git_branches(self, context):
IfcGitData.data["branch_names"].remove("main")
IfcGitData.data["branch_names"] = ["main"] + IfcGitData.data["branch_names"]
if IfcGitData.data["remotes"]:
props = context.scene.IfcGitProperties
IfcGitData.data["branch_names"] += [r.name for r in IfcGitData.data["remotes"][props.select_remote].refs]
return [(myname, myname, myname) for myname in IfcGitData.data["branch_names"]]
def git_remotes(self, context):
"""remotes enum"""
IfcGitData.data["remote_names"] = sorted([remote.name for remote in IfcGitData.data["remotes"]])
if "origin" in IfcGitData.data["remote_names"]:
IfcGitData.data["remote_names"].remove("origin")
IfcGitData.data["remote_names"] = ["origin"] + IfcGitData.data["remote_names"]
return [(myname, myname, myname) for myname in IfcGitData.data["remote_names"]]
def update_revlist(self, context):
"""wrapper to trigger update of the revision list"""
@@ -109,6 +125,7 @@ class IfcGitProperties(PropertyGroup):
subtype="DIR_PATH",
)
display_branch: EnumProperty(items=git_branches, update=update_revlist)
select_remote: EnumProperty(items=git_remotes)
ifcgit_filter: EnumProperty(
items=[
("all", "All", "All revisions"),
@@ -154,13 +154,23 @@ class IFCGIT_PT_panel(bpy.types.Panel):
# TODO
# item.operator("ifcgit.delete_tag", icon="PANEL_CLOSE")
row = layout.row()
box = layout.box()
row = box.row()
row.prop(props, "new_tag_name")
row = layout.row()
row = box.row()
row.prop(props, "new_tag_message")
row = layout.row()
row = box.row()
row.operator("ifcgit.add_tag", icon="GREASEPENCIL")
if IfcGitData.data["remotes"]:
row = layout.row()
row.prop(props, "select_remote", text="Select remote")
urls = IfcGitData.data["remote_urls"]
row.label(text=urls[props.select_remote])
row = layout.row()
row.operator("ifcgit.push", icon="EXPERIMENTAL")
row.operator("ifcgit.fetch", icon="IMPORT")
class COMMIT_UL_List(bpy.types.UIList):
"""List of Git commits"""
@@ -31,13 +31,16 @@ classes = (
operator.ContractMaterialCategory,
operator.CopyMaterial,
operator.DisableEditingAssignedMaterial,
operator.DisableEditingMaterial,
operator.DisableEditingMaterials,
operator.DisableEditingMaterialSetItem,
operator.DisableEditingMaterialSetItemProfile,
operator.DisableEditingMaterials,
operator.EditAssignedMaterial,
operator.EditMaterial,
operator.EditMaterialSetItem,
operator.EditMaterialSetItemProfile,
operator.EnableEditingAssignedMaterial,
operator.EnableEditingMaterial,
operator.EnableEditingMaterialSetItem,
operator.EnableEditingMaterialSetItemProfile,
operator.ExpandMaterialCategory,
@@ -96,6 +96,7 @@ class ObjectMaterialData:
cls.data["total_thickness"] = cls.total_thickness()
cls.data["materials"] = cls.materials()
cls.data["type_material"] = cls.type_material()
cls.data["material_type"] = cls.material_type()
cls.is_loaded = True
@classmethod
@@ -244,3 +245,17 @@ class ObjectMaterialData:
else:
name_attr = "Name"
return getattr(material, name_attr, "Unnamed") or "Unnamed"
@classmethod
def material_type(cls):
material_types = [
"IfcMaterial",
"IfcMaterialConstituentSet",
"IfcMaterialLayerSet",
"IfcMaterialProfileSet",
"IfcMaterialList",
]
version = tool.Ifc.get_schema()
if version == "IFC2X3":
material_types = ["IfcMaterial", "IfcMaterialLayerSet", "IfcMaterialList"]
return [(m, m, ifcopenshell.util.doc.get_entity_doc(version, m).get("description", "")) for m in material_types]
@@ -59,6 +59,36 @@ class SelectByMaterial(bpy.types.Operator, tool.Ifc.Operator):
core.select_by_material(tool.Material, material=tool.Ifc.get().by_id(self.material))
class EnableEditingMaterial(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.enable_editing_material"
bl_label = "Enable Editing Material"
bl_options = {"REGISTER", "UNDO"}
material: bpy.props.IntProperty()
def _execute(self, context):
core.enable_editing_material(tool.Material, material=tool.Ifc.get().by_id(self.material))
class EditMaterial(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.edit_material"
bl_label = "Edit Material"
bl_options = {"REGISTER", "UNDO"}
material: bpy.props.IntProperty()
def _execute(self, context):
core.edit_material(tool.Ifc, tool.Material, material=tool.Ifc.get().by_id(self.material))
class DisableEditingMaterial(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.disable_editing_material"
bl_label = "Disable Editing Material"
bl_options = {"REGISTER", "UNDO"}
material: bpy.props.IntProperty()
def _execute(self, context):
core.disable_editing_material(tool.Material)
class AssignParameterizedProfile(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.assign_parameterized_profile"
bl_label = "Assign Parameterized Profile"
@@ -159,14 +189,22 @@ class AssignMaterial(bpy.types.Operator, tool.Ifc.Operator):
material=material,
)
assigned_material = ifcopenshell.util.element.get_material(element)
if assigned_material.is_a("IfcMaterialLayerSet"):
if assigned_material.is_a("IfcMaterialConstituentSet"):
if not assigned_material.MaterialConstituents:
ifcopenshell.api.run(
"material.add_constituent",
tool.Ifc.get(),
constituent_set=assigned_material,
material=material,
)
elif assigned_material.is_a() == "IfcMaterialLayerSet":
if not assigned_material.MaterialLayers:
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
layer = ifcopenshell.api.run(
"material.add_layer",
tool.Ifc.get(),
layer_set=assigned_material,
material=tool.Ifc.get().by_type("IfcMaterial")[0],
material=material,
)
thickness = 0.1 # Arbitrary metric thickness for now
layer.LayerThickness = thickness / unit_scale
@@ -243,12 +281,12 @@ class RemoveConstituent(bpy.types.Operator, tool.Ifc.Operator):
constituent: bpy.props.IntProperty()
def _execute(self, context):
for inverse in tool.Ifc.get().get_inverse(tool.Ifc.get().by_id(self.constituent)):
if inverse.is_a("IfcMaterialConstituentSet") and len(inverse.MaterialConstituents) == 1:
return
ifcopenshell.api.run(
"material.remove_constituent", tool.Ifc.get(), constituent=tool.Ifc.get().by_id(self.constituent)
)
constituent = tool.Ifc.get().by_id(self.constituent)
for material_set in layer.ToMaterialConstituentSet:
if len(material_set.MaterialConstituents) == 1:
self.report({"ERROR"}, "At least one constituent must exist")
return {"ERROR"}
ifcopenshell.api.run("material.remove_constituent", tool.Ifc.get(), constituent=constituent)
class AddProfile(bpy.types.Operator, tool.Ifc.Operator):
@@ -278,10 +316,12 @@ class RemoveProfile(bpy.types.Operator, tool.Ifc.Operator):
profile: bpy.props.IntProperty()
def _execute(self, context):
for inverse in tool.Ifc.get().get_inverse(tool.Ifc.get().by_id(self.profile)):
if inverse.is_a("IfcMaterialProfileSet") and len(inverse.MaterialProfiles) == 1:
return
ifcopenshell.api.run("material.remove_profile", tool.Ifc.get(), profile=tool.Ifc.get().by_id(self.profile))
profile = tool.Ifc.get().by_id(self.profile)
for material_set in profile.ToMaterialProfileSet:
if len(material_set.MaterialProfiles) == 1:
self.report({"ERROR"}, "At least one profile must exist")
return {"ERROR"}
ifcopenshell.api.run("material.remove_profile", tool.Ifc.get(), profile=profile)
class AddLayer(bpy.types.Operator, tool.Ifc.Operator):
@@ -294,7 +334,7 @@ class AddLayer(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
self.file = IfcStore.get_file()
ifcopenshell.api.run(
layer = ifcopenshell.api.run(
"material.add_layer",
self.file,
**{
@@ -303,6 +343,10 @@ class AddLayer(bpy.types.Operator, tool.Ifc.Operator):
},
)
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
thickness = 0.1 # Arbitrary metric thickness for now
layer.LayerThickness = thickness / unit_scale
class ReorderMaterialSetItem(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.reorder_material_set_item"
@@ -336,10 +380,12 @@ class RemoveLayer(bpy.types.Operator, tool.Ifc.Operator):
layer: bpy.props.IntProperty()
def _execute(self, context):
for inverse in tool.Ifc.get().get_inverse(tool.Ifc.get().by_id(self.layer)):
if inverse.is_a("IfcMaterialLayerSet") and len(inverse.MaterialLayers) == 1:
return
ifcopenshell.api.run("material.remove_layer", tool.Ifc.get(), layer=tool.Ifc.get().by_id(self.layer))
layer = tool.Ifc.get().by_id(self.layer)
for material_set in layer.ToMaterialLayerSet:
if len(material_set.MaterialLayers) == 1:
self.report({"ERROR"}, "At least one layer must exist")
return {"ERROR"}
ifcopenshell.api.run("material.remove_layer", tool.Ifc.get(), layer=layer)
class AddListItem(bpy.types.Operator, tool.Ifc.Operator):
@@ -85,24 +85,10 @@ def get_materials(self, context):
return ObjectMaterialData.data["materials"]
def get_object_material_types(self, context):
global materialtypes_enum
if len(materialtypes_enum) == 0 and IfcStore.get_file():
material_types = [
"IfcMaterial",
"IfcMaterialConstituentSet",
"IfcMaterialLayerSet",
"IfcMaterialLayerSetUsage",
"IfcMaterialProfileSet",
"IfcMaterialProfileSetUsage",
"IfcMaterialList",
]
version = tool.Ifc.get_schema()
if version == "IFC2X3":
material_types = ["IfcMaterial", "IfcMaterialLayerSet", "IfcMaterialLayerSetUsage", "IfcMaterialList"]
materialtypes_enum.clear()
materialtypes_enum = [(m, m, get_entity_doc(version, m).get("description", "")) for m in material_types]
return materialtypes_enum
def get_object_material_type(self, context):
if not ObjectMaterialData.is_loaded:
ObjectMaterialData.load()
return ObjectMaterialData.data["material_type"]
def get_material_types(self, context):
@@ -131,10 +117,13 @@ class BIMMaterialProperties(PropertyGroup):
materials: CollectionProperty(name="Materials", type=Material)
active_material_index: IntProperty(name="Active Material Index")
profiles: EnumProperty(items=get_profiles, name="Profiles")
active_material_id: IntProperty(name="Active Material ID")
material_attributes: CollectionProperty(name="Material Attributes", type=Attribute)
editing_material_type = StringProperty(name="Editing Material Type")
class BIMObjectMaterialProperties(PropertyGroup):
material_type: EnumProperty(items=get_object_material_types, name="Material Type")
material_type: EnumProperty(items=get_object_material_type, name="Material Type")
material: EnumProperty(items=get_materials, name="Material")
is_editing: BoolProperty(name="Is Editing", default=False)
material_set_usage_attributes: CollectionProperty(name="Material Set Usage Attributes", type=Attribute)
@@ -57,13 +57,21 @@ class BIM_PT_materials(Panel):
row.alignment = "RIGHT"
if self.props.material_type == "IfcMaterial":
row.operator("bim.add_material", text="", icon="ADD")
if self.props.materials and self.props.active_material_index < len(self.props.materials):
material = self.props.materials[self.props.active_material_index]
if material.ifc_definition_id:
op = row.operator("bim.select_by_material", text="", icon="RESTRICT_SELECT_OFF")
op.material = material.ifc_definition_id
row.operator("bim.remove_material", text="", icon="X").material = material.ifc_definition_id
if self.props.active_material_id:
row.operator("bim.edit_material", text="", icon="CHECKMARK").material = material.ifc_definition_id
row.operator("bim.disable_editing_material", text="", icon="CANCEL").material = material.ifc_definition_id
self.draw_editable_material_attributes_ui()
else:
row.operator("bim.add_material", text="", icon="ADD")
op = row.operator("bim.select_by_material", text="", icon="RESTRICT_SELECT_OFF")
op.material = material.ifc_definition_id
row.operator("bim.enable_editing_material", text="", icon="GREASEPENCIL").material = material.ifc_definition_id
row.operator("bim.remove_material", text="", icon="X").material = material.ifc_definition_id
else:
row.operator("bim.add_material", text="", icon="ADD")
else:
row.operator("bim.add_material_set", text="", icon="ADD").set_type = self.props.material_type
if self.props.materials and self.props.active_material_index < len(self.props.materials):
@@ -76,6 +84,9 @@ class BIM_PT_materials(Panel):
self.layout.template_list("BIM_UL_materials", "", self.props, "materials", self.props, "active_material_index")
def draw_editable_material_attributes_ui(self):
blenderbim.bim.helper.draw_attributes(self.props.material_attributes, self.layout)
class BIM_PT_material(Panel):
bl_label = "IFC Material"
bl_idname = "BIM_PT_material"
@@ -144,8 +155,7 @@ class BIM_PT_object_material(Panel):
row = self.layout.row(align=True)
prop_with_search(row, self.props, "material_type", text="")
if self.props.material_type == "IfcMaterial" or self.props.material_type == "IfcMaterialList":
prop_with_search(row, self.props, "material", text="")
prop_with_search(row, self.props, "material", text="")
row.operator("bim.assign_material", icon="ADD", text="")
def draw_material_ui(self):
@@ -126,7 +126,6 @@ classes = (
pie.OpenPieClass,
pie.PieUpdateContainer,
pie.PieAddOpening,
pie.PieAssignObjectAggregation,
pie.VIEW3D_MT_PIE_bim,
pie.VIEW3D_MT_PIE_bim_class,
sverchok_modifier.CreateNewSverchokGraph,
@@ -86,9 +86,10 @@ class EnableEditingArray(bpy.types.Operator, tool.Ifc.Operator):
data = json.loads(ifcopenshell.util.element.get_pset(element, "BBIM_Array", "Data"))[self.item]
props = obj.BIMArrayProperties
props.count = data["count"]
props.x = data["x"]
props.y = data["y"]
props.z = data["z"]
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
props.x = data["x"] * si_conversion
props.y = data["y"] * si_conversion
props.z = data["z"] * si_conversion
props.use_local_space = data.get("use_local_space", False)
props.sync_children = data.get("sync_children", False)
props.method = data.get("method", "OFFSET")
@@ -106,15 +107,16 @@ class EditArray(bpy.types.Operator, tool.Ifc.Operator):
obj = context.active_object
element = tool.Ifc.get_entity(obj)
props = obj.BIMArrayProperties
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
pset = ifcopenshell.util.element.get_pset(element, "BBIM_Array")
data = json.loads(pset["Data"])
data[self.item] = {
"children": data[self.item]["children"],
"count": props.count,
"x": props.x,
"y": props.y,
"z": props.z,
"x": props.x / si_conversion,
"y": props.y / si_conversion,
"z": props.z / si_conversion,
"use_local_space": props.use_local_space,
"sync_children": props.sync_children,
"method": props.method,
@@ -187,6 +189,7 @@ class SelectArrayParent(bpy.types.Operator):
obj.select_set(True)
return {"FINISHED"}
class Input3DCursorXArray(bpy.types.Operator):
bl_idname = "bim.input_cursor_x_array"
bl_label = "Get 3d Cursor X Input for Array"
@@ -202,6 +205,7 @@ class Input3DCursorXArray(bpy.types.Operator):
props.x = cursor.location.x - obj.location.x
return {"FINISHED"}
class Input3DCursorYArray(bpy.types.Operator):
bl_idname = "bim.input_cursor_y_array"
bl_label = "Get 3d Cursor Y Input for Array"
@@ -217,6 +221,7 @@ class Input3DCursorYArray(bpy.types.Operator):
props.y = cursor.location.y - obj.location.y
return {"FINISHED"}
class Input3DCursorZArray(bpy.types.Operator):
bl_idname = "bim.input_cursor_z_array"
bl_label = "Get 3d Cursor Z Input for Array"
@@ -76,22 +76,6 @@ class PieUpdateContainer(bpy.types.Operator):
break
return {"FINISHED"}
class PieAssignObjectAggregation(bpy.types.Operator,tool.Ifc.Operator):
bl_idname = "bim.pie_assign_object_aggregation"
bl_label = "Assign Parts to Active Object"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
for obj in context.selected_objects:
if obj == context.active_object:
continue
blenderbim.core.aggregate.assign_object(
tool.Ifc,
tool.Aggregate,
tool.Collector,
relating_obj=context.active_object,
related_obj=obj,
)
class VIEW3D_MT_PIE_bim(bpy.types.Menu):
bl_label = "Geometry"
@@ -103,7 +87,8 @@ class VIEW3D_MT_PIE_bim(bpy.types.Menu):
pie.operator("bim.pie_add_opening")
pie.operator("bim.pie_update_container")
pie.operator("bim.open_pie_class", text="Assign IFC Class")
pie.operator("bim.pie_assign_object_aggregation", text ="Assign Aggregation")
pie.operator("bim.assign_object", text ="Assign Aggregation")
pie.operator("bim.unassign_object", text ="Unassign Aggregation")
class VIEW3D_MT_PIE_bim_class(bpy.types.Menu):
@@ -57,7 +57,7 @@ def get_type_predefined_type(self, context):
def update_ifc_class(self, context):
bpy.ops.bim.load_type_thumbnails(ifc_class=self.ifc_class)
AuthoringData.data["relating_type_id"] = AuthoringData.relating_types()
AuthoringData.data["relating_type_id"] = AuthoringData.relating_type_id()
AuthoringData.data["type_thumbnail"] = AuthoringData.type_thumbnail()
@@ -82,7 +82,7 @@ def update_type_page(self, context):
class BIMModelProperties(PropertyGroup):
ifc_class: bpy.props.EnumProperty(items=get_ifc_class, name="Construction Class", update=update_ifc_class)
relating_type_id: bpy.props.EnumProperty(
items=get_relating_type_id, name="Construction Type", update=update_relating_type_id
items=get_relating_type_id, name="Relating Type", update=update_relating_type_id
)
icon_id: bpy.props.IntProperty()
updating: bpy.props.BoolProperty(default=False)
@@ -155,9 +155,9 @@ class BIMArrayProperties(PropertyGroup):
default=-1, description="Currently edited array index. -1 if not in array editing mode."
)
count: bpy.props.IntProperty(name="Count", default=0, min=0)
x: bpy.props.FloatProperty(name="X", default=0)
y: bpy.props.FloatProperty(name="Y", default=0)
z: bpy.props.FloatProperty(name="Z", default=0)
x: bpy.props.FloatProperty(name="X", default=0, subtype="DISTANCE")
y: bpy.props.FloatProperty(name="Y", default=0, subtype="DISTANCE")
z: bpy.props.FloatProperty(name="Z", default=0, subtype="DISTANCE")
use_local_space: bpy.props.BoolProperty(
name="Use Local Space",
description="Use local space for array items offset instead of world space",
@@ -176,6 +176,7 @@ class BIMArrayProperties(PropertyGroup):
class BIMStairProperties(PropertyGroup):
non_si_units_props = ("is_editing", "number_of_treads", "has_top_nib", "stair_type")
stair_types = (
("CONCRETE", "Concrete", ""),
("WOOD/STEEL", "Wood / Steel", ""),
@@ -184,17 +185,17 @@ class BIMStairProperties(PropertyGroup):
stair_added_previously: bpy.props.BoolProperty(default=False)
is_editing: bpy.props.IntProperty(default=-1)
width: bpy.props.FloatProperty(name="Width", default=1.2, soft_min=0.01)
height: bpy.props.FloatProperty(name="Height", default=1.0, soft_min=0.01)
width: bpy.props.FloatProperty(name="Width", default=1.2, soft_min=0.01, subtype="DISTANCE")
height: bpy.props.FloatProperty(name="Height", default=1.0, soft_min=0.01, subtype="DISTANCE")
number_of_treads: bpy.props.IntProperty(name="Number of treads", default=6, soft_min=1)
tread_depth: bpy.props.FloatProperty(name="Tread Depth", default=0.25, soft_min=0.01)
tread_run: bpy.props.FloatProperty(name="Tread Run", default=0.3, soft_min=0.01)
base_slab_depth: bpy.props.FloatProperty(name="Base slab depth", default=0.25, soft_min=0)
top_slab_depth: bpy.props.FloatProperty(name="Top slab depth", default=0.25, soft_min=0)
tread_depth: bpy.props.FloatProperty(name="Tread Depth", default=0.25, soft_min=0.01, subtype="DISTANCE")
tread_run: bpy.props.FloatProperty(name="Tread Run", default=0.3, soft_min=0.01, subtype="DISTANCE")
base_slab_depth: bpy.props.FloatProperty(name="Base slab depth", default=0.25, soft_min=0, subtype="DISTANCE")
top_slab_depth: bpy.props.FloatProperty(name="Top slab depth", default=0.25, soft_min=0, subtype="DISTANCE")
has_top_nib: bpy.props.BoolProperty(name="Has top nib", default=True)
stair_type: bpy.props.EnumProperty(name="Stair type", items=stair_types, default="CONCRETE")
def get_props_kwargs(self):
def get_props_kwargs(self, convert_to_project_units=False):
stair_kwargs = {
"stair_type": self.stair_type,
"width": self.width,
@@ -204,27 +205,34 @@ class BIMStairProperties(PropertyGroup):
}
if self.stair_type == "CONCRETE":
stair_kwargs.update(
{
"base_slab_depth": self.base_slab_depth,
"top_slab_depth": self.top_slab_depth,
"has_top_nib": self.has_top_nib,
"tread_depth": self.tread_depth,
}
)
return stair_kwargs
concrete_props = {
"base_slab_depth": self.base_slab_depth,
"top_slab_depth": self.top_slab_depth,
"has_top_nib": self.has_top_nib,
"tread_depth": self.tread_depth,
}
stair_kwargs.update(concrete_props)
elif self.stair_type == "WOOD/STEEL":
stair_kwargs.update(
{
"tread_depth": self.tread_depth,
}
)
return stair_kwargs
wood_steel_props = {
"tread_depth": self.tread_depth,
}
stair_kwargs.update(wood_steel_props)
elif self.stair_type == "GENERIC":
pass
if not convert_to_project_units:
return stair_kwargs
stair_kwargs = tool.Model.convert_data_to_project_units(stair_kwargs, self.non_si_units_props)
return stair_kwargs
def set_props_kwargs_from_ifc_data(self, kwargs):
kwargs = tool.Model.convert_data_to_si_units(kwargs, self.non_si_units_props)
for prop_name in kwargs:
setattr(self, prop_name, kwargs[prop_name])
class BIMSverchokProperties(PropertyGroup):
node_group: bpy.props.PointerProperty(name="Node Group", type=NodeTree)
@@ -232,15 +240,13 @@ class BIMSverchokProperties(PropertyGroup):
def window_type_prop_update(self, context):
number_of_panels, panels_data = self.window_types_panels[self.window_type]
si_coversion = 1.0 / ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
panels_data = [[v * si_coversion for v in data] for data in panels_data]
self.first_mullion_offset, self.second_mullion_offset = panels_data[0]
self.first_transom_offset, self.second_transom_offset = panels_data[1]
# default prop values are in mm and converted later
class BIMWindowProperties(PropertyGroup):
non_si_units_props = ("is_editing", "window_type", "window_added_previously")
window_types = (
("SINGLE_PANEL", "SINGLE_PANEL", ""),
("DOUBLE_PANEL_HORIZONTAL", "DOUBLE_PANEL_HORIZONTAL", ""),
@@ -273,50 +279,63 @@ class BIMWindowProperties(PropertyGroup):
window_type: bpy.props.EnumProperty(
name="Window Type", items=window_types, default="SINGLE_PANEL", update=window_type_prop_update
)
overall_height: bpy.props.FloatProperty(name="Overall Height", default=0.9)
overall_width: bpy.props.FloatProperty(name="Overall Width", default=0.6)
overall_height: bpy.props.FloatProperty(name="Overall Height", default=0.9, subtype="DISTANCE")
overall_width: bpy.props.FloatProperty(name="Overall Width", default=0.6, subtype="DISTANCE")
# lining properties
lining_depth: bpy.props.FloatProperty(name="Lining Depth", default=0.050)
lining_thickness: bpy.props.FloatProperty(name="Lining Thickness", default=0.050)
lining_offset: bpy.props.FloatProperty(name="Lining Offset", default=0.050)
lining_to_panel_offset_x: bpy.props.FloatProperty(name="Lining to Panel Offset X", default=0.025)
lining_to_panel_offset_y: bpy.props.FloatProperty(name="Lining to Panel Offset Y", default=0.025)
mullion_thickness: bpy.props.FloatProperty(name="Mullion Thickness", default=0.050)
lining_depth: bpy.props.FloatProperty(name="Lining Depth", default=0.050, subtype="DISTANCE")
lining_thickness: bpy.props.FloatProperty(name="Lining Thickness", default=0.050, subtype="DISTANCE")
lining_offset: bpy.props.FloatProperty(name="Lining Offset", default=0.050, subtype="DISTANCE")
lining_to_panel_offset_x: bpy.props.FloatProperty(
name="Lining to Panel Offset X", default=0.025, subtype="DISTANCE"
)
lining_to_panel_offset_y: bpy.props.FloatProperty(
name="Lining to Panel Offset Y", default=0.025, subtype="DISTANCE"
)
mullion_thickness: bpy.props.FloatProperty(name="Mullion Thickness", default=0.050, subtype="DISTANCE")
first_mullion_offset: bpy.props.FloatProperty(
name="First Mullion Offset",
description="Distance from the first lining to the first mullion center",
default=0.3,
subtype="DISTANCE",
)
second_mullion_offset: bpy.props.FloatProperty(
name="Second Mullion Offset",
description="Distance from the first lining to the second mullion center",
default=0.45,
subtype="DISTANCE",
)
transom_thickness: bpy.props.FloatProperty(name="Transom Thickness", default=0.050)
transom_thickness: bpy.props.FloatProperty(name="Transom Thickness", default=0.050, subtype="DISTANCE")
first_transom_offset: bpy.props.FloatProperty(
name="First Transom Offset",
description="Distance from the first lining to the first transom center",
default=0.3,
subtype="DISTANCE",
)
second_transom_offset: bpy.props.FloatProperty(
name="Second Transom Offset",
description="Distance from the first lining to the second transom center",
default=0.6,
subtype="DISTANCE",
)
# panel properties
frame_depth: bpy.props.FloatVectorProperty(name="Frame Depth", size=3, default=[0.035] * 3)
frame_thickness: bpy.props.FloatVectorProperty(name="Frame Thickness", size=3, default=[0.035] * 3)
frame_depth: bpy.props.FloatVectorProperty(name="Frame Depth", size=3, default=[0.035] * 3, subtype="TRANSLATION")
frame_thickness: bpy.props.FloatVectorProperty(
name="Frame Thickness", size=3, default=[0.035] * 3, subtype="TRANSLATION"
)
def get_general_kwargs(self):
return {
def get_general_kwargs(self, convert_to_project_units=False):
kwargs = {
"window_type": self.window_type,
"overall_height": self.overall_height,
"overall_width": self.overall_width,
}
if not convert_to_project_units:
return kwargs
return tool.Model.convert_data_to_project_units(kwargs, ["window_type"])
def get_lining_kwargs(self):
def get_lining_kwargs(self, convert_to_project_units=False):
kwargs = {
"lining_depth": self.lining_depth,
"lining_thickness": self.lining_thickness,
@@ -353,13 +372,23 @@ class BIMWindowProperties(PropertyGroup):
if self.window_type in ("TRIPLE_PANEL_HORIZONTAL",):
kwargs["second_transom_offset"] = self.second_transom_offset
return kwargs
if not convert_to_project_units:
return kwargs
return tool.Model.convert_data_to_project_units(kwargs)
def get_panel_kwargs(self):
return {
def get_panel_kwargs(self, convert_to_project_units=False):
kwargs = {
"frame_depth": self.frame_depth,
"frame_thickness": self.frame_thickness,
}
if not convert_to_project_units:
return kwargs
return tool.Model.convert_data_to_project_units(kwargs)
def set_props_kwargs_from_ifc_data(self, kwargs):
kwargs = tool.Model.convert_data_to_si_units(kwargs, self.non_si_units_props)
for prop_name in kwargs:
setattr(self, prop_name, kwargs[prop_name])
class BIMDoorProperties(PropertyGroup):
@@ -87,11 +87,29 @@ class GenerateHippedRoof(bpy.types.Operator, tool.Ifc.Operator):
return {"CANCELLED"}
bm = tool.Blender.get_bmesh_for_mesh(obj.data)
op_status, error_message = is_valid_roof_footprint(bm)
if error_message:
self.report(op_status, error_message)
return {"CANCELLED"}
generate_hiped_roof_bmesh(bm, self.mode, self.height, self.angle)
tool.Blender.apply_bmesh(obj.data, bm)
return {"FINISHED"}
def is_valid_roof_footprint(bm):
# should be bmesh to support edit mode
bm.verts.ensure_lookup_table()
base_z = bm.verts[0].co.z
all_verts_same_level = all([float_is_zero(v.co.z - base_z) for v in bm.verts[1:]])
if not all_verts_same_level:
return (
{"ERROR"},
"\nAll roof footprint vertices should have same Z-level.\nCurrently Z-level doesn't completely match",
)
return ({"FINISHED"}, "")
def generate_hiped_roof_bmesh(bm, mode="ANGLE", height=1.0, angle=pi / 18, mutate_current_bmesh=True):
"""return bmesh with gable roof geometry
@@ -584,6 +602,11 @@ class EnableEditingRoofPath(bpy.types.Operator, tool.Ifc.Operator):
# copying to make sure not to mutate the edit mode bmesh
bm = tool.Blender.get_bmesh_for_mesh(obj.data)
main_bm = bm.copy()
op_status, error_message = is_valid_roof_footprint(main_bm)
if error_message:
print("Error: %s" % error_message)
return main_bm
main_bm.edges.layers.int.new("BBIM_preview")
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
@@ -637,6 +660,12 @@ class FinishEditingRoofPath(bpy.types.Operator, tool.Ifc.Operator):
element = tool.Ifc.get_entity(obj)
props = obj.BIMRoofProperties
bm = tool.Blender.get_bmesh_for_mesh(obj.data)
op_status, error_message = is_valid_roof_footprint(bm)
if error_message:
self.report(op_status, error_message)
return {"CANCELLED"}
roof_data = props.get_general_kwargs()
path_data = get_path_data(obj)
roof_data["path_data"] = path_data
@@ -646,6 +675,7 @@ class FinishEditingRoofPath(bpy.types.Operator, tool.Ifc.Operator):
update_bbim_roof_pset(element, roof_data)
refresh() # RoofData has to be updated before run update_roof_modifier_bmesh
update_roof_modifier_bmesh(context)
update_roof_modifier_ifc_data(context)
if bpy.context.object.mode == "EDIT":
bpy.ops.object.mode_set(mode="OBJECT")
@@ -252,9 +252,17 @@ class DumbSlabPlaner:
new_material = ifcopenshell.util.element.get_material(settings["relating_type"])
if not new_material or not new_material.is_a("IfcMaterialLayerSet"):
return
parametric = ifcopenshell.util.element.get_psets(settings["relating_type"]).get("EPset_Parametric")
layer_set_direction = None
if parametric:
layer_set_direction = parametric.get("LayerSetDirection", layer_set_direction)
new_thickness = sum([l.LayerThickness for l in new_material.MaterialLayers])
material = ifcopenshell.util.element.get_material(settings["related_object"])
if material and material.is_a("IfcMaterialLayerSetUsage") and material.LayerSetDirection == "AXIS3":
if not material or not material.is_a("IfcMaterialLayerSetUsage"):
return
if layer_set_direction:
material.LayerSetDirection = layer_set_direction
if material.LayerSetDirection == "AXIS3":
self.change_thickness(settings["related_object"], new_thickness)
def change_thickness(self, element, thickness):
@@ -18,7 +18,7 @@
import bpy
from bpy.types import Operator
from bpy.props import FloatProperty, IntProperty, BoolProperty
from bpy.props import FloatProperty, IntProperty
from bpy_extras.object_utils import AddObjectHelper, object_data_add
import bmesh
@@ -28,7 +28,6 @@ import ifcopenshell
from ifcopenshell.util.shape_builder import V, ShapeBuilder
import blenderbim
import blenderbim.tool as tool
from blenderbim.bim.helper import convert_property_group_from_si
from mathutils import Vector
from pprint import pprint
@@ -214,14 +213,6 @@ def generate_stair_2d_profile(
def update_stair_modifier(context):
obj = context.active_object
props_kwargs = obj.BIMStairProperties.get_props_kwargs()
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
for prop_name in props_kwargs:
if prop_name in ("is_editing", "number_of_treads", "has_top_nib", "stair_type"):
continue
prop_value = props_kwargs[prop_name]
props_kwargs[prop_name] = prop_value * si_conversion
vertices, edges, faces = generate_stair_2d_profile(**props_kwargs)
obj = context.object
@@ -360,14 +351,7 @@ class AddStair(bpy.types.Operator, tool.Ifc.Operator):
self.report({"ERROR"}, "Object has to be IfcStairFlight/IfcStairFlightType to add a stair.")
return {"CANCELLED"}
# need to make sure all default props will have correct units
if not props.stair_added_previously:
convert_property_group_from_si(
props,
skip_props=("stair_added_previously", "is_editing", "number_of_treads", "has_top_nib", "stair_type"),
)
stair_data = props.get_props_kwargs()
stair_data = props.get_props_kwargs(convert_to_project_units=True)
pset = tool.Pset.get_element_pset(element, "BBIM_Stair")
if not pset:
pset = ifcopenshell.api.run("pset.add_pset", ifc_file, product=element, name="BBIM_Stair")
@@ -393,8 +377,7 @@ class CancelEditingStair(bpy.types.Operator, tool.Ifc.Operator):
data = json.loads(ifcopenshell.util.element.get_pset(element, "BBIM_Stair", "Data"))
props = obj.BIMStairProperties
# restore previous settings since editing was canceled
for prop_name in data:
setattr(props, prop_name, data[prop_name])
props.set_props_kwargs_from_ifc_data(data)
update_stair_modifier(context)
props.is_editing = -1
@@ -412,7 +395,7 @@ class FinishEditingStair(bpy.types.Operator, tool.Ifc.Operator):
element = tool.Ifc.get_entity(obj)
props = obj.BIMStairProperties
data = props.get_props_kwargs()
data = props.get_props_kwargs(convert_to_project_units=True)
props.is_editing = -1
update_stair_modifier(context)
@@ -436,15 +419,7 @@ class EnableEditingStair(bpy.types.Operator, tool.Ifc.Operator):
element = tool.Ifc.get_entity(obj)
data = json.loads(ifcopenshell.util.element.get_pset(element, "BBIM_Stair", "Data"))
# required since we could load pset from .ifc and BIMStairProperties won't be set
for prop_name in data:
setattr(props, prop_name, data[prop_name])
# need to make sure all props that weren't used before
# will have correct units
skip_props = ("stair_added_previously", "is_editing", "number_of_treads", "has_top_nib", "stair_type")
skip_props += tuple(data.keys())
convert_property_group_from_si(props, skip_props=skip_props)
props.set_props_kwargs_from_ifc_data(data)
props.is_editing = 1
return {"FINISHED"}
@@ -117,7 +117,7 @@ class LaunchTypeManager(bpy.types.Operator):
op = row.operator("bim.select_type", icon="OBJECT_DATA", text="")
op.relating_type = relating_type["id"]
op = row.operator("bim.duplicate_type", icon="COPYDOWN", text="")
op = row.operator("bim.duplicate_type", icon="DUPLICATE", text="")
op.element = relating_type["id"]
op = row.operator("bim.remove_type", icon="X", text="")
op.element = relating_type["id"]
@@ -699,8 +699,16 @@ class DumbWallPlaner:
new_material = ifcopenshell.util.element.get_material(settings["relating_type"])
if not new_material or not new_material.is_a("IfcMaterialLayerSet"):
return
parametric = ifcopenshell.util.element.get_psets(settings["relating_type"]).get("EPset_Parametric")
layer_set_direction = None
if parametric:
layer_set_direction = parametric.get("LayerSetDirection", layer_set_direction)
material = ifcopenshell.util.element.get_material(settings["related_object"])
if material and material.is_a("IfcMaterialLayerSetUsage") and material.LayerSetDirection == "AXIS2":
if not material or not material.is_a("IfcMaterialLayerSetUsage"):
return
if layer_set_direction:
material.LayerSetDirection = layer_set_direction
if material.LayerSetDirection == "AXIS2":
DumbWallRecalculator().recalculate([obj])
@@ -27,11 +27,7 @@ import blenderbim.core.root
import blenderbim.core.geometry
from ifcopenshell.api.geometry.add_window_representation import DEFAULT_PANEL_SCHEMAS
from ifcopenshell.util.shape_builder import V
from bpy.types import Operator
from bpy.props import FloatProperty, IntProperty, BoolProperty
from bmesh.types import BMVert
from blenderbim.bim.helper import convert_property_group_from_si
from blenderbim.bim.ifc import IfcStore
from mathutils import Vector
@@ -105,31 +101,32 @@ def update_window_modifier_representation(context):
element = tool.Ifc.get_entity(obj)
props = obj.BIMWindowProperties
ifc_file = tool.Ifc.get()
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(ifc_file)
representation_data = {
"partition_type": props.window_type,
"overall_height": props.overall_height,
"overall_width": props.overall_width,
"overall_height": props.overall_height / si_conversion,
"overall_width": props.overall_width / si_conversion,
"lining_properties": {
"LiningDepth": props.lining_depth,
"LiningThickness": props.lining_thickness,
"LiningOffset": props.lining_offset,
"LiningToPanelOffsetX": props.lining_to_panel_offset_x,
"LiningToPanelOffsetY": props.lining_to_panel_offset_y,
"MullionThickness": props.mullion_thickness,
"FirstMullionOffset": props.first_mullion_offset,
"SecondMullionOffset": props.second_mullion_offset,
"TransomThickness": props.transom_thickness,
"FirstTransomOffset": props.first_transom_offset,
"SecondTransomOffset": props.second_transom_offset,
"LiningDepth": props.lining_depth / si_conversion,
"LiningThickness": props.lining_thickness / si_conversion,
"LiningOffset": props.lining_offset / si_conversion,
"LiningToPanelOffsetX": props.lining_to_panel_offset_x / si_conversion,
"LiningToPanelOffsetY": props.lining_to_panel_offset_y / si_conversion,
"MullionThickness": props.mullion_thickness / si_conversion,
"FirstMullionOffset": props.first_mullion_offset / si_conversion,
"SecondMullionOffset": props.second_mullion_offset / si_conversion,
"TransomThickness": props.transom_thickness / si_conversion,
"FirstTransomOffset": props.first_transom_offset / si_conversion,
"SecondTransomOffset": props.second_transom_offset / si_conversion,
},
"panel_properties": [],
}
number_of_panels, panels_data = props.window_types_panels[props.window_type]
for panel_i in range(number_of_panels):
panel_data = {
"FrameDepth": props.frame_depth[panel_i],
"FrameThickness": props.frame_thickness[panel_i],
"FrameDepth": props.frame_depth[panel_i] / si_conversion,
"FrameThickness": props.frame_thickness[panel_i] / si_conversion,
}
representation_data["panel_properties"].append(panel_data)
@@ -175,10 +172,10 @@ def update_window_modifier_representation(context):
# occurences attributes
occurences = tool.Ifc.get_all_element_occurences(element)
for occurence in occurences:
occurence.OverallWidth = props.overall_width
occurence.OverallHeight = props.overall_height
occurence.OverallWidth = props.overall_width / si_conversion
occurence.OverallHeight = props.overall_height / si_conversion
update_simple_openings(element, props.overall_width, props.overall_height)
update_simple_openings(element, props.overall_width / si_conversion, props.overall_height / si_conversion)
def create_bm_window_frame(bm, size: Vector, thickness: list, position: Vector = V(0, 0, 0).freeze()):
@@ -291,27 +288,26 @@ def create_bm_window(
def update_window_modifier_bmesh(context):
obj = context.object
props = obj.BIMWindowProperties
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
panel_schema = DEFAULT_PANEL_SCHEMAS[props.window_type]
accumulated_height = [0] * len(panel_schema[0])
built_panels = []
overall_width = props.overall_width * si_conversion
lining_depth = props.lining_depth * si_conversion
overall_height = props.overall_height * si_conversion
lining_to_panel_offset_x = props.lining_to_panel_offset_x * si_conversion
lining_to_panel_offset_y = props.lining_to_panel_offset_y * si_conversion
lining_thickness = props.lining_thickness * si_conversion
lining_offset = props.lining_offset * si_conversion
overall_width = props.overall_width
lining_depth = props.lining_depth
overall_height = props.overall_height
lining_to_panel_offset_x = props.lining_to_panel_offset_x
lining_to_panel_offset_y = props.lining_to_panel_offset_y
lining_thickness = props.lining_thickness
lining_offset = props.lining_offset
mullion_thickness = props.mullion_thickness * si_conversion / 2
first_mullion_offset = props.first_mullion_offset * si_conversion
second_mullion_offset = props.second_mullion_offset * si_conversion
transom_thickness = props.transom_thickness * si_conversion / 2
first_transom_offset = props.first_transom_offset * si_conversion
second_transom_offset = props.second_transom_offset * si_conversion
mullion_thickness = props.mullion_thickness / 2
first_mullion_offset = props.first_mullion_offset
second_mullion_offset = props.second_mullion_offset
transom_thickness = props.transom_thickness / 2
first_transom_offset = props.first_transom_offset
second_transom_offset = props.second_transom_offset
glass_thickness = 0.01 * si_conversion
glass_thickness = 0.01
bm = bmesh.new()
panel_schema = list(reversed(panel_schema))
@@ -349,8 +345,8 @@ def update_window_modifier_bmesh(context):
accumulated_width += panel_width
continue
frame_depth = props.frame_depth[panel_i] * si_conversion
frame_thickness = props.frame_thickness[panel_i] * si_conversion
frame_depth = props.frame_depth[panel_i]
frame_thickness = props.frame_thickness[panel_i]
# add window
window_lining_size = V(
@@ -456,13 +452,9 @@ class AddWindow(bpy.types.Operator, tool.Ifc.Operator):
self.report({"ERROR"}, "Object has to be IfcWindow/IfcWindowType type to add a window.")
return {"CANCELLED"}
# need to make sure all default props will have correct units
if not props.window_added_previously:
convert_property_group_from_si(props, skip_props=("is_editing", "window_type", "window_added_previously"))
window_data = props.get_general_kwargs()
lining_props = props.get_lining_kwargs()
panel_props = props.get_panel_kwargs()
window_data = props.get_general_kwargs(convert_to_project_units=True)
lining_props = props.get_lining_kwargs(convert_to_project_units=True)
panel_props = props.get_panel_kwargs(convert_to_project_units=True)
window_data["lining_properties"] = lining_props
window_data["panel_properties"] = panel_props
@@ -490,8 +482,7 @@ class CancelEditingWindow(bpy.types.Operator, tool.Ifc.Operator):
element = tool.Ifc.get_entity(obj)
data = json.loads(ifcopenshell.util.element.get_pset(element, "BBIM_Window", "Data"))
props = obj.BIMWindowProperties
for prop_name in data:
setattr(props, prop_name, data[prop_name])
props.set_props_kwargs_from_ifc_data(data)
body = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
blenderbim.core.geometry.switch_representation(
@@ -518,9 +509,9 @@ class FinishEditingWindow(bpy.types.Operator, tool.Ifc.Operator):
element = tool.Ifc.get_entity(obj)
props = obj.BIMWindowProperties
window_data = props.get_general_kwargs()
lining_props = props.get_lining_kwargs()
panel_props = props.get_panel_kwargs()
window_data = props.get_general_kwargs(convert_to_project_units=True)
lining_props = props.get_lining_kwargs(convert_to_project_units=True)
panel_props = props.get_panel_kwargs(convert_to_project_units=True)
window_data["lining_properties"] = lining_props
window_data["panel_properties"] = panel_props
@@ -549,14 +540,7 @@ class EnableEditingWindow(bpy.types.Operator, tool.Ifc.Operator):
data.update(data.pop("panel_properties"))
# required since we could load pset from .ifc and BIMWindowProperties won't be set
for prop_name in data:
setattr(props, prop_name, data[prop_name])
# need to make sure all props that weren't used before
# will have correct units
skip_props = ("is_editing", "window_type", "window_added_previously")
skip_props += tuple(data.keys())
convert_property_group_from_si(props, skip_props=skip_props)
props.set_props_kwargs_from_ifc_data(data)
props.is_editing = 1
return {"FINISHED"}
@@ -62,6 +62,8 @@ class BimTool(WorkSpaceTool):
("bim.hotkey", {"type": "D", "value": "PRESS", "alt": True}, {"properties": [("hotkey", "A_D")]}),
("bim.hotkey", {"type": "E", "value": "PRESS", "alt": True}, {"properties": [("hotkey", "A_E")]}),
("bim.hotkey", {"type": "O", "value": "PRESS", "alt": True}, {"properties": [("hotkey", "A_O")]}),
("bim.hotkey", {"type": "P", "value": "PRESS", "ctrl": True}, {"properties": [("hotkey", "C_P")]}),
("bim.hotkey", {"type": "P", "value": "PRESS", "alt": True}, {"properties": [("hotkey", "A_P")]}),
)
def draw_settings(context, layout, ws_tool):
@@ -323,15 +325,15 @@ class BimToolUI:
def draw_basic_bim_tool_interface(cls):
cls.draw_type_selection_interface()
if cls.props.ifc_class:
box = cls.layout.box()
if AuthoringData.data["type_thumbnail"]:
box.template_icon(icon_value=AuthoringData.data["type_thumbnail"], scale=5)
else:
op = box.operator("bim.load_type_thumbnails", text="Load Thumbnails", icon="FILE_REFRESH")
op.ifc_class = cls.props.ifc_class
if AuthoringData.data["ifc_classes"]:
if cls.props.ifc_class:
box = cls.layout.box()
if AuthoringData.data["type_thumbnail"]:
box.template_icon(icon_value=AuthoringData.data["type_thumbnail"], scale=5)
else:
op = box.operator("bim.load_type_thumbnails", text="Load Thumbnails", icon="FILE_REFRESH")
op.ifc_class = cls.props.ifc_class
row = cls.layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="", icon="EVENT_A")
@@ -404,6 +406,16 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator):
bpy.ops.bim.calculate_all_quantities()
def hotkey_C_P(self):
if not bpy.context.selected_objects:
return
bpy.ops.bim.assign_object()
def hotkey_A_P(self):
if not bpy.context.selected_objects:
return
bpy.ops.bim.unassign_object()
def hotkey_S_C(self):
if not bpy.context.selected_objects:
return
@@ -44,6 +44,7 @@ class IfcClassData:
cls.data["name"] = cls.name()
cls.data["ifc_class"] = cls.ifc_class()
cls.data["ifc_predefined_types"] = cls.ifc_predefined_types()
cls.data["can_reassign_class"] = cls.can_reassign_class()
@classmethod
def ifc_products(cls):
@@ -172,3 +173,13 @@ class IfcClassData:
element = tool.Ifc.get_entity(bpy.context.active_object)
if element:
return element.is_a()
@classmethod
def can_reassign_class(cls):
element = tool.Ifc.get_entity(bpy.context.active_object)
if element:
if element.is_a("IfcOpeningElement") or element.is_a("IfcOpeningStandardCase"):
return False
for product in cls.ifc_products():
if element.is_a(product[0]):
return True
@@ -156,6 +156,7 @@ class UnlinkObject(bpy.types.Operator):
bl_label = "Unlink Object"
bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
should_delete: bpy.props.BoolProperty(name="Should Delete", default=True)
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
@@ -167,7 +168,14 @@ class UnlinkObject(bpy.types.Operator):
else:
objects = context.selected_objects
for obj in objects:
if obj.BIMObjectProperties.ifc_definition_id:
element = tool.Ifc.get_entity(obj)
if element:
if self.should_delete:
obj_copy = obj.copy()
for collection in obj.users_collection:
collection.objects.link(obj_copy)
tool.Geometry.delete_ifc_object(obj)
obj = obj_copy
if obj in IfcStore.edited_objs:
IfcStore.edited_objs.remove(obj)
IfcStore.unlink_element(obj=obj)
@@ -182,6 +190,13 @@ class UnlinkObject(bpy.types.Operator):
obj.name = "/".join(obj.name.split("/")[1:])
return {"FINISHED"}
def draw(self, context):
row = self.layout.row()
row.prop(self, "should_delete")
def invoke(self, context, event):
return context.window_manager.invoke_props_dialog(self)
class CopyClass(bpy.types.Operator, Operator):
bl_idname = "bim.copy_class"
@@ -62,9 +62,8 @@ class BIM_PT_class(Panel):
row.label(text=IfcClassData.data["name"])
op = row.operator("bim.select_ifc_class", text="", icon="RESTRICT_SELECT_OFF")
op.ifc_class = IfcClassData.data["ifc_class"]
row.operator("bim.copy_class", icon="DUPLICATE", text="")
row.operator("bim.unlink_object", icon="UNLINKED", text="")
if IfcStore.get_file().by_id(props.ifc_definition_id).is_a("IfcRoot"):
if IfcClassData.data["can_reassign_class"]:
row.operator("bim.enable_reassign_class", icon="GREASEPENCIL", text="")
else:
ifc_predefined_types = root_prop.get_ifc_predefined_types(context.scene.BIMRootProperties, context)
@@ -380,6 +380,7 @@ class BIM_PT_work_schedules(Panel):
row.operator("bim.add_summary_task", text="Add Summary Task", icon="ADD").work_schedule = work_schedule_id
row.operator("bim.expand_all_tasks", text="Expand All")
row.operator("bim.contract_all_tasks", text="Contract All")
row = self.layout.row(align=True)
self.draw_task_operators()
self.layout.template_list(
"BIM_UL_tasks",
@@ -438,6 +439,11 @@ class BIM_PT_work_schedules(Panel):
op.lag_time = sequence["TimeLag"]
op = row.operator("bim.enable_editing_sequence_attributes", text="Edit Sequence", icon="GREASEPENCIL")
op.sequence = sequence["id"]
if process_type == "RelatingProcess":
op = row.operator("bim.unassign_predecessor", text="", icon="X")
elif process_type == "RelatedProcess":
op = row.operator("bim.unassign_successor", text="", icon="X")
op.task = task["id"]
def draw_editable_sequence_attributes_ui(self):
blenderbim.bim.helper.draw_attributes(self.props.sequence_attributes, self.layout)
@@ -51,7 +51,6 @@ class AssignType(bpy.types.Operator, Operator):
)
for obj in related_objects:
core.assign_type(tool.Ifc, tool.Type, element=tool.Ifc.get_entity(obj), type=type)
oprops = obj.BIMObjectProperties
class UnassignType(bpy.types.Operator):
@@ -118,6 +117,7 @@ class EnableEditingType(bpy.types.Operator):
def execute(self, context):
context.active_object.BIMTypeProperties.is_editing_type = True
context.active_object.BIMTypeProperties.relating_type_object = None
return {"FINISHED"}
@@ -143,8 +143,12 @@ class SelectType(bpy.types.Operator):
element = tool.Ifc.get().by_id(self.relating_type)
obj = tool.Ifc.get_object(element)
if obj:
context.view_layer.objects.active = obj
obj.select_set(True)
if obj in context.selectable_objects:
tool.Blender.select_and_activate_single_object(context, obj)
else:
self.report({"INFO"}, "Type object can't be selected : It may be hidden or in an excluded collection.")
context.scene.BIMModelProperties.ifc_class = element.is_a()
context.scene.BIMModelProperties.relating_type_id = str(self.relating_type)
return {"FINISHED"}
@@ -177,10 +181,17 @@ class SelectTypeObjects(bpy.types.Operator):
def execute(self, context):
self.file = IfcStore.get_file()
relating_type = bpy.data.objects.get(self.relating_type) if self.relating_type else context.active_object
at_least_one_selectable_typed_object = False
for element in ifcopenshell.util.element.get_types(tool.Ifc.get_entity(relating_type)):
obj = tool.Ifc.get_object(element)
if obj:
if obj and obj in context.selectable_objects:
obj.select_set(True)
at_least_one_selectable_typed_object = True
if at_least_one_selectable_typed_object:
context.active_object.select_set(False)
context.view_layer.objects.active = context.selected_objects[0]
else:
self.report({"INFO"}, "Typed objects can't be selected : They may be hidden or in an excluded collection.")
return {"FINISHED"}
@@ -451,6 +462,12 @@ class DuplicateType(bpy.types.Operator, tool.Ifc.Operator):
new = blenderbim.core.root.copy_class(tool.Ifc, tool.Collector, tool.Geometry, tool.Root, obj=new_obj)
new.Name += " Copy"
bpy.ops.bim.load_type_thumbnails(ifc_class=new.is_a())
if obj in context.selectable_objects:
tool.Blender.select_and_activate_single_object(context, new_obj)
else:
self.report({"INFO"}, "Type object can't be selected : It may be hidden or in an excluded collection.")
context.scene.BIMModelProperties.ifc_class = new.is_a()
context.scene.BIMModelProperties.relating_type_id = str(new_obj.BIMObjectProperties.ifc_definition_id)
return {"FINISHED"}
@@ -21,6 +21,7 @@ import ifcopenshell.util.type
from blenderbim.bim.module.type.data import TypeData
from blenderbim.bim.prop import StrProperty, Attribute
from blenderbim.bim.ifc import IfcStore
import blenderbim.tool as tool
from bpy.types import PropertyGroup
from bpy.props import (
PointerProperty,
@@ -50,9 +51,42 @@ def update_relating_type_class(self, context):
TypeData.is_loaded = False
def update_relating_type_from_object(self, context):
if self.relating_type_object is None:
return
element = tool.Ifc.get_entity(self.relating_type_object)
if not element:
return
element_type = ifcopenshell.util.element.get_type(element)
if not element_type:
return
self.relating_type = str(element_type.id())
bpy.ops.bim.assign_type()
def is_object_class_applicable(self, obj):
if not TypeData.is_loaded:
TypeData.load()
element = tool.Ifc.get_entity(obj)
if not element:
return
element_type = ifcopenshell.util.element.get_type(element)
if element_type is None:
return False
return str(element_type.is_a()) in (r_t_c[0] for r_t_c in TypeData.data["relating_type_classes"])
class BIMTypeProperties(PropertyGroup):
is_editing_type: BoolProperty(name="Is Editing Type")
relating_type_class: EnumProperty(
items=get_relating_type_class, name="Relating Type Class", update=update_relating_type_class
items=get_relating_type_class,
name="Relating Type Class",
update=update_relating_type_class,
)
relating_type: EnumProperty(items=get_relating_type, name="Relating Type")
relating_type_object: PointerProperty(
type=bpy.types.Object,
name="Copy Class",
update=update_relating_type_from_object,
poll=is_object_class_applicable,
)
@@ -59,24 +59,31 @@ class BIM_PT_type(Panel):
oprops = context.active_object.BIMObjectProperties
row = self.layout.row(align=True)
row.label(text=f"{TypeData.data['total_instances']} Typed Objects")
row.operator("bim.select_type_objects", icon="RESTRICT_SELECT_OFF", text="")
select_type_objects_row = row.row(align=True)
select_type_objects_row.operator("bim.select_type_objects", icon="RESTRICT_SELECT_OFF", text="")
select_type_objects_row.enabled = int(TypeData.data["total_instances"]) > 0
op = row.operator("bim.duplicate_type", icon="DUPLICATE", text="")
op.element = context.active_object.BIMObjectProperties.ifc_definition_id
def draw_product_ui(self, context):
layout = self.layout
props = context.active_object.BIMTypeProperties
oprops = context.active_object.BIMObjectProperties
if props.is_editing_type:
row = self.layout.row(align=True)
row = layout.row(align=True)
row_object = layout.row(align=True)
row.prop(props, "relating_type_class", text="")
if type_prop.get_relating_type(None, context):
prop_with_search(row, props, "relating_type", text="")
row.operator("bim.assign_type", icon="CHECKMARK", text="")
row_object.prop(props, "relating_type_object", icon="COPYDOWN")
else:
row.label(text="No Types Found")
row.operator("bim.disable_editing_type", icon="CANCEL", text="")
else:
row = self.layout.row(align=True)
row = layout.row(align=True)
if TypeData.data["relating_type"]:
row.label(text=TypeData.data["relating_type"]["name"])
op = row.operator("bim.select_type", icon="OBJECT_DATA", text="")
@@ -34,25 +34,34 @@ class AddOpening(bpy.types.Operator, tool.Ifc.Operator):
props = context.scene.BIMModelProperties
if len(context.selected_objects) != 2:
return {"FINISHED"}
# The convention is that element1 is the element and element2 is the opening.
obj1, obj2 = context.selected_objects
element1 = tool.Ifc.get_entity(obj1)
element2 = tool.Ifc.get_entity(obj2)
if type(element1) == type(element2):
if (
element1
and element2
and not element1.is_a("IfcOpeningElement")
and not element2.is_a("IfcOpeningElement")
):
if element1.is_a("IfcWindow") or element1.is_a("IfcDoor"):
if not element1 and not element2:
return {"FINISHED"} # Both are not IFC objects.
if element1 and element2:
if element1.is_a("IfcOpeningElement") and element2.is_a("IfcOpeningElement"):
return {"FINISHED"} # You can't add an opening to another opening.
elif not element1.is_a("IfcOpeningElement") and not element2.is_a("IfcOpeningElement"):
if element1.is_a("IfcWindow") or element1.is_a("IfcDoor"): # Add a fill to an element.
obj1, obj2 = obj2, obj1
FilledOpeningGenerator().generate(obj2, obj1, target=obj2.matrix_world.translation)
return {"FINISHED"}
return {"FINISHED"}
elif element1.is_a("IfcOpeningElement") or element2.is_a("IfcOpeningElement"):
if element1.is_a("IfcOpeningElement"): # Reassign an opening to another element.
obj1, obj2 = obj2, obj1
element1, element2 = element2, element1
if element2 and not element1:
obj1, obj2 = obj2, obj1
element1, element2 = element2, element1
if element1.is_a("IfcOpeningElement"):
return {"FINISHED"}
return {"FINISHED"} # You can't add an opening to another opening.
if tool.Ifc.is_moved(obj1):
blenderbim.core.geometry.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj1)
@@ -64,17 +73,21 @@ class AddOpening(bpy.types.Operator, tool.Ifc.Operator):
break
body_context = ifcopenshell.util.representation.get_context(IfcStore.get_file(), "Model", "Body")
element2 = blenderbim.core.root.assign_class(
tool.Ifc,
tool.Collector,
tool.Root,
obj=obj2,
ifc_class="IfcOpeningElement",
should_add_representation=True,
context=body_context,
)
if not element2:
element2 = blenderbim.core.root.assign_class(
tool.Ifc,
tool.Collector,
tool.Root,
obj=obj2,
ifc_class="IfcOpeningElement",
should_add_representation=True,
context=body_context,
)
ifcopenshell.api.run("void.add_opening", tool.Ifc.get(), opening=element2, element=element1)
if tool.Ifc.is_moved(obj2):
blenderbim.core.geometry.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj2)
voided_objs = [obj1]
for subelement in ifcopenshell.util.element.get_decomposition(element1):
subobj = tool.Ifc.get_object(subelement)
+10 -5
View File
@@ -40,11 +40,16 @@ def assign_object(ifc, aggregator, collector, relating_obj=None, related_obj=Non
def unassign_object(ifc, aggregate, collector, relating_obj=None, related_obj=None):
related_element = ifc.get_entity(related_obj)
container = aggregate.get_container(related_element)
ifc.run("aggregate.unassign_object", product=related_element)
if container:
ifc.run("spatial.assign_container", product=related_element, relating_structure=container)
collector.assign(relating_obj)
collector.assign(related_obj)
if not relating_obj:
relating_element = aggregate.get_relating_object(related_element)
if related_element:
relating_obj = ifc.get_object(relating_element)
if relating_obj:
ifc.run("aggregate.unassign_object", product=related_element)
if container:
ifc.run("spatial.assign_container", product=related_element, relating_structure=container)
collector.assign(relating_obj)
collector.assign(related_obj)
def add_part_to_object(ifc, aggregator, collector, blender, obj, part_class, part_name=None):
+12 -5
View File
@@ -130,7 +130,7 @@ def assign_cost_value(ifc, cost_item, cost_rate):
ifc.run("cost.assign_cost_value", cost_item=cost_item, cost_rate=cost_rate)
def load_schedule_of_rates(cost, schedule_of_rates):
cost.load_schedule_of_rates(schedule_of_rates)
cost.load_schedule_of_rates_tree(schedule_of_rates)
def unassign_cost_item_quantity(ifc, cost, cost_item, products):
ifc.run("cost.unassign_cost_item_quantity", cost_item=cost_item, products=products)
@@ -236,8 +236,8 @@ def calculate_cost_item_resource_value(ifc, cost_item):
ifc.run("cost.calculate_cost_item_resource_value", cost_item=cost_item)
def export_cost_schedules(cost, format):
cost.export_cost_schedules(format)
def export_cost_schedules(cost, format, cost_schedule=None):
cost.export_cost_schedules(format, cost_schedule)
def clear_cost_item_assignments(ifc, cost, cost_item, related_object_type):
@@ -249,7 +249,7 @@ def clear_cost_item_assignments(ifc, cost, cost_item, related_object_type):
def select_unassigned_products(ifc, cost, spatial):
spatial.deselect_all()
spatial.deselect_objects()
products = ifc.get().by_type("IfcElement")
cost_schedule = cost.get_active_cost_schedule()
selection = [product for product in products if not cost.has_cost_assignments(product, cost_schedule)]
@@ -276,4 +276,11 @@ def change_parent_cost_item(ifc, cost, new_parent):
if cost_item :
ifc.run("nest.change_nest", item=cost_item, new_parent=new_parent)
cost.disable_editing_cost_item_parent()
cost.load_cost_schedule_tree()
cost.load_cost_schedule_tree()
def copy_cost_item(ifc, cost):
cost_item = cost.get_highlighted_cost_item()
if cost_item:
cost_item = ifc.run("cost.copy_cost_item", cost_item=cost_item)
cost.disable_editing_cost_item_parent()
cost.load_cost_schedule_tree()
@@ -270,6 +270,9 @@ def duplicate_drawing(ifc, drawing_tool, drawing=None, should_duplicate_annotati
def remove_drawing(ifc, drawing_tool, drawing=None):
if drawing_tool.is_active_drawing(drawing):
drawing_tool.run_drawing_activate_model()
collection = drawing_tool.get_drawing_collection(drawing)
if collection:
drawing_tool.delete_collection(collection)
@@ -72,3 +72,18 @@ def disable_editing_materials(material):
def select_by_material(material_tool, material=None):
material_tool.select_elements(material_tool.get_elements_by_material(material))
def enable_editing_material(material_tool, material):
material_tool.load_material_attributes(material)
material_tool.enable_editing_material(material)
def edit_material(ifc, material_tool, material):
attributes = material_tool.get_material_attributes()
ifc.run("material.edit_material", material=material, attributes=attributes)
material_tool.disable_editing_material()
material_type = material_tool.get_active_material_type()
material_tool.import_material_definitions(material_type)
material_tool.enable_editing_materials()
def disable_editing_material(material_tool):
material_tool.disable_editing_material()
@@ -56,6 +56,9 @@ def create_project(ifc, project, schema=None, template=None):
project.run_context_add_context(
context_type="Model", context_identifier="Annotation", target_view="MODEL_VIEW", parent=model
)
project.run_context_add_context(
context_type="Model", context_identifier="Annotation", target_view="PLAN_VIEW", parent=model
)
project.run_context_add_context(
context_type="Model", context_identifier="Profile", target_view="ELEVATION_VIEW", parent=model
)
+1 -1
View File
@@ -443,7 +443,7 @@ def select_work_schedule_products(sequence, spatial, work_schedule=None):
def select_unassigned_work_schedule_products(ifc, sequence, spatial):
spatial.deselect_all()
spatial.deselect_objects()
products = ifc.get().by_type("IfcElement")
work_schedule = sequence.get_active_work_schedule()
schedule_products = sequence.get_work_schedule_products(work_schedule)
+10 -4
View File
@@ -165,8 +165,7 @@ class Cost:
def expand_cost_item_rate(cls, cost_item): pass
def expand_cost_item(cls, cost_item): pass
def expand_cost_items(cls): pass
def export_cost_schedules(cls, format): pass
def export_cost_schedules(cls, format): pass
def export_cost_schedules(cls, format, cost_schedule): pass
def format_unit(cls, unit): pass
def get_active_cost_item(cls): pass
def get_active_cost_schedule(cls): pass
@@ -274,9 +273,8 @@ class Drawing:
def get_body_context(cls): pass
def get_default_drawing_path(cls, name): pass
def get_default_drawing_resource_path(cls, resource): pass
def get_default_shading_style(cls): pass
def setup_shading_styles_path(cls, resource_path): pass
def get_default_layout_path(cls, identification, name): pass
def get_default_shading_style(cls): pass
def get_default_sheet_path(cls, identification, name): pass
def get_default_sheet_path(cls, identification, name): pass
def get_default_titleblock_path(cls, name): pass
@@ -302,17 +300,20 @@ class Drawing:
def import_schedules(cls): pass
def import_sheets(cls): pass
def import_text_attributes(cls, obj): pass
def is_active_drawing(cls, drawing): pass
def is_camera_orthographic(cls): pass
def is_drawing_active(cls): pass
def move_file(cls, src, dest): pass
def open_spreadsheet(cls, uri): pass
def open_svg(cls, filepath): pass
def remove_literal_from_annotation(cls, obj, literal): pass
def run_drawing_activate_model(cls): pass
def run_root_assign_class(cls, obj=None, ifc_class=None, predefined_type=None, should_add_representation=True, context=None, ifc_representation_class=None): pass
def select_assigned_product(cls, drawing): pass
def set_drawing_collection_name(cls, group, collection): pass
def set_name(cls, element, name): pass
def setup_annotation_object(cls, obj, object_type): pass
def setup_shading_styles_path(cls, resource_path): pass
def show_decorations(cls): pass
def sync_object_placement(cls, obj): pass
def synchronise_ifc_and_text_attributes(cls, obj): pass
@@ -432,14 +433,19 @@ class Loader:
class Material:
def add_default_material_object(cls): pass
def delete_object(cls, obj): pass
def disable_editing_material(cls): pass
def disable_editing_materials(cls): pass
def enable_editing_material(cls, material): pass
def enable_editing_materials(cls): pass
def get_active_material_type(cls): pass
def get_active_material_type(cls): pass
def get_elements_by_material(cls, material): pass
def get_material_attributes(cls): pass
def get_name(cls, obj): pass
def import_material_definitions(cls, material_type): pass
def is_editing_materials(cls): pass
def is_material_used_in_sets(cls, material): pass
def load_material_attributes(cls, material): pass
def select_elements(cls, elements): pass
@@ -55,3 +55,9 @@ class Aggregate(blenderbim.core.tool.Aggregate):
@classmethod
def get_container(cls, element):
return ifcopenshell.util.element.get_container(element)
@classmethod
def get_relating_object(cls, related_element):
for rel in related_element.Decomposes:
if rel.is_a("IfcRelAggregates"):
return rel.RelatingObject
+6 -7
View File
@@ -452,7 +452,7 @@ class Cost(blenderbim.core.tool.Cost):
contracted_cost_item_rates = json.loads(props.contracted_cost_item_rates)
contracted_cost_item_rates.remove(cost_item)
props.contracted_cost_item_rates = json.dumps(contracted_cost_item_rates)
cls.load_schedule_of_rates(schedule_of_rates=tool.Ifc.get().by_id(int(props.schedule_of_rates)))
cls.load_schedule_of_rates_tree(schedule_of_rates=tool.Ifc.get().by_id(int(props.schedule_of_rates)))
@classmethod
def contract_cost_item_rate(cls, cost_item):
@@ -460,7 +460,7 @@ class Cost(blenderbim.core.tool.Cost):
contracted_cost_item_rates = json.loads(props.contracted_cost_item_rates)
contracted_cost_item_rates.append(cost_item)
props.contracted_cost_item_rates = json.dumps(contracted_cost_item_rates)
cls.load_schedule_of_rates(schedule_of_rates=tool.Ifc.get().by_id(int(props.schedule_of_rates)))
cls.load_schedule_of_rates_tree(schedule_of_rates=tool.Ifc.get().by_id(int(props.schedule_of_rates)))
@classmethod
def create_new_cost_item_li(cls, props_collection, cost_item, level_index, type="cost_rate"):
@@ -498,24 +498,23 @@ class Cost(blenderbim.core.tool.Cost):
props.is_cost_update_enabled = True
@classmethod
def export_cost_schedules(cls, format=None):
def export_cost_schedules(cls, format=None, cost_schedule=None):
path = os.path.join(bpy.context.scene.BIMProperties.data_dir, "build", "cost_schedules")
if format == "CSV":
from ifc5d.ifc5Dspreadsheet import Ifc5DCsvWriter
if not os.path.exists(path):
os.makedirs(path)
writer = Ifc5DCsvWriter(file=tool.Ifc.get(), output=path)
writer = Ifc5DCsvWriter(file=tool.Ifc.get(), output=path, cost_schedule=cost_schedule)
writer.write()
elif format == "ODS":
from ifc5d.ifc5Dspreadsheet import Ifc5DOdsWriter
writer = Ifc5DOdsWriter(file=tool.Ifc.get(), output=path)
writer = Ifc5DOdsWriter(file=tool.Ifc.get(), output=path, cost_schedule=cost_schedule)
writer.write()
elif format == "XLSX":
from ifc5d.ifc5Dspreadsheet import Ifc5DXlsxWriter
writer = Ifc5DXlsxWriter(file=tool.Ifc.get(), output=path)
writer = Ifc5DXlsxWriter(file=tool.Ifc.get(), output=path, cost_schedule=cost_schedule )
writer.write()
@classmethod
+17 -19
View File
@@ -37,7 +37,7 @@ import blenderbim.bim.module.drawing.annotation as annotation
import blenderbim.bim.module.drawing.helper as helper
from blenderbim.bim.module.drawing.data import FONT_SIZES, DecoratorData
from blenderbim.bim.module.drawing.prop import get_diagram_scales, BOX_ALIGNMENT_POSITIONS
from blenderbim.bim.module.drawing.prop import get_diagram_scales, BOX_ALIGNMENT_POSITIONS, ANNOTATION_TYPES_DATA
from mathutils import Vector
import collections
@@ -55,28 +55,18 @@ class Drawing(blenderbim.core.tool.Drawing):
tool.Ifc.get(), source.Representation, exclude=["IfcGeometricRepresentationContext"]
)
@classmethod
def get_annotation_data_type(cls, object_type):
return ANNOTATION_TYPES_DATA[object_type][3]
@classmethod
def create_annotation_object(cls, drawing, object_type):
data_type = {
"ANGLE": "curve",
"BATTING": "mesh",
"BREAKLINE": "mesh",
"DIAMETER": "curve",
"DIMENSION": "curve",
"FALL": "curve",
"FILL_AREA": "mesh",
"HIDDEN_LINE": "mesh",
"LINEWORK": "mesh",
"PLAN_LEVEL": "curve",
"RADIUS": "curve",
"SECTION_LEVEL": "curve",
"STAIR_ARROW": "curve",
"TEXT": "empty",
"TEXT_LEADER": "curve",
}[object_type]
data_type = cls.get_annotation_data_type(object_type)
obj = annotation.Annotator.get_annotation_obj(drawing, object_type, data_type)
if object_type == "FILL_AREA":
obj = annotation.Annotator.add_plane_to_annotation(obj)
elif object_type == "REVISION_CLOUD":
obj = annotation.Annotator.add_plane_to_annotation(obj, remove_face=True)
elif object_type == "TEXT_LEADER":
co1, _, co2, _ = annotation.Annotator.get_placeholder_coords()
obj = annotation.Annotator.add_line_to_annotation(obj, co2, co1)
@@ -217,8 +207,8 @@ class Drawing(blenderbim.core.tool.Drawing):
@classmethod
def delete_drawing_elements(cls, elements):
for element in elements:
ifcopenshell.api.run("root.remove_product", tool.Ifc.get(), product=element)
obj = tool.Ifc.get_object(element)
ifcopenshell.api.run("root.remove_product", tool.Ifc.get(), product=element)
if obj:
obj_data = obj.data
bpy.data.objects.remove(obj)
@@ -1442,6 +1432,14 @@ class Drawing(blenderbim.core.tool.Drawing):
camera = bpy.context.scene.camera
return True if (camera and camera.data.type == "ORTHO") else False
@classmethod
def is_active_drawing(cls, drawing):
return drawing.id() == bpy.context.scene.DocProperties.active_drawing_id
@classmethod
def run_drawing_activate_model(cls):
bpy.ops.bim.activate_model()
@classmethod
def activate_drawing(cls, camera):
area = next(area for area in bpy.context.screen.areas if area.type == "VIEW_3D")
+46 -5
View File
@@ -3,7 +3,10 @@ import re
# allows git import even if git executable isn't found
os.environ["GIT_PYTHON_REFRESH"] = "quiet"
import git
try:
import git
except:
print("Warning: GitPython not available.")
import bpy
from blenderbim.bim.ifc import IfcStore
import blenderbim.tool as tool
@@ -13,10 +16,15 @@ class IfcGit:
@classmethod
def init_repo(cls, path_dir):
IfcGitRepo.repo = git.Repo.init(path_dir)
cls.config_info_attributes(IfcGitRepo.repo)
@classmethod
def clone_repo(cls, remote_url, local_folder):
IfcGitRepo.repo = git.Repo.clone_from(remote_url, local_folder)
IfcGitRepo.repo = git.Repo.clone_from(
url=remote_url,
to_path=local_folder,
)
cls.config_info_attributes(IfcGitRepo.repo)
return IfcGitRepo.repo
@classmethod
@@ -61,6 +69,8 @@ class IfcGit:
@classmethod
def add_file_to_repo(cls, repo, path_file):
if os.name == "nt":
cls.dos2unix(path_file)
repo.index.add(path_file)
repo.index.commit(message="Added " + os.path.relpath(path_file, repo.working_dir))
bpy.ops.ifcgit.refresh()
@@ -73,6 +83,8 @@ class IfcGit:
def git_commit(cls, path_file):
props = bpy.context.scene.IfcGitProperties
repo = IfcGitRepo.repo
if os.name == "nt":
cls.dos2unix(path_file)
repo.index.add(path_file)
repo.index.commit(message=props.commit_message)
props.commit_message = ""
@@ -180,6 +192,7 @@ class IfcGit:
bpy.data.orphans_purge(do_recursive=True)
bpy.ops.bim.load_project(filepath=path_ifc)
bpy.ops.object.select_all(action='DESELECT')
@classmethod
def branches_by_hexsha(cls, repo):
@@ -191,6 +204,13 @@ class IfcGit:
result[branch.commit.hexsha].append(branch)
else:
result[branch.commit.hexsha] = [branch]
if repo.remotes:
for remote in repo.remotes:
for ref in remote.refs:
if ref.commit.hexsha in result:
result[ref.commit.hexsha].append(ref)
else:
result[ref.commit.hexsha] = [ref]
return result
@classmethod
@@ -292,6 +312,7 @@ class IfcGit:
def colourise(cls, step_ids):
area = next(area for area in bpy.context.screen.areas if area.type == "VIEW_3D")
area.spaces[0].shading.color_type = "OBJECT"
bpy.ops.object.select_all(action='DESELECT')
for obj in bpy.context.visible_objects:
if not obj.BIMObjectProperties.ifc_definition_id:
@@ -299,10 +320,13 @@ class IfcGit:
step_id = obj.BIMObjectProperties.ifc_definition_id
if step_id in step_ids["modified"]:
obj.color = (0.3, 0.3, 1.0, 1)
obj.select_set(True)
elif step_id in step_ids["added"]:
obj.color = (0.2, 0.8, 0.2, 1)
obj.select_set(True)
elif step_id in step_ids["removed"]:
obj.color = (1.0, 0.2, 0.2, 1)
obj.select_set(True)
else:
obj.color = (1.0, 1.0, 1.0, 0.5)
@@ -317,9 +341,9 @@ class IfcGit:
for branch in lookup[item.hexsha]:
if branch.name == props.display_branch:
branch.checkout()
else:
# NOTE this is calling the git binary in a subprocess
repo.git.checkout(item.hexsha)
return
# NOTE this is calling the git binary in a subprocess
repo.git.checkout(item.hexsha)
@classmethod
def delete_collection(cls, blender_collection):
@@ -345,6 +369,23 @@ class IfcGit:
config_writer.set_value(section, "cmd", "ifcmerge $BASE $LOCAL $REMOTE $MERGED")
config_writer.set_value(section, "trustExitCode", True)
@classmethod
def config_info_attributes(cls, repo):
"""Set IFC files as text in .git/info/attributes"""
path_attributes = os.path.join(repo.git_dir, "info", "attributes")
if not os.path.exists(path_attributes):
with open(path_attributes, "w") as f:
# attributes patterns are case-insensitive
f.write("*.ifc text")
@classmethod
def dos2unix(cls, path_file):
with open(path_file, "rb") as infile:
content = infile.read()
with open(path_file, "wb") as output:
for line in content.splitlines():
output.write(line + b"\n")
@classmethod
def execute_merge(cls, path_ifc, operator):
props = bpy.context.scene.IfcGitProperties
@@ -107,3 +107,29 @@ class Material(blenderbim.core.tool.Material):
obj = tool.Ifc.get_object(element)
if obj:
obj.select_set(True)
@classmethod
def get_active_material_type(cls):
return bpy.context.scene.BIMMaterialProperties.material_type
@classmethod
def load_material_attributes(cls, material):
props = bpy.context.scene.BIMMaterialProperties
props.material_attributes.clear()
blenderbim.bim.helper.import_attributes2(material, props.material_attributes)
@classmethod
def enable_editing_material(cls, material):
props = bpy.context.scene.BIMMaterialProperties
props.active_material_id = material.id()
props.editing_material_type = "ATTRIBUTES"
@classmethod
def get_material_attributes(cls):
return blenderbim.bim.helper.export_attributes(bpy.context.scene.BIMMaterialProperties.material_attributes)
@classmethod
def disable_editing_material(cls):
props = bpy.context.scene.BIMMaterialProperties
props.active_material_id = 0
props.editing_material_type = ""
+35 -5
View File
@@ -28,6 +28,7 @@ import blenderbim.core.geometry as geometry
from mathutils import Matrix, Vector
from blenderbim.bim import import_ifc
from blenderbim.bim.module.geometry.helper import Helper
import collections
class Model(blenderbim.core.tool.Model):
@@ -43,6 +44,33 @@ class Model(blenderbim.core.tool.Model):
return [v * cls.unit_scale for v in value]
return value * cls.unit_scale
@classmethod
def convert_data_to_project_units(cls, data, non_si_props=[]):
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
for prop_name in data:
if prop_name in non_si_props:
continue
prop_value = data[prop_name]
if isinstance(prop_value, collections.abc.Iterable):
data[prop_name] = [v / si_conversion for v in prop_value]
else:
data[prop_name] = prop_value / si_conversion
return data
@classmethod
def convert_data_to_si_units(cls, data, non_si_props=[]):
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
for prop_name in data:
if prop_name in non_si_props:
continue
prop_value = data[prop_name]
print(prop_name, prop_value)
if isinstance(prop_value, collections.abc.Iterable):
data[prop_name] = [v * si_conversion for v in prop_value]
else:
data[prop_name] = prop_value * si_conversion
return data
@classmethod
def export_curve(cls, position, edge_indices, points=None):
position_i = position.inverted()
@@ -147,11 +175,13 @@ class Model(blenderbim.core.tool.Model):
cls.bm.edges.ensure_lookup_table()
surface = tool.Ifc.get().createIfcCurveBoundedPlane()
surface.BasisSurface = tool.Ifc.get().createIfcPlane(tool.Ifc.get().createIfcAxis2Placement3D(
tool.Ifc.get().createIfcCartesianPoint([o / cls.unit_scale for o in p1]),
tool.Ifc.get().createIfcDirection([float(o) for o in z_axis]),
tool.Ifc.get().createIfcDirection([float(o) for o in x_axis]),
))
surface.BasisSurface = tool.Ifc.get().createIfcPlane(
tool.Ifc.get().createIfcAxis2Placement3D(
tool.Ifc.get().createIfcCartesianPoint([o / cls.unit_scale for o in p1]),
tool.Ifc.get().createIfcDirection([float(o) for o in z_axis]),
tool.Ifc.get().createIfcDirection([float(o) for o in x_axis]),
)
)
if tool.Ifc.get().schema != "IFC2X3":
cls.points = cls.export_points(position, indices["points"])
+2 -2
View File
@@ -154,7 +154,7 @@ class Sequence(blenderbim.core.tool.Sequence):
if column_type == "IfcTask":
return task.get_info(task)[name] or ""
elif column_type == "IfcTaskTime" and task.TaskTime:
return task.TaskTime.get_info(task)[name]
return task.TaskTime.get_info(task)[name] if task.TaskTime.get_info(task)[name] else ""
return task.Identification or ""
def natural_sort_key(i, _nsre=re.compile("([0-9]+)")):
@@ -212,7 +212,7 @@ class Sequence(blenderbim.core.tool.Sequence):
item.calendar = ""
item.derived_calendar = calendar.Name or "Unnamed" if calendar else ""
if task.TaskTime:
if task.TaskTime and (task.TaskTime.ScheduleStart or task.TaskTime.ScheduleFinish or task.TaskTime.ScheduleDuration):
task_time = task.TaskTime
item.start = (
canonicalise_time(ifcopenshell.util.date.ifc2datetime(task_time.ScheduleStart))
+33
View File
@@ -156,6 +156,39 @@ optional message text.
Similar to commit messages, tag messages should be 50 characters or less,
though there is no practical limit.
Remote operations
-----------------
Git is a *distributed revision control system*, your local repository can be a
version of a remote repository and vice-versa. This is conceptually similar to
local branching except this remote repository could belong to someone else or
could be hosted by an online Git-forge service.
Your repository can have multiple remote repositories registered, each
can have potentially multiple branches.
BlenderBIM allows you to make a local *clone* of a remote repository. You will
need to provide a URL *origin* to fetch, and an empty local folder to become
the local repository.
The *Fetch* operator retrieves new data from the remote repository. This isn't
automatically merged, each branch fetched from the remote repository appears as
a branch that can be browsed, switched-to or merged just like a local branch.
These remote branches have prefixed names, eg. `origin/main`.
Once you have committed changes to your local repository, the *Push* operator
tries to update the remote branch using changes from the selected local branch.
.. Warning::
Remote repositories can be accessed in multiple ways; ssh, ftp or https
protocols, for example, can require authentication. This authentication may
expect you to generate and upload ssh keys, store API tokens, save
username/password pairs, or use some other form of credential.
BlenderBIM can't configure these credentials for you, follow the
configuration instructions provided by your online service before trying
actions that require authentication.
Using other Git tools
---------------------
@@ -74,6 +74,21 @@ Scenario: Remove drawing - via object deletion
When I press "bim.override_object_delete"
Then the collection "IfcGroup/PLAN_VIEW" does not exist
Scenario: Remove drawing - deleting active drawing
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And the variable "wall1" is "IfcStore.get_file().by_type('IfcWall')[-1].id()"
And I press "bim.add_drawing"
And the variable "drawing" is "IfcStore.get_file().by_type('IfcAnnotation')[0].id()"
And I set "scene.DocProperties.active_drawing_index" to "0"
And I press "bim.activate_drawing(drawing={drawing})"
And the object "IfcAnnotation/PLAN_VIEW" is selected
When I press "bim.override_object_delete"
Then the collection "IfcGroup/PLAN_VIEW" does not exist
Scenario: Reproducing freeze on generating drawing - Issue 3169
Given an empty IFC project
And I add a cube
+2
View File
@@ -345,6 +345,8 @@ class TestDuplicateDrawing:
class TestRemoveDrawing:
def test_run(self, ifc, drawing):
drawing.is_active_drawing("drawing").should_be_called().will_return(True)
drawing.run_drawing_activate_model().should_be_called()
drawing.get_drawing_collection("drawing").should_be_called().will_return("collection")
drawing.get_drawing_group("drawing").should_be_called().will_return("group")
drawing.get_group_elements("group").should_be_called().will_return("elements")
+20 -84
View File
@@ -26,18 +26,7 @@ class TestCreateProject:
ifc.get().should_be_called().will_return("ifc")
subject.create_project(ifc, project, schema="IFC4", template=None)
def test_create_an_ifc4_project(self, ifc, project):
ifc.get().should_be_called().will_return(None)
ifc.run("project.create_file", version="IFC4").should_be_called().will_return("ifc")
ifc.set("ifc").should_be_called()
project.create_empty("My Project").should_be_called().will_return("project")
project.create_empty("My Site").should_be_called().will_return("site")
project.create_empty("My Building").should_be_called().will_return("building")
project.create_empty("My Storey").should_be_called().will_return("storey")
project.run_root_assign_class(obj="project", ifc_class="IfcProject").should_be_called()
project.run_unit_assign_scene_units().should_be_called()
def check_contexts(self, project):
project.run_context_add_context(
context_type="Model", context_identifier="", target_view="", parent=0
).should_be_called().will_return("model")
@@ -59,6 +48,9 @@ class TestCreateProject:
project.run_context_add_context(
context_type="Model", context_identifier="Annotation", target_view="MODEL_VIEW", parent="model"
).should_be_called()
project.run_context_add_context(
context_type="Model", context_identifier="Annotation", target_view="PLAN_VIEW", parent="model"
).should_be_called()
project.run_context_add_context(
context_type="Model", context_identifier="Profile", target_view="ELEVATION_VIEW", parent="model"
).should_be_called()
@@ -75,6 +67,20 @@ class TestCreateProject:
context_type="Plan", context_identifier="Annotation", target_view="PLAN_VIEW", parent="plan"
).should_be_called()
def test_create_an_ifc4_project(self, ifc, project):
ifc.get().should_be_called().will_return(None)
ifc.run("project.create_file", version="IFC4").should_be_called().will_return("ifc")
ifc.set("ifc").should_be_called()
project.create_empty("My Project").should_be_called().will_return("project")
project.create_empty("My Site").should_be_called().will_return("site")
project.create_empty("My Building").should_be_called().will_return("building")
project.create_empty("My Storey").should_be_called().will_return("storey")
project.run_root_assign_class(obj="project", ifc_class="IfcProject").should_be_called()
project.run_unit_assign_scene_units().should_be_called()
self.check_contexts(project)
project.run_root_assign_class(obj="site", ifc_class="IfcSite", context="body").should_be_called()
project.run_root_assign_class(obj="building", ifc_class="IfcBuilding", context="body").should_be_called()
project.run_root_assign_class(obj="storey", ifc_class="IfcBuildingStorey", context="body").should_be_called()
@@ -104,42 +110,7 @@ class TestCreateProject:
project.run_root_assign_class(obj="project", ifc_class="IfcProject").should_be_called()
project.run_unit_assign_scene_units().should_be_called()
project.run_context_add_context(
context_type="Model", context_identifier="", target_view="", parent=0
).should_be_called().will_return("model")
project.run_context_add_context(
context_type="Model", context_identifier="Body", target_view="MODEL_VIEW", parent="model"
).should_be_called().will_return("body")
project.run_context_add_context(
context_type="Model", context_identifier="Axis", target_view="GRAPH_VIEW", parent="model"
).should_be_called()
project.run_context_add_context(
context_type="Model", context_identifier="Box", target_view="MODEL_VIEW", parent="model"
).should_be_called()
project.run_context_add_context(
context_type="Model", context_identifier="Annotation", target_view="SECTION_VIEW", parent="model"
).should_be_called()
project.run_context_add_context(
context_type="Model", context_identifier="Annotation", target_view="ELEVATION_VIEW", parent="model"
).should_be_called()
project.run_context_add_context(
context_type="Model", context_identifier="Annotation", target_view="MODEL_VIEW", parent="model"
).should_be_called()
project.run_context_add_context(
context_type="Model", context_identifier="Profile", target_view="ELEVATION_VIEW", parent="model"
).should_be_called()
project.run_context_add_context(
context_type="Plan", context_identifier="", target_view="", parent=0
).should_be_called().will_return("plan")
project.run_context_add_context(
context_type="Plan", context_identifier="Axis", target_view="GRAPH_VIEW", parent="plan"
).should_be_called()
project.run_context_add_context(
context_type="Plan", context_identifier="Body", target_view="PLAN_VIEW", parent="plan"
).should_be_called()
project.run_context_add_context(
context_type="Plan", context_identifier="Annotation", target_view="PLAN_VIEW", parent="plan"
).should_be_called()
self.check_contexts(project)
project.run_root_assign_class(obj="site", ifc_class="IfcSite", context="body").should_be_called()
project.run_root_assign_class(obj="building", ifc_class="IfcBuilding", context="body").should_be_called()
@@ -179,42 +150,7 @@ class TestCreateProject:
project.run_root_assign_class(obj="project", ifc_class="IfcProject").should_be_called()
project.run_unit_assign_scene_units().should_be_called()
project.run_context_add_context(
context_type="Model", context_identifier="", target_view="", parent=0
).should_be_called().will_return("model")
project.run_context_add_context(
context_type="Model", context_identifier="Body", target_view="MODEL_VIEW", parent="model"
).should_be_called().will_return("body")
project.run_context_add_context(
context_type="Model", context_identifier="Axis", target_view="GRAPH_VIEW", parent="model"
).should_be_called()
project.run_context_add_context(
context_type="Model", context_identifier="Box", target_view="MODEL_VIEW", parent="model"
).should_be_called()
project.run_context_add_context(
context_type="Model", context_identifier="Annotation", target_view="SECTION_VIEW", parent="model"
).should_be_called()
project.run_context_add_context(
context_type="Model", context_identifier="Annotation", target_view="ELEVATION_VIEW", parent="model"
).should_be_called()
project.run_context_add_context(
context_type="Model", context_identifier="Annotation", target_view="MODEL_VIEW", parent="model"
).should_be_called()
project.run_context_add_context(
context_type="Model", context_identifier="Profile", target_view="ELEVATION_VIEW", parent="model"
).should_be_called()
project.run_context_add_context(
context_type="Plan", context_identifier="", target_view="", parent=0
).should_be_called().will_return("plan")
project.run_context_add_context(
context_type="Plan", context_identifier="Axis", target_view="GRAPH_VIEW", parent="plan"
).should_be_called()
project.run_context_add_context(
context_type="Plan", context_identifier="Body", target_view="PLAN_VIEW", parent="plan"
).should_be_called()
project.run_context_add_context(
context_type="Plan", context_identifier="Annotation", target_view="PLAN_VIEW", parent="plan"
).should_be_called()
self.check_contexts(project)
project.run_root_assign_class(obj="site", ifc_class="IfcSite", context="body").should_be_called()
project.run_root_assign_class(obj="building", ifc_class="IfcBuilding", context="body").should_be_called()
+27
View File
@@ -753,3 +753,30 @@ class TestUpdateTextValue(NewFile):
# test first literal value is unchanged
assert props.literals[0].attributes["Literal"].string_value == "Literal"
assert ifc.by_type("IfcTextLiteralWithExtent")[0].Literal == "Literal"
class TestDrawingStyles(NewFile):
def setup_project_with_drawing(self):
bpy.ops.bim.create_project()
bpy.ops.bim.load_drawings()
bpy.ops.bim.add_drawing()
ifc = tool.Ifc.get()
drawing = ifc.by_type("IfcAnnotation")[0]
bpy.ops.bim.activate_drawing(drawing=drawing.id())
self.drawing_styles = bpy.context.scene.DocProperties.drawing_styles
def test_drawing_styles_not_loaded_if_underlay_is_inactive(self):
self.setup_project_with_drawing()
assert len(self.drawing_styles) == 0
def test_drawing_styles_loaded_on_underlay_enabled(self):
self.setup_project_with_drawing()
bpy.context.scene.camera.data.BIMCameraProperties.has_underlay = True
assert len(self.drawing_styles) == 3
def test_drawing_styles_reload(self):
self.setup_project_with_drawing()
bpy.ops.bim.reload_drawing_styles()
assert len(self.drawing_styles) == 3
+6 -1
View File
@@ -195,12 +195,13 @@ class IfcDataGetter:
class Ifc5Dwriter:
def __init__(self, file=None, output=None):
def __init__(self, file=None, output=None, cost_schedule=None):
self.output = output
if isinstance(file, str):
self.file = ifcopenshell.open(file)
else:
self.file = file
self.cost_schedule = cost_schedule
self.cost_schedules = []
self.sheet_data = {}
self.column_indexes = []
@@ -220,6 +221,10 @@ class Ifc5Dwriter:
self.used_names = []
for i in range(26):
self.column_indexes.append("ABCDEFGHIJKLMNOPQRSTUVWXYZ"[i % 26])
if self.cost_schedule:
for cost_schedule in self.cost_schedules:
if cost_schedule.id() != self.cost_schedule.id():
self.cost_schedules.remove(cost_schedule)
for cost_schedule in self.cost_schedules:
sheet_id = cost_schedule.id()
self.sheet_data[sheet_id] = {}
+1 -1
View File
@@ -45,7 +45,7 @@ except:
class IfcAttributeSetter:
@staticmethod
def set_element_key(ifc_file, element, key, value):
if key == "type" and element.is_a() != value:
if key == "class" and element.is_a() != value:
return ifcopenshell.util.schema.reassign_class(ifc_file, element, value)
if hasattr(element, key):
setattr(element, key, value)
@@ -20,6 +20,7 @@ related information in ``shape.geometry``:
import ifcopenshell
import ifcopenshell.geom
import ifcopenshell.util.shape
ifc_file = ifcopenshell.open('model.ifc')
element = ifc_file.by_type('IfcWall')[0]
@@ -55,19 +56,25 @@ related information in ``shape.geometry``:
# Objects are never scaled, so the scale factor of the matrix is always 1.
matrix = shape.transformation.matrix.data
# Indices of vertices per triangle face e.g. [f1v1, f1v2, f1v3, f2v1, f2v2, f2v3, ...]
faces = shape.geometry.faces
# Indices of vertices per edge e.g. [e1v1, e1v2, e2v1, e2v2, ...]
edges = shape.geometry.edges
# For convenience, you might want the matrix as a nested numpy array, so you can do matrix math.
matrix = ifcopenshell.util.shape.get_shape_matrix(shape)
# X Y Z of vertices in flattened list e.g. [v1x, v1y, v1z, v2x, v2y, v2z, ...]
verts = shape.geometry.verts
# Indices of vertices per edge e.g. [e1v1, e1v2, e2v1, e2v2, ...]
edges = shape.geometry.edges
# Indices of vertices per triangle face e.g. [f1v1, f1v2, f1v3, f2v1, f2v2, f2v3, ...]
faces = shape.geometry.faces
# Since the lists are flattened, you may prefer to group them like so depending on your geometry kernel
grouped_verts = [[verts[i], verts[i + 1], verts[i + 2]] for i in range(0, len(verts), 3)]
grouped_edges = [[edges[i], edges[i + 1]] for i in range(0, len(edges), 2)]
grouped_faces = [[faces[i], faces[i + 1], faces[i + 2]] for i in range(0, len(faces), 3)]
# A nested numpy array e.g. [[v1x, v1y, v1z], [v2x, v2y, v2z], ...]
grouped_verts = ifcopenshell.util.shape.get_vertices(shape.geometry)
# A nested numpy array e.g. [[e1v1, e1v2], [e2v1, e2v2], ...]
grouped_edges = ifcopenshell.util.shape.get_edges(shape.geometry)
# A nested numpy array e.g. [[f1v1, f1v2, f1v3], [f2v1, f2v2, f2v3], ...]
grouped_faces = ifcopenshell.util.shape.get_faces(shape.geometry)
# A list of styles that are relevant to this shape
styles = shape.geometry.materials
@@ -96,7 +96,7 @@ class Usecase:
break
if decomposes and decomposes == is_decomposed_by:
return
return decomposes
container = ifcopenshell.util.element.get_container(self.settings["product"], should_get_direct=True)
if container:
@@ -0,0 +1,110 @@
# IfcOpenShell - IFC toolkit and geometry engine
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# This file is part of IfcOpenShell.
#
# IfcOpenShell is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# IfcOpenShell is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell
import ifcopenshell.util.element
class Usecase:
def __init__(self, file, cost_item=None):
"""Copies all cost items and related relationships
The following relationships are also duplicated:
* The copy will have the same attributes and property sets as the original cost item
* The copy will be assigned to the parent cost schedule
* The copy will have duplicated nested cost items
:param cost_item: The cost item to be duplicated
:type cost_item: ifcopenshell.entity_instance.entity_instance
:return: The duplicated cost item or the list of duplicated cost items if the latter has children
:rtype: ifcopenshell.entity_instance.entity_instance or list of ifcopenshell.entity_instance.entity_instance
Example:
.. code:: python
# We have a cost item
cost_item = CostItem(name="Design new feature", deadline="2023-03-01")
# And now we have two
duplicated_cost_item = project.duplicate_cost_item(cost_item)
"""
self.file = file
self.settings = {"cost_item": cost_item}
def execute(self):
self.new_cost_items = []
self.duplicate_cost_item(self.settings["cost_item"])
def duplicate_cost_item(self, cost_item):
new_cost_item = ifcopenshell.util.element.copy_deep(self.file, cost_item)
self.new_cost_items.append(new_cost_item)
self.copy_indirect_attributes(cost_item, new_cost_item)
return new_cost_item
def copy_indirect_attributes(self, from_element, to_element):
for inverse in self.file.get_inverse(from_element):
if inverse.is_a("IfcRelDefinesByProperties"):
inverse = ifcopenshell.util.element.copy(self.file, inverse)
inverse.RelatedObjects = [to_element]
pset = ifcopenshell.util.element.copy_deep(
self.file, inverse.RelatingPropertyDefinition
)
inverse.RelatingPropertyDefinition = pset
elif inverse.is_a("IfcRelNests") and inverse.RelatingObject == from_element:
nested_cost_items = [e for e in inverse.RelatedObjects]
if nested_cost_items:
new_cost_items = []
for cost_item in nested_cost_items:
new_cost_item = self.duplicate_cost_item(cost_item)
new_cost_items.append(new_cost_item)
inverse = ifcopenshell.util.element.copy(self.file, inverse)
inverse.RelatingObject = to_element
inverse.RelatedObjects = new_cost_items
for cost_item in new_cost_items:
ifcopenshell.api.run(
"nest.unassign_object", self.file, related_object=cost_item
)
rel = ifcopenshell.api.run(
"nest.assign_object",
self.file,
related_object=cost_item,
relating_object=to_element,
)
# elif inverse.is_a("IfcRelAssignsToProduct"):
# continue
# to_element.IsDefinedBy = inverse.RelatingOrder
# elif inverse.is_a("IfcRelAssignsToProcess"):
# to_element.IsDefinedBy = inverse.RelatingProcess
# elif inverse.is_a("IfcRelAssignsToResource"):
# continue
# elif inverse.is_a("IfcRelAssignsToControl"):
# continue
# elif inverse.is_a("IfcRelDefinesByObject"):
# continue
else:
for i, value in enumerate(inverse):
if value == from_element:
new_inverse = ifcopenshell.util.element.copy(self.file, inverse)
new_inverse[i] = to_element
elif isinstance(value, (tuple, list)) and from_element in value:
new_value = list(value)
new_value.append(to_element)
inverse[i] = new_value
@@ -0,0 +1,29 @@
# IfcOpenShell - IFC toolkit and geometry engine
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# This file is part of IfcOpenShell.
#
# IfcOpenShell is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# IfcOpenShell is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
class Usecase:
def __init__(self, file, material=None, attributes=None):
"""Edits the attributes of an IfcMaterial"""
self.file = file
self.settings = {"material": material, "attributes": attributes or {}}
def execute(self):
for name, value in self.settings["attributes"].items():
setattr(self.settings["material"], name, value)
@@ -64,7 +64,7 @@ class Usecase:
become IfcLabel, float values will become IfcReal, booleans will
become IfcBoolean, and integers will become IfcInteger. If more
control is desired, you may explicitly specify IFC data objects
directly.
directly. Note that provided `properties` might be mutated in the process.
:type properties: dict
:param pset_template: If a property set template is provided, this will
be used to determine data types. If no user-defined template is
@@ -76,6 +76,13 @@ class Usecase:
)
elif not inverse.RelatedObjects:
self.file.remove(inverse)
elif self.settings["task"] in inverse.RelatedObjects:
related_objects = list(inverse.RelatedObjects)
related_objects.remove(self.settings["task"])
if not related_objects:
self.file.remove(inverse)
else:
inverse.RelatedObjects = related_objects
elif inverse.is_a("IfcRelAssignsToControl"):
if (
inverse.RelatingControl == self.settings["task"]
@@ -186,7 +186,6 @@ def string_to_duration(duration_string):
def parse_duration(value):
print("parsing duration", value)
if not value:
return None
if isinstance(value, str):
@@ -68,7 +68,7 @@ def reassign_class(ifc_file, element, new_class):
try:
declaration = schema.declaration_by_name(new_class)
except:
print(f"Class of {element} could not be changed to {new_class} as the class does not exist")
raise Exception(f"Class of {element} could not be changed to {new_class} as the class does not exist")
info = element.get_info()
@@ -66,6 +66,11 @@ def get_z(geometry):
return max(z_values) - min(z_values)
def get_shape_matrix(shape):
m = shape.transformation.matrix.data
return np.array(([m[0], m[3], m[6], m[9]], [m[1], m[4], m[7], m[10]], [m[2], m[5], m[8], m[11]], [0, 0, 0, 1]))
def get_bbox_centroid(geometry):
x_values = [geometry.verts[i] for i in range(0, len(geometry.verts), 3)]
y_values = [geometry.verts[i + 1] for i in range(0, len(geometry.verts), 3)]
@@ -89,9 +94,7 @@ def get_element_bbox_centroid(element, geometry):
def get_shape_bbox_centroid(shape, geometry):
centroid = get_bbox_centroid(geometry)
m = shape.transformation.matrix.data
mat = np.array(([m[0], m[3], m[6], m[9]], [m[1], m[4], m[7], m[10]], [m[2], m[5], m[8], m[11]], [0, 0, 0, 1]))
return (mat @ np.array([*centroid, 1.0]))[0:3]
return (get_shape_matrix(shape) @ np.array([*centroid, 1.0]))[0:3]
def get_vertices(geometry):
@@ -110,14 +113,15 @@ def get_faces(geometry):
def get_shape_vertices(shape, geometry):
m = shape.transformation.matrix.data
mat = np.array(([m[0], m[3], m[6], m[9]], [m[1], m[4], m[7], m[10]], [m[2], m[5], m[8], m[11]], [0, 0, 0, 1]))
verts = get_vertices(geometry)
mat = get_shape_matrix(shape)
return np.array([mat @ np.array([verts[i], verts[i + 1], verts[i + 2]]) for i in range(0, len(verts), 3)])
def get_element_vertices(element, geometry):
verts = get_vertices(geometry)
if not element.ObjectPlacement or not element.ObjectPlacement.is_a("IfcLocalPlacement"):
return get_shape_vertices(geometry)
return verts
mat = ifcopenshell.util.placement.get_local_placement(element.ObjectPlacement)
return np.array([mat @ np.array([verts[i], verts[i + 1], verts[i + 2]]) for i in range(0, len(verts), 3)])
+3 -1
View File
@@ -58,6 +58,8 @@ class Facet:
return results
def parse(self, xml):
setattr(self, "minOccurs", 1)
setattr(self, "maxOccurs", 1)
for name, value in xml.items():
name = name.replace("@", "")
if isinstance(value, dict) and "simpleValue" in value.keys():
@@ -893,7 +895,7 @@ class ClassificationResult(Result):
return "The entity has no classification"
elif self.reason["type"] == "VALUE":
return f"The references \"{str(self.reason['actual'])}\" do not match the requirements"
elif self.reason["type"] == "system":
elif self.reason["type"] == "SYSTEM":
return f"The systems \"{str(self.reason['actual'])}\" do not match the requirements"
elif self.reason["type"] == "PROHIBITED":
return f"The classification should not have met the requirement"