diff --git a/src/blenderbim/blenderbim/bim/data/assets/shading_styles.json b/src/blenderbim/blenderbim/bim/data/assets/shading_styles.json index 01dd9742b3..e89fb84f10 100644 --- a/src/blenderbim/blenderbim/bim/data/assets/shading_styles.json +++ b/src/blenderbim/blenderbim/bim/data/assets/shading_styles.json @@ -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 + } } } \ No newline at end of file diff --git a/src/blenderbim/blenderbim/bim/handler.py b/src/blenderbim/blenderbim/bim/handler.py index 4456fc34fa..e16e10ab88 100644 --- a/src/blenderbim/blenderbim/bim/handler.py +++ b/src/blenderbim/blenderbim/bim/handler.py @@ -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 diff --git a/src/blenderbim/blenderbim/bim/import_ifc.py b/src/blenderbim/blenderbim/bim/import_ifc.py index 77f75dc17b..1399f001a0 100644 --- a/src/blenderbim/blenderbim/bim/import_ifc.py +++ b/src/blenderbim/blenderbim/bim/import_ifc.py @@ -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"): diff --git a/src/blenderbim/blenderbim/bim/module/aggregate/operator.py b/src/blenderbim/blenderbim/bim/module/aggregate/operator.py index dd3d767377..9ab00d205c 100644 --- a/src/blenderbim/blenderbim/bim/module/aggregate/operator.py +++ b/src/blenderbim/blenderbim/bim/module/aggregate/operator.py @@ -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): diff --git a/src/blenderbim/blenderbim/bim/module/cost/__init__.py b/src/blenderbim/blenderbim/bim/module/cost/__init__.py index b05e41a5ad..e11a6ef850 100644 --- a/src/blenderbim/blenderbim/bim/module/cost/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/cost/__init__.py @@ -76,6 +76,7 @@ classes = ( operator.LoadCostItemTaskQuantities, operator.LoadCostItemResourceQuantities, operator.ChangeParentCostItem, + operator.CopyCostItem, prop.CostItem, prop.CostItemQuantity, prop.CostItemType, diff --git a/src/blenderbim/blenderbim/bim/module/cost/operator.py b/src/blenderbim/blenderbim/bim/module/cost/operator.py index 29c2fc1141..c76178e320 100644 --- a/src/blenderbim/blenderbim/bim/module/cost/operator.py +++ b/src/blenderbim/blenderbim/bim/module/cost/operator.py @@ -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"} diff --git a/src/blenderbim/blenderbim/bim/module/cost/prop.py b/src/blenderbim/blenderbim/bim/module/cost/prop.py index cdd5e08e92..eddc816e14 100644 --- a/src/blenderbim/blenderbim/bim/module/cost/prop.py +++ b/src/blenderbim/blenderbim/bim/module/cost/prop.py @@ -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) diff --git a/src/blenderbim/blenderbim/bim/module/cost/ui.py b/src/blenderbim/blenderbim/bim/module/cost/ui.py index f0aa85c2e5..db39cc51f1 100644 --- a/src/blenderbim/blenderbim/bim/module/cost/ui.py +++ b/src/blenderbim/blenderbim/bim/module/cost/ui.py @@ -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", diff --git a/src/blenderbim/blenderbim/bim/module/drawing/annotation.py b/src/blenderbim/blenderbim/bim/module/drawing/annotation.py index 8eae554b15..ac6130e604 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/annotation.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/annotation.py @@ -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 diff --git a/src/blenderbim/blenderbim/bim/module/drawing/decoration.py b/src/blenderbim/blenderbim/bim/module/drawing/decoration.py index d4e30af08f..3a18f10166 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/decoration.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/decoration.py @@ -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) diff --git a/src/blenderbim/blenderbim/bim/module/drawing/helper.py b/src/blenderbim/blenderbim/bim/module/drawing/helper.py index dfc2df727c..edc6ce4a60 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/helper.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/helper.py @@ -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: diff --git a/src/blenderbim/blenderbim/bim/module/drawing/operator.py b/src/blenderbim/blenderbim/bim/module/drawing/operator.py index 05e3297276..52b3a9631e 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/operator.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/operator.py @@ -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] diff --git a/src/blenderbim/blenderbim/bim/module/drawing/prop.py b/src/blenderbim/blenderbim/bim/module/drawing/prop.py index 71e9ec8c9b..a89e83ffff 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/prop.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/prop.py @@ -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( diff --git a/src/blenderbim/blenderbim/bim/module/drawing/svgwriter.py b/src/blenderbim/blenderbim/bim/module/drawing/svgwriter.py index 21451b9c94..b94ea9f37d 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/svgwriter.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/svgwriter.py @@ -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, diff --git a/src/blenderbim/blenderbim/bim/module/drawing/ui.py b/src/blenderbim/blenderbim/bim/module/drawing/ui.py index b4a9ba8723..24d10c8add 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/ui.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/ui.py @@ -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") diff --git a/src/blenderbim/blenderbim/bim/module/ifcgit/__init__.py b/src/blenderbim/blenderbim/bim/module/ifcgit/__init__.py index eba6e3af33..5fde698280 100644 --- a/src/blenderbim/blenderbim/bim/module/ifcgit/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/ifcgit/__init__.py @@ -28,7 +28,9 @@ classes = ( operator.DiscardUncommitted, operator.DisplayRevision, operator.DisplayUncommitted, + operator.Fetch, operator.Merge, + operator.Push, operator.RefreshGit, operator.SwitchRevision, prop.IfcGitTag, diff --git a/src/blenderbim/blenderbim/bim/module/ifcgit/data.py b/src/blenderbim/blenderbim/bim/module/ifcgit/data.py index d9d99fefd3..4373aa8df5 100644 --- a/src/blenderbim/blenderbim/bim/module/ifcgit/data.py +++ b/src/blenderbim/blenderbim/bim/module/ifcgit/data.py @@ -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() diff --git a/src/blenderbim/blenderbim/bim/module/ifcgit/operator.py b/src/blenderbim/blenderbim/bim/module/ifcgit/operator.py index 1df3063755..1253c5bbf5 100644 --- a/src/blenderbim/blenderbim/bim/module/ifcgit/operator.py +++ b/src/blenderbim/blenderbim/bim/module/ifcgit/operator.py @@ -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"} diff --git a/src/blenderbim/blenderbim/bim/module/ifcgit/prop.py b/src/blenderbim/blenderbim/bim/module/ifcgit/prop.py index 88fcd68aaa..7a1655be24 100644 --- a/src/blenderbim/blenderbim/bim/module/ifcgit/prop.py +++ b/src/blenderbim/blenderbim/bim/module/ifcgit/prop.py @@ -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"), diff --git a/src/blenderbim/blenderbim/bim/module/ifcgit/ui.py b/src/blenderbim/blenderbim/bim/module/ifcgit/ui.py index f9b67cfc7c..e9a8ba5dd8 100644 --- a/src/blenderbim/blenderbim/bim/module/ifcgit/ui.py +++ b/src/blenderbim/blenderbim/bim/module/ifcgit/ui.py @@ -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""" diff --git a/src/blenderbim/blenderbim/bim/module/material/__init__.py b/src/blenderbim/blenderbim/bim/module/material/__init__.py index 4a2b06b8dc..bb67ee7a96 100644 --- a/src/blenderbim/blenderbim/bim/module/material/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/material/__init__.py @@ -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, diff --git a/src/blenderbim/blenderbim/bim/module/material/data.py b/src/blenderbim/blenderbim/bim/module/material/data.py index 81be795f0d..9e1d4154b9 100644 --- a/src/blenderbim/blenderbim/bim/module/material/data.py +++ b/src/blenderbim/blenderbim/bim/module/material/data.py @@ -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] diff --git a/src/blenderbim/blenderbim/bim/module/material/operator.py b/src/blenderbim/blenderbim/bim/module/material/operator.py index 5992a33b5c..756b2d8e9b 100644 --- a/src/blenderbim/blenderbim/bim/module/material/operator.py +++ b/src/blenderbim/blenderbim/bim/module/material/operator.py @@ -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): diff --git a/src/blenderbim/blenderbim/bim/module/material/prop.py b/src/blenderbim/blenderbim/bim/module/material/prop.py index 9abce3f47c..f09075cdf0 100644 --- a/src/blenderbim/blenderbim/bim/module/material/prop.py +++ b/src/blenderbim/blenderbim/bim/module/material/prop.py @@ -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) diff --git a/src/blenderbim/blenderbim/bim/module/material/ui.py b/src/blenderbim/blenderbim/bim/module/material/ui.py index a413c426b3..da1490aae1 100644 --- a/src/blenderbim/blenderbim/bim/module/material/ui.py +++ b/src/blenderbim/blenderbim/bim/module/material/ui.py @@ -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): diff --git a/src/blenderbim/blenderbim/bim/module/model/__init__.py b/src/blenderbim/blenderbim/bim/module/model/__init__.py index 509df1ecde..5a38a99e01 100644 --- a/src/blenderbim/blenderbim/bim/module/model/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/model/__init__.py @@ -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, diff --git a/src/blenderbim/blenderbim/bim/module/model/array.py b/src/blenderbim/blenderbim/bim/module/model/array.py index 42851123f3..e5cc88bf1a 100644 --- a/src/blenderbim/blenderbim/bim/module/model/array.py +++ b/src/blenderbim/blenderbim/bim/module/model/array.py @@ -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" diff --git a/src/blenderbim/blenderbim/bim/module/model/pie.py b/src/blenderbim/blenderbim/bim/module/model/pie.py index 15e8c6bd10..5c15b2c3d9 100644 --- a/src/blenderbim/blenderbim/bim/module/model/pie.py +++ b/src/blenderbim/blenderbim/bim/module/model/pie.py @@ -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): diff --git a/src/blenderbim/blenderbim/bim/module/model/prop.py b/src/blenderbim/blenderbim/bim/module/model/prop.py index 0e6773431d..66c45333cb 100644 --- a/src/blenderbim/blenderbim/bim/module/model/prop.py +++ b/src/blenderbim/blenderbim/bim/module/model/prop.py @@ -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): diff --git a/src/blenderbim/blenderbim/bim/module/model/roof.py b/src/blenderbim/blenderbim/bim/module/model/roof.py index 239a9218d2..7df1d3bf4e 100644 --- a/src/blenderbim/blenderbim/bim/module/model/roof.py +++ b/src/blenderbim/blenderbim/bim/module/model/roof.py @@ -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") diff --git a/src/blenderbim/blenderbim/bim/module/model/slab.py b/src/blenderbim/blenderbim/bim/module/model/slab.py index 8b9efc0ef7..7c9bbceebc 100644 --- a/src/blenderbim/blenderbim/bim/module/model/slab.py +++ b/src/blenderbim/blenderbim/bim/module/model/slab.py @@ -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): diff --git a/src/blenderbim/blenderbim/bim/module/model/stair.py b/src/blenderbim/blenderbim/bim/module/model/stair.py index 5e60e595fb..3868743785 100644 --- a/src/blenderbim/blenderbim/bim/module/model/stair.py +++ b/src/blenderbim/blenderbim/bim/module/model/stair.py @@ -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"} diff --git a/src/blenderbim/blenderbim/bim/module/model/ui.py b/src/blenderbim/blenderbim/bim/module/model/ui.py index d3b1515e82..5e3a0239d9 100644 --- a/src/blenderbim/blenderbim/bim/module/model/ui.py +++ b/src/blenderbim/blenderbim/bim/module/model/ui.py @@ -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"] diff --git a/src/blenderbim/blenderbim/bim/module/model/wall.py b/src/blenderbim/blenderbim/bim/module/model/wall.py index 174fd97e2d..0a43c1d021 100644 --- a/src/blenderbim/blenderbim/bim/module/model/wall.py +++ b/src/blenderbim/blenderbim/bim/module/model/wall.py @@ -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]) diff --git a/src/blenderbim/blenderbim/bim/module/model/window.py b/src/blenderbim/blenderbim/bim/module/model/window.py index a9bcb7a153..e3aeccfc4e 100644 --- a/src/blenderbim/blenderbim/bim/module/model/window.py +++ b/src/blenderbim/blenderbim/bim/module/model/window.py @@ -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"} diff --git a/src/blenderbim/blenderbim/bim/module/model/workspace.py b/src/blenderbim/blenderbim/bim/module/model/workspace.py index a658af8316..ddb1368eaf 100644 --- a/src/blenderbim/blenderbim/bim/module/model/workspace.py +++ b/src/blenderbim/blenderbim/bim/module/model/workspace.py @@ -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 diff --git a/src/blenderbim/blenderbim/bim/module/root/data.py b/src/blenderbim/blenderbim/bim/module/root/data.py index 123440327c..4d4d9c4a8e 100644 --- a/src/blenderbim/blenderbim/bim/module/root/data.py +++ b/src/blenderbim/blenderbim/bim/module/root/data.py @@ -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 diff --git a/src/blenderbim/blenderbim/bim/module/root/operator.py b/src/blenderbim/blenderbim/bim/module/root/operator.py index 89ff3c7fca..ad960d650c 100644 --- a/src/blenderbim/blenderbim/bim/module/root/operator.py +++ b/src/blenderbim/blenderbim/bim/module/root/operator.py @@ -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" diff --git a/src/blenderbim/blenderbim/bim/module/root/ui.py b/src/blenderbim/blenderbim/bim/module/root/ui.py index df06f4bae0..2004f7ed08 100644 --- a/src/blenderbim/blenderbim/bim/module/root/ui.py +++ b/src/blenderbim/blenderbim/bim/module/root/ui.py @@ -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) diff --git a/src/blenderbim/blenderbim/bim/module/sequence/ui.py b/src/blenderbim/blenderbim/bim/module/sequence/ui.py index ca667dc6bf..79e7191b6a 100644 --- a/src/blenderbim/blenderbim/bim/module/sequence/ui.py +++ b/src/blenderbim/blenderbim/bim/module/sequence/ui.py @@ -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) diff --git a/src/blenderbim/blenderbim/bim/module/type/operator.py b/src/blenderbim/blenderbim/bim/module/type/operator.py index dc41a89912..b7f825d10f 100644 --- a/src/blenderbim/blenderbim/bim/module/type/operator.py +++ b/src/blenderbim/blenderbim/bim/module/type/operator.py @@ -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"} diff --git a/src/blenderbim/blenderbim/bim/module/type/prop.py b/src/blenderbim/blenderbim/bim/module/type/prop.py index 1b41f3af59..7c9775cd79 100644 --- a/src/blenderbim/blenderbim/bim/module/type/prop.py +++ b/src/blenderbim/blenderbim/bim/module/type/prop.py @@ -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, + ) diff --git a/src/blenderbim/blenderbim/bim/module/type/ui.py b/src/blenderbim/blenderbim/bim/module/type/ui.py index d2a1d8b2ae..d7feaf34bb 100644 --- a/src/blenderbim/blenderbim/bim/module/type/ui.py +++ b/src/blenderbim/blenderbim/bim/module/type/ui.py @@ -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="") diff --git a/src/blenderbim/blenderbim/bim/module/void/operator.py b/src/blenderbim/blenderbim/bim/module/void/operator.py index 3559dea9c9..7cd620d754 100644 --- a/src/blenderbim/blenderbim/bim/module/void/operator.py +++ b/src/blenderbim/blenderbim/bim/module/void/operator.py @@ -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) diff --git a/src/blenderbim/blenderbim/core/aggregate.py b/src/blenderbim/blenderbim/core/aggregate.py index b9e6f8ff7e..5445a5647c 100644 --- a/src/blenderbim/blenderbim/core/aggregate.py +++ b/src/blenderbim/blenderbim/core/aggregate.py @@ -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): diff --git a/src/blenderbim/blenderbim/core/cost.py b/src/blenderbim/blenderbim/core/cost.py index 94f911099f..e96541e52b 100644 --- a/src/blenderbim/blenderbim/core/cost.py +++ b/src/blenderbim/blenderbim/core/cost.py @@ -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() \ No newline at end of file + 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() diff --git a/src/blenderbim/blenderbim/core/drawing.py b/src/blenderbim/blenderbim/core/drawing.py index 873c554ba0..0fd08697ec 100644 --- a/src/blenderbim/blenderbim/core/drawing.py +++ b/src/blenderbim/blenderbim/core/drawing.py @@ -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) diff --git a/src/blenderbim/blenderbim/core/material.py b/src/blenderbim/blenderbim/core/material.py index f22698ee34..ca0c872199 100644 --- a/src/blenderbim/blenderbim/core/material.py +++ b/src/blenderbim/blenderbim/core/material.py @@ -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() diff --git a/src/blenderbim/blenderbim/core/project.py b/src/blenderbim/blenderbim/core/project.py index fc5ccd1d66..920cd5716f 100644 --- a/src/blenderbim/blenderbim/core/project.py +++ b/src/blenderbim/blenderbim/core/project.py @@ -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 ) diff --git a/src/blenderbim/blenderbim/core/sequence.py b/src/blenderbim/blenderbim/core/sequence.py index f25bf70c7e..4a2b625cf1 100644 --- a/src/blenderbim/blenderbim/core/sequence.py +++ b/src/blenderbim/blenderbim/core/sequence.py @@ -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) diff --git a/src/blenderbim/blenderbim/core/tool.py b/src/blenderbim/blenderbim/core/tool.py index 5c1a55aa7e..1a5dcefd5d 100644 --- a/src/blenderbim/blenderbim/core/tool.py +++ b/src/blenderbim/blenderbim/core/tool.py @@ -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 diff --git a/src/blenderbim/blenderbim/tool/aggregate.py b/src/blenderbim/blenderbim/tool/aggregate.py index 2da8d0b484..e6827f691c 100644 --- a/src/blenderbim/blenderbim/tool/aggregate.py +++ b/src/blenderbim/blenderbim/tool/aggregate.py @@ -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 \ No newline at end of file diff --git a/src/blenderbim/blenderbim/tool/cost.py b/src/blenderbim/blenderbim/tool/cost.py index 2ebdcd7f1f..5079729cec 100644 --- a/src/blenderbim/blenderbim/tool/cost.py +++ b/src/blenderbim/blenderbim/tool/cost.py @@ -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 diff --git a/src/blenderbim/blenderbim/tool/drawing.py b/src/blenderbim/blenderbim/tool/drawing.py index ee0b4f3590..c10abf326f 100644 --- a/src/blenderbim/blenderbim/tool/drawing.py +++ b/src/blenderbim/blenderbim/tool/drawing.py @@ -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") diff --git a/src/blenderbim/blenderbim/tool/ifcgit.py b/src/blenderbim/blenderbim/tool/ifcgit.py index f37d9289ba..e72d58a5b7 100644 --- a/src/blenderbim/blenderbim/tool/ifcgit.py +++ b/src/blenderbim/blenderbim/tool/ifcgit.py @@ -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 diff --git a/src/blenderbim/blenderbim/tool/material.py b/src/blenderbim/blenderbim/tool/material.py index 60bf3e32e6..27aaab96f5 100644 --- a/src/blenderbim/blenderbim/tool/material.py +++ b/src/blenderbim/blenderbim/tool/material.py @@ -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 = "" diff --git a/src/blenderbim/blenderbim/tool/model.py b/src/blenderbim/blenderbim/tool/model.py index a8d8b4dc93..7c27eafc74 100644 --- a/src/blenderbim/blenderbim/tool/model.py +++ b/src/blenderbim/blenderbim/tool/model.py @@ -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"]) diff --git a/src/blenderbim/blenderbim/tool/sequence.py b/src/blenderbim/blenderbim/tool/sequence.py index fe6d4d136b..1fd9b465e7 100644 --- a/src/blenderbim/blenderbim/tool/sequence.py +++ b/src/blenderbim/blenderbim/tool/sequence.py @@ -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)) diff --git a/src/blenderbim/docs/users/git_support.rst b/src/blenderbim/docs/users/git_support.rst index 0379750d4f..5ddc8ef235 100644 --- a/src/blenderbim/docs/users/git_support.rst +++ b/src/blenderbim/docs/users/git_support.rst @@ -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 --------------------- diff --git a/src/blenderbim/test/bim/feature/drawing.feature b/src/blenderbim/test/bim/feature/drawing.feature index 012aafdc80..e2ca0a2b74 100644 --- a/src/blenderbim/test/bim/feature/drawing.feature +++ b/src/blenderbim/test/bim/feature/drawing.feature @@ -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 diff --git a/src/blenderbim/test/core/test_drawing.py b/src/blenderbim/test/core/test_drawing.py index e35be93fe0..4f901d7a62 100644 --- a/src/blenderbim/test/core/test_drawing.py +++ b/src/blenderbim/test/core/test_drawing.py @@ -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") diff --git a/src/blenderbim/test/core/test_project.py b/src/blenderbim/test/core/test_project.py index b062bcec4e..199bd455cd 100644 --- a/src/blenderbim/test/core/test_project.py +++ b/src/blenderbim/test/core/test_project.py @@ -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() diff --git a/src/blenderbim/test/tool/test_drawing.py b/src/blenderbim/test/tool/test_drawing.py index 5aa2a96860..1c0c38526d 100644 --- a/src/blenderbim/test/tool/test_drawing.py +++ b/src/blenderbim/test/tool/test_drawing.py @@ -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 + + \ No newline at end of file diff --git a/src/ifc5d/ifc5d/ifc5Dspreadsheet.py b/src/ifc5d/ifc5d/ifc5Dspreadsheet.py index 546f2bd2c1..3fa9e9b1c5 100644 --- a/src/ifc5d/ifc5d/ifc5Dspreadsheet.py +++ b/src/ifc5d/ifc5d/ifc5Dspreadsheet.py @@ -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] = {} diff --git a/src/ifccsv/ifccsv.py b/src/ifccsv/ifccsv.py index 8fc4adb5a1..e415f4bad2 100755 --- a/src/ifccsv/ifccsv.py +++ b/src/ifccsv/ifccsv.py @@ -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) diff --git a/src/ifcopenshell-python/docs/ifcopenshell-python/geometry_processing.rst b/src/ifcopenshell-python/docs/ifcopenshell-python/geometry_processing.rst index 20ee185476..5bd4131902 100644 --- a/src/ifcopenshell-python/docs/ifcopenshell-python/geometry_processing.rst +++ b/src/ifcopenshell-python/docs/ifcopenshell-python/geometry_processing.rst @@ -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 diff --git a/src/ifcopenshell-python/ifcopenshell/api/aggregate/assign_object.py b/src/ifcopenshell-python/ifcopenshell/api/aggregate/assign_object.py index 10ec5076b3..f8a1c3c261 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/aggregate/assign_object.py +++ b/src/ifcopenshell-python/ifcopenshell/api/aggregate/assign_object.py @@ -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: diff --git a/src/ifcopenshell-python/ifcopenshell/api/cost/copy_cost_item.py b/src/ifcopenshell-python/ifcopenshell/api/cost/copy_cost_item.py new file mode 100644 index 0000000000..55c4660b9f --- /dev/null +++ b/src/ifcopenshell-python/ifcopenshell/api/cost/copy_cost_item.py @@ -0,0 +1,110 @@ +# IfcOpenShell - IFC toolkit and geometry engine +# Copyright (C) 2021 Dion Moult +# +# 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 . + +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 \ No newline at end of file diff --git a/src/ifcopenshell-python/ifcopenshell/api/material/edit_material.py b/src/ifcopenshell-python/ifcopenshell/api/material/edit_material.py new file mode 100644 index 0000000000..c712af15ac --- /dev/null +++ b/src/ifcopenshell-python/ifcopenshell/api/material/edit_material.py @@ -0,0 +1,29 @@ +# IfcOpenShell - IFC toolkit and geometry engine +# Copyright (C) 2021 Dion Moult +# +# 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 . + + +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) diff --git a/src/ifcopenshell-python/ifcopenshell/api/pset/edit_pset.py b/src/ifcopenshell-python/ifcopenshell/api/pset/edit_pset.py index 37b269d433..46fee7b392 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/pset/edit_pset.py +++ b/src/ifcopenshell-python/ifcopenshell/api/pset/edit_pset.py @@ -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 diff --git a/src/ifcopenshell-python/ifcopenshell/api/sequence/remove_task.py b/src/ifcopenshell-python/ifcopenshell/api/sequence/remove_task.py index 485d2c23ea..a7e7708b08 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/sequence/remove_task.py +++ b/src/ifcopenshell-python/ifcopenshell/api/sequence/remove_task.py @@ -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"] diff --git a/src/ifcopenshell-python/ifcopenshell/util/date.py b/src/ifcopenshell-python/ifcopenshell/util/date.py index d99e23f632..8f7c45c054 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/date.py +++ b/src/ifcopenshell-python/ifcopenshell/util/date.py @@ -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): diff --git a/src/ifcopenshell-python/ifcopenshell/util/schema.py b/src/ifcopenshell-python/ifcopenshell/util/schema.py index 41c717ebe2..a5d545b513 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/schema.py +++ b/src/ifcopenshell-python/ifcopenshell/util/schema.py @@ -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() diff --git a/src/ifcopenshell-python/ifcopenshell/util/shape.py b/src/ifcopenshell-python/ifcopenshell/util/shape.py index 7de09a4f2c..9053838679 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/shape.py +++ b/src/ifcopenshell-python/ifcopenshell/util/shape.py @@ -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)]) diff --git a/src/ifctester/ifctester/facet.py b/src/ifctester/ifctester/facet.py index 5e47dba907..f2b6396605 100644 --- a/src/ifctester/ifctester/facet.py +++ b/src/ifctester/ifctester/facet.py @@ -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"