Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender-addons.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore3
-rw-r--r--add_advanced_objects_menu/__init__.py40
-rw-r--r--add_advanced_objects_menu/add_light_template.py10
-rw-r--r--add_advanced_objects_menu/arrange_on_curve.py4
-rw-r--r--add_advanced_objects_menu/copy2.py2
-rw-r--r--add_advanced_objects_menu/mesh_easylattice.py4
-rw-r--r--add_advanced_objects_menu/pixelate_3d.py2
-rw-r--r--add_advanced_objects_menu/random_box_structure.py10
-rw-r--r--add_advanced_objects_menu/rope_alpha.py2
-rw-r--r--add_advanced_objects_menu/scene_objects_bi.py6
-rw-r--r--add_advanced_objects_menu/scene_objects_cycles.py6
-rw-r--r--add_advanced_objects_menu/scene_texture_render.py2
-rw-r--r--add_advanced_objects_menu/trilighting.py6
-rw-r--r--add_advanced_objects_panels/drop_to_ground.py6
-rw-r--r--add_advanced_objects_panels/object_laplace_lightning.py2
-rw-r--r--add_advanced_objects_panels/unfold_transition.py6
-rw-r--r--add_curve_extra_objects/__init__.py90
-rw-r--r--add_curve_extra_objects/add_curve_aceous_galore.py189
-rw-r--r--add_curve_extra_objects/add_curve_braid.py40
-rw-r--r--add_curve_extra_objects/add_curve_celtic_links.py20
-rw-r--r--add_curve_extra_objects/add_curve_curly.py20
-rw-r--r--add_curve_extra_objects/add_curve_simple.py627
-rw-r--r--add_curve_extra_objects/add_curve_spirals.py65
-rw-r--r--add_curve_extra_objects/add_curve_spirofit_bouncespline.py158
-rw-r--r--add_curve_extra_objects/add_curve_torus_knots.py127
-rw-r--r--add_curve_extra_objects/add_surface_plane_cone.py51
-rw-r--r--add_curve_extra_objects/beveltaper_curve.py38
-rw-r--r--add_curve_ivygen.py375
-rw-r--r--add_curve_sapling/__init__.py261
-rw-r--r--add_curve_sapling/utils.py78
-rw-r--r--add_mesh_BoltFactory/Boltfactory.py26
-rw-r--r--add_mesh_BoltFactory/__init__.py17
-rw-r--r--add_mesh_BoltFactory/createMesh.py2
-rw-r--r--add_mesh_extra_objects/__init__.py52
-rw-r--r--add_mesh_extra_objects/add_empty_as_parent.py8
-rw-r--r--add_mesh_extra_objects/add_mesh_gears.py6
-rw-r--r--ant_landscape/__init__.py4
-rw-r--r--ant_landscape/ant_functions.py20
-rw-r--r--archimesh/__init__.py24
-rw-r--r--archimesh/achm_door_maker.py4
-rw-r--r--archimesh/achm_lamp_maker.py26
-rw-r--r--archimesh/achm_main_panel.py4
-rw-r--r--archimesh/achm_window_maker.py2
-rw-r--r--archimesh/achm_window_panel.py2
-rw-r--r--archipack/__init__.py221
-rw-r--r--archipack/archipack_2d.py40
-rw-r--r--archipack/archipack_autoboolean.py115
-rw-r--r--archipack/archipack_cutter.py65
-rw-r--r--archipack/archipack_door.py276
-rw-r--r--archipack/archipack_fence.py214
-rw-r--r--archipack/archipack_floor.py185
-rw-r--r--archipack/archipack_gl.py743
-rw-r--r--archipack/archipack_handle.py7
-rw-r--r--archipack/archipack_keymaps.py10
-rw-r--r--archipack/archipack_manipulator.py347
-rw-r--r--archipack/archipack_material.py73
-rw-r--r--archipack/archipack_object.py61
-rw-r--r--archipack/archipack_preset.py32
-rw-r--r--archipack/archipack_reference_point.py45
-rw-r--r--archipack/archipack_rendering.py517
-rw-r--r--archipack/archipack_roof.py366
-rw-r--r--archipack/archipack_slab.py195
-rw-r--r--archipack/archipack_snap.py192
-rw-r--r--archipack/archipack_stair.py244
-rw-r--r--archipack/archipack_thumbs.py198
-rw-r--r--archipack/archipack_truss.py36
-rw-r--r--archipack/archipack_wall2.py387
-rw-r--r--archipack/archipack_window.py362
-rw-r--r--archipack/bmesh_utils.py6
-rw-r--r--blender_id/CHANGELOG.md13
-rw-r--r--blender_id/README.md3
-rw-r--r--blender_id/__init__.py52
-rw-r--r--blender_id/communication.py7
-rw-r--r--bone_selection_sets.py160
-rw-r--r--btrace/bTrace.py18
-rw-r--r--camera_dolly_crane_rigs.py70
-rw-r--r--camera_turnaround.py2
-rw-r--r--curve_simplify.py78
-rw-r--r--depsgraph_debug.py29
-rw-r--r--development_api_navigator.py2
-rw-r--r--development_icon_get.py144
-rw-r--r--development_iskeyfree.py8
-rw-r--r--game_engine_publishing.py576
-rw-r--r--game_engine_save_as_runtime.py258
-rw-r--r--io_anim_acclaim/__init__.py14
-rw-r--r--io_anim_bvh/__init__.py73
-rw-r--r--io_anim_bvh/export_bvh.py8
-rw-r--r--io_anim_bvh/import_bvh.py24
-rw-r--r--io_anim_c3d/__init__.py8
-rw-r--r--io_anim_camera.py26
-rw-r--r--io_anim_nuke_chan/__init__.py8
-rw-r--r--io_blend_utils/__init__.py4
-rw-r--r--io_blend_utils/blend/blendfile_path_walker.py2
-rw-r--r--io_blend_utils/blender_bam-unpacked.whl/bam/blend/blendfile_path_walker.py2
-rw-r--r--io_coat3D/__init__.py1477
-rw-r--r--io_coat3D/coat.py626
-rw-r--r--io_coat3D/tex.py640
-rw-r--r--io_convert_image_to_mesh_img/__init__.py4
-rw-r--r--io_curve_svg/__init__.py16
-rw-r--r--io_curve_svg/import_svg.py75
-rw-r--r--io_export_after_effects.py6
-rw-r--r--io_export_dxf/__init__.py4
-rw-r--r--io_export_dxf/export_dxf.py6
-rw-r--r--io_export_dxf/operator.py10
-rw-r--r--io_export_dxf/primitive_exporters/lamp_exporter.py2
-rw-r--r--io_export_paper_model.py8
-rw-r--r--io_export_pc2.py130
-rw-r--r--io_export_unreal_psk_psa.py18
-rw-r--r--io_import_dxf/__init__.py4
-rw-r--r--io_import_dxf/dxfimport/do.py28
-rw-r--r--io_import_gimp_image_to_scene.py6
-rw-r--r--io_import_images_as_planes.py163
-rw-r--r--io_import_scene_lwo.py4
-rw-r--r--io_import_scene_unreal_psa_psk.py6
-rw-r--r--io_mesh_pdb/__init__.py14
-rw-r--r--io_mesh_pdb/import_pdb.py32
-rw-r--r--io_mesh_ply/__init__.py38
-rw-r--r--io_mesh_ply/export_ply.py40
-rw-r--r--io_mesh_ply/import_ply.py80
-rw-r--r--io_mesh_raw/__init__.py8
-rw-r--r--io_mesh_stl/__init__.py65
-rw-r--r--io_mesh_stl/blender_utils.py37
-rw-r--r--io_mesh_uv_layout/__init__.py299
-rw-r--r--io_mesh_uv_layout/export_uv_eps.py118
-rw-r--r--io_mesh_uv_layout/export_uv_png.py223
-rw-r--r--io_mesh_uv_layout/export_uv_svg.py74
-rw-r--r--io_online_sketchfab/__init__.py4
-rw-r--r--io_online_sketchfab/pack_for_export.py4
-rw-r--r--io_scene_3ds/__init__.py19
-rw-r--r--io_scene_3ds/import_3ds.py52
-rw-r--r--io_scene_fbx/__init__.py147
-rw-r--r--io_scene_fbx/export_fbx_bin.py1245
-rw-r--r--io_scene_fbx/fbx_utils.py151
-rw-r--r--io_scene_fbx/import_fbx.py482
-rwxr-xr-xio_scene_gltf2/__init__.py520
-rwxr-xr-xio_scene_gltf2/blender/com/gltf2_blender_conversion.py42
-rwxr-xr-xio_scene_gltf2/blender/com/gltf2_blender_data_path.py28
-rwxr-xr-xio_scene_gltf2/blender/com/gltf2_blender_image.py32
-rwxr-xr-xio_scene_gltf2/blender/com/gltf2_blender_image_util.py121
-rwxr-xr-xio_scene_gltf2/blender/com/gltf2_blender_json.py38
-rwxr-xr-xio_scene_gltf2/blender/com/gltf2_blender_material_helpers.py59
-rwxr-xr-xio_scene_gltf2/blender/com/gltf2_blender_math.py172
-rwxr-xr-xio_scene_gltf2/blender/exp/gltf2_blender_animate.py638
-rwxr-xr-xio_scene_gltf2/blender/exp/gltf2_blender_export.py128
-rwxr-xr-xio_scene_gltf2/blender/exp/gltf2_blender_export_keys.py63
-rwxr-xr-xio_scene_gltf2/blender/exp/gltf2_blender_extract.py1116
-rwxr-xr-xio_scene_gltf2/blender/exp/gltf2_blender_filter.py455
-rwxr-xr-xio_scene_gltf2/blender/exp/gltf2_blender_gather.py69
-rwxr-xr-xio_scene_gltf2/blender/exp/gltf2_blender_gather_animation_channel_target.py84
-rwxr-xr-xio_scene_gltf2/blender/exp/gltf2_blender_gather_animation_channels.py131
-rwxr-xr-xio_scene_gltf2/blender/exp/gltf2_blender_gather_animation_sampler_keyframes.py200
-rwxr-xr-xio_scene_gltf2/blender/exp/gltf2_blender_gather_animation_samplers.py169
-rwxr-xr-xio_scene_gltf2/blender/exp/gltf2_blender_gather_animations.py169
-rwxr-xr-xio_scene_gltf2/blender/exp/gltf2_blender_gather_cache.py60
-rwxr-xr-xio_scene_gltf2/blender/exp/gltf2_blender_gather_cameras.py122
-rwxr-xr-xio_scene_gltf2/blender/exp/gltf2_blender_gather_image.py177
-rwxr-xr-xio_scene_gltf2/blender/exp/gltf2_blender_gather_joints.py80
-rw-r--r--io_scene_gltf2/blender/exp/gltf2_blender_gather_light_spots.py45
-rw-r--r--io_scene_gltf2/blender/exp/gltf2_blender_gather_lights.py132
-rwxr-xr-xio_scene_gltf2/blender/exp/gltf2_blender_gather_material_normal_texture_info_class.py131
-rwxr-xr-xio_scene_gltf2/blender/exp/gltf2_blender_gather_material_occlusion_texture_info_class.py113
-rwxr-xr-xio_scene_gltf2/blender/exp/gltf2_blender_gather_materials.py154
-rwxr-xr-xio_scene_gltf2/blender/exp/gltf2_blender_gather_materials_pbr_metallic_roughness.py103
-rwxr-xr-xio_scene_gltf2/blender/exp/gltf2_blender_gather_mesh.py94
-rwxr-xr-xio_scene_gltf2/blender/exp/gltf2_blender_gather_nodes.py240
-rwxr-xr-xio_scene_gltf2/blender/exp/gltf2_blender_gather_primitive_attributes.py217
-rwxr-xr-xio_scene_gltf2/blender/exp/gltf2_blender_gather_primitives.py197
-rwxr-xr-xio_scene_gltf2/blender/exp/gltf2_blender_gather_sampler.py98
-rwxr-xr-xio_scene_gltf2/blender/exp/gltf2_blender_gather_skins.py150
-rwxr-xr-xio_scene_gltf2/blender/exp/gltf2_blender_gather_texture.py100
-rwxr-xr-xio_scene_gltf2/blender/exp/gltf2_blender_gather_texture_info.py119
-rwxr-xr-xio_scene_gltf2/blender/exp/gltf2_blender_generate_extras.py64
-rwxr-xr-xio_scene_gltf2/blender/exp/gltf2_blender_get.py423
-rwxr-xr-xio_scene_gltf2/blender/exp/gltf2_blender_gltf2_exporter.py301
-rwxr-xr-xio_scene_gltf2/blender/exp/gltf2_blender_search_node_tree.py98
-rwxr-xr-xio_scene_gltf2/blender/exp/gltf2_blender_search_scene.py89
-rwxr-xr-xio_scene_gltf2/blender/exp/gltf2_blender_utils.py68
-rwxr-xr-xio_scene_gltf2/blender/imp/gltf2_blender_KHR_materials_pbrSpecularGlossiness.py327
-rwxr-xr-xio_scene_gltf2/blender/imp/gltf2_blender_animation.py35
-rwxr-xr-xio_scene_gltf2/blender/imp/gltf2_blender_animation_bone.py194
-rwxr-xr-xio_scene_gltf2/blender/imp/gltf2_blender_animation_node.py142
-rwxr-xr-xio_scene_gltf2/blender/imp/gltf2_blender_camera.py47
-rwxr-xr-xio_scene_gltf2/blender/imp/gltf2_blender_gltf.py225
-rwxr-xr-xio_scene_gltf2/blender/imp/gltf2_blender_image.py101
-rwxr-xr-xio_scene_gltf2/blender/imp/gltf2_blender_map_emissive.py110
-rwxr-xr-xio_scene_gltf2/blender/imp/gltf2_blender_map_normal.py89
-rwxr-xr-xio_scene_gltf2/blender/imp/gltf2_blender_map_occlusion.py41
-rwxr-xr-xio_scene_gltf2/blender/imp/gltf2_blender_material.py156
-rwxr-xr-xio_scene_gltf2/blender/imp/gltf2_blender_mesh.py175
-rwxr-xr-xio_scene_gltf2/blender/imp/gltf2_blender_node.py184
-rwxr-xr-xio_scene_gltf2/blender/imp/gltf2_blender_pbrMetallicRoughness.py284
-rwxr-xr-xio_scene_gltf2/blender/imp/gltf2_blender_primitive.py181
-rwxr-xr-xio_scene_gltf2/blender/imp/gltf2_blender_scene.py98
-rwxr-xr-xio_scene_gltf2/blender/imp/gltf2_blender_skin.py209
-rwxr-xr-xio_scene_gltf2/blender/imp/gltf2_blender_texture.py39
-rwxr-xr-xio_scene_gltf2/io/__init__.py16
-rwxr-xr-xio_scene_gltf2/io/com/gltf2_io.py1200
-rwxr-xr-xio_scene_gltf2/io/com/gltf2_io_constants.py132
-rwxr-xr-xio_scene_gltf2/io/com/gltf2_io_debug.py143
-rw-r--r--io_scene_gltf2/io/com/gltf2_io_extensions.py38
-rwxr-xr-xio_scene_gltf2/io/com/gltf2_io_functional.py41
-rwxr-xr-xio_scene_gltf2/io/com/gltf2_io_image.py154
-rw-r--r--io_scene_gltf2/io/com/gltf2_io_lights_punctual.py76
-rwxr-xr-xio_scene_gltf2/io/com/gltf2_io_trs.py68
-rwxr-xr-xio_scene_gltf2/io/exp/gltf2_io_binary_data.py36
-rwxr-xr-xio_scene_gltf2/io/exp/gltf2_io_buffer.py61
-rwxr-xr-xio_scene_gltf2/io/exp/gltf2_io_export.py97
-rwxr-xr-xio_scene_gltf2/io/exp/gltf2_io_get.py316
-rwxr-xr-xio_scene_gltf2/io/exp/gltf2_io_image_data.py136
-rwxr-xr-xio_scene_gltf2/io/imp/__init__.py16
-rwxr-xr-xio_scene_gltf2/io/imp/gltf2_io_binary.py178
-rwxr-xr-xio_scene_gltf2/io/imp/gltf2_io_gltf.py199
-rw-r--r--io_scene_ms3d/__init__.py12
-rw-r--r--io_scene_ms3d/ms3d_import.py8
-rw-r--r--io_scene_ms3d/ms3d_ui.py2
-rw-r--r--io_scene_obj/__init__.py88
-rw-r--r--io_scene_obj/export_obj.py231
-rw-r--r--io_scene_obj/import_obj.py523
-rw-r--r--io_scene_vrml2/__init__.py12
-rw-r--r--io_scene_vrml2/export_vrml2.py2
-rw-r--r--io_scene_x/__init__.py4
-rw-r--r--io_scene_x3d/__init__.py19
-rw-r--r--io_scene_x3d/export_x3d.py128
-rw-r--r--io_scene_x3d/import_x3d.py14
-rw-r--r--io_shape_mdd/__init__.py37
-rw-r--r--io_shape_mdd/export_mdd.py12
-rw-r--r--light_field_tools/light_field_tools.py4
-rw-r--r--materials_utils/__init__.py2
-rw-r--r--measureit/__init__.py95
-rw-r--r--measureit/measureit_geometry.py175
-rw-r--r--measureit/measureit_main.py517
-rw-r--r--measureit/measureit_render.py349
-rw-r--r--mesh_carver.py54
-rw-r--r--mesh_custom_normals_tools.py90
-rw-r--r--mesh_extra_tools/face_inset_fillet.py2
-rw-r--r--mesh_extra_tools/mesh_extrude_and_reshape.py4
-rw-r--r--mesh_extra_tools/mesh_fastloop.py2
-rw-r--r--mesh_extra_tools/mesh_filletplus.py2
-rw-r--r--mesh_extra_tools/mesh_offset_edges.py4
-rw-r--r--mesh_extra_tools/mesh_vertex_chamfer.py2
-rw-r--r--mesh_f2.py43
-rw-r--r--mesh_looptools.py336
-rw-r--r--mesh_snap_utilities_line.py1191
-rw-r--r--mesh_snap_utilities_line/__init__.py112
-rw-r--r--mesh_snap_utilities_line/common_classes.py510
-rw-r--r--mesh_snap_utilities_line/common_utilities.py269
-rw-r--r--mesh_snap_utilities_line/icons/ops.mesh.make_line.datbin0 -> 1340 bytes
-rw-r--r--mesh_snap_utilities_line/ops_line.py553
-rw-r--r--mesh_snap_utilities_line/preferences.py113
-rw-r--r--mesh_snap_utilities_line/snap_context_l/__init__.py (renamed from modules/snap_context/__init__.py)246
-rw-r--r--mesh_snap_utilities_line/snap_context_l/mesh_drawing.py412
-rw-r--r--mesh_snap_utilities_line/snap_context_l/shaders/ID_color_frag.glsl (renamed from modules/snap_context/resources/primitive_id_frag.glsl)14
-rw-r--r--mesh_snap_utilities_line/snap_context_l/shaders/ID_color_vert.glsl25
-rw-r--r--mesh_snap_utilities_line/snap_context_l/utils_projection.py (renamed from modules/snap_context/utils_projection.py)14
-rw-r--r--mesh_tissue/tessellate_numpy.py2
-rw-r--r--mesh_tissue/uv_to_mesh.py2
-rw-r--r--modules/rna_manual_reference.py57
-rw-r--r--modules/snap_context/mesh_drawing.py518
-rw-r--r--modules/snap_context/resources/3D_vert.glsl26
-rw-r--r--modules/snap_context/utils_shader.py85
-rw-r--r--netrender/ui.py4
-rw-r--r--node_wrangler.py269
-rw-r--r--object_boolean_tools.py54
-rw-r--r--object_cloud_gen.py16
-rw-r--r--object_edit_linked.py165
-rw-r--r--object_fracture/__init__.py10
-rw-r--r--object_fracture/fracture_ops.py6
-rw-r--r--object_fracture_cell/__init__.py10
-rw-r--r--object_grease_scatter.py401
-rw-r--r--object_print3d_utils/__init__.py160
-rw-r--r--object_print3d_utils/export.py16
-rw-r--r--object_print3d_utils/mesh_helpers.py46
-rw-r--r--object_print3d_utils/operators.py76
-rw-r--r--object_print3d_utils/ui.py18
-rw-r--r--object_scatter/__init__.py41
-rw-r--r--object_scatter/operator.py508
-rw-r--r--object_scatter/ui.py138
-rw-r--r--oscurart_tools/__init__.py2
-rw-r--r--oscurart_tools/oscurart_files.py4
-rw-r--r--oscurart_tools/oscurart_meshes.py9
-rw-r--r--oscurart_tools/oscurart_objects.py28
-rw-r--r--oscurart_tools/oscurart_overrides.py6
-rw-r--r--oscurart_tools/oscurart_render.py2
-rw-r--r--paint_palette.py6
-rw-r--r--pie_menus_official/__init__.py38
-rw-r--r--pie_menus_official/pie_manipulator_of.py12
-rw-r--r--pie_menus_official/pie_pivot_of.py4
-rw-r--r--pie_menus_official/pie_view_of.py2
-rw-r--r--render_copy_settings/__init__.py6
-rw-r--r--render_copy_settings/data.py28
-rw-r--r--render_copy_settings/operator.py8
-rw-r--r--render_copy_settings/panel.py10
-rw-r--r--render_copy_settings/presets.py3
-rw-r--r--render_povray/__init__.py12
-rw-r--r--render_povray/nodes.py14
-rw-r--r--render_povray/primitives.py2
-rw-r--r--render_povray/render.py397
-rw-r--r--render_povray/ui.py121
-rw-r--r--rigify/__init__.py308
-rw-r--r--rigify/generate.py117
-rw-r--r--rigify/legacy/__init__.py8
-rw-r--r--rigify/legacy/generate.py46
-rw-r--r--rigify/legacy/metarig_menu.py4
-rw-r--r--rigify/legacy/rig_ui_pitchipoy_template.py92
-rw-r--r--rigify/legacy/rig_ui_template.py82
-rw-r--r--rigify/legacy/rigs/biped/limb_common.py4
-rw-r--r--rigify/legacy/rigs/pitchipoy/limbs/limb_utils.py4
-rw-r--r--rigify/legacy/rigs/pitchipoy/super_face.py7
-rw-r--r--rigify/legacy/rigs/pitchipoy/super_torso_turbo.py4
-rw-r--r--rigify/legacy/rigs/pitchipoy/super_widgets.py1
-rw-r--r--rigify/legacy/ui.py24
-rw-r--r--rigify/legacy/utils.py56
-rw-r--r--rigify/metarig_menu.py22
-rw-r--r--rigify/metarigs/Animals/bird.py58
-rw-r--r--rigify/metarigs/Animals/cat.py58
-rw-r--r--rigify/metarigs/Animals/horse.py58
-rw-r--r--rigify/metarigs/Animals/shark.py58
-rw-r--r--rigify/metarigs/Animals/wolf.py58
-rw-r--r--rigify/metarigs/Basic/basic_human.py58
-rw-r--r--rigify/metarigs/Basic/basic_quadruped.py58
-rw-r--r--rigify/metarigs/human.py58
-rw-r--r--rigify/rig_ui_template.py143
-rw-r--r--rigify/rigs/experimental/super_chain.py4
-rw-r--r--rigify/rigs/faces/super_face.py7
-rw-r--r--rigify/rigs/limbs/limb_utils.py4
-rw-r--r--rigify/rigs/spines/super_spine.py4
-rw-r--r--rigify/rigs/widgets.py1
-rw-r--r--rigify/rot_mode.py77
-rw-r--r--rigify/ui.py239
-rw-r--r--rigify/utils.py60
-rw-r--r--sequencer_kinoraw_tools/ui.py2
-rw-r--r--space_clip_editor_refine_solution.py30
-rw-r--r--space_view3d_3d_navigation.py8
-rw-r--r--space_view3d_copy_attributes.py312
-rw-r--r--space_view3d_display_tools/__init__.py10
-rw-r--r--space_view3d_display_tools/display.py12
-rw-r--r--space_view3d_display_tools/fast_navigate.py16
-rw-r--r--space_view3d_display_tools/select_tools.py10
-rw-r--r--space_view3d_display_tools/selection_restrictor.py22
-rw-r--r--space_view3d_display_tools/useless_tools.py4
-rw-r--r--space_view3d_math_vis/__init__.py22
-rw-r--r--space_view3d_math_vis/draw.py280
-rw-r--r--space_view3d_pie_menus/__init__.py125
-rw-r--r--space_view3d_pie_menus/pie_align_menu.py16
-rw-r--r--space_view3d_pie_menus/pie_animation_menu.py2
-rw-r--r--space_view3d_pie_menus/pie_apply_transform_menu.py20
-rw-r--r--space_view3d_pie_menus/pie_cursor.py12
-rw-r--r--space_view3d_pie_menus/pie_delete_menu.py8
-rw-r--r--space_view3d_pie_menus/pie_editor_switch_menu.py18
-rw-r--r--space_view3d_pie_menus/pie_manipulator_menu.py138
-rw-r--r--space_view3d_pie_menus/pie_modes_menu.py8
-rw-r--r--space_view3d_pie_menus/pie_orientation_menu.py6
-rw-r--r--space_view3d_pie_menus/pie_origin.py18
-rw-r--r--space_view3d_pie_menus/pie_pivot_point_menu.py2
-rw-r--r--space_view3d_pie_menus/pie_proportional_menu.py2
-rw-r--r--space_view3d_pie_menus/pie_save_open_menu.py14
-rw-r--r--space_view3d_pie_menus/pie_sculpt_menu.py4
-rw-r--r--space_view3d_pie_menus/pie_select_menu.py18
-rw-r--r--space_view3d_pie_menus/pie_shading_menu.py4
-rw-r--r--space_view3d_pie_menus/pie_snap_menu.py38
-rw-r--r--space_view3d_pie_menus/pie_views_numpad_menu.py20
-rw-r--r--space_view3d_spacebar_menu.py203
-rw-r--r--system_blend_info.py8
-rw-r--r--system_demo_mode/__init__.py189
-rw-r--r--system_demo_mode/config.py2
-rw-r--r--system_demo_mode/demo_mode.py56
-rw-r--r--system_property_chart.py4
-rw-r--r--ui_layer_manager.py8
-rw-r--r--ui_translate/__init__.py30
-rw-r--r--ui_translate/edit_translation.py295
-rw-r--r--ui_translate/settings.py79
-rw-r--r--ui_translate/update_addon.py98
-rw-r--r--ui_translate/update_svn.py52
-rw-r--r--ui_translate/update_ui.py182
-rw-r--r--uv_texture_atlas.py42
375 files changed, 27400 insertions, 14896 deletions
diff --git a/.gitignore b/.gitignore
index 80fb6e48..665a0414 100644
--- a/.gitignore
+++ b/.gitignore
@@ -15,3 +15,6 @@ __pycache__/
Thumbs.db
ehthumbs.db
Desktop.ini
+
+# external addons
+cycles/
diff --git a/add_advanced_objects_menu/__init__.py b/add_advanced_objects_menu/__init__.py
index 42a33445..32bbbd7f 100644
--- a/add_advanced_objects_menu/__init__.py
+++ b/add_advanced_objects_menu/__init__.py
@@ -92,8 +92,8 @@ from bpy.props import (
# Define the "Scenes" menu
-class INFO_MT_scene_elements_add(Menu):
- bl_idname = "INFO_MT_scene_elements"
+class VIEW3D_MT_scene_elements_add(Menu):
+ bl_idname = "VIEW3D_MT_scene_elements"
bl_label = "Test Scenes"
def draw(self, context):
@@ -108,8 +108,8 @@ class INFO_MT_scene_elements_add(Menu):
# Define the "Lights" menu
-class INFO_MT_mesh_lamps_add(Menu):
- bl_idname = "INFO_MT_scene_lamps"
+class VIEW3D_MT_mesh_lights_add(Menu):
+ bl_idname = "VIEW3D_MT_scene_lights"
bl_label = "Lighting Sets"
def draw(self, context):
@@ -122,8 +122,8 @@ class INFO_MT_mesh_lamps_add(Menu):
# Define the "Chains" menu
-class INFO_MT_mesh_chain_add(Menu):
- bl_idname = "INFO_MT_mesh_chain"
+class VIEW3D_MT_mesh_chain_add(Menu):
+ bl_idname = "VIEW3D_MT_mesh_chain"
bl_label = "Chains"
def draw(self, context):
@@ -134,15 +134,15 @@ class INFO_MT_mesh_chain_add(Menu):
# Define the "Array" Menu
-class INFO_MT_array_mods_add(Menu):
- bl_idname = "INFO_MT_array_mods"
+class VIEW3D_MT_array_mods_add(Menu):
+ bl_idname = "VIEW3D_MT_array_mods"
bl_label = "Array Mods"
def draw(self, context):
layout = self.layout
layout.operator_context = 'INVOKE_REGION_WIN'
- layout.menu("INFO_MT_mesh_chain", icon="LINKED")
+ layout.menu("VIEW3D_MT_mesh_chain", icon="LINKED")
layout.operator("objects.circle_array_operator",
text="Circle Array", icon="MOD_ARRAY")
@@ -153,8 +153,8 @@ class INFO_MT_array_mods_add(Menu):
# Define the "Blocks" Menu
-class INFO_MT_quick_blocks_add(Menu):
- bl_idname = "INFO_MT_quick_tools"
+class VIEW3D_MT_quick_blocks_add(Menu):
+ bl_idname = "VIEW3D_MT_quick_tools"
bl_label = "Block Tools"
def draw(self, context):
@@ -171,8 +171,8 @@ class INFO_MT_quick_blocks_add(Menu):
# Define the "Phsysics Tools" Menu
-class INFO_MT_Physics_tools_add(Menu):
- bl_idname = "INFO_MT_physics_tools"
+class VIEW3D_MT_Physics_tools_add(Menu):
+ bl_idname = "VIEW3D_MT_physics_tools"
bl_label = "Physics Tools"
def draw(self, context):
@@ -190,12 +190,12 @@ def menu(self, context):
layout = self.layout
layout.operator_context = 'INVOKE_REGION_WIN'
self.layout.separator()
- self.layout.menu("INFO_MT_scene_elements", icon="SCENE_DATA")
- self.layout.menu("INFO_MT_scene_lamps", icon="LAMP_SPOT")
+ self.layout.menu("VIEW3D_MT_scene_elements", icon="SCENE_DATA")
+ self.layout.menu("VIEW3D_MT_scene_lights", icon="LIGHT_SPOT")
self.layout.separator()
- self.layout.menu("INFO_MT_array_mods", icon="MOD_ARRAY")
- self.layout.menu("INFO_MT_quick_tools", icon="MOD_BUILD")
- self.layout.menu("INFO_MT_physics_tools", icon="PHYSICS")
+ self.layout.menu("VIEW3D_MT_array_mods", icon="MOD_ARRAY")
+ self.layout.menu("VIEW3D_MT_quick_tools", icon="MOD_BUILD")
+ self.layout.menu("VIEW3D_MT_physics_tools", icon="PHYSICS")
# Addons Preferences
@@ -524,7 +524,7 @@ def register():
)
# Add "Extras" menu to the "Add" menu
- bpy.types.INFO_MT_add.append(menu)
+ bpy.types.VIEW3D_MT_add.append(menu)
try:
bpy.types.VIEW3D_MT_AddMenu.append(menu)
except:
@@ -533,7 +533,7 @@ def register():
def unregister():
# Remove "Extras" menu from the "Add" menu.
- bpy.types.INFO_MT_add.remove(menu)
+ bpy.types.VIEW3D_MT_add.remove(menu)
try:
bpy.types.VIEW3D_MT_AddMenu.remove(menu)
except:
diff --git a/add_advanced_objects_menu/add_light_template.py b/add_advanced_objects_menu/add_light_template.py
index 9e2c139f..5b4fbc86 100644
--- a/add_advanced_objects_menu/add_light_template.py
+++ b/add_advanced_objects_menu/add_light_template.py
@@ -5,10 +5,10 @@ from bpy.types import Operator
from bpy.props import BoolProperty
-def add_lamps(self, context):
+def add_lights(self, context):
if self.bKeyLight:
- keyLight = bpy.data.lamps.new(name="Key_Light", type="SPOT")
+ keyLight = bpy.data.lights.new(name="Key_Light", type="SPOT")
ob = bpy.data.objects.new("Key_Light", keyLight)
constraint = ob.constraints.new(type='COPY_LOCATION')
constraint.use_offset = True
@@ -23,7 +23,7 @@ def add_lamps(self, context):
ob.rotation_euler[2] = -0.785398
if self.bFillLight:
- fillLight = bpy.data.lamps.new(name="Fill_Light", type="SPOT")
+ fillLight = bpy.data.lights.new(name="Fill_Light", type="SPOT")
ob = bpy.data.objects.new("Fill_Light", fillLight)
constraint = ob.constraints.new(type='COPY_LOCATION')
constraint.use_offset = True
@@ -39,7 +39,7 @@ def add_lamps(self, context):
ob.data.energy = 0.3
if self.bBackLight:
- backLight = bpy.data.lamps.new(name="Back_Light", type="SPOT")
+ backLight = bpy.data.lights.new(name="Back_Light", type="SPOT")
ob = bpy.data.objects.new("Back_Light", backLight)
constraint = ob.constraints.new(type='COPY_LOCATION')
constraint.use_offset = True
@@ -121,7 +121,7 @@ class OBJECT_OT_add_light_template(Operator):
self.target = context.active_object
self.camera = context.scene.camera
- add_lamps(self, context)
+ add_lights(self, context)
except Exception as e:
self.report({'WARNING'},
diff --git a/add_advanced_objects_menu/arrange_on_curve.py b/add_advanced_objects_menu/arrange_on_curve.py
index c292e341..bfed37a7 100644
--- a/add_advanced_objects_menu/arrange_on_curve.py
+++ b/add_advanced_objects_menu/arrange_on_curve.py
@@ -59,7 +59,7 @@ class PanelDupliCurve(Panel):
elif adv_obj.arrange_c_select_type == 'G':
layout.column(align=True).prop_search(
adv_obj, "arrange_c_obj_arranjar",
- bpy.data, "groups"
+ bpy.data, "collections"
)
if context.object.type == 'CURVE':
layout.operator("object.arranjar_numa_curva", text="Arrange Objects")
@@ -238,7 +238,7 @@ class DupliCurve(Operator):
elif adv_obj.arrange_c_select_type == 'O':
G_Objeto = bpy.data.objects[adv_obj.arrange_c_obj_arranjar],
elif adv_obj.arrange_c_select_type == 'G':
- G_Objeto = bpy.data.groups[adv_obj.arrange_c_obj_arranjar].objects
+ G_Objeto = bpy.data.collections[adv_obj.arrange_c_obj_arranjar].objects
yawMatrix = mathutils.Matrix.Rotation(self.Yaw, 4, 'X')
pitchMatrix = mathutils.Matrix.Rotation(self.Pitch, 4, 'Y')
diff --git a/add_advanced_objects_menu/copy2.py b/add_advanced_objects_menu/copy2.py
index 489f6dee..9a94ac1d 100644
--- a/add_advanced_objects_menu/copy2.py
+++ b/add_advanced_objects_menu/copy2.py
@@ -168,7 +168,7 @@ def copy_to_from(scene, to_obj, from_obj, copymode, axes, edgescale, scale):
if copymode == 'E':
# don't pass edgescalling to object types that cannot be scaled
- if from_obj.type in ["CAMERA", "LAMP", "EMPTY", "ARMATURE", "SPEAKER", "META"]:
+ if from_obj.type in ["CAMERA", "LIGHT", "EMPTY", "ARMATURE", "SPEAKER", "META"]:
edgescale = False
edge_copy(scene, to_obj, from_obj, axes, edgescale, scale)
diff --git a/add_advanced_objects_menu/mesh_easylattice.py b/add_advanced_objects_menu/mesh_easylattice.py
index cdb165bd..d2e608e9 100644
--- a/add_advanced_objects_menu/mesh_easylattice.py
+++ b/add_advanced_objects_menu/mesh_easylattice.py
@@ -63,7 +63,7 @@ def createLattice(context, obj, props):
# the rotation comes from the combined obj world
# matrix which was converted to euler pairs
ob.rotation_euler = buildRot_World(obj)
- ob.show_x_ray = True
+ ob.show_in_front = True
# Link object to scene
scn = context.scene
@@ -102,7 +102,7 @@ def createVertexGroup(obj):
if obj.mode == "EDIT":
bpy.ops.object.editmode_toggle()
- group = obj.vertex_groups.new("easy_lattice_group")
+ group = obj.vertex_groups.new(name="easy_lattice_group")
for vert in vertices:
if vert.select is True:
diff --git a/add_advanced_objects_menu/pixelate_3d.py b/add_advanced_objects_menu/pixelate_3d.py
index 3a9fc886..567e882a 100644
--- a/add_advanced_objects_menu/pixelate_3d.py
+++ b/add_advanced_objects_menu/pixelate_3d.py
@@ -28,7 +28,7 @@ def pix(self, obj):
mes.transform(obj.matrix_world)
dup = bpy.data.objects.new('dup', mes)
sce.objects.link(dup)
- dup.dupli_type = 'VERTS'
+ dup.instance_type = 'VERTS'
sce.objects.active = dup
bpy.ops.object.mode_set()
ver = mes.vertices
diff --git a/add_advanced_objects_menu/random_box_structure.py b/add_advanced_objects_menu/random_box_structure.py
index fa4b6497..ead19bd1 100644
--- a/add_advanced_objects_menu/random_box_structure.py
+++ b/add_advanced_objects_menu/random_box_structure.py
@@ -94,8 +94,8 @@ class makestructure(Operator):
rsdchange = self.rsd
oblst = []
uvyes = 0
- bpy.ops.group.create(name='Cubagrouper')
- bpy.ops.group.objects_remove()
+ bpy.ops.collection.create(name='Cubagrouper')
+ bpy.ops.collection.objects_remove()
for ob in bpy.context.selected_objects:
oblst.append(ob)
@@ -107,7 +107,7 @@ class makestructure(Operator):
uvyes = 1
else:
uvyes = 0
- bpy.ops.object.group_link(group='Cubagrouper')
+ bpy.ops.object.collection_link(group='Cubagrouper')
dim = obj.dimensions
rot = obj.rotation_euler
if self.uf is True:
@@ -155,7 +155,7 @@ class makestructure(Operator):
)
bpy.ops.object.mode_set(mode='OBJECT')
select = bpy.context.object # This is used to keep something selected for poll()
- bpy.ops.object.group_link(group='Cubagrouper')
+ bpy.ops.object.collection_link(group='Cubagrouper')
rsdchange += 3
bpy.ops.object.select_grouped(type='GROUP')
bpy.ops.transform.rotate(
@@ -180,7 +180,7 @@ class makestructure(Operator):
if uvyes == 1:
bpy.ops.object.join_uvs()
- bpy.ops.group.objects_remove()
+ bpy.ops.collection.objects_remove()
bpy.context.scene.objects.active = select
if self.dc is True:
diff --git a/add_advanced_objects_menu/rope_alpha.py b/add_advanced_objects_menu/rope_alpha.py
index 2815c7c6..f0bd5dd7 100644
--- a/add_advanced_objects_menu/rope_alpha.py
+++ b/add_advanced_objects_menu/rope_alpha.py
@@ -622,7 +622,7 @@ class BallRope(Operator):
bpy.ops.rigidbody.objects_add(type='ACTIVE')
bpy.context.object.name = "CubeLink"
if n != 0:
- bpy.context.object.draw_type = 'WIRE'
+ bpy.context.object.display_type = 'WIRE'
bpy.context.object.hide_render = True
n += 1
bpy.context.object.scale.z = (longitud * 2) / (segmentos * 2) - separation
diff --git a/add_advanced_objects_menu/scene_objects_bi.py b/add_advanced_objects_menu/scene_objects_bi.py
index f189bb11..94bda4bc 100644
--- a/add_advanced_objects_menu/scene_objects_bi.py
+++ b/add_advanced_objects_menu/scene_objects_bi.py
@@ -43,11 +43,11 @@ class add_BI_scene(Operator):
)
cam = bpy.context.active_object.data
cam.lens = 35
- cam.draw_size = 0.1
+ cam.display_size = 0.1
bpy.ops.view3d.viewnumpad(type='CAMERA')
# add point lamp
- bpy.ops.object.lamp_add(
+ bpy.ops.object.light_add(
type="POINT", location=(4.07625, 1.00545, 5.90386),
rotation=(0.650328, 0.055217, 1.866391)
)
@@ -59,7 +59,7 @@ class add_BI_scene(Operator):
lamp1.use_sphere = True
# add point lamp2
- bpy.ops.object.lamp_add(
+ bpy.ops.object.light_add(
type="POINT", location=(-0.57101, -4.24586, 5.53674),
rotation=(1.571, 0, 0.785)
)
diff --git a/add_advanced_objects_menu/scene_objects_cycles.py b/add_advanced_objects_menu/scene_objects_cycles.py
index 85e85867..7158b02f 100644
--- a/add_advanced_objects_menu/scene_objects_cycles.py
+++ b/add_advanced_objects_menu/scene_objects_cycles.py
@@ -43,11 +43,11 @@ class add_cycles_scene(Operator):
)
cam = bpy.context.active_object.data
cam.lens = 35
- cam.draw_size = 0.1
+ cam.display_size = 0.1
bpy.ops.view3d.viewnumpad(type='CAMERA')
# add point lamp
- bpy.ops.object.lamp_add(
+ bpy.ops.object.light_add(
type="POINT", location=(4.07625, 1.00545, 5.90386),
rotation=(0.650328, 0.055217, 1.866391)
)
@@ -59,7 +59,7 @@ class add_cycles_scene(Operator):
lamp1.use_sphere = True
# add point lamp2
- bpy.ops.object.lamp_add(
+ bpy.ops.object.light_add(
type="POINT", location=(-0.57101, -4.24586, 5.53674),
rotation=(1.571, 0, 0.785)
)
diff --git a/add_advanced_objects_menu/scene_texture_render.py b/add_advanced_objects_menu/scene_texture_render.py
index 02d6490b..0f9ccc2b 100644
--- a/add_advanced_objects_menu/scene_texture_render.py
+++ b/add_advanced_objects_menu/scene_texture_render.py
@@ -43,7 +43,7 @@ class add_texture_scene(Operator):
)
cam = bpy.context.active_object.data
cam.lens = 35
- cam.draw_size = 0.1
+ cam.display_size = 0.1
# add plane
bpy.ops.mesh.primitive_plane_add(enter_editmode=True, location=(0, 0, 0))
diff --git a/add_advanced_objects_menu/trilighting.py b/add_advanced_objects_menu/trilighting.py
index e0068e66..c163a894 100644
--- a/add_advanced_objects_menu/trilighting.py
+++ b/add_advanced_objects_menu/trilighting.py
@@ -173,7 +173,7 @@ class TriLighting(Operator):
backx = obj_position.x + self.distance * singleback_vector.x
backy = obj_position.y + self.distance * singleback_vector.y
- backData = bpy.data.lamps.new(name="TriLamp-Back", type=self.secondarytype)
+ backData = bpy.data.lights.new(name="TriLamp-Back", type=self.secondarytype)
backData.energy = backEnergy
backLamp = bpy.data.objects.new(name="TriLamp-Back", object_data=backData)
@@ -196,7 +196,7 @@ class TriLighting(Operator):
rightx = obj_position.x + self.distance * singleright_vector.x
righty = obj_position.y + self.distance * singleright_vector.y
- rightData = bpy.data.lamps.new(name="TriLamp-Fill", type=self.secondarytype)
+ rightData = bpy.data.lights.new(name="TriLamp-Fill", type=self.secondarytype)
rightData.energy = fillEnergy
rightLamp = bpy.data.objects.new(name="TriLamp-Fill", object_data=rightData)
scene.objects.link(rightLamp)
@@ -215,7 +215,7 @@ class TriLighting(Operator):
leftx = obj_position.x + self.distance * singleleft_vector.x
lefty = obj_position.y + self.distance * singleleft_vector.y
- leftData = bpy.data.lamps.new(name="TriLamp-Key", type=self.primarytype)
+ leftData = bpy.data.lights.new(name="TriLamp-Key", type=self.primarytype)
leftData.energy = keyEnergy
leftLamp = bpy.data.objects.new(name="TriLamp-Key", object_data=leftData)
diff --git a/add_advanced_objects_panels/drop_to_ground.py b/add_advanced_objects_panels/drop_to_ground.py
index e6facbcf..8c66fb46 100644
--- a/add_advanced_objects_panels/drop_to_ground.py
+++ b/add_advanced_objects_panels/drop_to_ground.py
@@ -87,13 +87,13 @@ def get_lowest_world_co(context, ob, mat_parent=None):
if ob.type == 'MESH':
return get_lowest_world_co_from_mesh(ob)
- elif ob.type == 'EMPTY' and ob.dupli_type == 'GROUP':
- if not ob.dupli_group:
+ elif ob.type == 'EMPTY' and ob.instance_type == 'COLLECTION':
+ if not ob.instance_collection:
return None
else:
lowest_co = None
- for ob_l in ob.dupli_group.objects:
+ for ob_l in ob.instance_collection.objects:
if ob_l.type == 'MESH':
lowest_ob_l = get_lowest_world_co_from_mesh(ob_l, ob.matrix_world)
if not lowest_co:
diff --git a/add_advanced_objects_panels/object_laplace_lightning.py b/add_advanced_objects_panels/object_laplace_lightning.py
index fa50afd3..15e6730f 100644
--- a/add_advanced_objects_panels/object_laplace_lightning.py
+++ b/add_advanced_objects_panels/object_laplace_lightning.py
@@ -1087,7 +1087,7 @@ def setupObjects():
bpy.context.scene.objects.link(oOB)
gOB = bpy.data.objects.new('ELground', None)
- gOB.empty_draw_type = 'ARROWS'
+ gOB.empty_display_type = 'ARROWS'
bpy.context.scene.objects.link(gOB)
cME = makeMeshCube(1)
diff --git a/add_advanced_objects_panels/unfold_transition.py b/add_advanced_objects_panels/unfold_transition.py
index 42d8c3ba..f832c21d 100644
--- a/add_advanced_objects_panels/unfold_transition.py
+++ b/add_advanced_objects_panels/unfold_transition.py
@@ -129,8 +129,8 @@ class Set_Up_Fold(Operator):
scn.objects.link(rig)
scn.objects.active = rig
bpy.ops.object.mode_set(mode="EDIT")
- arm.draw_type = "WIRE"
- rig.show_x_ray = True
+ arm.display_type = "WIRE"
+ rig.show_in_front = True
mod = obj.modifiers.new("UnFold", "ARMATURE")
mod.show_in_editmode = True
mod.object = rig
@@ -154,7 +154,7 @@ class Set_Up_Fold(Operator):
b.select = False
b.layers = vis
b.parent = root
- vg = obj.vertex_groups.new(b.name)
+ vg = obj.vertex_groups.new(name=b.name)
vg.add(f.vertices, 1, "ADD")
bpy.ops.object.mode_set()
diff --git a/add_curve_extra_objects/__init__.py b/add_curve_extra_objects/__init__.py
index 42ae2d44..2622401b 100644
--- a/add_curve_extra_objects/__init__.py
+++ b/add_curve_extra_objects/__init__.py
@@ -23,8 +23,8 @@
bl_info = {
"name": "Extra Objects",
"author": "Multiple Authors",
- "version": (0, 1, 2),
- "blender": (2, 76, 0),
+ "version": (0, 1, 3),
+ "blender": (2, 80, 0),
"location": "View3D > Add > Curve > Extra Objects",
"description": "Add extra curve object types",
"warning": "",
@@ -140,10 +140,10 @@ class CurveExtraObjectsAddonPreferences(AddonPreferences):
"curve_type": ['POLY', 'NURBS'],
"spiral_direction": ['COUNTER_CLOCKWISE', 'CLOCKWISE']
}
- update_spiral_presets_msg = StringProperty(
+ update_spiral_presets_msg : StringProperty(
default="Nothing to do"
)
- update_spiral_presets = BoolProperty(
+ update_spiral_presets : BoolProperty(
name="Update Old Presets",
description="Update presets to reflect data changes",
default=False,
@@ -155,12 +155,12 @@ class CurveExtraObjectsAddonPreferences(AddonPreferences):
fixdic=spiral_fixdic
)
)
- show_menu_list = BoolProperty(
+ show_menu_list : BoolProperty(
name="Menu List",
description="Show/Hide the Add Menu items",
default=False
)
- show_panel_list = BoolProperty(
+ show_panel_list : BoolProperty(
name="Panels List",
description="Show/Hide the Panel items",
default=False
@@ -236,7 +236,10 @@ class INFO_MT_curve_knots_add(Menu):
layout.operator("curve.torus_knot_plus", text="Torus Knot Plus")
layout.operator("curve.celtic_links", text="Celtic Links")
- layout.operator("mesh.add_braid", text="Braid Knot")
+ layout.operator("curve.add_braid", text="Braid Knot")
+ layout.operator("object.add_spirofit_spline", icon="FORCE_MAGNETIC")
+ layout.operator("object.add_bounce_spline", icon="FORCE_HARMONIC")
+ layout.operator("object.add_catenary_curve", icon="FORCE_CURVE")
# Define "Extras" menus
@@ -247,55 +250,72 @@ def menu_func(self, context):
layout = self.layout
- layout.operator_menu_enum("mesh.curveaceous_galore", "ProfileType",
- icon='CURVE_DATA')
- layout.operator_menu_enum("curve.spirals", "spiral_type",
- icon='CURVE_DATA')
+ layout.operator_menu_enum("curve.curveaceous_galore", "ProfileType", icon='CURVE_DATA')
+ layout.operator_menu_enum("curve.spirals", "spiral_type", icon='CURVE_DATA')
layout.separator()
layout.menu(INFO_MT_curve_knots_add.bl_idname, text="Knots", icon='CURVE_DATA')
layout.separator()
- layout.operator("curve.curlycurve", text="Curly Curve",
- icon='CURVE_DATA')
- layout.menu("OBJECT_MT_bevel_taper_curve_menu", text="Bevel/Taper",
- icon='CURVE_DATA')
+ layout.operator("curve.curlycurve", text="Curly Curve", icon='CURVE_DATA')
+ #layout.menu(VIEW3D_MT_bevel_taper_curve_menu, text="Bevel/Taper", icon='CURVE_DATA')
def menu_surface(self, context):
self.layout.separator()
if context.mode == 'EDIT_SURFACE':
- self.layout.operator("curve.smooth_x_times",
- text="Special Smooth", icon="MOD_CURVE")
+ self.layout.operator("curve.smooth_x_times", text="Special Smooth", icon="MOD_CURVE")
elif context.mode == 'OBJECT':
- self.layout.operator("object.add_surface_wedge", text="Wedge",
- icon="SURFACE_DATA")
- self.layout.operator("object.add_surface_cone", text="Cone",
- icon="SURFACE_DATA")
- self.layout.operator("object.add_surface_star", text="Star",
- icon="SURFACE_DATA")
- self.layout.operator("object.add_surface_plane", text="Plane",
- icon="SURFACE_DATA")
+ self.layout.operator("object.add_surface_wedge", text="Wedge", icon="SURFACE_DATA")
+ self.layout.operator("object.add_surface_cone", text="Cone", icon="SURFACE_DATA")
+ self.layout.operator("object.add_surface_star", text="Star", icon="SURFACE_DATA")
+ self.layout.operator("object.add_surface_plane", text="Plane", icon="SURFACE_DATA")
+# Register
+classes = [
+ CurveExtraObjectsAddonPreferences,
+ INFO_MT_curve_knots_add
+]
def register():
+ from bpy.utils import register_class
+ for cls in classes:
+ register_class(cls)
+
add_curve_simple.register()
- bpy.utils.register_module(__name__)
-
+ add_curve_spirals.register()
+ add_curve_aceous_galore.register()
+ add_curve_torus_knots.register()
+ add_curve_braid.register()
+ add_curve_celtic_links.register()
+ add_curve_curly.register()
+ add_curve_spirofit_bouncespline.register()
+ add_surface_plane_cone.register()
+
# Add "Extras" menu to the "Add Curve" menu
- bpy.types.INFO_MT_curve_add.append(menu_func)
+ bpy.types.VIEW3D_MT_curve_add.append(menu_func)
# Add "Extras" menu to the "Add Surface" menu
- bpy.types.INFO_MT_surface_add.append(menu_surface)
+ bpy.types.VIEW3D_MT_surface_add.append(menu_surface)
def unregister():
- add_curve_simple.unregister()
# Remove "Extras" menu from the "Add Curve" menu.
- bpy.types.INFO_MT_curve_add.remove(menu_func)
+ bpy.types.VIEW3D_MT_curve_add.remove(menu_func)
# Remove "Extras" menu from the "Add Surface" menu.
- bpy.types.INFO_MT_surface_add.remove(menu_surface)
-
- bpy.utils.unregister_module(__name__)
-
+ bpy.types.VIEW3D_MT_surface_add.remove(menu_surface)
+
+ add_surface_plane_cone.unregister()
+ add_curve_spirofit_bouncespline.unregister()
+ add_curve_curly.unregister()
+ add_curve_celtic_links.unregister()
+ add_curve_braid.unregister()
+ add_curve_torus_knots.unregister()
+ add_curve_aceous_galore.unregister()
+ add_curve_spirals.unregister()
+ add_curve_simple.unregister()
+
+ from bpy.utils import unregister_class
+ for cls in reversed(classes):
+ unregister_class(cls)
if __name__ == "__main__":
register()
diff --git a/add_curve_extra_objects/add_curve_aceous_galore.py b/add_curve_extra_objects/add_curve_aceous_galore.py
index d823cbb5..246e0551 100644
--- a/add_curve_extra_objects/add_curve_aceous_galore.py
+++ b/add_curve_extra_objects/add_curve_aceous_galore.py
@@ -20,8 +20,8 @@
bl_info = {
"name": "Curveaceous Galore!",
"author": "Jimmy Hazevoet, testscreenings",
- "version": (0, 2, 1),
- "blender": (2, 59),
+ "version": (0, 2, 2),
+ "blender": (2, 80),
"location": "View3D > Add > Curve",
"description": "Adds many different types of Curves",
"warning": "",
@@ -38,7 +38,7 @@ from bpy.props import (
FloatProperty,
IntProperty,
)
-from mathutils import Matrix
+from mathutils import Matrix, Vector
from bpy.types import Operator
from math import (
sin, cos, pi
@@ -78,7 +78,7 @@ def randnum(low=0.0, high=1.0, seed=0):
# ------------------------------------------------------------
# Make some noise:
-def vTurbNoise(x, y, z, iScale=0.25, Size=1.0, Depth=6, Hard=0, Basis=0, Seed=0):
+def vTurbNoise(x, y, z, iScale=0.25, Size=1.0, Depth=6, Hard=False, Basis=0, Seed=0):
"""
vTurbNoise((x,y,z), iScale=0.25, Size=1.0, Depth=6, Hard=0, Basis=0, Seed=0 )
@@ -93,7 +93,7 @@ def vTurbNoise(x, y, z, iScale=0.25, Size=1.0, Depth=6, Hard=0, Basis=0, Seed=0)
(type=float)
Depth - number of noise values added.
(type=int)
- Hard - noise hardness: 0 - soft noise; 1 - hard noise
+ Hard - noise hardness: True - soft noise; False - hard noise
(type=int)
basis - type of noise used for turbulence
(type=int)
@@ -106,8 +106,9 @@ def vTurbNoise(x, y, z, iScale=0.25, Size=1.0, Depth=6, Hard=0, Basis=0, Seed=0)
rand = randnum(-100, 100, Seed)
if Basis is 9:
Basis = 14
- vTurb = Noise.turbulence_vector((x / Size + rand, y / Size + rand, z / Size + rand),
- Depth, Hard, Basis)
+ vec = Vector((x / Size + rand, y / Size + rand, z / Size + rand))
+ vTurb = Noise.turbulence_vector(vec, Depth, Hard)
+ #mathutils.noise.turbulence_vector(position, octaves, hard, noise_basis='PERLIN_ORIGINAL', amplitude_scale=0.5, frequency_scale=2.0)
tx = vTurb[0] * iScale
ty = vTurb[1] * iScale
tz = vTurb[2] * iScale
@@ -533,7 +534,7 @@ def SplatCurve(sides=24, scale=1.0, seed=0, basis=0, radius=1.0):
i = 0
while i < sides:
t = i * step
- turb = vTurbNoise(t, t, t, 1.0, scale, 6, 0, basis, seed)
+ turb = vTurbNoise(t, t, t, 1.0, scale, 6, False, basis, seed)
turb = turb[2] * 0.5 + 0.5
x = sin(t * pi) * radius * turb
y = cos(t * pi) * radius * turb
@@ -684,7 +685,7 @@ def NoiseCurve(type=0, number=100, length=2.0, size=0.5,
# noise circle
while i < number:
t = i * step
- v = vTurbNoise(t, t, t, 1.0, size, octaves, 0, basis, seed)
+ v = vTurbNoise(t, t, t, 1.0, size, octaves, False, basis, seed)
x = sin(t * pi) + (v[0] * scale[0])
y = cos(t * pi) + (v[1] * scale[1])
z = v[2] * scale[2]
@@ -694,7 +695,7 @@ def NoiseCurve(type=0, number=100, length=2.0, size=0.5,
# noise knot / ball
while i < number:
t = i * step
- v = vTurbNoise(t, t, t, 1.0, 1.0, octaves, 0, basis, seed)
+ v = vTurbNoise(t, t, t, 1.0, 1.0, octaves, False, basis, seed)
x = v[0] * scale[0] * size
y = v[1] * scale[1] * size
z = v[2] * scale[2] * size
@@ -704,7 +705,7 @@ def NoiseCurve(type=0, number=100, length=2.0, size=0.5,
# noise linear
while i < number:
t = i * step
- v = vTurbNoise(t, t, t, 1.0, size, octaves, 0, basis, seed)
+ v = vTurbNoise(t, t, t, 1.0, size, octaves, False, basis, seed)
x = t + v[0] * scale[0]
y = v[1] * scale[1]
z = v[2] * scale[2]
@@ -727,24 +728,24 @@ def align_matrix(context):
else:
rot = Matrix()
- align_matrix = loc * rot
+ align_matrix = loc @ rot
return align_matrix
# ------------------------------------------------------------
# Curve creation functions, sets bezierhandles to auto
-def setBezierHandles(obj, mode='AUTOMATIC'):
+def setBezierHandles(obj, mode='AUTO'):
scene = bpy.context.scene
if obj.type != 'CURVE':
return
- scene.objects.active = obj
- bpy.ops.object.mode_set(mode='EDIT', toggle=True)
- bpy.ops.curve.select_all(action='SELECT')
- bpy.ops.curve.handle_type_set(type=mode)
- bpy.ops.object.mode_set(mode='OBJECT', toggle=True)
-
+ #scene.objects.active = obj
+ #bpy.ops.object.mode_set(mode='EDIT', toggle=True)
+ #bpy.ops.curve.select_all(action='SELECT')
+ #obj.select_set(action='SELECT')
+ #bpy.ops.curve.handle_type_set(type=mode)
+ #bpy.ops.object.mode_set(mode='OBJECT', toggle=True)
# get array of vertcoordinates according to splinetype
def vertsToPoints(Verts, splineType):
@@ -772,7 +773,7 @@ def vertsToPoints(Verts, splineType):
# create new CurveObject from vertarray and splineType
def createCurve(context, vertArray, self, align_matrix):
- scene = context.scene
+ scene = bpy.context.scene
# output splineType 'POLY' 'NURBS' 'BEZIER'
splineType = self.outputType
@@ -788,6 +789,9 @@ def createCurve(context, vertArray, self, align_matrix):
if splineType == 'BEZIER':
newSpline.bezier_points.add(int(len(vertArray) * 0.33))
newSpline.bezier_points.foreach_set('co', vertArray)
+ for point in newSpline.bezier_points:
+ point.handle_right_type = self.handleType
+ point.handle_left_type = self.handleType
else:
newSpline.points.add(int(len(vertArray) * 0.25 - 1))
newSpline.points.foreach_set('co', vertArray)
@@ -801,14 +805,15 @@ def createCurve(context, vertArray, self, align_matrix):
# create object with newCurve
new_obj = bpy.data.objects.new(name, newCurve)
- scene.objects.link(new_obj)
- new_obj.select = True
- scene.objects.active = new_obj
+ scene.collection.objects.link(new_obj)
+ new_obj.select_set(True)
+ #scene.objects.active = new_obj
new_obj.matrix_world = align_matrix
# set bezierhandles
- if splineType == 'BEZIER':
- setBezierHandles(new_obj, self.handleType)
+ #if splineType == 'BEZIER':
+ #bpy.ops.curve.handle_type_set(type='AUTO')
+ #setBezierHandles(new_obj, self.handleType)
return
@@ -817,7 +822,7 @@ def createCurve(context, vertArray, self, align_matrix):
# Main Function
def main(context, self, align_matrix):
# deselect all objects
- bpy.ops.object.select_all(action='DESELECT')
+ #bpy.ops.object.select_all(action='DESELECT')
# options
proType = self.ProfileType
@@ -930,7 +935,7 @@ def main(context, self, align_matrix):
class Curveaceous_galore(Operator):
- bl_idname = "mesh.curveaceous_galore"
+ bl_idname = "curve.curveaceous_galore"
bl_label = "Curve Profiles"
bl_description = "Construct many types of curves"
bl_options = {'REGISTER', 'UNDO', 'PRESET'}
@@ -939,7 +944,7 @@ class Curveaceous_galore(Operator):
align_matrix = None
# general properties
- ProfileType = EnumProperty(
+ ProfileType : EnumProperty(
name="Type",
description="Form of Curve to create",
items=[
@@ -956,7 +961,7 @@ class Curveaceous_galore(Operator):
('Splat', "Splat", "Splat"),
('Star', "Star", "Star")]
)
- outputType = EnumProperty(
+ outputType : EnumProperty(
name="Output splines",
description="Type of splines to output",
items=[
@@ -965,7 +970,7 @@ class Curveaceous_galore(Operator):
('BEZIER', "Bezier", "Bezier Spline type")]
)
# Curve Options
- shape = EnumProperty(
+ shape : EnumProperty(
name="2D / 3D",
description="2D or 3D Curve",
items=[
@@ -973,135 +978,135 @@ class Curveaceous_galore(Operator):
('3D', "3D", "3D")
]
)
- use_cyclic_u = BoolProperty(
+ use_cyclic_u : BoolProperty(
name="Cyclic",
default=True,
description="make curve closed"
)
- endp_u = BoolProperty(
+ endp_u : BoolProperty(
name="Use endpoint u",
default=True,
description="stretch to endpoints"
)
- order_u = IntProperty(
+ order_u : IntProperty(
name="Order u",
default=4,
min=2, soft_min=2,
max=6, soft_max=6,
description="Order of nurbs spline"
)
- handleType = EnumProperty(
+ handleType : EnumProperty(
name="Handle type",
- default='AUTOMATIC',
+ default='AUTO',
description="Bezier handles type",
items=[
('VECTOR', "Vector", "Vector type Bezier handles"),
- ('AUTOMATIC', "Auto", "Automatic type Bezier handles")]
+ ('AUTO', "Auto", "Automatic type Bezier handles")]
)
# ProfileCurve properties
- ProfileCurveType = IntProperty(
+ ProfileCurveType : IntProperty(
name="Type",
min=1,
max=5,
default=1,
description="Type of Curve's Profile"
)
- ProfileCurvevar1 = FloatProperty(
+ ProfileCurvevar1 : FloatProperty(
name="Variable 1",
default=0.25,
description="Variable 1 of Curve's Profile"
)
- ProfileCurvevar2 = FloatProperty(
+ ProfileCurvevar2 : FloatProperty(
name="Variable 2",
default=0.25,
description="Variable 2 of Curve's Profile"
)
# Arrow, Rectangle, MiscCurve properties
- MiscCurveType = IntProperty(
+ MiscCurveType : IntProperty(
name="Type",
min=0,
max=3,
default=0,
description="Type of Curve"
)
- MiscCurvevar1 = FloatProperty(
+ MiscCurvevar1 : FloatProperty(
name="Variable 1",
default=1.0,
description="Variable 1 of Curve"
)
- MiscCurvevar2 = FloatProperty(
+ MiscCurvevar2 : FloatProperty(
name="Variable 2",
default=0.5,
description="Variable 2 of Curve"
)
- MiscCurvevar3 = FloatProperty(
+ MiscCurvevar3 : FloatProperty(
name="Variable 3",
default=0.1,
min=0,
description="Variable 3 of Curve"
)
# Common properties
- innerRadius = FloatProperty(
+ innerRadius : FloatProperty(
name="Inner radius",
default=0.5,
min=0,
description="Inner radius"
)
- middleRadius = FloatProperty(
+ middleRadius : FloatProperty(
name="Middle radius",
default=0.95,
min=0,
description="Middle radius"
)
- outerRadius = FloatProperty(
+ outerRadius : FloatProperty(
name="Outer radius",
default=1.0,
min=0,
description="Outer radius"
)
# Flower properties
- petals = IntProperty(
+ petals : IntProperty(
name="Petals",
default=8,
min=2,
description="Number of petals"
)
- petalWidth = FloatProperty(
+ petalWidth : FloatProperty(
name="Petal width",
default=2.0,
min=0.01,
description="Petal width"
)
# Star properties
- starPoints = IntProperty(
+ starPoints : IntProperty(
name="Star points",
default=8,
min=2,
description="Number of star points"
)
- starTwist = FloatProperty(
+ starTwist : FloatProperty(
name="Twist",
default=0.0,
description="Twist"
)
# Arc properties
- arcSides = IntProperty(
+ arcSides : IntProperty(
name="Arc sides",
default=6,
min=1,
description="Sides of arc"
)
- startAngle = FloatProperty(
+ startAngle : FloatProperty(
name="Start angle",
default=0.0,
description="Start angle"
)
- endAngle = FloatProperty(
+ endAngle : FloatProperty(
name="End angle",
default=90.0,
description="End angle"
)
- arcType = IntProperty(
+ arcType : IntProperty(
name="Arc type",
default=3,
min=1,
@@ -1109,13 +1114,13 @@ class Curveaceous_galore(Operator):
description="Sides of arc"
)
# Cogwheel properties
- teeth = IntProperty(
+ teeth : IntProperty(
name="Teeth",
default=8,
min=2,
description="number of teeth"
)
- bevel = FloatProperty(
+ bevel : FloatProperty(
name="Bevel",
default=0.5,
min=0,
@@ -1123,32 +1128,32 @@ class Curveaceous_galore(Operator):
description="Bevel"
)
# Nsided property
- Nsides = IntProperty(
+ Nsides : IntProperty(
name="Sides",
default=8,
min=3,
description="Number of sides"
)
# Splat properties
- splatSides = IntProperty(
+ splatSides : IntProperty(
name="Splat sides",
default=24,
min=3,
description="Splat sides"
)
- splatScale = FloatProperty(
+ splatScale : FloatProperty(
name="Splat scale",
default=1.0,
min=0.0001,
description="Splat scale"
)
- seed = IntProperty(
+ seed : IntProperty(
name="Seed",
default=0,
min=0,
description="Seed"
)
- basis = IntProperty(
+ basis : IntProperty(
name="Basis",
default=0,
min=0,
@@ -1156,134 +1161,134 @@ class Curveaceous_galore(Operator):
description="Basis"
)
# Helix properties
- helixPoints = IntProperty(
+ helixPoints : IntProperty(
name="Resolution",
default=100,
min=3,
description="Resolution"
)
- helixHeight = FloatProperty(
+ helixHeight : FloatProperty(
name="Height",
default=2.0,
min=0,
description="Helix height"
)
- helixStart = FloatProperty(
+ helixStart : FloatProperty(
name="Start angle",
default=0.0,
description="Helix start angle"
)
- helixEnd = FloatProperty(
+ helixEnd : FloatProperty(
name="Endangle",
default=360.0,
description="Helix end angle"
)
- helixWidth = FloatProperty(
+ helixWidth : FloatProperty(
name="Width",
default=1.0,
description="Helix width"
)
- helix_a = FloatProperty(
+ helix_a : FloatProperty(
name="Variable 1",
default=0.0,
description="Helix Variable 1"
)
- helix_b = FloatProperty(
+ helix_b : FloatProperty(
name="Variable 2",
default=0.0,
description="Helix Variable 2"
)
# Cycloid properties
- cycloPoints = IntProperty(
+ cycloPoints : IntProperty(
name="Resolution",
default=100,
min=3,
soft_min=3,
description="Resolution"
)
- cycloType = IntProperty(
+ cycloType : IntProperty(
name="Type",
default=1,
min=0,
max=2,
description="Type: Cycloid , Hypocycloid / Hypotrochoid , Epicycloid / Epitrochoid"
)
- cyclo_a = FloatProperty(
+ cyclo_a : FloatProperty(
name="R",
default=1.0,
min=0.01,
description="Cycloid: R radius a"
)
- cyclo_b = FloatProperty(
+ cyclo_b : FloatProperty(
name="r",
default=0.25,
min=0.01,
description="Cycloid: r radius b"
)
- cyclo_d = FloatProperty(
+ cyclo_d : FloatProperty(
name="d",
default=0.25,
description="Cycloid: d distance"
)
# Noise properties
- noiseType = IntProperty(
+ noiseType : IntProperty(
name="Type",
default=0,
min=0,
max=2,
description="Noise curve type: Linear, Circular or Knot"
)
- noisePoints = IntProperty(
+ noisePoints : IntProperty(
name="Resolution",
default=100,
min=3,
description="Resolution"
)
- noiseLength = FloatProperty(
+ noiseLength : FloatProperty(
name="Length",
default=2.0,
min=0.01,
description="Curve Length"
)
- noiseSize = FloatProperty(
+ noiseSize : FloatProperty(
name="Noise size",
default=1.0,
min=0.0001,
description="Noise size"
)
- noiseScaleX = FloatProperty(
+ noiseScaleX : FloatProperty(
name="Noise x",
default=1.0,
min=0.0001,
description="Noise x"
)
- noiseScaleY = FloatProperty(
+ noiseScaleY : FloatProperty(
name="Noise y",
default=1.0,
min=0.0001,
description="Noise y"
)
- noiseScaleZ = FloatProperty(
+ noiseScaleZ : FloatProperty(
name="Noise z",
default=1.0,
min=0.0001,
description="Noise z"
)
- noiseOctaves = IntProperty(
+ noiseOctaves : IntProperty(
name="Octaves",
default=2,
min=1,
max=16,
description="Basis"
)
- noiseBasis = IntProperty(
+ noiseBasis : IntProperty(
name="Basis",
default=0,
min=0,
max=9,
description="Basis"
)
- noiseSeed = IntProperty(
+ noiseSeed : IntProperty(
name="Seed",
default=1,
min=0,
@@ -1460,3 +1465,21 @@ class Curveaceous_galore(Operator):
self.execute(context)
return {'FINISHED'}
+
+# Register
+classes = [
+ Curveaceous_galore
+]
+
+def register():
+ from bpy.utils import register_class
+ for cls in classes:
+ register_class(cls)
+
+def unregister():
+ from bpy.utils import unregister_class
+ for cls in reversed(classes):
+ unregister_class(cls)
+
+if __name__ == "__main__":
+ register() \ No newline at end of file
diff --git a/add_curve_extra_objects/add_curve_braid.py b/add_curve_extra_objects/add_curve_braid.py
index 90b41e5f..fcaaf15e 100644
--- a/add_curve_extra_objects/add_curve_braid.py
+++ b/add_curve_extra_objects/add_curve_braid.py
@@ -4,8 +4,8 @@
bl_info = {
"name": "New Braid",
"author": "Jared Forsyth <github.com/jaredly>",
- "version": (1, 0, 2),
- "blender": (2, 6, 0),
+ "version": (1, 0, 3),
+ "blender": (2, 80, 0),
"location": "View3D > Add > Mesh > New Braid",
"description": "Adds a new Braid",
"warning": "",
@@ -134,13 +134,13 @@ def star_pts(r=1, ir=None, points=5, center=(0, 0)):
def defaultCircle(w=.6):
circle = nurbs_circle('braid_circle', w, w)
- circle.hide = True
+ circle.hide_select = True
return circle
def defaultStar():
star = poly_lines('star', 'staz', [tuple(star_pts(points=5, r=.5, ir=.05))], type='NURBS')
- star.hide = True
+ star.hide_select = True
return star
@@ -151,46 +151,46 @@ def awesome_braid(strands=3, sides=5, bevel='braid_circle', pointy=False, **kwds
class Braid(Operator):
- bl_idname = "mesh.add_braid"
+ bl_idname = "curve.add_braid"
bl_label = "New Braid"
bl_description = ("Construct a new Braid\n"
"Creates two objects - the hidden one is used as the Bevel control")
bl_options = {'REGISTER', 'UNDO', 'PRESET'}
- strands = IntProperty(
+ strands : IntProperty(
name="Strands",
description="Number of Strands",
min=2, max=100,
default=3
)
- sides = IntProperty(
+ sides : IntProperty(
name="Sides",
description="Number of Knot sides",
min=2, max=100,
default=5
)
- radius = FloatProperty(
+ radius : FloatProperty(
name="Radius",
description="Increase / decrease the diameter in X,Y axis",
default=1
)
- thickness = FloatProperty(
+ thickness : FloatProperty(
name="Thickness",
description="The ratio between inner and outside diameters",
default=.3
)
- strandsize = FloatProperty(
+ strandsize : FloatProperty(
name="Bevel Depth",
description="Individual strand diameter (similar to Curve's Bevel depth)",
default=.3,
min=.01, max=10
)
- width = FloatProperty(
+ width : FloatProperty(
name="Width",
description="Stretch the Braids along the Z axis",
default=.2
)
- resolution = IntProperty(
+ resolution : IntProperty(
name="Bevel Resolution",
description="Resolution of the Created curve\n"
"Increasing this value, will produce heavy geometry",
@@ -198,7 +198,7 @@ class Braid(Operator):
max=100, soft_max=24,
default=2
)
- pointy = BoolProperty(
+ pointy : BoolProperty(
name="Pointy",
description="Switch between round and sharp corners",
default=False
@@ -209,7 +209,7 @@ class Braid(Operator):
box = layout.box()
col = box.column(align=True)
- col.label("Settings:")
+ col.label(text="Settings:")
col.prop(self, "strands")
col.prop(self, "sides")
@@ -223,13 +223,13 @@ class Braid(Operator):
box = layout.box()
col = box.column(align=True)
- col.label("Geometry Options:")
+ col.label(text="Geometry Options:")
col.prop(self, "strandsize")
col.prop(self, "resolution")
def execute(self, context):
circle = defaultCircle(self.strandsize)
- context.scene.objects.link(circle)
+ context.scene.collection.objects.link(circle)
braid = awesome_braid(
self.strands, self.sides,
bevel=circle.name,
@@ -239,12 +239,12 @@ class Braid(Operator):
mz=self.width,
resolution=self.resolution
)
- base = context.scene.objects.link(braid)
+ base = context.scene.collection.objects.link(braid)
for ob in context.scene.objects:
- ob.select = False
- base.select = True
- context.scene.objects.active = braid
+ ob.select_set(False)
+ #base.select_set(True)
+ #context.scene.objects.active = braid
return {'FINISHED'}
diff --git a/add_curve_extra_objects/add_curve_celtic_links.py b/add_curve_extra_objects/add_curve_celtic_links.py
index f4313c85..c5b581fa 100644
--- a/add_curve_extra_objects/add_curve_celtic_links.py
+++ b/add_curve_extra_objects/add_curve_celtic_links.py
@@ -26,8 +26,8 @@ bl_info = {
"name": "Celtic Knot",
"description": "",
"author": "Adam Newgas",
- "version": (0, 1, 2),
- "blender": (2, 74, 0),
+ "version": (0, 1, 3),
+ "blender": (2, 80, 0),
"location": "View3D > Add > Curve",
"warning": "",
"wiki_url": "https://github.com/BorisTheBrave/celtic-knot/wiki",
@@ -53,13 +53,13 @@ class CelticKnotOperator(Operator):
bl_description = "Select a low poly Mesh Object to cover with Knitted Links"
bl_options = {'REGISTER', 'UNDO', 'PRESET'}
- weave_up = FloatProperty(
+ weave_up : FloatProperty(
name="Weave Up",
description="Distance to shift curve upwards over knots",
subtype="DISTANCE",
unit="LENGTH"
)
- weave_down = FloatProperty(
+ weave_down : FloatProperty(
name="Weave Down",
description="Distance to shift curve downward under knots",
subtype="DISTANCE",
@@ -69,7 +69,7 @@ class CelticKnotOperator(Operator):
('ALIGNED', "Aligned", "Points at a fixed crossing angle"),
('AUTO', "Auto", "Automatic control points")
]
- handle_type = EnumProperty(
+ handle_type : EnumProperty(
items=handle_types,
name="Handle Type",
description="Controls what type the bezier control points use",
@@ -78,7 +78,7 @@ class CelticKnotOperator(Operator):
handle_type_map = {"AUTO": "AUTOMATIC", "ALIGNED": "ALIGNED"}
- crossing_angle = FloatProperty(
+ crossing_angle : FloatProperty(
name="Crossing Angle",
description="Aligned only: the angle between curves in a knot",
default=pi / 4,
@@ -86,14 +86,14 @@ class CelticKnotOperator(Operator):
subtype="ANGLE",
unit="ROTATION"
)
- crossing_strength = FloatProperty(
+ crossing_strength : FloatProperty(
name="Crossing Strength",
description="Aligned only: strength of bezier control points",
soft_min=0,
subtype="DISTANCE",
unit="LENGTH"
)
- geo_bDepth = FloatProperty(
+ geo_bDepth : FloatProperty(
name="Bevel Depth",
default=0.04,
min=0, soft_min=0,
@@ -211,7 +211,7 @@ class CelticKnotOperator(Operator):
forward = loop.vert.index == v
if not first:
- current_spline.bezier_points.add()
+ current_spline.bezier_points.add(1)
first = False
midpoint = midpoints[loop.edge.index]
normal = loop.calc_normal() + prev_loop.calc_normal()
@@ -267,7 +267,7 @@ class CelticKnotOperator(Operator):
curve_obj.data.bevel_depth = self.geo_bDepth
except:
pass
- context.scene.objects.active = orig_obj
+ #context.scene.objects.active = orig_obj
return {'FINISHED'}
diff --git a/add_curve_extra_objects/add_curve_curly.py b/add_curve_extra_objects/add_curve_curly.py
index 779689a9..8cd7d8b9 100644
--- a/add_curve_extra_objects/add_curve_curly.py
+++ b/add_curve_extra_objects/add_curve_curly.py
@@ -5,8 +5,8 @@
bl_info = {
"name": "Curly Curves",
"author": "Cmomoney",
- "version": (1, 1, 8),
- "blender": (2, 69, 0),
+ "version": (1, 1, 9),
+ "blender": (2, 80, 0),
"location": "View3D > Add > Curve > Curly Curve",
"description": "Adds a new Curly Curve",
"warning": "",
@@ -405,20 +405,20 @@ class add_curlycurve(Operator, AddObjectHelper):
bl_idname = "curve.curlycurve"
bl_label = "Add Curly Curve"
bl_description = "Create a Curly Curve"
- bl_options = {'REGISTER', 'UNDO'}
+ bl_options = {'REGISTER', 'UNDO', 'PRESET'}
- types = IntProperty(
+ types : IntProperty(
name="Type",
description="Type of curly curve",
default=1,
min=1, max=10
)
- scale_x = FloatProperty(
+ scale_x : FloatProperty(
name="Scale X",
description="Scale on X axis",
default=1.0
)
- scale_y = FloatProperty(
+ scale_y : FloatProperty(
name="Scale Y",
description="Scale on Y axis",
default=1.0
@@ -434,11 +434,11 @@ class add_curlycurve(Operator, AddObjectHelper):
col.prop(self, "rotation")
col = layout.column()
- col.label("Curve:")
+ col.label(text = "Curve:")
col.prop(self, "types")
col = layout.column(align=True)
- col.label("Resize:")
+ col.label(text = "Resize:")
col.prop(self, "scale_x")
col.prop(self, "scale_y")
@@ -479,12 +479,12 @@ def add_curlycurve_button(self, context):
def register():
bpy.utils.register_class(add_curlycurve)
- bpy.types.INFO_MT_curve_add.append(add_curlycurve_button)
+ #bpy.types.INFO_MT_curve_add.append(add_curlycurve_button)
def unregister():
bpy.utils.unregister_class(add_curlycurve)
- bpy.types.INFO_MT_curve_add.remove(add_curlycurve_button)
+ #bpy.types.INFO_MT_curve_add.remove(add_curlycurve_button)
if __name__ == "__main__":
diff --git a/add_curve_extra_objects/add_curve_simple.py b/add_curve_extra_objects/add_curve_simple.py
index 15dd3e1a..0d4b9224 100644
--- a/add_curve_extra_objects/add_curve_simple.py
+++ b/add_curve_extra_objects/add_curve_simple.py
@@ -19,8 +19,8 @@
bl_info = {
"name": "Simple Curve",
"author": "Spivak Vladimir (http://cwolf3d.korostyshev.net)",
- "version": (1, 5, 3),
- "blender": (2, 6, 9),
+ "version": (1, 5, 4),
+ "blender": (2, 80, 0),
"location": "View3D > Add > Curve",
"description": "Adds Simple Curve",
"warning": "",
@@ -388,7 +388,7 @@ def align_matrix(context, location):
rot = context.space_data.region_3d.view_matrix.to_3x3().inverted().to_4x4()
else:
rot = Matrix()
- align_matrix = loc * rot
+ align_matrix = loc @ rot
return align_matrix
@@ -397,19 +397,30 @@ def align_matrix(context, location):
# Main Function
def main(context, self, align_matrix):
- # deselect all objects
- bpy.ops.object.select_all(action='DESELECT')
-
# create object
- name = self.Simple_Type # Type as name
-
- # create curve
scene = bpy.context.scene
- newCurve = bpy.data.curves.new(name, type='CURVE') # curvedatablock
- newSpline = newCurve.splines.new('BEZIER') # spline
+
+ if bpy.context.mode == 'EDIT_CURVE':
+ newCurve = context.active_object.data
+ newSpline = newCurve.splines.new('BEZIER') # spline
+ else:
+ name = self.Simple_Type # Type as name
+ # create curve
+
+ newCurve = bpy.data.curves.new(name, type='CURVE') # curvedatablock
+ newSpline = newCurve.splines.new('BEZIER') # spline
+
+ # set curveOptions
+ newCurve.dimensions = self.shape
+
+ # create object with newCurve
+ SimpleCurve = bpy.data.objects.new(name, newCurve) # object
+ scene.collection.objects.link(SimpleCurve) # place in active scene
+ SimpleCurve.select_set(True)
+ SimpleCurve.matrix_world = align_matrix # apply matrix
+ SimpleCurve.rotation_euler = self.Simple_rotation_euler
- # set curveOptions
- newCurve.dimensions = self.shape
+
newSpline.use_endpoint_u = True
sides = abs(int((self.Simple_endangle - self.Simple_startangle) / 90))
@@ -525,14 +536,6 @@ def main(context, self, align_matrix):
newSpline.bezier_points.add(int(len(vertArray) * 0.333333333))
newSpline.bezier_points.foreach_set('co', vertArray)
- # create object with newCurve
- SimpleCurve = bpy.data.objects.new(name, newCurve) # object
- scene.objects.link(SimpleCurve) # place in active scene
- SimpleCurve.select = True # set as selected
- scene.objects.active = SimpleCurve # set as active
- SimpleCurve.matrix_world = align_matrix # apply matrix
- SimpleCurve.rotation_euler = self.Simple_rotation_euler
-
all_points = [p for p in newSpline.bezier_points]
d = 2 * 0.27606262
n = 0
@@ -761,45 +764,73 @@ def main(context, self, align_matrix):
all_points[int(n / 2) - 1].handle_right_type = 'VECTOR'
all_points[int(n / 2)].handle_left_type = 'VECTOR'
- SimpleCurve.s_curve.Simple = True
- SimpleCurve.s_curve.Simple_Change = False
- SimpleCurve.s_curve.Simple_Type = self.Simple_Type
- SimpleCurve.s_curve.Simple_startlocation = self.Simple_startlocation
- SimpleCurve.s_curve.Simple_endlocation = self.Simple_endlocation
- SimpleCurve.s_curve.Simple_a = self.Simple_a
- SimpleCurve.s_curve.Simple_b = self.Simple_b
- SimpleCurve.s_curve.Simple_h = self.Simple_h
- SimpleCurve.s_curve.Simple_angle = self.Simple_angle
- SimpleCurve.s_curve.Simple_startangle = self.Simple_startangle
- SimpleCurve.s_curve.Simple_endangle = self.Simple_endangle
- SimpleCurve.s_curve.Simple_rotation_euler = self.Simple_rotation_euler
- SimpleCurve.s_curve.Simple_sides = self.Simple_sides
- SimpleCurve.s_curve.Simple_radius = self.Simple_radius
- SimpleCurve.s_curve.Simple_center = self.Simple_center
- SimpleCurve.s_curve.Simple_width = self.Simple_width
- SimpleCurve.s_curve.Simple_length = self.Simple_length
- SimpleCurve.s_curve.Simple_rounded = self.Simple_rounded
-
- bpy.ops.object.mode_set(mode='EDIT', toggle=True)
- bpy.ops.curve.select_all(action='SELECT')
- bpy.ops.object.mode_set(mode='OBJECT', toggle=True)
-
return
+# ### MENU append ###
+def Simple_curve_edit_menu(self, context):
+ bl_label = 'Simple edit'
+
+ self.layout.operator("curve.bezier_points_fillet", text="Fillet")
+ self.layout.operator("curve.bezier_spline_divide", text="Divide")
+ self.layout.separator()
-# ------------------------------------------------------------
-# Delete simple curve
+def menu(self, context):
+ oper1 = self.layout.operator(Simple.bl_idname, text="Angle", icon="MOD_CURVE")
+ oper1.Simple_Change = False
+ oper1.Simple_Type = "Angle"
-def SimpleDelete(name):
- if bpy.ops.object.mode_set.poll():
- bpy.ops.object.mode_set(mode='OBJECT')
+ oper2 = self.layout.operator(Simple.bl_idname, text="Arc", icon="MOD_CURVE")
+ oper2.Simple_Change = False
+ oper2.Simple_Type = "Arc"
- bpy.context.scene.objects.active = bpy.data.objects[name]
- bpy.ops.object.delete()
+ oper3 = self.layout.operator(Simple.bl_idname, text="Circle", icon="MOD_CURVE")
+ oper3.Simple_Change = False
+ oper3.Simple_Type = "Circle"
- return
+ oper4 = self.layout.operator(Simple.bl_idname, text="Distance", icon="MOD_CURVE")
+ oper4.Simple_Change = False
+ oper4.Simple_Type = "Distance"
+
+ oper5 = self.layout.operator(Simple.bl_idname, text="Ellipse", icon="MOD_CURVE")
+ oper5.Simple_Change = False
+ oper5.Simple_Type = "Ellipse"
+ oper6 = self.layout.operator(Simple.bl_idname, text="Line", icon="MOD_CURVE")
+ oper6.Simple_Change = False
+ oper6.Simple_Type = "Line"
+ oper7 = self.layout.operator(Simple.bl_idname, text="Point", icon="MOD_CURVE")
+ oper7.Simple_Change = False
+ oper7.Simple_Type = "Point"
+
+ oper8 = self.layout.operator(Simple.bl_idname, text="Polygon", icon="MOD_CURVE")
+ oper8.Simple_Change = False
+ oper8.Simple_Type = "Polygon"
+
+ oper9 = self.layout.operator(Simple.bl_idname, text="Polygon ab", icon="MOD_CURVE")
+ oper9.Simple_Change = False
+ oper9.Simple_Type = "Polygon_ab"
+
+ oper10 = self.layout.operator(Simple.bl_idname, text="Rectangle", icon="MOD_CURVE")
+ oper10.Simple_Change = False
+ oper10.Simple_Type = "Rectangle"
+
+ oper11 = self.layout.operator(Simple.bl_idname, text="Rhomb", icon="MOD_CURVE")
+ oper11.Simple_Change = False
+ oper11.Simple_Type = "Rhomb"
+
+ oper12 = self.layout.operator(Simple.bl_idname, text="Sector", icon="MOD_CURVE")
+ oper12.Simple_Change = False
+ oper12.Simple_Type = "Sector"
+
+ oper13 = self.layout.operator(Simple.bl_idname, text="Segment", icon="MOD_CURVE")
+ oper13.Simple_Change = False
+ oper13.Simple_Type = "Segment"
+
+ oper14 = self.layout.operator(Simple.bl_idname, text="Trapezoid", icon="MOD_CURVE")
+ oper14.Simple_Change = False
+ oper14.Simple_Type = "Trapezoid"
+
# ------------------------------------------------------------
# Simple operator
@@ -810,20 +841,20 @@ class Simple(Operator):
bl_options = {'REGISTER', 'UNDO'}
# align_matrix for the invoke
- align_matrix = Matrix()
+ align_matrix : Matrix()
# change properties
- Simple = BoolProperty(
+ Simple : BoolProperty(
name="Simple",
default=True,
description="Simple Curve"
)
- Simple_Change = BoolProperty(
+ Simple_Change : BoolProperty(
name="Change",
default=False,
description="Change Simple Curve"
)
- Simple_Delete = StringProperty(
+ Simple_Delete : StringProperty(
name="Delete",
description="Delete Simple Curve"
)
@@ -843,84 +874,84 @@ class Simple(Operator):
('Polygon_ab', "Polygon ab", "Construct a Polygon ab"),
('Trapezoid', "Trapezoid", "Construct a Trapezoid")
]
- Simple_Type = EnumProperty(
+ Simple_Type : EnumProperty(
name="Type",
description="Form of Curve to create",
items=Types
)
# Line properties
- Simple_startlocation = FloatVectorProperty(
+ Simple_startlocation : FloatVectorProperty(
name="",
description="Start location",
default=(0.0, 0.0, 0.0),
subtype='TRANSLATION'
)
- Simple_endlocation = FloatVectorProperty(
+ Simple_endlocation : FloatVectorProperty(
name="",
description="End location",
default=(2.0, 2.0, 2.0),
subtype='TRANSLATION'
)
- Simple_rotation_euler = FloatVectorProperty(
+ Simple_rotation_euler : FloatVectorProperty(
name="",
description="Rotation",
default=(0.0, 0.0, 0.0),
subtype='EULER'
)
# Trapezoid properties
- Simple_a = FloatProperty(
+ Simple_a : FloatProperty(
name="Side a",
default=2.0,
min=0.0, soft_min=0.0,
unit='LENGTH',
description="a side Value"
)
- Simple_b = FloatProperty(
+ Simple_b : FloatProperty(
name="Side b",
default=1.0,
min=0.0, soft_min=0.0,
unit='LENGTH',
description="b side Value"
)
- Simple_h = FloatProperty(
+ Simple_h : FloatProperty(
name="Height",
default=1.0,
unit='LENGTH',
description="Height of the Trapezoid - distance between a and b"
)
- Simple_angle = FloatProperty(
+ Simple_angle : FloatProperty(
name="Angle",
default=45.0,
description="Angle"
)
- Simple_startangle = FloatProperty(
+ Simple_startangle : FloatProperty(
name="Start angle",
default=0.0,
min=-360.0, soft_min=-360.0,
max=360.0, soft_max=360.0,
description="Start angle"
)
- Simple_endangle = FloatProperty(
+ Simple_endangle : FloatProperty(
name="End angle",
default=45.0,
min=-360.0, soft_min=-360.0,
max=360.0, soft_max=360.0,
description="End angle"
)
- Simple_sides = IntProperty(
+ Simple_sides : IntProperty(
name="Sides",
default=3,
min=0, soft_min=0,
description="Sides"
)
- Simple_radius = FloatProperty(
+ Simple_radius : FloatProperty(
name="Radius",
default=1.0,
min=0.0, soft_min=0.0,
unit='LENGTH',
description="Radius"
)
- Simple_center = BoolProperty(
+ Simple_center : BoolProperty(
name="Length center",
default=True,
description="Length center"
@@ -928,27 +959,27 @@ class Simple(Operator):
Angle_types = [('Degrees', "Degrees", "Use Degrees"),
('Radians', "Radians", "Use Radians")]
- Simple_degrees_or_radians = EnumProperty(
+ Simple_degrees_or_radians : EnumProperty(
name="Degrees or radians",
description="Degrees or radians",
items=Angle_types
)
# Rectangle properties
- Simple_width = FloatProperty(
+ Simple_width : FloatProperty(
name="Width",
default=2.0,
min=0.0, soft_min=0,
unit='LENGTH',
description="Width"
)
- Simple_length = FloatProperty(
+ Simple_length : FloatProperty(
name="Length",
default=2.0,
min=0.0, soft_min=0.0,
unit='LENGTH',
description="Length"
)
- Simple_rounded = FloatProperty(
+ Simple_rounded : FloatProperty(
name="Rounded",
default=0.0,
min=0.0, soft_min=0.0,
@@ -959,7 +990,7 @@ class Simple(Operator):
shapeItems = [
('2D', "2D", "2D shape Curve"),
('3D', "3D", "3D shape Curve")]
- shape = EnumProperty(
+ shape : EnumProperty(
name="2D / 3D",
items=shapeItems,
description="2D or 3D Curve"
@@ -1140,10 +1171,10 @@ class Simple(Operator):
row = layout.row()
row.prop(self, "shape", expand=True)
box = layout.box()
- box.label("Location:")
+ box.label(text="Location:")
box.prop(self, "Simple_startlocation")
box = layout.box()
- box.label("Rotation:")
+ box.label(text="Rotation:")
box.prop(self, "Simple_rotation_euler")
if l != 0 or s != 0:
@@ -1151,49 +1182,22 @@ class Simple(Operator):
box.label(text="Statistics:", icon="INFO")
if l != 0:
l_str = str(round(l, 4))
- box.label("Length: " + l_str)
+ box.label(text="Length: " + l_str)
if s != 0:
s_str = str(round(s, 4))
- box.label("Area: " + s_str)
+ box.label(text="Area: " + s_str)
@classmethod
def poll(cls, context):
return context.scene is not None
def execute(self, context):
- if self.Simple_Change:
- SimpleDelete(self.Simple_Delete)
-
- # go to object mode
- if bpy.ops.object.mode_set.poll():
- bpy.ops.object.mode_set(mode='OBJECT')
-
- # turn off undo
- undo = bpy.context.user_preferences.edit.use_global_undo
- bpy.context.user_preferences.edit.use_global_undo = False
-
# main function
self.align_matrix = align_matrix(context, self.Simple_startlocation)
main(context, self, self.align_matrix)
- # restore pre operator undo state
- bpy.context.user_preferences.edit.use_global_undo = undo
-
- return {'FINISHED'}
-
- def invoke(self, context, event):
- # store creation_matrix
- if self.Simple_Change:
- bpy.context.scene.cursor_location = self.Simple_startlocation
- else:
- self.Simple_startlocation = bpy.context.scene.cursor_location
-
- self.align_matrix = align_matrix(context, self.Simple_startlocation)
- self.execute(context)
-
return {'FINISHED'}
-
# ------------------------------------------------------------
# Fillet
@@ -1201,9 +1205,9 @@ class BezierPointsFillet(Operator):
bl_idname = "curve.bezier_points_fillet"
bl_label = "Bezier points Fillet"
bl_description = "Bezier points Fillet"
- bl_options = {'REGISTER', 'UNDO'}
+ bl_options = {'REGISTER', 'UNDO', 'PRESET'}
- Fillet_radius = FloatProperty(
+ Fillet_radius : FloatProperty(
name="Radius",
default=0.25,
unit='LENGTH',
@@ -1211,7 +1215,7 @@ class BezierPointsFillet(Operator):
)
Types = [('Round', "Round", "Round"),
('Chamfer', "Chamfer", "Chamfer")]
- Fillet_Type = EnumProperty(
+ Fillet_Type : EnumProperty(
name="Type",
description="Fillet type",
items=Types
@@ -1230,15 +1234,6 @@ class BezierPointsFillet(Operator):
return context.scene is not None
def execute(self, context):
- # go to object mode
- if bpy.ops.object.mode_set.poll():
- bpy.ops.object.mode_set(mode='OBJECT')
- bpy.ops.object.mode_set(mode='EDIT')
-
- # turn off undo
- undo = bpy.context.user_preferences.edit.use_global_undo
- bpy.context.user_preferences.edit.use_global_undo = False
-
# main function
spline = bpy.context.object.data.splines.active
selected = [p for p in spline.bezier_points if p.select_control_point]
@@ -1321,17 +1316,8 @@ class BezierPointsFillet(Operator):
bpy.ops.curve.select_all(action='SELECT')
bpy.ops.curve.spline_type_set(type='BEZIER')
- # restore pre operator undo state
- bpy.context.user_preferences.edit.use_global_undo = undo
-
return {'FINISHED'}
- def invoke(self, context, event):
- self.execute(context)
-
- return {'FINISHED'}
-
-
def subdivide_cubic_bezier(p1, p2, p3, p4, t):
p12 = (p2 - p1) * t + p1
p23 = (p3 - p2) * t + p2
@@ -1352,9 +1338,9 @@ class BezierDivide(Operator):
bl_options = {'REGISTER', 'UNDO'}
# align_matrix for the invoke
- align_matrix = Matrix()
+ align_matrix : Matrix()
- Bezier_t = FloatProperty(
+ Bezier_t : FloatProperty(
name="t (0% - 100%)",
default=50.0,
min=0.0, soft_min=0.0,
@@ -1367,15 +1353,6 @@ class BezierDivide(Operator):
return context.scene is not None
def execute(self, context):
- # go to object mode
- if bpy.ops.object.mode_set.poll():
- bpy.ops.object.mode_set(mode='OBJECT')
- bpy.ops.object.mode_set(mode='EDIT')
-
- # turn off undo
- undo = bpy.context.user_preferences.edit.use_global_undo
- bpy.context.user_preferences.edit.use_global_undo = False
-
# main function
spline = bpy.context.object.data.splines.active
selected_all = [p for p in spline.bezier_points if p.select_control_point]
@@ -1414,376 +1391,30 @@ class BezierDivide(Operator):
selected_all[2].handle_right = h[3]
selected_all[0].handle_left = h[4]
- # restore pre operator undo state
- bpy.context.user_preferences.edit.use_global_undo = undo
-
return {'FINISHED'}
- def invoke(self, context, event):
- self.execute(context)
-
- return {'FINISHED'}
-
-
-# ------------------------------------------------------------
-# Simple change panel
-
-class SimplePanel(Panel):
- bl_idname = "VIEW3D_PT_simple_curve"
- bl_label = "Simple Curve"
- bl_space_type = "VIEW_3D"
- bl_region_type = "TOOLS"
- bl_options = {'DEFAULT_CLOSED'}
- bl_category = "Tools"
-
- @classmethod
- def poll(cls, context):
- if not context.active_object:
- pass
- elif context.object.s_curve.Simple is True:
- return (context.object)
-
- def draw(self, context):
- if context.object.s_curve.Simple is True:
- layout = self.layout
- obj = context.object
- row = layout.row()
-
- simple_change = row.operator("curve.simple", text="Change",
- icon="OUTLINER_DATA_CURVE")
- simple_change.Simple_Change = True
- simple_change.Simple_Delete = obj.name
- simple_change.Simple_Type = obj.s_curve.Simple_Type
- simple_change.Simple_startlocation = obj.location
- simple_change.Simple_endlocation = obj.s_curve.Simple_endlocation
-
- simple_change.Simple_a = obj.s_curve.Simple_a
- simple_change.Simple_b = obj.s_curve.Simple_b
- simple_change.Simple_h = obj.s_curve.Simple_h
-
- simple_change.Simple_angle = obj.s_curve.Simple_angle
- simple_change.Simple_startangle = obj.s_curve.Simple_startangle
- simple_change.Simple_endangle = obj.s_curve.Simple_endangle
- simple_change.Simple_rotation_euler = obj.rotation_euler
-
- simple_change.Simple_sides = obj.s_curve.Simple_sides
- simple_change.Simple_radius = obj.s_curve.Simple_radius
- simple_change.Simple_center = obj.s_curve.Simple_center
- simple_change.Simple_width = obj.s_curve.Simple_width
- simple_change.Simple_length = obj.s_curve.Simple_length
- simple_change.Simple_rounded = obj.s_curve.Simple_rounded
-
-
-# ------------------------------------------------------------
-# Fillet tools panel
-
-class SimpleEdit(Operator):
- bl_idname = "object._simple_edit"
- bl_label = "Create Curves"
- bl_description = "Subdivide and Fillet Curves"
- bl_options = {'REGISTER', 'UNDO'}
-
- @classmethod
- def poll(cls, context):
- vertex = []
- nselected = []
- n = 0
- obj = context.active_object
- if obj is not None:
- if obj.type == 'CURVE':
- for i in obj.data.splines:
- for j in i.bezier_points:
- n += 1
- if j.select_control_point:
- nselected.append(n)
- vertex.append(obj.matrix_world * j.co)
-
- if len(vertex) > 0 and n > 2:
- return (context.active_object)
- if len(vertex) == 2 and abs(nselected[0] - nselected[1]) == 1:
- return (context.active_object)
-
- selected = 0
- for obj in context.selected_objects:
- if obj.type == 'CURVE':
- selected += 1
-
- if selected >= 2:
- return (context.selected_objects)
-
- def draw(self, context):
- vertex = []
- selected = []
- n = 0
- obj = context.active_object
- if obj is not None:
- if obj.type == 'CURVE':
- for i in obj.data.splines:
- for j in i.bezier_points:
- n += 1
- if j.select_control_point:
- selected.append(n)
- vertex.append(obj.matrix_world * j.co)
-
- if len(vertex) > 0 and n > 2:
- layout = self.layout
- row = layout.row()
- row.operator("curve.bezier_points_fillet", text="Fillet")
-
- if len(vertex) == 2 and abs(selected[0] - selected[1]) == 1:
- layout = self.layout
- row = layout.row()
- row.operator("curve.bezier_spline_divide", text="Divide")
-
-
-# ------------------------------------------------------------
-# location update
-
-def StartLocationUpdate(self, context):
-
- bpy.context.scene.cursor_location = self.Simple_startlocation
- return
-
-
-# ------------------------------------------------------------
-# Add properties to objects
-
-class SimpleVariables(PropertyGroup):
-
- Simple = BoolProperty()
- Simple_Change = BoolProperty()
-
- # general properties
- Types = [('Point', "Point", "Construct a Point"),
- ('Line', "Line", "Construct a Line"),
- ('Distance', "Distance", "Construct a two point Distance"),
- ('Angle', "Angle", "Construct an Angle"),
- ('Circle', "Circle", "Construct a Circle"),
- ('Ellipse', "Ellipse", "Construct an Ellipse"),
- ('Arc', "Arc", "Construct an Arc"),
- ('Sector', "Sector", "Construct a Sector"),
- ('Segment', "Segment", "Construct a Segment"),
- ('Rectangle', "Rectangle", "Construct a Rectangle"),
- ('Rhomb', "Rhomb", "Construct a Rhomb"),
- ('Polygon', "Polygon", "Construct a Polygon"),
- ('Polygon_ab', "Polygon ab", "Construct a Polygon ab"),
- ('Trapezoid', "Trapezoid", "Construct a Trapezoid")
- ]
- Simple_Type = EnumProperty(
- name="Type",
- description="Form of Curve to create",
- items=Types
- )
- # Line properties
- Simple_startlocation = FloatVectorProperty(
- name="Start location",
- description="Start location",
- default=(0.0, 0.0, 0.0),
- subtype='TRANSLATION',
- update=StartLocationUpdate
- )
- Simple_endlocation = FloatVectorProperty(
- name="End location",
- description="End location",
- default=(2.0, 2.0, 2.0),
- subtype='TRANSLATION'
- )
- Simple_rotation_euler = FloatVectorProperty(
- name="Rotation",
- description="Rotation",
- default=(0.0, 0.0, 0.0),
- subtype='EULER'
- )
- # Trapezoid properties
- Simple_a = FloatProperty(
- name="Side a",
- default=2.0,
- min=0.0, soft_min=0.0,
- unit='LENGTH',
- description="a side Value"
- )
- Simple_b = FloatProperty(
- name="Side b",
- default=1.0,
- min=0.0, soft_min=0.0,
- unit='LENGTH',
- description="b side Value"
- )
- Simple_h = FloatProperty(
- name="Height",
- default=1.0,
- unit='LENGTH',
- description="Height of the Trapezoid - distance between a and b"
- )
- Simple_angle = FloatProperty(
- name="Angle",
- default=45.0,
- description="Angle"
- )
- Simple_startangle = FloatProperty(
- name="Start angle",
- default=0.0,
- min=-360.0, soft_min=-360.0,
- max=360.0, soft_max=360.0,
- description="Start angle"
- )
- Simple_endangle = FloatProperty(
- name="End angle",
- default=45.0,
- min=-360.0, soft_min=-360.0,
- max=360.0, soft_max=360.0,
- description="End angle"
- )
- Simple_sides = IntProperty(
- name="Sides",
- default=3,
- min=3, soft_min=3,
- description="Number of Sides"
- )
- Simple_radius = FloatProperty(
- name="Radius",
- default=1.0,
- min=0.0, soft_min=0.0,
- unit='LENGTH',
- description="Radius"
- )
- Simple_center = BoolProperty(
- name="Length center",
- default=True,
- description="Length center"
- )
- # Rectangle properties
- Simple_width = FloatProperty(
- name="Width",
- default=2.0,
- min=0.0, soft_min=0.0,
- unit='LENGTH',
- description="Width"
- )
- Simple_length = FloatProperty(
- name="Length",
- default=2.0,
- min=0.0, soft_min=0.0,
- unit='LENGTH',
- description="Length"
- )
- Simple_rounded = FloatProperty(
- name="Rounded",
- default=0.0,
- unit='LENGTH',
- description="Rounded corners"
- )
-
-
-class INFO_MT_simple_menu(Menu):
- bl_idname = "INFO_MT_simple_menu"
- bl_label = "2D Objects"
-
- def draw(self, context):
- self.layout.operator_context = 'INVOKE_REGION_WIN'
-
- oper1 = self.layout.operator(Simple.bl_idname, text="Angle", icon="MOD_CURVE")
- oper1.Simple_Change = False
- oper1.Simple_Type = "Angle"
-
- oper2 = self.layout.operator(Simple.bl_idname, text="Arc", icon="MOD_CURVE")
- oper2.Simple_Change = False
- oper2.Simple_Type = "Arc"
-
- oper3 = self.layout.operator(Simple.bl_idname, text="Circle", icon="MOD_CURVE")
- oper3.Simple_Change = False
- oper3.Simple_Type = "Circle"
-
- oper4 = self.layout.operator(Simple.bl_idname, text="Distance", icon="MOD_CURVE")
- oper4.Simple_Change = False
- oper4.Simple_Type = "Distance"
-
- oper5 = self.layout.operator(Simple.bl_idname, text="Ellipse", icon="MOD_CURVE")
- oper5.Simple_Change = False
- oper5.Simple_Type = "Ellipse"
-
- oper6 = self.layout.operator(Simple.bl_idname, text="Line", icon="MOD_CURVE")
- oper6.Simple_Change = False
- oper6.Simple_Type = "Line"
-
- oper7 = self.layout.operator(Simple.bl_idname, text="Point", icon="MOD_CURVE")
- oper7.Simple_Change = False
- oper7.Simple_Type = "Point"
-
- oper8 = self.layout.operator(Simple.bl_idname, text="Polygon", icon="MOD_CURVE")
- oper8.Simple_Change = False
- oper8.Simple_Type = "Polygon"
-
- oper9 = self.layout.operator(Simple.bl_idname, text="Polygon ab", icon="MOD_CURVE")
- oper9.Simple_Change = False
- oper9.Simple_Type = "Polygon_ab"
-
- oper10 = self.layout.operator(Simple.bl_idname, text="Rectangle", icon="MOD_CURVE")
- oper10.Simple_Change = False
- oper10.Simple_Type = "Rectangle"
-
- oper11 = self.layout.operator(Simple.bl_idname, text="Rhomb", icon="MOD_CURVE")
- oper11.Simple_Change = False
- oper11.Simple_Type = "Rhomb"
-
- oper12 = self.layout.operator(Simple.bl_idname, text="Sector", icon="MOD_CURVE")
- oper12.Simple_Change = False
- oper12.Simple_Type = "Sector"
-
- oper13 = self.layout.operator(Simple.bl_idname, text="Segment", icon="MOD_CURVE")
- oper13.Simple_Change = False
- oper13.Simple_Type = "Segment"
-
- oper14 = self.layout.operator(Simple.bl_idname, text="Trapezoid", icon="MOD_CURVE")
- oper14.Simple_Change = False
- oper14.Simple_Type = "Trapezoid"
-
-
# Register
-
-def Simple_button(self, context):
- layout = self.layout
- layout.separator()
- self.layout.menu("INFO_MT_simple_menu", icon="MOD_CURVE")
-
-class VIEW3D_MT_simple_edit_curve_menu(bpy.types.Menu):
- bl_label = 'Simple edit'
-
- def draw(self, context):
- self.layout.operator("curve.bezier_points_fillet", text="Fillet")
- self.layout.operator("curve.bezier_spline_divide", text="Divide")
-
-def Simple_curve_edit_menu(self, context):
- self.layout.menu('VIEW3D_MT_simple_edit_curve_menu')
- self.layout.separator()
+classes = [
+ Simple,
+ BezierDivide,
+ BezierPointsFillet
+]
def register():
- bpy.utils.register_class(Simple)
- bpy.utils.register_class(BezierPointsFillet)
- bpy.utils.register_class(BezierDivide)
- bpy.utils.register_class(SimplePanel)
- bpy.utils.register_class(SimpleEdit)
- bpy.utils.register_class(INFO_MT_simple_menu)
- bpy.utils.register_class(SimpleVariables)
-
- bpy.types.INFO_MT_curve_add.append(Simple_button)
- bpy.types.VIEW3D_MT_edit_curve_specials.prepend(Simple_curve_edit_menu)
-
- bpy.types.Object.s_curve = PointerProperty(type=SimpleVariables)
+ from bpy.utils import register_class
+ for cls in classes:
+ register_class(cls)
+ bpy.types.VIEW3D_MT_curve_add.append(menu)
+ bpy.types.VIEW3D_MT_edit_curve_specials.prepend(Simple_curve_edit_menu)
def unregister():
- bpy.utils.unregister_class(Simple)
- bpy.utils.unregister_class(BezierPointsFillet)
- bpy.utils.unregister_class(BezierDivide)
- bpy.utils.unregister_class(SimplePanel)
- bpy.utils.unregister_class(SimpleEdit)
- bpy.utils.unregister_class(INFO_MT_simple_menu)
- bpy.utils.unregister_class(SimpleVariables)
-
- bpy.types.INFO_MT_curve_add.remove(Simple_button)
- del bpy.types.Object.s_curve
+ from bpy.utils import unregister_class
+ for cls in reversed(classes):
+ unregister_class(cls)
+ bpy.types.VIEW3D_MT_curve_add.remove(menu)
+ bpy.types.VIEW3D_MT_edit_curve_specials.remove(Simple_curve_edit_menu)
if __name__ == "__main__":
register()
diff --git a/add_curve_extra_objects/add_curve_spirals.py b/add_curve_extra_objects/add_curve_spirals.py
index 9abfebd8..c8c11789 100644
--- a/add_curve_extra_objects/add_curve_spirals.py
+++ b/add_curve_extra_objects/add_curve_spirals.py
@@ -5,8 +5,8 @@ bl_info = {
"name": "Spirals",
"description": "Make spirals",
"author": "Alejandro Omar Chocano Vasquez",
- "version": (1, 2, 1),
- "blender": (2, 62, 0),
+ "version": (1, 2, 2),
+ "blender": (2, 80, 0),
"location": "View3D > Add > Curve",
"warning": "",
"wiki_url": "https://wiki.blender.org/index.php/Extensions:2.4/Py/"
@@ -204,9 +204,9 @@ class CURVE_OT_spirals(Operator):
bl_idname = "curve.spirals"
bl_label = "Curve Spirals"
bl_description = "Create different types of spirals"
- bl_options = {'REGISTER', 'UNDO'}
+ bl_options = {'REGISTER', 'UNDO', 'PRESET'}
- spiral_type = EnumProperty(
+ spiral_type : EnumProperty(
items=[('ARCH', "Archemedian", "Archemedian"),
("LOG", "Logarithmic", "Logarithmic"),
("SPHERE", "Spheric", "Spheric"),
@@ -215,14 +215,14 @@ class CURVE_OT_spirals(Operator):
name="Spiral Type",
description="Type of spiral to add"
)
- curve_type = EnumProperty(
+ curve_type : EnumProperty(
items=[('POLY', "Poly", "PolyLine"),
("NURBS", "NURBS", "NURBS")],
default='POLY',
name="Curve Type",
description="Type of spline to use"
)
- spiral_direction = EnumProperty(
+ spiral_direction : EnumProperty(
items=[('COUNTER_CLOCKWISE', "Counter Clockwise",
"Wind in a counter clockwise direction"),
("CLOCKWISE", "Clockwise",
@@ -231,62 +231,62 @@ class CURVE_OT_spirals(Operator):
name="Spiral Direction",
description="Direction of winding"
)
- turns = IntProperty(
+ turns : IntProperty(
default=1,
min=1, max=1000,
description="Length of Spiral in 360 deg"
)
- steps = IntProperty(
+ steps : IntProperty(
default=24,
min=2, max=1000,
description="Number of Vertices per turn"
)
- radius = FloatProperty(
+ radius : FloatProperty(
default=1.00,
min=0.00, max=100.00,
description="Radius for first turn"
)
- dif_z = FloatProperty(
+ dif_z : FloatProperty(
default=0,
min=-10.00, max=100.00,
description="Increase in Z axis per turn"
)
# needed for 1 and 2 spiral_type
# Archemedian variables
- dif_radius = FloatProperty(
+ dif_radius : FloatProperty(
default=0.00,
min=-50.00, max=50.00,
description="Radius increment in each turn"
)
# step between turns(one turn equals 360 deg)
# Log variables
- B_force = FloatProperty(
+ B_force : FloatProperty(
default=1.00,
min=0.00, max=30.00,
description="Factor of exponent"
)
# Torus variables
- inner_radius = FloatProperty(
+ inner_radius : FloatProperty(
default=0.20,
min=0.00, max=100,
description="Inner Radius of Torus"
)
- dif_inner_radius = FloatProperty(
+ dif_inner_radius : FloatProperty(
default=0,
min=-10, max=100,
description="Increase of inner Radius per Cycle"
)
- dif_radius = FloatProperty(
+ dif_radius : FloatProperty(
default=0,
min=-10, max=100,
description="Increase of Torus Radius per Cycle"
)
- cycles = FloatProperty(
+ cycles : FloatProperty(
default=1,
min=0.00, max=1000,
description="Number of Cycles"
)
- curves_number = IntProperty(
+ curves_number : IntProperty(
default=1,
min=1, max=400,
description="Number of curves of spiral"
@@ -300,13 +300,13 @@ class CURVE_OT_spirals(Operator):
layout = self.layout
col = layout.column_flow(align=True)
- col.label("Presets:")
+ col.label(text="Presets:")
row = col.row(align=True)
row.menu("OBJECT_MT_spiral_curve_presets",
text=bpy.types.OBJECT_MT_spiral_curve_presets.bl_label)
- row.operator("curve_extras.spiral_presets", text="", icon='ZOOMIN')
- op = row.operator("curve_extras.spiral_presets", text="", icon='ZOOMOUT')
+ row.operator("curve_extras.spiral_presets", text="")
+ op = row.operator("curve_extras.spiral_presets", text="")
op.remove_active = True
layout.prop(self, "spiral_type")
@@ -320,25 +320,25 @@ class CURVE_OT_spirals(Operator):
box = layout.box()
if self.spiral_type == 'ARCH':
- box.label("Archemedian Settings:")
+ box.label(text="Archemedian Settings:")
col = box.column(align=True)
col.prop(self, "dif_radius", text="Radius Growth")
col.prop(self, "radius", text="Radius")
col.prop(self, "dif_z", text="Height")
if self.spiral_type == 'LOG':
- box.label("Logarithmic Settings:")
+ box.label(text="Logarithmic Settings:")
col = box.column(align=True)
col.prop(self, "radius", text="Radius")
col.prop(self, "B_force", text="Expansion Force")
col.prop(self, "dif_z", text="Height")
if self.spiral_type == 'SPHERE':
- box.label("Spheric Settings:")
+ box.label(text="Spheric Settings:")
box.prop(self, "radius", text="Radius")
if self.spiral_type == 'TORUS':
- box.label("Torus Settings:")
+ box.label(text="Torus Settings:")
col = box.column(align=True)
col.prop(self, "cycles", text="Number of Cycles")
@@ -412,13 +412,22 @@ class OBJECT_MT_spiral_curve_presets(Menu):
draw = bpy.types.Menu.draw_preset
-def register():
- bpy.utils.register_module(__name__)
+# Register
+classes = [
+ CURVE_OT_spirals,
+ CURVE_EXTRAS_OT_spirals_presets,
+ OBJECT_MT_spiral_curve_presets
+]
+def register():
+ from bpy.utils import register_class
+ for cls in classes:
+ register_class(cls)
def unregister():
- bpy.utils.unregister_module(__name__)
-
+ from bpy.utils import unregister_class
+ for cls in reversed(classes):
+ unregister_class(cls)
if __name__ == "__main__":
register()
diff --git a/add_curve_extra_objects/add_curve_spirofit_bouncespline.py b/add_curve_extra_objects/add_curve_spirofit_bouncespline.py
index 382b2d6b..f2dd0fb5 100644
--- a/add_curve_extra_objects/add_curve_spirofit_bouncespline.py
+++ b/add_curve_extra_objects/add_curve_spirofit_bouncespline.py
@@ -20,8 +20,8 @@
bl_info = {
"name": "SpiroFit, BounceSpline and Catenary",
"author": "Antonio Osprite, Liero, Atom, Jimmy Hazevoet",
- "version": (0, 2, 1),
- "blender": (2, 78, 0),
+ "version": (0, 2, 2),
+ "blender": (2, 80, 0),
"location": "Toolshelf > Create Tab",
"description": "SpiroFit, BounceSpline and Catenary adds "
"splines to selected mesh or objects",
@@ -123,19 +123,19 @@ class SpiroFitSpline(Operator):
bl_description = "Wrap selected mesh in a spiral"
bl_options = {'REGISTER', 'UNDO', 'PRESET'}
- map_method = EnumProperty(
+ map_method : EnumProperty(
name="Mapping",
default='RAYCAST',
description="Mapping method",
items=[('RAYCAST', 'Ray cast', 'Ray casting'),
('CLOSESTPOINT', 'Closest point', 'Closest point on mesh')]
)
- direction = BoolProperty(
+ direction : BoolProperty(
name="Direction",
description="Spire direction",
default=False
)
- spire_resolution = IntProperty(
+ spire_resolution : IntProperty(
name="Spire Resolution",
default=8,
min=3,
@@ -143,7 +143,7 @@ class SpiroFitSpline(Operator):
soft_max=128,
description="Number of steps for one turn"
)
- spires = IntProperty(
+ spires : IntProperty(
name="Spires",
default=4,
min=1,
@@ -151,72 +151,72 @@ class SpiroFitSpline(Operator):
soft_max=128,
description="Number of turns"
)
- offset = FloatProperty(
+ offset : FloatProperty(
name="Offset",
default=0.0,
precision=3,
description="Use normal direction to offset spline"
)
- waves = IntProperty(
+ waves : IntProperty(
name="Wave",
default=0,
min=0,
description="Wave amount"
)
- wave_iscale = FloatProperty(
+ wave_iscale : FloatProperty(
name="Wave Intensity",
default=0.0,
min=0.0,
precision=3,
description="Wave intensity scale"
)
- rndm_spire = FloatProperty(
+ rndm_spire : FloatProperty(
name="Randomise",
default=0.0,
min=0.0,
precision=3,
description="Randomise spire"
)
- spline_name = StringProperty(
+ spline_name : StringProperty(
name="Name",
default="SpiroFit"
)
- spline_type = EnumProperty(
+ spline_type : EnumProperty(
name="Spline",
default='BEZIER',
description="Spline type",
items=[('POLY', 'Poly', 'Poly spline'),
('BEZIER', 'Bezier', 'Bezier spline')]
)
- resolution_u = IntProperty(
+ resolution_u : IntProperty(
name="Resolution U",
default=12,
min=0,
max=64,
description="Curve resolution u"
)
- bevel = FloatProperty(
+ bevel : FloatProperty(
name="Bevel Radius",
default=0.0,
min=0.0,
precision=3,
description="Bevel depth"
)
- bevel_res = IntProperty(
+ bevel_res : IntProperty(
name="Bevel Resolution",
default=0,
min=0,
max=32,
description="Bevel resolution"
)
- extrude = FloatProperty(
+ extrude : FloatProperty(
name="Extrude",
default=0.0,
min=0.0,
precision=3,
description="Extrude amount"
)
- twist_mode = EnumProperty(
+ twist_mode : EnumProperty(
name="Twisting",
default='MINIMUM',
description="Twist method, type of tilt calculation",
@@ -224,48 +224,48 @@ class SpiroFitSpline(Operator):
('MINIMUM', "Minimum", 'Minimum'),
('TANGENT', "Tangent", 'Tangent')]
)
- twist_smooth = FloatProperty(
+ twist_smooth : FloatProperty(
name="Smooth",
default=0.0,
min=0.0,
precision=3,
description="Twist smoothing amount for tangents"
)
- tilt = FloatProperty(
+ tilt : FloatProperty(
name="Tilt",
default=0.0,
precision=3,
description="Spline handle tilt"
)
- random_radius = FloatProperty(
+ random_radius : FloatProperty(
name="Randomise",
default=0.0,
min=0.0,
precision=3,
description="Randomise radius of spline controlpoints"
)
- x_ray = BoolProperty(
+ x_ray : BoolProperty(
name="X-Ray",
default=False,
description="X-Ray - make the object draw in front of others"
)
- random_seed = IntProperty(
+ random_seed : IntProperty(
name="Random Seed",
default=1,
min=0,
description="Random seed number"
)
- origin_to_start = BoolProperty(
+ origin_to_start : BoolProperty(
name="Origin at Start",
description="Set origin at first point of spline",
default=False
)
- refresh = BoolProperty(
+ refresh : BoolProperty(
name="Refresh",
description="Refresh spline",
default=False
)
- auto_refresh = BoolProperty(
+ auto_refresh : BoolProperty(
name="Auto",
description="Auto refresh spline",
default=True
@@ -323,10 +323,10 @@ class SpiroFitSpline(Operator):
"Active Object is not a Mesh. Operation Cancelled")
return {'CANCELLED'}
- undo = context.user_preferences.edit.use_global_undo
- context.user_preferences.edit.use_global_undo = False
+ #undo = context.user_preferences.edit.use_global_undo
+ #context.user_preferences.edit.use_global_undo = False
- bpy.ops.object.select_all(action='DESELECT')
+ #bpy.ops.object.select_all(action='DESELECT')
r.seed(self.random_seed)
@@ -364,7 +364,7 @@ class SpiroFitSpline(Operator):
if self.auto_refresh is False:
self.refresh = False
- context.user_preferences.edit.use_global_undo = undo
+ #context.user_preferences.edit.use_global_undo = undo
return {'FINISHED'}
@@ -426,7 +426,7 @@ class BounceSpline(Operator):
bl_description = "Fill selected mesh with a spline"
bl_options = {'REGISTER', 'UNDO', 'PRESET'}
- bounce_number = IntProperty(
+ bounce_number : IntProperty(
name="Bounces",
default=1000,
min=1,
@@ -434,71 +434,71 @@ class BounceSpline(Operator):
soft_max=10000,
description="Number of bounces"
)
- ang_noise = FloatProperty(
+ ang_noise : FloatProperty(
name="Angular Noise",
default=0.25,
min=0.0,
precision=3,
description="Add some noise to ray direction"
)
- offset = FloatProperty(
+ offset : FloatProperty(
name="Offset",
default=0.0,
precision=3,
description="Use normal direction to offset spline"
)
- extra = IntProperty(
+ extra : IntProperty(
name="Extra",
default=50,
min=0,
max=1000,
description="Number of extra tries if it fails to hit mesh"
)
- active_face = BoolProperty(
+ active_face : BoolProperty(
name="Active Face",
default=False,
description="Starts from active face or a random one"
)
- spline_name = StringProperty(
+ spline_name : StringProperty(
name="Name",
default="BounceSpline"
)
- spline_type = EnumProperty(
+ spline_type : EnumProperty(
name="Spline",
default='BEZIER',
description="Spline type",
items=[('POLY', "Poly", "Poly spline"),
('BEZIER', "Bezier", "Bezier spline")]
)
- resolution_u = IntProperty(
+ resolution_u : IntProperty(
name="Resolution U",
default=12,
min=0,
max=64,
description="Curve resolution u"
)
- bevel = FloatProperty(
+ bevel : FloatProperty(
name="Bevel Radius",
default=0.0,
min=0.0,
precision=3,
description="Bevel depth"
)
- bevel_res = IntProperty(
+ bevel_res : IntProperty(
name="Bevel Resolution",
default=0,
min=0,
max=32,
description="Bevel resolution"
)
- extrude = FloatProperty(
+ extrude : FloatProperty(
name="Extrude",
default=0.0,
min=0.0,
precision=3,
description="Extrude amount"
)
- twist_mode = EnumProperty(
+ twist_mode : EnumProperty(
name="Twisting",
default='MINIMUM',
description="Twist method, type of tilt calculation",
@@ -506,48 +506,48 @@ class BounceSpline(Operator):
('MINIMUM', "Minimum", 'Minimum'),
('TANGENT', "Tangent", 'Tangent')]
)
- twist_smooth = FloatProperty(
+ twist_smooth : FloatProperty(
name="Smooth",
default=0.0,
min=0.0,
precision=3,
description="Twist smoothing amount for tangents"
)
- tilt = FloatProperty(
+ tilt : FloatProperty(
name="Tilt",
default=0.0,
precision=3,
description="Spline handle tilt"
)
- random_radius = FloatProperty(
+ random_radius : FloatProperty(
name="Randomise",
default=0.0,
min=0.0,
precision=3,
description="Randomise radius of spline controlpoints"
)
- x_ray = BoolProperty(
+ x_ray : BoolProperty(
name="X-Ray",
default=False,
description="X-Ray - make the object draw in front of others"
)
- random_seed = IntProperty(
+ random_seed : IntProperty(
name="Random Seed",
default=1,
min=0,
description="Random seed number"
)
- origin_to_start = BoolProperty(
+ origin_to_start : BoolProperty(
name="Origin at Start",
description="Set origin at first point of spline",
default=False
)
- refresh = BoolProperty(
+ refresh : BoolProperty(
name="Refresh",
description="Refresh spline",
default=False
)
- auto_refresh = BoolProperty(
+ auto_refresh : BoolProperty(
name="Auto",
description="Auto refresh spline",
default=True
@@ -572,7 +572,7 @@ class BounceSpline(Operator):
col.prop(self, "spline_name")
col.separator()
col.prop(self, "bounce_number")
- row = col.row(align=True).split(0.9, align=True)
+ row = col.row(align=True).split(factor=0.9, align=True)
row.prop(self, "ang_noise")
row.prop(self, "active_face", toggle=True, text="", icon="SNAP_FACE")
col.prop(self, "offset")
@@ -598,10 +598,10 @@ class BounceSpline(Operator):
if obj.type != 'MESH':
return {'CANCELLED'}
- undo = context.user_preferences.edit.use_global_undo
- context.user_preferences.edit.use_global_undo = False
+ #undo = context.user_preferences.edit.use_global_undo
+ #context.user_preferences.edit.use_global_undo = False
- bpy.ops.object.select_all(action='DESELECT')
+ #bpy.ops.object.select_all(action='DESELECT')
r.seed(self.random_seed)
@@ -636,7 +636,7 @@ class BounceSpline(Operator):
if self.auto_refresh is False:
self.refresh = False
- context.user_preferences.edit.use_global_undo = undo
+ #context.user_preferences.edit.use_global_undo = undo
return {'FINISHED'}
@@ -675,14 +675,14 @@ class CatenaryCurve(Operator):
bl_description = "Hang a curve between two selected objects"
bl_options = {'REGISTER', 'UNDO', 'PRESET'}
- steps = IntProperty(
+ steps : IntProperty(
name="Steps",
description="Resolution of the curve",
default=24,
min=2,
max=1024,
)
- var_a = FloatProperty(
+ var_a : FloatProperty(
name="a",
description="Catenary variable a",
precision=3,
@@ -690,46 +690,46 @@ class CatenaryCurve(Operator):
min=0.01,
max=100.0
)
- spline_name = StringProperty(
+ spline_name : StringProperty(
name="Name",
default="Catenary"
)
- spline_type = EnumProperty(
+ spline_type : EnumProperty(
name="Spline",
default='BEZIER',
description="Spline type",
items=[('POLY', "Poly", "Poly spline"),
('BEZIER', "Bezier", "Bezier spline")]
)
- resolution_u = IntProperty(
+ resolution_u : IntProperty(
name="Resolution U",
default=12,
min=0,
max=64,
description="Curve resolution u"
)
- bevel = FloatProperty(
+ bevel : FloatProperty(
name="Bevel Radius",
default=0.0,
min=0.0,
precision=3,
description="Bevel depth"
)
- bevel_res = IntProperty(
+ bevel_res : IntProperty(
name="Bevel Resolution",
default=0,
min=0,
max=32,
description="Bevel resolution"
)
- extrude = FloatProperty(
+ extrude : FloatProperty(
name="Extrude",
default=0.0,
min=0.0,
precision=3,
description="Extrude amount"
)
- twist_mode = EnumProperty(
+ twist_mode : EnumProperty(
name="Twisting",
default='MINIMUM',
description="Twist method, type of tilt calculation",
@@ -737,48 +737,48 @@ class CatenaryCurve(Operator):
('MINIMUM', "Minimum", "Minimum"),
('TANGENT', "Tangent", "Tangent")]
)
- twist_smooth = FloatProperty(
+ twist_smooth : FloatProperty(
name="Smooth",
default=0.0,
min=0.0,
precision=3,
description="Twist smoothing amount for tangents"
)
- tilt = FloatProperty(
+ tilt : FloatProperty(
name="Tilt",
default=0.0,
precision=3,
description="Spline handle tilt"
)
- random_radius = FloatProperty(
+ random_radius : FloatProperty(
name="Randomise",
default=0.0,
min=0.0,
precision=3,
description="Randomise radius of spline controlpoints"
)
- x_ray = BoolProperty(
+ x_ray : BoolProperty(
name="X-Ray",
default=False,
description="X-Ray - make the object draw in front of others"
)
- random_seed = IntProperty(
+ random_seed : IntProperty(
name="Random Seed",
default=1,
min=0,
description="Random seed number"
)
- origin_to_start = BoolProperty(
+ origin_to_start : BoolProperty(
name="Origin at Start",
description="Set origin at first point of spline",
default=False
)
- refresh = BoolProperty(
+ refresh : BoolProperty(
name="Refresh",
description="Refresh spline",
default=False
)
- auto_refresh = BoolProperty(
+ auto_refresh : BoolProperty(
name="Auto",
description="Auto refresh spline",
default=True
@@ -839,10 +839,10 @@ class CatenaryCurve(Operator):
"Catenary could not be completed. Operation Cancelled")
return {'CANCELLED'}
- bpy.ops.object.select_all(action='DESELECT')
+ #bpy.ops.object.select_all(action='DESELECT')
- undo = context.user_preferences.edit.use_global_undo
- context.user_preferences.edit.use_global_undo = False
+ #undo = context.user_preferences.edit.use_global_undo
+ #context.user_preferences.edit.use_global_undo = False
r.seed(self.random_seed)
@@ -876,7 +876,7 @@ class CatenaryCurve(Operator):
if self.auto_refresh is False:
self.refresh = False
- context.user_preferences.edit.use_global_undo = undo
+ #context.user_preferences.edit.use_global_undo = undo
return {'FINISHED'}
@@ -920,7 +920,7 @@ def add_curve_object(
for i in range(len(verts)):
spline.points[i].co = verts[i][0], verts[i][1], verts[i][2], 1
- bpy.context.scene.objects.link(cur)
+ bpy.context.scene.collection.objects.link(cur)
cur.data.use_uv_as_generated = True
cur.data.resolution_u = resolution_u
cur.data.fill_mode = 'FULL'
@@ -930,8 +930,8 @@ def add_curve_object(
cur.data.twist_mode = twist_mode
cur.data.twist_smooth = twist_smooth
cur.matrix_world = matrix
- bpy.context.scene.objects.active = cur
- cur.select = True
+ #bpy.context.scene.objects.active = cur
+ cur.select_set(True)
if x_ray is True:
cur.show_x_ray = x_ray
return
@@ -984,7 +984,7 @@ class SplinePanel(Panel):
bl_context = "objectmode"
bl_region_type = "TOOLS"
bl_label = "Spline"
- bl_category = "Create"
+ #bl_category = "Create"
bl_options = {'DEFAULT_CLOSED'}
def draw(self, context):
diff --git a/add_curve_extra_objects/add_curve_torus_knots.py b/add_curve_extra_objects/add_curve_torus_knots.py
index 4401112d..d138508e 100644
--- a/add_curve_extra_objects/add_curve_torus_knots.py
+++ b/add_curve_extra_objects/add_curve_torus_knots.py
@@ -20,8 +20,8 @@
bl_info = {
"name": "Torus Knots",
"author": "Marius Giurgi (DolphinDream), testscreenings",
- "version": (0, 2),
- "blender": (2, 76, 0),
+ "version": (0, 3),
+ "blender": (2, 80, 0),
"location": "View3D > Add > Curve",
"description": "Adds many types of (torus) knots",
"warning": "",
@@ -160,22 +160,22 @@ def align_matrix(self, context):
else:
rot = Matrix()
- align_matrix = userLoc * loc * rot * userRot
+ align_matrix = userLoc @ loc @ rot @ userRot
return align_matrix
# ------------------------------------------------------------------------------
# Set curve BEZIER handles to auto
-def setBezierHandles(obj, mode='AUTOMATIC'):
+def setBezierHandles(obj, mode='AUTO'):
scene = bpy.context.scene
if obj.type != 'CURVE':
return
- scene.objects.active = obj
- bpy.ops.object.mode_set(mode='EDIT', toggle=True)
- bpy.ops.curve.select_all(action='SELECT')
- bpy.ops.curve.handle_type_set(type=mode)
- bpy.ops.object.mode_set(mode='OBJECT', toggle=True)
+ #scene.objects.active = obj
+ #bpy.ops.object.mode_set(mode='EDIT', toggle=True)
+ #bpy.ops.curve.select_all(action='SELECT')
+ #bpy.ops.curve.handle_type_set(type=mode)
+ #bpy.ops.object.mode_set(mode='OBJECT', toggle=True)
# ------------------------------------------------------------------------------
@@ -237,6 +237,9 @@ def create_torus_knot(self, context):
if splineType == 'BEZIER':
spline.bezier_points.add(int(len(vertArray) * 1.0 / 3 - 1))
spline.bezier_points.foreach_set('co', vertArray)
+ for point in spline.bezier_points:
+ point.handle_right_type = self.handleType
+ point.handle_left_type = self.handleType
else:
spline.points.add(int(len(vertArray) * 1.0 / 4 - 1))
spline.points.foreach_set('co', vertArray)
@@ -265,14 +268,14 @@ def create_torus_knot(self, context):
# set object in the scene
scene = bpy.context.scene
- scene.objects.link(new_obj) # place in active scene
- new_obj.select = True # set as selected
- scene.objects.active = new_obj # set as active
+ scene.collection.objects.link(new_obj) # place in active scene
+ new_obj.select_set(True) # set as selected
+ #scene.objects.active = new_obj # set as active
new_obj.matrix_world = self.align_matrix # apply matrix
# set BEZIER handles
- if splineType == 'BEZIER':
- setBezierHandles(new_obj, self.handleType)
+ #if splineType == 'BEZIER':
+ # setBezierHandles(new_obj, self.handleType)
return
@@ -355,123 +358,123 @@ class torus_knot_plus(Operator, AddObjectHelper):
align_matrix = None
# GENERAL options
- options_plus = BoolProperty(
+ options_plus : BoolProperty(
name="Extra Options",
default=False,
description="Show more options (the plus part)",
)
- absolute_location = BoolProperty(
+ absolute_location : BoolProperty(
name="Absolute Location",
default=False,
description="Set absolute location instead of relative to 3D cursor",
)
# COLOR options
- use_colors = BoolProperty(
+ use_colors : BoolProperty(
name="Use Colors",
default=False,
description="Show torus links in colors",
)
- colorSet = EnumProperty(
+ colorSet : EnumProperty(
name="Color Set",
items=(('1', "RGBish", "RGBsish ordered colors"),
('2', "Rainbow", "Rainbow ordered colors")),
)
- random_colors = BoolProperty(
+ random_colors : BoolProperty(
name="Randomize Colors",
default=False,
description="Randomize link colors",
)
- saturation = FloatProperty(
+ saturation : FloatProperty(
name="Saturation",
default=0.75,
min=0.0, max=1.0,
description="Color saturation",
)
# SURFACE Options
- geo_surface = BoolProperty(
+ geo_surface : BoolProperty(
name="Surface",
default=True,
description="Create surface",
)
- geo_bDepth = FloatProperty(
+ geo_bDepth : FloatProperty(
name="Bevel Depth",
default=0.04,
min=0, soft_min=0,
description="Bevel Depth",
)
- geo_bRes = IntProperty(
+ geo_bRes : IntProperty(
name="Bevel Resolution",
default=2,
min=0, soft_min=0,
max=5, soft_max=5,
description="Bevel Resolution"
)
- geo_extrude = FloatProperty(
+ geo_extrude : FloatProperty(
name="Extrude",
default=0.0,
min=0, soft_min=0,
description="Amount of curve extrusion"
)
- geo_offset = FloatProperty(
+ geo_offset : FloatProperty(
name="Offset",
default=0.0,
min=0, soft_min=0,
description="Offset the surface relative to the curve"
)
# TORUS KNOT Options
- torus_p = IntProperty(
+ torus_p : IntProperty(
name="p",
default=2,
min=1, soft_min=1,
description="Number of Revolutions around the torus hole before closing the knot"
)
- torus_q = IntProperty(
+ torus_q : IntProperty(
name="q",
default=3,
min=1, soft_min=1,
description="Number of Spins through the torus hole before closing the knot"
)
- flip_p = BoolProperty(
+ flip_p : BoolProperty(
name="Flip p",
default=False,
description="Flip Revolution direction"
)
- flip_q = BoolProperty(
+ flip_q : BoolProperty(
name="Flip q",
default=False,
description="Flip Spin direction"
)
- multiple_links = BoolProperty(
+ multiple_links : BoolProperty(
name="Multiple Links",
default=True,
description="Generate all links or just one link when q and q are not co-primes"
)
- torus_u = IntProperty(
+ torus_u : IntProperty(
name="Rev. Multiplier",
default=1,
min=1, soft_min=1,
description="Revolutions Multiplier"
)
- torus_v = IntProperty(
+ torus_v : IntProperty(
name="Spin Multiplier",
default=1,
min=1, soft_min=1,
description="Spin multiplier"
)
- torus_rP = FloatProperty(
+ torus_rP : FloatProperty(
name="Revolution Phase",
default=0.0,
min=0.0, soft_min=0.0,
description="Phase revolutions by this radian amount"
)
- torus_sP = FloatProperty(
+ torus_sP : FloatProperty(
name="Spin Phase",
default=0.0,
min=0.0, soft_min=0.0,
description="Phase spins by this radian amount"
)
# TORUS DIMENSIONS options
- mode = EnumProperty(
+ mode : EnumProperty(
name="Torus Dimensions",
items=(("MAJOR_MINOR", "Major/Minor",
"Use the Major/Minor radii for torus dimensions."),
@@ -479,7 +482,7 @@ class torus_knot_plus(Operator, AddObjectHelper):
"Use the Exterior/Interior radii for torus dimensions.")),
update=mode_update_callback,
)
- torus_R = FloatProperty(
+ torus_R : FloatProperty(
name="Major Radius",
min=0.00, max=100.0,
default=1.0,
@@ -487,7 +490,7 @@ class torus_knot_plus(Operator, AddObjectHelper):
unit='LENGTH',
description="Radius from the torus origin to the center of the cross section"
)
- torus_r = FloatProperty(
+ torus_r : FloatProperty(
name="Minor Radius",
min=0.00, max=100.0,
default=.25,
@@ -495,7 +498,7 @@ class torus_knot_plus(Operator, AddObjectHelper):
unit='LENGTH',
description="Radius of the torus' cross section"
)
- torus_iR = FloatProperty(
+ torus_iR : FloatProperty(
name="Interior Radius",
min=0.00, max=100.0,
default=.75,
@@ -503,7 +506,7 @@ class torus_knot_plus(Operator, AddObjectHelper):
unit='LENGTH',
description="Interior radius of the torus (closest to the torus center)"
)
- torus_eR = FloatProperty(
+ torus_eR : FloatProperty(
name="Exterior Radius",
min=0.00, max=100.0,
default=1.25,
@@ -511,26 +514,26 @@ class torus_knot_plus(Operator, AddObjectHelper):
unit='LENGTH',
description="Exterior radius of the torus (farthest from the torus center)"
)
- torus_s = FloatProperty(
+ torus_s : FloatProperty(
name="Scale",
min=0.01, max=100.0,
default=1.00,
description="Scale factor to multiply the radii"
)
- torus_h = FloatProperty(
+ torus_h : FloatProperty(
name="Height",
default=1.0,
min=0.0, max=100.0,
description="Scale along the local Z axis"
)
# CURVE options
- torus_res = IntProperty(
+ torus_res : IntProperty(
name="Curve Resolution",
default=100,
min=3, soft_min=3,
description="Number of control vertices in the curve"
)
- segment_res = IntProperty(
+ segment_res : IntProperty(
name="Segment Resolution",
default=12,
min=1, soft_min=1,
@@ -540,7 +543,7 @@ class torus_knot_plus(Operator, AddObjectHelper):
('POLY', "Poly", "Poly type"),
('NURBS', "Nurbs", "Nurbs type"),
('BEZIER', "Bezier", "Bezier type")]
- outputType = EnumProperty(
+ outputType : EnumProperty(
name="Output splines",
default='BEZIER',
description="Type of splines to output",
@@ -548,15 +551,15 @@ class torus_knot_plus(Operator, AddObjectHelper):
)
bezierHandles = [
('VECTOR', "Vector", "Bezier Handles type - Vector"),
- ('AUTOMATIC', "Auto", "Bezier Handles type - Automatic"),
+ ('AUTO', "Auto", "Bezier Handles type - Automatic"),
]
- handleType = EnumProperty(
+ handleType : EnumProperty(
name="Handle type",
- default='AUTOMATIC',
+ default='AUTO',
items=bezierHandles,
description="Bezier handle type",
)
- adaptive_resolution = BoolProperty(
+ adaptive_resolution : BoolProperty(
name="Adaptive Resolution",
default=False,
description="Auto adjust curve resolution based on TK length",
@@ -573,12 +576,12 @@ class torus_knot_plus(Operator, AddObjectHelper):
col.label(text="Torus Knot Parameters:")
box = layout.box()
- split = box.split(percentage=0.85, align=True)
+ split = box.split(factor=0.85, align=True)
split.prop(self, "torus_p", text="Revolutions")
split.prop(self, "flip_p", toggle=True, text="",
icon='ARROW_LEFTRIGHT')
- split = box.split(percentage=0.85, align=True)
+ split = box.split(factor=0.85, align=True)
split.prop(self, "torus_q", text="Spins")
split.prop(self, "flip_q", toggle=True, text="",
icon='ARROW_LEFTRIGHT')
@@ -709,14 +712,14 @@ class torus_knot_plus(Operator, AddObjectHelper):
self.align_matrix = align_matrix(self, context)
# turn off undo
- undo = bpy.context.user_preferences.edit.use_global_undo
- bpy.context.user_preferences.edit.use_global_undo = False
+ #undo = bpy.context.user_preferences.edit.use_global_undo
+ #bpy.context.user_preferences.edit.use_global_undo = False
# create the curve
create_torus_knot(self, context)
# restore pre operator undo state
- bpy.context.user_preferences.edit.use_global_undo = undo
+ #bpy.context.user_preferences.edit.use_global_undo = undo
return {'FINISHED'}
@@ -724,3 +727,21 @@ class torus_knot_plus(Operator, AddObjectHelper):
self.execute(context)
return {'FINISHED'}
+
+# Register
+classes = [
+ torus_knot_plus
+]
+
+def register():
+ from bpy.utils import register_class
+ for cls in classes:
+ register_class(cls)
+
+def unregister():
+ from bpy.utils import unregister_class
+ for cls in reversed(classes):
+ unregister_class(cls)
+
+if __name__ == "__main__":
+ register() \ No newline at end of file
diff --git a/add_curve_extra_objects/add_surface_plane_cone.py b/add_curve_extra_objects/add_surface_plane_cone.py
index ad5e1eb9..4f34f654 100644
--- a/add_curve_extra_objects/add_surface_plane_cone.py
+++ b/add_curve_extra_objects/add_surface_plane_cone.py
@@ -4,8 +4,8 @@ bl_info = {
"name": "Surface: Plane / Cone/ Star / Wedge",
"description": "Create a NURBS surface plane",
"author": "Folkert de Vries",
- "version": (1, 0, 1),
- "blender": (2, 5, 9),
+ "version": (1, 0, 2),
+ "blender": (2, 80, 0),
"location": "View3D > Add > Surface",
"warning": "",
"wiki_url": "",
@@ -31,7 +31,7 @@ from bpy.types import Operator
# generic class for inheritance
class MakeSurfaceHelpers:
# get input for size and resolution
- size = FloatProperty(
+ size : FloatProperty(
name="Size",
description="Size of the object",
default=1.0,
@@ -39,14 +39,14 @@ class MakeSurfaceHelpers:
max=100.0,
unit="LENGTH",
)
- res_u = IntProperty(
+ res_u : IntProperty(
name="Resolution U",
description="Surface resolution in u direction",
default=1,
min=1,
max=500,
)
- res_v = IntProperty(
+ res_v : IntProperty(
name="Resolution V",
description="Surface resolution in v direction",
default=1,
@@ -71,7 +71,7 @@ class MakeSurfaceWedge(Operator, MakeSurfaceHelpers):
bl_idname = "object.add_surface_wedge"
bl_label = "Add Surface Wedge"
bl_description = "Construct a Surface Wedge"
- bl_options = {'REGISTER', 'UNDO'}
+ bl_options = {'REGISTER', 'UNDO', 'PRESET'}
def execute(self, context):
# variables
@@ -137,7 +137,7 @@ class MakeSurfaceCone(Operator, MakeSurfaceHelpers):
bl_idname = "object.add_surface_cone"
bl_label = "Add Surface Cone"
bl_description = "Construct a Surface Cone"
- bl_options = {'REGISTER', 'UNDO'}
+ bl_options = {'REGISTER', 'UNDO', 'PRESET'}
def execute(self, context):
size = self.size
@@ -283,7 +283,7 @@ class MakeSurfacePlane(Operator, MakeSurfaceHelpers):
bl_idname = "object.add_surface_plane"
bl_label = "Add Surface Plane"
bl_description = "Construct a Surface Plane"
- bl_options = {'REGISTER', 'UNDO'}
+ bl_options = {'REGISTER', 'UNDO', 'PRESET'}
def execute(self, context):
size = self.size
@@ -347,15 +347,15 @@ class MakeSurfacePlane(Operator, MakeSurfaceHelpers):
class SmoothXtimes(Operator):
bl_idname = "curve.smooth_x_times"
bl_label = "Smooth X Times"
- bl_space_type = "VIEW_3D"
- bl_options = {'REGISTER', 'UNDO'}
+ #bl_space_type = "VIEW_3D"
+ bl_options = {'REGISTER', 'UNDO', 'PRESET'}
# use of this class:
# lets you smooth till a thousand times. this is normally difficult, because
# you have to press w, click, press w, click etc.
# get values
- times = IntProperty(
+ times : IntProperty(
name="Smooth x Times",
min=1,
max=1000,
@@ -375,24 +375,25 @@ class SmoothXtimes(Operator):
return{'FINISHED'}
+# Register
+classes = [
+ #MakeSurfaceHelpers,
+ MakeSurfacePlane,
+ MakeSurfaceCone,
+ MakeSurfaceStar,
+ MakeSurfaceWedge,
+ SmoothXtimes
+]
def register():
- bpy.utils.register_class(MakeSurfaceHelpers)
- bpy.utils.register_class(MakeSurfacePlane)
- bpy.utils.register_class(MakeSurfaceCone)
- bpy.utils.register_class(MakeSurfaceStar)
- bpy.utils.register_class(MakeSurfaceWedge)
- bpy.utils.register_class(SmoothXtimes)
-
+ from bpy.utils import register_class
+ for cls in classes:
+ register_class(cls)
def unregister():
- bpy.utils.unregister_class(MakeSurfaceHelpers)
- bpy.utils.unregister_class(MakeSurfacePlane)
- bpy.utils.unregister_class(MakeSurfaceCone)
- bpy.utils.unregister_class(MakeSurfaceStar)
- bpy.utils.unregister_class(MakeSurfaceWedge)
- bpy.utils.unregister_class(SmoothXtimes)
-
+ from bpy.utils import unregister_class
+ for cls in reversed(classes):
+ unregister_class(cls)
if __name__ == "__main__":
register()
diff --git a/add_curve_extra_objects/beveltaper_curve.py b/add_curve_extra_objects/beveltaper_curve.py
index f91eb8b3..016a0bc4 100644
--- a/add_curve_extra_objects/beveltaper_curve.py
+++ b/add_curve_extra_objects/beveltaper_curve.py
@@ -20,8 +20,8 @@
bl_info = {
"name": "Bevel/Taper Curve",
"author": "Cmomoney",
- "version": (1, 1),
- "blender": (2, 69, 0),
+ "version": (1, 2),
+ "blender": (2, 80, 0),
"location": "View3D > Object > Bevel/Taper",
"description": "Adds bevel and/or taper curve to active curve",
"warning": "",
@@ -238,38 +238,38 @@ class add_tapercurve(Operator):
bl_label = "Add Curve as Taper"
bl_description = ("Add taper curve to Active Curve\n"
"Needs an existing Active Curve")
- bl_options = {'REGISTER', 'UNDO'}
+ bl_options = {'REGISTER', 'UNDO', 'PRESET'}
- scale_ends1 = FloatProperty(
+ scale_ends1 : FloatProperty(
name="End Width Left",
description="Adjust left end taper",
default=0.0,
min=0.0
)
- scale_ends2 = FloatProperty(
+ scale_ends2 : FloatProperty(
name="End Width Right",
description="Adjust right end taper",
default=0.0,
min=0.0
)
- scale_mid = FloatProperty(
+ scale_mid : FloatProperty(
name="Center Width",
description="Adjust taper at center",
default=1.0,
min=0.0
)
- link1 = BoolProperty(
+ link1 : BoolProperty(
name="Link Ends",
description="Link the End Width Left / Right settings\n"
"End Width Left will be editable ",
default=True
)
- link2 = BoolProperty(
+ link2 : BoolProperty(
name="Link Ends / Center",
description="Link the End Widths with the Center Width",
default=False
)
- diff = FloatProperty(
+ diff : FloatProperty(
name="Difference",
default=1,
description="Difference between ends and center while linked"
@@ -325,25 +325,25 @@ class add_bevelcurve(Operator, AddObjectHelper):
bl_label = "Add Curve as Bevel"
bl_description = ("Add bevel curve to Active Curve\n"
"Needs an existing Active Curve")
- bl_options = {'REGISTER', 'UNDO'}
+ bl_options = {'REGISTER', 'UNDO', 'PRESET'}
- types = IntProperty(
+ types : IntProperty(
name="Type",
description="Type of bevel curve",
default=1,
min=1, max=5
)
- scale_x = FloatProperty(
+ scale_x : FloatProperty(
name="Scale X",
description="Scale on X axis",
default=1.0
)
- scale_y = FloatProperty(
+ scale_y : FloatProperty(
name="Scale Y",
description="Scale on Y axis",
default=1.0
)
- link = BoolProperty(
+ link : BoolProperty(
name="Link XY",
description="Link the Scale on X/Y axis",
default=True
@@ -364,7 +364,7 @@ class add_bevelcurve(Operator, AddObjectHelper):
col.prop(self, "rotation")
col = layout.column(align=True)
- col.label("Settings:")
+ col.label(text = "Settings:")
col.prop(self, "types")
split = layout.split(percentage=0.95, align=True)
@@ -394,7 +394,7 @@ class add_bevelcurve(Operator, AddObjectHelper):
class Bevel_Taper_Curve_Menu(Menu):
bl_label = "Bevel/Taper"
- bl_idname = "OBJECT_MT_bevel_taper_curve_menu"
+ bl_idname = "VIEW3D_MT_bevel_taper_curve_menu"
def draw(self, context):
layout = self.layout
@@ -405,16 +405,16 @@ class Bevel_Taper_Curve_Menu(Menu):
def menu_funcs(self, context):
if bpy.context.scene.objects.active.type == "CURVE":
- self.layout.menu("OBJECT_MT_bevel_taper_curve_menu")
+ self.layout.menu("VIEW3D_MT_bevel_taper_curve_menu")
def register():
- bpy.utils.register_module(__name__)
+ #bpy.utils.register_module(__name__)
bpy.types.VIEW3D_MT_object.append(menu_funcs)
def unregister():
- bpy.utils.unregister_module(__name__)
+ #bpy.utils.unregister_module(__name__)
bpy.types.VIEW3D_MT_object.remove(menu_funcs)
diff --git a/add_curve_ivygen.py b/add_curve_ivygen.py
index 89b6abf0..efb55f98 100644
--- a/add_curve_ivygen.py
+++ b/add_curve_ivygen.py
@@ -21,9 +21,9 @@
bl_info = {
"name": "IvyGen",
"author": "testscreenings, PKHG, TrumanBlending",
- "version": (0, 1, 4),
- "blender": (2, 59, 0),
- "location": "View3D > Tool Shelf > Create > Ivy Generator",
+ "version": (0, 1, 5),
+ "blender": (2, 80, 0),
+ "location": "View3D > Properties Panel > Ivy Generator",
"description": "Adds generated ivy to a mesh object starting "
"at the 3D cursor",
"warning": "",
@@ -35,29 +35,30 @@ bl_info = {
import bpy
from bpy.types import (
- Operator,
- Panel,
- PropertyGroup,
- )
+ Operator,
+ Panel,
+ PropertyGroup,
+)
from bpy.props import (
- BoolProperty,
- FloatProperty,
- IntProperty,
- PointerProperty,
- )
+ BoolProperty,
+ FloatProperty,
+ IntProperty,
+ PointerProperty,
+)
+from mathutils.bvhtree import BVHTree
from mathutils import (
- Vector,
- Matrix,
- )
+ Vector,
+ Matrix,
+)
from collections import deque
from math import (
- pow, cos,
- pi, atan2,
- )
+ pow, cos,
+ pi, atan2,
+)
from random import (
- random as rand_val,
- seed as rand_seed,
- )
+ random as rand_val,
+ seed as rand_seed,
+)
import time
@@ -193,7 +194,7 @@ def createIvyGeometry(IVY, growLeaves):
# Add the object and link to scene
newCurve = bpy.data.objects.new("IVY_Curve", curve)
- bpy.context.scene.objects.link(newCurve)
+ bpy.context.collection.objects.link(newCurve)
if growLeaves:
faceList = [[4 * i + l for l in range(4)] for i in
@@ -204,9 +205,9 @@ def createIvyGeometry(IVY, growLeaves):
me.from_pydata(vertList, [], faceList)
me.update(calc_edges=True)
ob = bpy.data.objects.new('IvyLeaf', me)
- bpy.context.scene.objects.link(ob)
+ bpy.context.collection.objects.link(ob)
- me.uv_textures.new("Leaves")
+ me.uv_layers.new(name="Leaves")
# Set the uv texture coords
# TODO, this is non-functional, default uvs are ok?
@@ -292,7 +293,7 @@ class Ivy:
tmpRoot.ivyNodes.append(tmpIvy)
self.ivyRoots.append(tmpRoot)
- def grow(self, ob):
+ def grow(self, ob, bvhtree):
# Determine the local sizes
# local_ivySize = self.ivySize # * radius
# local_maxFloatLength = self.maxFloatLength # * radius
@@ -319,8 +320,8 @@ class Ivy:
randomVector.normalize()
# Calculate the adhesion vector
- adhesionVector = adhesion(prevIvy.pos, ob,
- self.maxAdhesionDistance)
+ adhesionVector = adhesion(
+ prevIvy.pos, bvhtree, self.maxAdhesionDistance)
# Calculate the growing vector
growVector = self.ivySize * (primaryVector * self.primaryWeight +
@@ -337,7 +338,7 @@ class Ivy:
newPos = prevIvy.pos + growVector + gravityVector
# Check for collisions with the object
- climbing = collision(ob, prevIvy.pos, newPos)
+ climbing, newPos = collision(bvhtree, prevIvy.pos, newPos)
# Update the growing vector for any collisions
growVector = newPos - prevIvy.pos - gravityVector
@@ -396,17 +397,13 @@ class Ivy:
return
-def adhesion(loc, ob, max_l):
- # Get transfor vector and transformed loc
- tran_mat = ob.matrix_world.inverted()
- tran_loc = tran_mat * loc
-
+def adhesion(loc, bvhtree, max_l):
# Compute the adhesion vector by finding the nearest point
- nearest_result = ob.closest_point_on_mesh(tran_loc, max_l)
+ nearest_location, *_ = bvhtree.find_nearest(loc, max_l)
adhesion_vector = Vector((0.0, 0.0, 0.0))
- if nearest_result[0]:
+ if nearest_location is not None:
# Compute the distance to the nearest point
- adhesion_vector = ob.matrix_world * nearest_result[1] - loc
+ adhesion_vector = nearest_location - loc
distance = adhesion_vector.length
# If it's less than the maximum allowed and not 0, continue
if distance:
@@ -417,32 +414,38 @@ def adhesion(loc, ob, max_l):
return adhesion_vector
-def collision(ob, pos, new_pos):
+def collision(bvhtree, pos, new_pos):
# Check for collision with the object
climbing = False
- # Transform vecs
- tran_mat = ob.matrix_world.inverted()
- tran_pos = tran_mat * pos
- tran_new_pos = tran_mat * new_pos
- tran_dir = tran_new_pos - tran_pos
+ corrected_new_pos = new_pos
+ direction = new_pos - pos
- ray_result = ob.ray_cast(tran_pos, tran_dir, tran_dir.length)
+ hit_location, hit_normal, *_ = bvhtree.ray_cast(pos, direction, direction.length)
# If there's a collision we need to check it
- if ray_result[0]:
+ if hit_location is not None:
# Check whether the collision is going into the object
- if tran_dir.dot(ray_result[2]) < 0.0:
- # Find projection of the point onto the plane
- p0 = tran_new_pos - (tran_new_pos -
- ray_result[1]).project(ray_result[2])
- # Reflect in the plane
- tran_new_pos += 2 * (p0 - tran_new_pos)
- new_pos *= 0
- new_pos += ob.matrix_world * tran_new_pos
+ if direction.dot(hit_normal) < 0.0:
+ reflected_direction = (new_pos - hit_location).reflect(hit_normal)
+
+ corrected_new_pos = hit_location + reflected_direction
climbing = True
- return climbing
+
+ return climbing, corrected_new_pos
+def bvhtree_from_object(ob):
+ import bmesh
+ bm = bmesh.new()
+
+ mesh = ob.to_mesh(bpy.context.depsgraph, True)
+ bm.from_mesh(mesh)
+ bm.transform(ob.matrix_world)
+
+ bvhtree = BVHTree.FromBMesh(bm)
+ bpy.data.meshes.remove(mesh)
+ return bvhtree
+
def check_mesh_faces(ob):
me = ob.data
if len(me.polygons) > 0:
@@ -457,16 +460,16 @@ class IvyGen(Operator):
bl_description = "Generate Ivy on an Mesh Object"
bl_options = {'REGISTER', 'UNDO'}
- updateIvy = BoolProperty(
- name="Update Ivy",
- description="Update the Ivy location based on the cursor and Panel settings",
- default=False
- )
- defaultIvy = BoolProperty(
- name="Default Ivy",
- options={"HIDDEN", "SKIP_SAVE"},
- default=False
- )
+ updateIvy: BoolProperty(
+ name="Update Ivy",
+ description="Update the Ivy location based on the cursor and Panel settings",
+ default=False
+ )
+ defaultIvy: BoolProperty(
+ name="Default Ivy",
+ options={"HIDDEN", "SKIP_SAVE"},
+ default=False
+ )
@classmethod
def poll(self, context):
@@ -510,6 +513,7 @@ class IvyGen(Operator):
# Get the selected object
ob = context.active_object
+ bvhtree = bvhtree_from_object(ob)
# Check if the mesh has at least one polygon since some functions
# are expecting them in the object's data (see T51753)
@@ -562,7 +566,7 @@ class IvyGen(Operator):
(IVY.maxLength < maxLength) and
(not checkTime or (time.time() - t < maxTime))):
# Grow the ivy for this iteration
- IVY.grow(ob)
+ IVY.grow(ob, bvhtree)
# Print the proportion of ivy growth to console
if (IVY.maxLength / maxLength * 100) > 10 * startPercent // 10:
@@ -596,9 +600,8 @@ class CURVE_PT_IvyGenPanel(Panel):
bl_label = "Ivy Generator"
bl_idname = "CURVE_PT_IvyGenPanel"
bl_space_type = "VIEW_3D"
- bl_region_type = "TOOLS"
- bl_category = "Create"
- bl_context = 'objectmode'
+ bl_region_type = "UI"
+ bl_category = "View"
bl_options = {"DEFAULT_CLOSED"}
def draw(self, context):
@@ -615,26 +618,26 @@ class CURVE_PT_IvyGenPanel(Panel):
prop_def.updateIvy = True
col = layout.column(align=True)
- col.label("Generation Settings:")
+ col.label(text="Generation Settings:")
col.prop(wm.ivy_gen_props, "randomSeed")
col.prop(wm.ivy_gen_props, "maxTime")
col = layout.column(align=True)
- col.label("Size Settings:")
+ col.label(text="Size Settings:")
col.prop(wm.ivy_gen_props, "maxIvyLength")
col.prop(wm.ivy_gen_props, "ivySize")
col.prop(wm.ivy_gen_props, "maxFloatLength")
col.prop(wm.ivy_gen_props, "maxAdhesionDistance")
col = layout.column(align=True)
- col.label("Weight Settings:")
+ col.label(text="Weight Settings:")
col.prop(wm.ivy_gen_props, "primaryWeight")
col.prop(wm.ivy_gen_props, "randomWeight")
col.prop(wm.ivy_gen_props, "gravityWeight")
col.prop(wm.ivy_gen_props, "adhesionWeight")
col = layout.column(align=True)
- col.label("Branch Settings:")
+ col.label(text="Branch Settings:")
col.prop(wm.ivy_gen_props, "branchingProbability")
col.prop(wm.ivy_gen_props, "ivyBranchSize")
@@ -643,124 +646,124 @@ class CURVE_PT_IvyGenPanel(Panel):
if wm.ivy_gen_props.growLeaves:
col = layout.column(align=True)
- col.label("Leaf Settings:")
+ col.label(text="Leaf Settings:")
col.prop(wm.ivy_gen_props, "ivyLeafSize")
col.prop(wm.ivy_gen_props, "leafProbability")
class IvyGenProperties(PropertyGroup):
- maxIvyLength = FloatProperty(
- name="Max Ivy Length",
- description="Maximum ivy length in Blender Units",
- default=1.0,
- min=0.0,
- soft_max=3.0,
- subtype='DISTANCE',
- unit='LENGTH'
- )
- primaryWeight = FloatProperty(
- name="Primary Weight",
- description="Weighting given to the current direction",
- default=0.5,
- min=0.0,
- soft_max=1.0
- )
- randomWeight = FloatProperty(
- name="Random Weight",
- description="Weighting given to the random direction",
- default=0.2,
- min=0.0,
- soft_max=1.0
- )
- gravityWeight = FloatProperty(
- name="Gravity Weight",
- description="Weighting given to the gravity direction",
- default=1.0,
- min=0.0,
- soft_max=1.0
- )
- adhesionWeight = FloatProperty(
- name="Adhesion Weight",
- description="Weighting given to the adhesion direction",
- default=0.1,
- min=0.0,
- soft_max=1.0
- )
- branchingProbability = FloatProperty(
- name="Branching Probability",
- description="Probability of a new branch forming",
- default=0.05,
- min=0.0,
- soft_max=1.0
- )
- leafProbability = FloatProperty(
- name="Leaf Probability",
- description="Probability of a leaf forming",
- default=0.35,
- min=0.0,
- soft_max=1.0
- )
- ivySize = FloatProperty(
- name="Ivy Size",
- description="The length of an ivy segment in Blender"
- " Units",
- default=0.02,
- min=0.0,
- soft_max=1.0,
- precision=3
- )
- ivyLeafSize = FloatProperty(
- name="Ivy Leaf Size",
- description="The size of the ivy leaves",
- default=0.02,
- min=0.0,
- soft_max=0.5,
- precision=3
- )
- ivyBranchSize = FloatProperty(
- name="Ivy Branch Size",
- description="The size of the ivy branches",
- default=0.001,
- min=0.0,
- soft_max=0.1,
- precision=4
- )
- maxFloatLength = FloatProperty(
- name="Max Float Length",
- description="The maximum distance that a branch "
- "can live while floating",
- default=0.5,
- min=0.0,
- soft_max=1.0
- )
- maxAdhesionDistance = FloatProperty(
- name="Max Adhesion Length",
- description="The maximum distance that a branch "
- "will feel the effects of adhesion",
- default=1.0,
- min=0.0,
- soft_max=2.0,
- precision=2
- )
- randomSeed = IntProperty(
- name="Random Seed",
- description="The seed governing random generation",
- default=0,
- min=0
- )
- maxTime = FloatProperty(
- name="Maximum Time",
- description="The maximum time to run the generation for "
- "in seconds generation (0.0 = Disabled)",
- default=0.0,
- min=0.0,
- soft_max=10
- )
- growLeaves = BoolProperty(
- name="Grow Leaves",
- description="Grow leaves or not",
- default=True
- )
+ maxIvyLength: FloatProperty(
+ name="Max Ivy Length",
+ description="Maximum ivy length in Blender Units",
+ default=1.0,
+ min=0.0,
+ soft_max=3.0,
+ subtype='DISTANCE',
+ unit='LENGTH'
+ )
+ primaryWeight: FloatProperty(
+ name="Primary Weight",
+ description="Weighting given to the current direction",
+ default=0.5,
+ min=0.0,
+ soft_max=1.0
+ )
+ randomWeight: FloatProperty(
+ name="Random Weight",
+ description="Weighting given to the random direction",
+ default=0.2,
+ min=0.0,
+ soft_max=1.0
+ )
+ gravityWeight: FloatProperty(
+ name="Gravity Weight",
+ description="Weighting given to the gravity direction",
+ default=1.0,
+ min=0.0,
+ soft_max=1.0
+ )
+ adhesionWeight: FloatProperty(
+ name="Adhesion Weight",
+ description="Weighting given to the adhesion direction",
+ default=0.1,
+ min=0.0,
+ soft_max=1.0
+ )
+ branchingProbability: FloatProperty(
+ name="Branching Probability",
+ description="Probability of a new branch forming",
+ default=0.05,
+ min=0.0,
+ soft_max=1.0
+ )
+ leafProbability: FloatProperty(
+ name="Leaf Probability",
+ description="Probability of a leaf forming",
+ default=0.35,
+ min=0.0,
+ soft_max=1.0
+ )
+ ivySize: FloatProperty(
+ name="Ivy Size",
+ description="The length of an ivy segment in Blender"
+ " Units",
+ default=0.02,
+ min=0.0,
+ soft_max=1.0,
+ precision=3
+ )
+ ivyLeafSize: FloatProperty(
+ name="Ivy Leaf Size",
+ description="The size of the ivy leaves",
+ default=0.02,
+ min=0.0,
+ soft_max=0.5,
+ precision=3
+ )
+ ivyBranchSize: FloatProperty(
+ name="Ivy Branch Size",
+ description="The size of the ivy branches",
+ default=0.001,
+ min=0.0,
+ soft_max=0.1,
+ precision=4
+ )
+ maxFloatLength: FloatProperty(
+ name="Max Float Length",
+ description="The maximum distance that a branch "
+ "can live while floating",
+ default=0.5,
+ min=0.0,
+ soft_max=1.0
+ )
+ maxAdhesionDistance: FloatProperty(
+ name="Max Adhesion Length",
+ description="The maximum distance that a branch "
+ "will feel the effects of adhesion",
+ default=1.0,
+ min=0.0,
+ soft_max=2.0,
+ precision=2
+ )
+ randomSeed: IntProperty(
+ name="Random Seed",
+ description="The seed governing random generation",
+ default=0,
+ min=0
+ )
+ maxTime: FloatProperty(
+ name="Maximum Time",
+ description="The maximum time to run the generation for "
+ "in seconds generation (0.0 = Disabled)",
+ default=0.0,
+ min=0.0,
+ soft_max=10
+ )
+ growLeaves: BoolProperty(
+ name="Grow Leaves",
+ description="Grow leaves or not",
+ default=True
+ )
classes = (
@@ -775,8 +778,8 @@ def register():
bpy.utils.register_class(cls)
bpy.types.WindowManager.ivy_gen_props = PointerProperty(
- type=IvyGenProperties
- )
+ type=IvyGenProperties
+ )
def unregister():
diff --git a/add_curve_sapling/__init__.py b/add_curve_sapling/__init__.py
index f49823b5..a657264a 100644
--- a/add_curve_sapling/__init__.py
+++ b/add_curve_sapling/__init__.py
@@ -19,9 +19,9 @@
bl_info = {
"name": "Sapling Tree Gen",
- "author": "Andrew Hale (TrumanBlending), Aaron Buchler",
- "version": (0, 3, 3),
- "blender": (2, 77, 0),
+ "author": "Andrew Hale (TrumanBlending), Aaron Buchler, CansecoGPC",
+ "version": (0, 3, 4),
+ "blender": (2, 80, 0),
"location": "View3D > Add > Curve",
"description": ("Adds a parametric tree. The method is presented by "
"Jason Weber & Joseph Penn in their paper 'Creation and Rendering of "
@@ -110,14 +110,7 @@ def getPresetpath():
"""Support user defined scripts directory
Find the first occurrence of add_curve_sapling/presets in possible script paths
and return it as preset path"""
- # presetpath = ""
- # for p in bpy.utils.script_paths():
- # presetpath = os.path.join(p, 'addons', 'add_curve_sapling_3', 'presets')
- # if os.path.exists(presetpath):
- # break
- # return presetpath
-
- # why not just do this
+
script_file = os.path.realpath(__file__)
directory = os.path.dirname(script_file)
directory = os.path.join(directory, "presets")
@@ -145,7 +138,7 @@ class ExportData(Operator):
bl_idname = 'sapling.exportdata'
bl_label = 'Export Preset'
- data = StringProperty()
+ data: StringProperty()
def execute(self, context):
# Unpack some data from the input
@@ -196,7 +189,7 @@ class ImportData(Operator):
bl_idname = "sapling.importdata"
bl_label = "Import Preset"
- filename = StringProperty()
+ filename: StringProperty()
def execute(self, context):
# Make sure the operator knows about the global variables
@@ -276,49 +269,49 @@ class AddTree(Operator):
def no_update_tree(self, context):
self.do_update = False
- do_update = BoolProperty(
+ do_update: BoolProperty(
name='Do Update',
default=True, options={'HIDDEN'}
)
- chooseSet = EnumProperty(
+ chooseSet: EnumProperty(
name='Settings',
description='Choose the settings to modify',
items=settings,
default='0', update=no_update_tree
)
- bevel = BoolProperty(
+ bevel: BoolProperty(
name='Bevel',
description='Whether the curve is beveled',
default=False, update=update_tree
)
- prune = BoolProperty(
+ prune: BoolProperty(
name='Prune',
description='Whether the tree is pruned',
default=False, update=update_tree
)
- showLeaves = BoolProperty(
+ showLeaves: BoolProperty(
name='Show Leaves',
description='Whether the leaves are shown',
default=False, update=update_tree
)
- useArm = BoolProperty(
+ useArm: BoolProperty(
name='Use Armature',
description='Whether the armature is generated',
default=False, update=update_tree
)
- seed = IntProperty(
+ seed: IntProperty(
name='Random Seed',
description='The seed of the random number generator',
default=0, update=update_tree
)
- handleType = IntProperty(
+ handleType: IntProperty(
name='Handle Type',
description='The type of curve handles',
min=0,
max=1,
default=0, update=update_tree
)
- levels = IntProperty(
+ levels: IntProperty(
name='Levels',
description='Number of recursive branches (Levels)',
min=1,
@@ -326,14 +319,14 @@ class AddTree(Operator):
soft_max=4,
default=3, update=update_tree
)
- length = FloatVectorProperty(
+ length: FloatVectorProperty(
name='Length',
description='The relative lengths of each branch level (nLength)',
min=0.000001,
default=[1, 0.3, 0.6, 0.45],
size=4, update=update_tree
)
- lengthV = FloatVectorProperty(
+ lengthV: FloatVectorProperty(
name='Length Variation',
description='The relative length variations of each level (nLengthV)',
min=0.0,
@@ -341,52 +334,52 @@ class AddTree(Operator):
default=[0, 0, 0, 0],
size=4, update=update_tree
)
- taperCrown = FloatProperty(
+ taperCrown: FloatProperty(
name='Taper Crown',
description='Shorten trunk splits toward outside of tree',
min=0.0,
soft_max=1.0,
default=0, update=update_tree
)
- branches = IntVectorProperty(
+ branches: IntVectorProperty(
name='Branches',
description='The number of branches grown at each level (nBranches)',
min=0,
default=[50, 30, 10, 10],
size=4, update=update_tree
)
- curveRes = IntVectorProperty(
+ curveRes: IntVectorProperty(
name='Curve Resolution',
description='The number of segments on each branch (nCurveRes)',
min=1,
default=[3, 5, 3, 1],
size=4, update=update_tree
)
- curve = FloatVectorProperty(
+ curve: FloatVectorProperty(
name='Curvature',
description='The angle of the end of the branch (nCurve)',
default=[0, -40, -40, 0],
size=4, update=update_tree
)
- curveV = FloatVectorProperty(
+ curveV: FloatVectorProperty(
name='Curvature Variation',
description='Variation of the curvature (nCurveV)',
default=[20, 50, 75, 0],
size=4, update=update_tree
)
- curveBack = FloatVectorProperty(
+ curveBack: FloatVectorProperty(
name='Back Curvature',
description='Curvature for the second half of a branch (nCurveBack)',
default=[0, 0, 0, 0],
size=4, update=update_tree
)
- baseSplits = IntProperty(
+ baseSplits: IntProperty(
name='Base Splits',
description='Number of trunk splits at its base (nBaseSplits)',
min=0,
default=0, update=update_tree
)
- segSplits = FloatVectorProperty(
+ segSplits: FloatVectorProperty(
name='Segment Splits',
description='Number of splits per segment (nSegSplits)',
min=0,
@@ -394,45 +387,45 @@ class AddTree(Operator):
default=[0, 0, 0, 0],
size=4, update=update_tree
)
- splitByLen = BoolProperty(
+ splitByLen: BoolProperty(
name='Split relative to length',
description='Split proportional to branch length',
default=False, update=update_tree
)
- rMode = EnumProperty(
+ rMode: EnumProperty(
name="", # "Branching Mode"
description='Branching and Rotation Mode',
items=branchmodes,
default="rotate", update=update_tree
)
- splitAngle = FloatVectorProperty(
+ splitAngle: FloatVectorProperty(
name='Split Angle',
description='Angle of branch splitting (nSplitAngle)',
default=[0, 0, 0, 0],
size=4, update=update_tree
)
- splitAngleV = FloatVectorProperty(
+ splitAngleV: FloatVectorProperty(
name='Split Angle Variation',
description='Variation in the split angle (nSplitAngleV)',
default=[0, 0, 0, 0],
size=4, update=update_tree
)
- scale = FloatProperty(
+ scale: FloatProperty(
name='Scale',
description='The tree scale (Scale)',
min=0.0,
default=13.0, update=update_tree)
- scaleV = FloatProperty(name='Scale Variation',
+ scaleV: FloatProperty(name='Scale Variation',
description='The variation in the tree scale (ScaleV)',
default=3.0, update=update_tree
)
- attractUp = FloatVectorProperty(
+ attractUp: FloatVectorProperty(
name='Vertical Attraction',
description='Branch upward attraction',
default=[0, 0, 0, 0],
size=4, update=update_tree
)
- attractOut = FloatVectorProperty(
+ attractOut: FloatVectorProperty(
name='Outward Attraction',
description='Branch outward attraction',
default=[0, 0, 0, 0],
@@ -440,19 +433,19 @@ class AddTree(Operator):
max=1.0,
size=4, update=update_tree
)
- shape = EnumProperty(
+ shape: EnumProperty(
name='Shape',
description='The overall shape of the tree (Shape)',
items=shapeList3,
default='7', update=update_tree
)
- shapeS = EnumProperty(
+ shapeS: EnumProperty(
name='Secondary Branches Shape',
description='The shape of secondary splits',
items=shapeList4,
default='4', update=update_tree
)
- customShape = FloatVectorProperty(
+ customShape: FloatVectorProperty(
name='Custom Shape',
description='custom shape branch length at (Base, Middle, Middle Position, Top)',
size=4,
@@ -460,76 +453,76 @@ class AddTree(Operator):
max=1,
default=[.5, 1.0, .3, .5], update=update_tree
)
- branchDist = FloatProperty(
+ branchDist: FloatProperty(
name='Branch Distribution',
description='Adjust branch spacing to put more branches at the top or bottom of the tree',
min=0.1,
soft_max=10,
default=1.0, update=update_tree
)
- nrings = IntProperty(
+ nrings: IntProperty(
name='Branch Rings',
description='grow branches in rings',
min=0,
default=0, update=update_tree
)
- baseSize = FloatProperty(
+ baseSize: FloatProperty(
name='Trunk Height',
description='Fraction of tree height with no branches (Base Size)',
min=0.0,
max=1.0,
default=0.4, update=update_tree
)
- baseSize_s = FloatProperty(
+ baseSize_s: FloatProperty(
name='Secondary Base Size',
description='Factor to decrease base size for each level',
min=0.0,
max=1.0,
default=0.25, update=update_tree
)
- splitHeight = FloatProperty(
+ splitHeight: FloatProperty(
name='Split Height',
description='Fraction of tree height with no splits',
min=0.0,
max=1.0,
default=0.2, update=update_tree
)
- splitBias = FloatProperty(
+ splitBias: FloatProperty(
name='splitBias',
description='Put more splits at the top or bottom of the tree',
soft_min=-2.0,
soft_max=2.0,
default=0.0, update=update_tree
)
- ratio = FloatProperty(
+ ratio: FloatProperty(
name='Ratio',
description='Base radius size (Ratio)',
min=0.0,
default=0.015, update=update_tree
)
- minRadius = FloatProperty(
+ minRadius: FloatProperty(
name='Minimum Radius',
description='Minimum branch Radius',
min=0.0,
default=0.0, update=update_tree
)
- closeTip = BoolProperty(
+ closeTip: BoolProperty(
name='Close Tip',
description='Set radius at branch tips to zero',
default=False, update=update_tree
)
- rootFlare = FloatProperty(
+ rootFlare: FloatProperty(
name='Root Flare',
description='Root radius factor',
min=1.0,
default=1.0, update=update_tree
)
- autoTaper = BoolProperty(
+ autoTaper: BoolProperty(
name='Auto Taper',
description='Calculate taper automatically based on branch lengths',
default=True, update=update_tree
)
- taper = FloatVectorProperty(
+ taper: FloatVectorProperty(
name='Taper',
description='The fraction of tapering on each branch (nTaper)',
min=0.0,
@@ -537,7 +530,7 @@ class AddTree(Operator):
default=[1, 1, 1, 1],
size=4, update=update_tree
)
- radiusTweak = FloatVectorProperty(
+ radiusTweak: FloatVectorProperty(
name='Tweak Radius',
description='multiply radius by this factor',
min=0.0,
@@ -545,164 +538,164 @@ class AddTree(Operator):
default=[1, 1, 1, 1],
size=4, update=update_tree
)
- ratioPower = FloatProperty(
+ ratioPower: FloatProperty(
name='Branch Radius Ratio',
description=('Power which defines the radius of a branch compared to '
'the radius of the branch it grew from (RatioPower)'),
min=0.0,
default=1.2, update=update_tree
)
- downAngle = FloatVectorProperty(
+ downAngle: FloatVectorProperty(
name='Down Angle',
description=('The angle between a new branch and the one it grew '
'from (nDownAngle)'),
default=[90, 60, 45, 45],
size=4, update=update_tree
)
- downAngleV = FloatVectorProperty(
+ downAngleV: FloatVectorProperty(
name='Down Angle Variation',
description="Angle to decrease Down Angle by towards end of parent branch "
"(negative values add random variation)",
default=[0, -50, 10, 10],
size=4, update=update_tree
)
- useOldDownAngle = BoolProperty(
+ useOldDownAngle: BoolProperty(
name='Use old down angle variation',
default=False, update=update_tree
)
- useParentAngle = BoolProperty(
+ useParentAngle: BoolProperty(
name='Use parent angle',
description='(first level) Rotate branch to match parent branch',
default=True, update=update_tree
)
- rotate = FloatVectorProperty(
+ rotate: FloatVectorProperty(
name='Rotate Angle',
description="The angle of a new branch around the one it grew from "
"(negative values rotate opposite from the previous)",
default=[137.5, 137.5, 137.5, 137.5],
size=4, update=update_tree
)
- rotateV = FloatVectorProperty(
+ rotateV: FloatVectorProperty(
name='Rotate Angle Variation',
description='Variation in the rotate angle (nRotateV)',
default=[0, 0, 0, 0],
size=4, update=update_tree
)
- scale0 = FloatProperty(
+ scale0: FloatProperty(
name='Radius Scale',
description='The scale of the trunk radius (0Scale)',
min=0.0,
default=1.0, update=update_tree
)
- scaleV0 = FloatProperty(
+ scaleV0: FloatProperty(
name='Radius Scale Variation',
description='Variation in the radius scale (0ScaleV)',
min=0.0,
max=1.0,
default=0.2, update=update_tree
)
- pruneWidth = FloatProperty(
+ pruneWidth: FloatProperty(
name='Prune Width',
description='The width of the envelope (PruneWidth)',
min=0.0,
default=0.4, update=update_tree
)
- pruneBase = FloatProperty(
+ pruneBase: FloatProperty(
name='Prune Base Height',
description='The height of the base of the envelope, bound by trunk height',
min=0.0,
max=1.0,
default=0.3, update=update_tree
)
- pruneWidthPeak = FloatProperty(
+ pruneWidthPeak: FloatProperty(
name='Prune Width Peak',
description=("Fraction of envelope height where the maximum width "
"occurs (PruneWidthPeak)"),
min=0.0,
default=0.6, update=update_tree
)
- prunePowerHigh = FloatProperty(
+ prunePowerHigh: FloatProperty(
name='Prune Power High',
description=('Power which determines the shape of the upper portion '
'of the envelope (PrunePowerHigh)'),
default=0.5, update=update_tree
)
- prunePowerLow = FloatProperty(
+ prunePowerLow: FloatProperty(
name='Prune Power Low',
description=('Power which determines the shape of the lower portion '
'of the envelope (PrunePowerLow)'),
default=0.001, update=update_tree
)
- pruneRatio = FloatProperty(
+ pruneRatio: FloatProperty(
name='Prune Ratio',
description='Proportion of pruned length (PruneRatio)',
min=0.0,
max=1.0,
default=1.0, update=update_tree
)
- leaves = IntProperty(
+ leaves: IntProperty(
name='Leaves',
description="Maximum number of leaves per branch (negative values grow "
"leaves from branch tip (palmate compound leaves))",
default=25, update=update_tree
)
- leafDownAngle = FloatProperty(
+ leafDownAngle: FloatProperty(
name='Leaf Down Angle',
description='The angle between a new leaf and the branch it grew from',
default=45, update=update_leaves
)
- leafDownAngleV = FloatProperty(
+ leafDownAngleV: FloatProperty(
name='Leaf Down Angle Variation',
description="Angle to decrease Down Angle by towards end of parent branch "
"(negative values add random variation)",
default=10, update=update_tree
)
- leafRotate = FloatProperty(
+ leafRotate: FloatProperty(
name='Leaf Rotate Angle',
description="The angle of a new leaf around the one it grew from "
"(negative values rotate opposite from previous)",
default=137.5, update=update_tree
)
- leafRotateV = FloatProperty(
+ leafRotateV: FloatProperty(
name='Leaf Rotate Angle Variation',
description='Variation in the rotate angle',
default=0.0, update=update_leaves
)
- leafScale = FloatProperty(
+ leafScale: FloatProperty(
name='Leaf Scale',
description='The scaling applied to the whole leaf (LeafScale)',
min=0.0,
default=0.17, update=update_leaves
)
- leafScaleX = FloatProperty(
+ leafScaleX: FloatProperty(
name='Leaf Scale X',
description=('The scaling applied to the x direction of the leaf '
'(LeafScaleX)'),
min=0.0,
default=1.0, update=update_leaves
)
- leafScaleT = FloatProperty(
+ leafScaleT: FloatProperty(
name='Leaf Scale Taper',
description='scale leaves toward the tip or base of the patent branch',
min=-1.0,
max=1.0,
default=0.0, update=update_leaves
)
- leafScaleV = FloatProperty(
+ leafScaleV: FloatProperty(
name='Leaf Scale Variation',
description='randomize leaf scale',
min=0.0,
max=1.0,
default=0.0, update=update_leaves
)
- leafShape = EnumProperty(
+ leafShape: EnumProperty(
name='Leaf Shape',
description='The shape of the leaves',
items=(('hex', 'Hexagonal', '0'), ('rect', 'Rectangular', '1'),
('dFace', 'DupliFaces', '2'), ('dVert', 'DupliVerts', '3')),
default='hex', update=update_leaves
)
- leafDupliObj = EnumProperty(
+ leafDupliObj: EnumProperty(
name='Leaf Object',
description='Object to use for leaf instancing if Leaf Shape is DupliFaces or DupliVerts',
items=objectList,
@@ -717,64 +710,64 @@ class AddTree(Operator):
default=0.0, update=update_leaves
)
"""
- leafangle = FloatProperty(
+ leafangle: FloatProperty(
name='Leaf Angle',
description='Leaf vertical attraction',
default=0.0, update=update_leaves
)
- horzLeaves = BoolProperty(
+ horzLeaves: BoolProperty(
name='Horizontal leaves',
description='Leaves face upwards',
default=True, update=update_leaves
)
- leafDist = EnumProperty(
+ leafDist: EnumProperty(
name='Leaf Distribution',
description='The way leaves are distributed on branches',
items=shapeList4,
default='6', update=update_tree
)
- bevelRes = IntProperty(
+ bevelRes: IntProperty(
name='Bevel Resolution',
description='The bevel resolution of the curves',
min=0,
max=32,
default=0, update=update_tree
)
- resU = IntProperty(
+ resU: IntProperty(
name='Curve Resolution',
description='The resolution along the curves',
min=1,
default=4, update=update_tree
)
- handleType = EnumProperty(
+ handleType: EnumProperty(
name='Handle Type',
description='The type of handles used in the spline',
items=handleList,
default='0', update=update_tree
)
- armAnim = BoolProperty(
+ armAnim: BoolProperty(
name='Armature Animation',
description='Whether animation is added to the armature',
default=False, update=update_tree
)
- previewArm = BoolProperty(
+ previewArm: BoolProperty(
name='Fast Preview',
description='Disable armature modifier, hide tree, and set bone display to wire, for fast playback',
# Disable skin modifier and hide tree and armature, for fast playback
default=False, update=update_tree
)
- leafAnim = BoolProperty(
+ leafAnim: BoolProperty(
name='Leaf Animation',
description='Whether animation is added to the leaves',
default=False, update=update_tree
)
- frameRate = FloatProperty(
+ frameRate: FloatProperty(
name='Animation Speed',
description=('Adjust speed of animation, relative to scene frame rate'),
min=0.001,
default=1, update=update_tree
)
- loopFrames = IntProperty(
+ loopFrames: IntProperty(
name='Loop Frames',
description='Number of frames to make the animation loop for, zero is disabled',
min=0,
@@ -792,66 +785,66 @@ class AddTree(Operator):
default=0.0, update=update_tree
)
"""
- wind = FloatProperty(
+ wind: FloatProperty(
name='Overall Wind Strength',
description='The intensity of the wind to apply to the armature',
default=1.0, update=update_tree
)
- gust = FloatProperty(
+ gust: FloatProperty(
name='Wind Gust Strength',
description='The amount of directional movement, (from the positive Y direction)',
default=1.0, update=update_tree
)
- gustF = FloatProperty(
+ gustF: FloatProperty(
name='Wind Gust Fequency',
description='The Frequency of directional movement',
default=0.075, update=update_tree
)
- af1 = FloatProperty(
+ af1: FloatProperty(
name='Amplitude',
description='Multiplier for noise amplitude',
default=1.0, update=update_tree
)
- af2 = FloatProperty(
+ af2: FloatProperty(
name='Frequency',
description='Multiplier for noise fequency',
default=1.0, update=update_tree
)
- af3 = FloatProperty(
+ af3: FloatProperty(
name='Randomness',
description='Random offset in noise',
default=4.0, update=update_tree
)
- makeMesh = BoolProperty(
+ makeMesh: BoolProperty(
name='Make Mesh',
description='Convert curves to mesh, uses skin modifier, enables armature simplification',
default=False, update=update_tree
)
- armLevels = IntProperty(
+ armLevels: IntProperty(
name='Armature Levels',
description='Number of branching levels to make bones for, 0 is all levels',
min=0,
default=2, update=update_tree
)
- boneStep = IntVectorProperty(
+ boneStep: IntVectorProperty(
name='Bone Length',
description='Number of stem segments per bone',
min=1,
default=[1, 1, 1, 1],
size=4, update=update_tree
)
- presetName = StringProperty(
+ presetName: StringProperty(
name='Preset Name',
description='The name of the preset to be saved',
default='',
subtype='FILE_NAME', update=no_update_tree
)
- limitImport = BoolProperty(
+ limitImport: BoolProperty(
name='Limit Import',
description='Limited imported tree to 2 levels & no leaves for speed',
default=True, update=no_update_tree
)
- overwrite = BoolProperty(
+ overwrite: BoolProperty(
name='Overwrite',
description='When checked, overwrite existing preset files when saving',
default=False, update=no_update_tree
@@ -881,7 +874,7 @@ class AddTree(Operator):
if self.chooseSet == '0':
box = layout.box()
- box.label("Geometry:")
+ box.label(text="Geometry:")
box.prop(self, 'bevel')
row = box.row()
@@ -900,7 +893,7 @@ class AddTree(Operator):
box.prop(self, 'nrings')
box.prop(self, 'seed')
- box.label("Tree Scale:")
+ box.label(text="Tree Scale:")
row = box.row()
row.prop(self, 'scale')
row.prop(self, 'scaleV')
@@ -927,7 +920,7 @@ class AddTree(Operator):
# Send the data dict and the file name to the exporter
row.operator('sapling.exportdata').data = repr([repr(data), self.presetName, self.overwrite])
row = box.row()
- row.label(" ")
+ row.label(text=" ")
row.prop(self, 'overwrite')
row = box.row()
row.menu('SAPLING_MT_preset', text='Load Preset')
@@ -935,7 +928,7 @@ class AddTree(Operator):
elif self.chooseSet == '1':
box = layout.box()
- box.label("Branch Radius:")
+ box.label(text="Branch Radius:")
row = box.row()
row.prop(self, 'bevel')
@@ -961,7 +954,7 @@ class AddTree(Operator):
elif self.chooseSet == '2':
box = layout.box()
- box.label("Branch Splitting:")
+ box.label(text="Branch Splitting:")
box.prop(self, 'levels')
box.prop(self, 'baseSplits')
row = box.row()
@@ -984,14 +977,14 @@ class AddTree(Operator):
col.prop(self, 'splitAngleV')
col.prop(self, 'rotateV')
- col.label("Branching Mode:")
+ col.label(text="Branching Mode:")
col.prop(self, 'rMode')
box.column().prop(self, 'curveRes')
elif self.chooseSet == '3':
box = layout.box()
- box.label("Branch Growth:")
+ box.label(text="Branch Growth:")
box.prop(self, 'taperCrown')
@@ -1014,7 +1007,7 @@ class AddTree(Operator):
elif self.chooseSet == '4':
box = layout.box()
- box.label("Prune:")
+ box.label(text="Prune:")
box.prop(self, 'prune')
box.prop(self, 'pruneRatio')
row = box.row()
@@ -1028,14 +1021,14 @@ class AddTree(Operator):
elif self.chooseSet == '5':
box = layout.box()
- box.label("Leaves:")
+ box.label(text="Leaves:")
box.prop(self, 'showLeaves')
box.prop(self, 'leafShape')
box.prop(self, 'leafDupliObj')
box.prop(self, 'leaves')
box.prop(self, 'leafDist')
- box.label("")
+ box.label(text="")
row = box.row()
row.prop(self, 'leafDownAngle')
row.prop(self, 'leafDownAngleV')
@@ -1043,7 +1036,7 @@ class AddTree(Operator):
row = box.row()
row.prop(self, 'leafRotate')
row.prop(self, 'leafRotateV')
- box.label("")
+ box.label(text="")
row = box.row()
row.prop(self, 'leafScale')
@@ -1056,22 +1049,22 @@ class AddTree(Operator):
box.prop(self, 'horzLeaves')
box.prop(self, 'leafangle')
- # box.label(" ")
+ # box.label(text=" ")
# box.prop(self, 'bend')
elif self.chooseSet == '6':
box = layout.box()
- box.label("Armature:")
+ box.label(text="Armature:")
row = box.row()
row.prop(self, 'useArm')
box.prop(self, 'makeMesh')
- box.label("Armature Simplification:")
+ box.label(text="Armature Simplification:")
box.prop(self, 'armLevels')
box.prop(self, 'boneStep')
elif self.chooseSet == '7':
box = layout.box()
- box.label("Finalize All Other Settings First!")
+ box.label(text="Finalize All Other Settings First!")
box.prop(self, 'armAnim')
box.prop(self, 'leafAnim')
box.prop(self, 'previewArm')
@@ -1082,13 +1075,13 @@ class AddTree(Operator):
# row.prop(self, 'windSpeed')
# row.prop(self, 'windGust')
- box.label('Wind Settings:')
+ box.label(text='Wind Settings:')
box.prop(self, 'wind')
row = box.row()
row.prop(self, 'gust')
row.prop(self, 'gustF')
- box.label('Leaf Wind Settings:')
+ box.label(text='Leaf Wind Settings:')
box.prop(self, 'af1')
box.prop(self, 'af2')
box.prop(self, 'af3')
@@ -1097,7 +1090,7 @@ class AddTree(Operator):
# Ensure the use of the global variables
global settings, useSet
start_time = time.time()
- # bpy.ops.ImportData.filename = "quaking_aspen"
+
# If we need to set the properties from a preset then do it here
if useSet:
for a, b in settings.items():
@@ -1115,24 +1108,32 @@ class AddTree(Operator):
return {'FINISHED'}
def invoke(self, context, event):
- bpy.ops.sapling.importdata(filename="quaking_aspen.py")
+ bpy.ops.sapling.importdata(filename="callistemon.py")
return self.execute(context)
def menu_func(self, context):
self.layout.operator(AddTree.bl_idname, text="Sapling Tree Gen", icon='CURVE_DATA')
+classes = (
+ AddTree,
+ PresetMenu,
+ ImportData,
+ ExportData,
+)
def register():
- bpy.utils.register_module(__name__)
-
- bpy.types.INFO_MT_curve_add.append(menu_func)
+ from bpy.utils import register_class
+ for cls in classes:
+ register_class(cls)
+ bpy.types.VIEW3D_MT_curve_add.append(menu_func)
def unregister():
- bpy.utils.unregister_module(__name__)
-
- bpy.types.INFO_MT_curve_add.remove(menu_func)
+ from bpy.utils import unregister_class
+ for cls in reversed(classes):
+ unregister_class(cls)
+ bpy.types.VIEW3D_MT_curve_add.remove(menu_func)
if __name__ == "__main__":
diff --git a/add_curve_sapling/utils.py b/add_curve_sapling/utils.py
index 87ae57fe..66ee91f6 100644
--- a/add_curve_sapling/utils.py
+++ b/add_curve_sapling/utils.py
@@ -19,6 +19,7 @@
import bpy
+
import time
import copy
@@ -489,7 +490,7 @@ def growSpline(n, stem, numSplit, splitAng, splitAngV, splineList,
end_co = stem.p.co.copy()
# Add the new point and adjust its coords, handles and radius
- newSpline.bezier_points.add()
+ newSpline.bezier_points.add(1)
newPoint = newSpline.bezier_points[-1]
(newPoint.co, newPoint.handle_left_type, newPoint.handle_right_type) = (end_co + dirVec, hType, hType)
newPoint.radius = (
@@ -561,7 +562,7 @@ def growSpline(n, stem, numSplit, splitAng, splitAngV, splineList,
# Get the end point position
end_co = stem.p.co.copy()
- stem.spline.bezier_points.add()
+ stem.spline.bezier_points.add(1)
newPoint = stem.spline.bezier_points[-1]
(newPoint.co, newPoint.handle_left_type, newPoint.handle_right_type) = (end_co + dirVec, hType, hType)
newPoint.radius = stem.radS * (1 - (stem.seg + 1) / stem.segMax) + \
@@ -730,19 +731,19 @@ def create_armature(armAnim, leafP, cu, frameRate, leafMesh, leafObj, leafVertSi
leafAnim, loopFrames, previewArm, armLevels, makeMesh, boneStep):
arm = bpy.data.armatures.new('tree')
armOb = bpy.data.objects.new('treeArm', arm)
- bpy.context.scene.objects.link(armOb)
+ bpy.context.scene.collection.objects.link(armOb)
# Create a new action to store all animation
newAction = bpy.data.actions.new(name='windAction')
armOb.animation_data_create()
armOb.animation_data.action = newAction
- arm.draw_type = 'STICK'
+ arm.display_type = 'STICK'
arm.use_deform_delay = True
# Add the armature modifier to the curve
armMod = treeOb.modifiers.new('windSway', 'ARMATURE')
if previewArm:
armMod.show_viewport = False
- arm.draw_type = 'WIRE'
- treeOb.hide = True
+ arm.display_type = 'WIRE'
+ treeOb.hide_viewport = True
armMod.use_apply_on_spline = True
armMod.object = armOb
armMod.use_bone_envelopes = True
@@ -753,15 +754,16 @@ def create_armature(armAnim, leafP, cu, frameRate, leafMesh, leafObj, leafVertSi
armMod.object = armOb
armMod.use_bone_envelopes = False
armMod.use_vertex_groups = True
+
# Make sure all objects are deselected (may not be required?)
for ob in bpy.data.objects:
- ob.select = False
+ ob.select_set(state=False)
fps = bpy.context.scene.render.fps
animSpeed = (24 / fps) * frameRate
# Set the armature as active and go to edit mode to add bones
- bpy.context.scene.objects.active = armOb
+ bpy.context.view_layer.objects.active = armOb
bpy.ops.object.mode_set(mode='EDIT')
# For all the splines in the curve we need to add bones at each bezier point
for i, parBone in enumerate(splineToBone):
@@ -821,9 +823,9 @@ def create_armature(armAnim, leafP, cu, frameRate, leafMesh, leafObj, leafVertSi
# If there are leaves then we need a new vertex group so they will attach to the bone
if not leafAnim:
if (len(levelCount) > 1) and (i >= levelCount[-2]) and leafObj:
- leafObj.vertex_groups.new(boneName)
+ leafObj.vertex_groups.new(name=boneName)
elif (len(levelCount) == 1) and leafObj:
- leafObj.vertex_groups.new(boneName)
+ leafObj.vertex_groups.new(name=boneName)
"""
# If this is first point of the spline then it must be parented to the level above it
if n == 0:
@@ -873,10 +875,10 @@ def create_armature(armAnim, leafP, cu, frameRate, leafMesh, leafObj, leafVertSi
# Add new fcurves for each sway as well as the modifiers
swayX = armOb.animation_data.action.fcurves.new(
- 'pose.bones["' + boneName + '"].rotation_euler', 0
+ 'pose.bones["' + boneName + '"].rotation_euler', index=0
)
swayY = armOb.animation_data.action.fcurves.new(
- 'pose.bones["' + boneName + '"].rotation_euler', 2
+ 'pose.bones["' + boneName + '"].rotation_euler', index=2
)
swayXMod1 = swayX.modifiers.new(type='FNGENERATOR')
swayXMod2 = swayX.modifiers.new(type='FNGENERATOR')
@@ -955,14 +957,14 @@ def create_armature(armAnim, leafP, cu, frameRate, leafMesh, leafObj, leafVertSi
# Add new fcurves for each sway as well as the modifiers
swayX = armOb.animation_data.action.fcurves.new(
- 'pose.bones["' + bname + '"].rotation_euler', 0
+ 'pose.bones["' + bname + '"].rotation_euler', index=0
)
swayY = armOb.animation_data.action.fcurves.new(
- 'pose.bones["' + bname + '"].rotation_euler', 2
+ 'pose.bones["' + bname + '"].rotation_euler', index=2
)
# Add keyframe so noise works
- swayX.keyframe_points.add()
- swayY.keyframe_points.add()
+ swayX.keyframe_points.add(1)
+ swayY.keyframe_points.add(1)
swayX.keyframe_points[0].co = (0, 0)
swayY.keyframe_points[0].co = (0, 0)
@@ -997,7 +999,7 @@ def create_armature(armAnim, leafP, cu, frameRate, leafMesh, leafObj, leafVertSi
)
for group in vertexGroups:
- leafObj.vertex_groups.new(group)
+ leafObj.vertex_groups.new(name=group)
leafObj.vertex_groups[group].add(vertexGroups[group], 1.0, 'ADD')
# Now we need the rotation mode to be 'XYZ' to ensure correct rotation
@@ -1257,7 +1259,7 @@ def fabricate_stems(addsplinetobone, addstem, baseSize, branches, childP, cu, cu
def perform_pruning(baseSize, baseSplits, childP, cu, currentMax, currentMin, currentScale, curve,
- curveBack, curveRes, deleteSpline, forceSprout, handles, n, oldMax, orginalSplineToBone,
+ curveBack, curveRes, deleteSpline, forceSprout, handles, n, oldMax, originalSplineToBone,
originalCo, originalCurv, originalCurvV, originalHandleL, originalHandleR, originalLength,
originalSeg, prune, prunePowerHigh, prunePowerLow, pruneRatio, pruneWidth, pruneBase,
pruneWidthPeak, randState, ratio, scaleVal, segSplits, splineToBone, splitAngle, splitAngleV,
@@ -1291,7 +1293,7 @@ def perform_pruning(baseSize, baseSplits, childP, cu, currentMax, currentMin, cu
st.seg = originalSeg
st.p = newPoint
newPoint.radius = st.radS
- splineToBone = orginalSplineToBone
+ splineToBone = originalSplineToBone
# Initialise the spline list for those contained in the current level of branching
splineList = [st]
@@ -1594,12 +1596,12 @@ def addTree(props):
handles = 'VECTOR'
for ob in bpy.data.objects:
- ob.select = False
+ ob.select_set(state=False)
# Initialise the tree object and curve and adjust the settings
cu = bpy.data.curves.new('tree', 'CURVE')
treeOb = bpy.data.objects.new('tree', cu)
- bpy.context.scene.objects.link(treeOb)
+ bpy.context.scene.collection.objects.link(treeOb)
# treeOb.location=bpy.context.scene.cursor_location attractUp
@@ -1621,14 +1623,14 @@ def addTree(props):
enCu = bpy.data.curves.new('envelope', 'CURVE')
enOb = bpy.data.objects.new('envelope', enCu)
enOb.parent = treeOb
- bpy.context.scene.objects.link(enOb)
+ bpy.context.scene.collection.objects.link(enOb)
newSpline = enCu.splines.new('BEZIER')
newPoint = newSpline.bezier_points[-1]
newPoint.co = Vector((0, 0, scaleVal))
(newPoint.handle_right_type, newPoint.handle_left_type) = (enHandle, enHandle)
# Set the coordinates by varying the z value, envelope will be aligned to the x-axis
for c in range(enNum):
- newSpline.bezier_points.add()
+ newSpline.bezier_points.add(1)
newPoint = newSpline.bezier_points[-1]
ratioVal = (c + 1) / (enNum)
zVal = scaleVal - scaleVal * (1 - pruneBase) * ratioVal
@@ -1646,7 +1648,7 @@ def addTree(props):
(newPoint.handle_right_type, newPoint.handle_left_type) = (enHandle, enHandle)
# Create a second envelope but this time on the y-axis
for c in range(enNum):
- newSpline.bezier_points.add()
+ newSpline.bezier_points.add(1)
newPoint = newSpline.bezier_points[-1]
ratioVal = (c + 1) / (enNum)
zVal = scaleVal - scaleVal * (1 - pruneBase) * ratioVal
@@ -1721,14 +1723,14 @@ def addTree(props):
currentScale = 1.0
oldMax = 1.0
deleteSpline = False
- orginalSplineToBone = copy.copy(splineToBone)
+ originalSplineToBone = copy.copy(splineToBone)
forceSprout = False
# Now do the iterative pruning, this uses a binary search and halts once the difference
# between upper and lower bounds of the search are less than 0.005
ratio, splineToBone = perform_pruning(
baseSize, baseSplits, childP, cu, currentMax, currentMin,
currentScale, curve, curveBack, curveRes, deleteSpline, forceSprout,
- handles, n, oldMax, orginalSplineToBone, originalCo, originalCurv,
+ handles, n, oldMax, originalSplineToBone, originalCo, originalCurv,
originalCurvV, originalHandleL, originalHandleR, originalLength,
originalSeg, prune, prunePowerHigh, prunePowerLow, pruneRatio,
pruneWidth, pruneBase, pruneWidthPeak, randState, ratio, scaleVal,
@@ -1787,7 +1789,7 @@ def addTree(props):
# edges are currently added by validating the mesh which isn't great
leafMesh = bpy.data.meshes.new('leaves')
leafObj = bpy.data.objects.new('leaves', leafMesh)
- bpy.context.scene.objects.link(leafObj)
+ bpy.context.scene.collection.objects.link(leafObj)
leafObj.parent = treeOb
leafMesh.from_pydata(leafVerts, (), leafFaces)
@@ -1797,17 +1799,17 @@ def addTree(props):
# enable duplication
if leafShape == 'dFace':
- leafObj.dupli_type = "FACES"
- leafObj.use_dupli_faces_scale = True
- leafObj.dupli_faces_scale = 10.0
+ leafObj.instance_type = "FACES"
+ leafObj.use_instance_faces_scale = True
+ leafObj.instance_faces_scale = 10.0
try:
if leafDupliObj not in "NONE":
bpy.data.objects[leafDupliObj].parent = leafObj
except KeyError:
pass
elif leafShape == 'dVert':
- leafObj.dupli_type = "VERTS"
- leafObj.use_dupli_vertices_rotation = True
+ leafObj.instance_type = "VERTS"
+ leafObj.use_instance_vertices_rotation = True
try:
if leafDupliObj not in "NONE":
bpy.data.objects[leafDupliObj].parent = leafObj
@@ -1816,7 +1818,7 @@ def addTree(props):
# add leaf UVs
if leafShape == 'rect':
- leafMesh.uv_textures.new("leafUV")
+ leafMesh.uv_layers.new(name='leafUV')
uvlayer = leafMesh.uv_layers.active.data
u1 = .5 * (1 - leafScaleX)
@@ -1829,7 +1831,7 @@ def addTree(props):
uvlayer[i * 4 + 3].uv = Vector((u1, 0))
elif leafShape == 'hex':
- leafMesh.uv_textures.new("leafUV")
+ leafMesh.uv_layers.new(name='leafUV')
uvlayer = leafMesh.uv_layers.active.data
u1 = .5 * (1 - leafScaleX)
@@ -1877,7 +1879,7 @@ def addTree(props):
treeMesh = bpy.data.meshes.new('treemesh')
treeObj = bpy.data.objects.new('treemesh', treeMesh)
- bpy.context.scene.objects.link(treeObj)
+ bpy.context.scene.collection.objects.link(treeObj)
treeVerts = []
treeEdges = []
@@ -1988,15 +1990,15 @@ def addTree(props):
treeMesh.from_pydata(treeVerts, treeEdges, ())
for group in vertexGroups:
- treeObj.vertex_groups.new(group)
+ treeObj.vertex_groups.new(name=group)
treeObj.vertex_groups[group].add(vertexGroups[group], 1.0, 'ADD')
# add armature
if useArm:
armMod = treeObj.modifiers.new('windSway', 'ARMATURE')
if previewArm:
- bpy.data.objects['treeArm'].hide = True
- bpy.data.armatures['tree'].draw_type = 'STICK'
+ bpy.data.objects['treeArm'].hide_viewport = True
+ bpy.data.armatures['tree'].display_type = 'STICK'
armMod.object = bpy.data.objects['treeArm']
armMod.use_bone_envelopes = False
armMod.use_vertex_groups = True
diff --git a/add_mesh_BoltFactory/Boltfactory.py b/add_mesh_BoltFactory/Boltfactory.py
index 25d90706..0b405f3c 100644
--- a/add_mesh_BoltFactory/Boltfactory.py
+++ b/add_mesh_BoltFactory/Boltfactory.py
@@ -330,3 +330,29 @@ class add_mesh_bolt(Operator, AddObjectHelper):
self.execute(context)
return {'FINISHED'}
+
+# Register:
+def menu_func_bolt(self, context):
+ self.layout.operator(
+ add_mesh_bolt.bl_idname,
+ text="Bolt",
+ icon="MOD_SCREW")
+
+classes = (
+ add_mesh_bolt,
+)
+
+
+
+def register():
+ from bpy.utils import register_class
+ for cls in classes:
+ register_class(cls)
+ bpy.types.VIEW3D_MT_mesh_add.append(menu_func_bolt)
+
+
+def unregister():
+ from bpy.utils import unregister_class
+ for cls in reversed(classes):
+ unregister_class(cls)
+ bpy.types.VIEW3D_MT_mesh_add.remove(menu_func_bolt) \ No newline at end of file
diff --git a/add_mesh_BoltFactory/__init__.py b/add_mesh_BoltFactory/__init__.py
index 46d250de..383ddc36 100644
--- a/add_mesh_BoltFactory/__init__.py
+++ b/add_mesh_BoltFactory/__init__.py
@@ -19,8 +19,8 @@
bl_info = {
"name": "BoltFactory",
"author": "Aaron Keith",
- "version": (0, 3, 4),
- "blender": (2, 78, 0),
+ "version": (0, 4, 0),
+ "blender": (2, 80, 0),
"location": "View3D > Add > Mesh",
"description": "Add a bolt or nut",
"wiki_url": "https://wiki.blender.org/index.php/Extensions:2.6/Py/"
@@ -42,22 +42,15 @@ import bpy
# ### REGISTER ###
-def add_mesh_bolt_button(self, context):
- self.layout.operator(Boltfactory.add_mesh_bolt.bl_idname, text="Bolt", icon="MOD_SCREW")
def register():
- bpy.utils.register_module(__name__)
-
- bpy.types.INFO_MT_mesh_add.append(add_mesh_bolt_button)
- # bpy.types.VIEW3D_PT_tools_objectmode.prepend(add_mesh_bolt_button) # just for testing
+ Boltfactory.register()
+
def unregister():
- bpy.utils.unregister_module(__name__)
-
- bpy.types.INFO_MT_mesh_add.remove(add_mesh_bolt_button)
- # bpy.types.VIEW3D_PT_tools_objectmode.remove(add_mesh_bolt_button) # just for testing
+ Boltfactory.unregister()
if __name__ == "__main__":
diff --git a/add_mesh_BoltFactory/createMesh.py b/add_mesh_BoltFactory/createMesh.py
index c59c9939..71f22bf7 100644
--- a/add_mesh_BoltFactory/createMesh.py
+++ b/add_mesh_BoltFactory/createMesh.py
@@ -169,7 +169,7 @@ def Get_Phillips_Bit_Height(Bit_Dia):
# Returns a list of verts rotated by the given matrix. Used by SpinDup
def Rot_Mesh(verts, matrix):
from mathutils import Vector
- return [(matrix * Vector(v))[:] for v in verts]
+ return [(matrix @ Vector(v))[:] for v in verts]
# Returns a list of faces that has there index incremented by offset
diff --git a/add_mesh_extra_objects/__init__.py b/add_mesh_extra_objects/__init__.py
index ecc023df..1197738c 100644
--- a/add_mesh_extra_objects/__init__.py
+++ b/add_mesh_extra_objects/__init__.py
@@ -107,9 +107,9 @@ from bpy.props import (
)
-class INFO_MT_mesh_vert_add(Menu):
+class VIEW3D_MT_mesh_vert_add(Menu):
# Define the "Single Vert" menu
- bl_idname = "INFO_MT_mesh_vert_add"
+ bl_idname = "VIEW3D_MT_mesh_vert_add"
bl_label = "Single Vert"
def draw(self, context):
@@ -126,9 +126,9 @@ class INFO_MT_mesh_vert_add(Menu):
text="Object Origin Mirrored")
-class INFO_MT_mesh_gears_add(Menu):
+class VIEW3D_MT_mesh_gears_add(Menu):
# Define the "Gears" menu
- bl_idname = "INFO_MT_mesh_gears_add"
+ bl_idname = "VIEW3D_MT_mesh_gears_add"
bl_label = "Gears"
def draw(self, context):
@@ -140,9 +140,9 @@ class INFO_MT_mesh_gears_add(Menu):
text="Worm")
-class INFO_MT_mesh_diamonds_add(Menu):
+class VIEW3D_MT_mesh_diamonds_add(Menu):
# Define the "Diamonds" menu
- bl_idname = "INFO_MT_mesh_diamonds_add"
+ bl_idname = "VIEW3D_MT_mesh_diamonds_add"
bl_label = "Diamonds"
def draw(self, context):
@@ -156,9 +156,9 @@ class INFO_MT_mesh_diamonds_add(Menu):
text="Gem")
-class INFO_MT_mesh_math_add(Menu):
+class VIEW3D_MT_mesh_math_add(Menu):
# Define the "Math Function" menu
- bl_idname = "INFO_MT_mesh_math_add"
+ bl_idname = "VIEW3D_MT_mesh_math_add"
bl_label = "Math Functions"
def draw(self, context):
@@ -172,29 +172,29 @@ class INFO_MT_mesh_math_add(Menu):
self.layout.operator("mesh.make_triangle", icon="MESH_DATA")
-class INFO_MT_mesh_mech(Menu):
+class VIEW3D_MT_mesh_mech(Menu):
# Define the "Math Function" menu
- bl_idname = "INFO_MT_mesh_mech_add"
+ bl_idname = "VIEW3D_MT_mesh_mech_add"
bl_label = "Mechanical"
def draw(self, context):
layout = self.layout
layout.operator_context = 'INVOKE_REGION_WIN'
- layout.menu("INFO_MT_mesh_pipe_joints_add",
+ layout.menu("VIEW3D_MT_mesh_pipe_joints_add",
text="Pipe Joints", icon="SNAP_PEEL_OBJECT")
- layout.menu("INFO_MT_mesh_gears_add",
+ layout.menu("VIEW3D_MT_mesh_gears_add",
text="Gears", icon="SCRIPTWIN")
-class INFO_MT_mesh_extras_add(Menu):
+class VIEW3D_MT_mesh_extras_add(Menu):
# Define the "Extra Objects" menu
- bl_idname = "INFO_MT_mesh_extras_add"
+ bl_idname = "VIEW3D_MT_mesh_extras_add"
bl_label = "Extras"
def draw(self, context):
layout = self.layout
layout.operator_context = 'INVOKE_REGION_WIN'
- layout.menu("INFO_MT_mesh_diamonds_add", text="Diamonds",
+ layout.menu("VIEW3D_MT_mesh_diamonds_add", text="Diamonds",
icon="PMARKER_SEL")
layout.separator()
layout.operator("mesh.add_beam",
@@ -214,9 +214,9 @@ class INFO_MT_mesh_extras_add(Menu):
text="Menger Sponge")
-class INFO_MT_mesh_torus_add(Menu):
+class VIEW3D_MT_mesh_torus_add(Menu):
# Define the "Torus Objects" menu
- bl_idname = "INFO_MT_mesh_torus_add"
+ bl_idname = "VIEW3D_MT_mesh_torus_add"
bl_label = "Torus Objects"
def draw(self, context):
@@ -230,9 +230,9 @@ class INFO_MT_mesh_torus_add(Menu):
text="Torus Knot")
-class INFO_MT_mesh_pipe_joints_add(Menu):
+class VIEW3D_MT_mesh_pipe_joints_add(Menu):
# Define the "Pipe Joints" menu
- bl_idname = "INFO_MT_mesh_pipe_joints_add"
+ bl_idname = "VIEW3D_MT_mesh_pipe_joints_add"
bl_label = "Pipe Joints"
def draw(self, context):
@@ -355,15 +355,15 @@ def menu_func(self, context):
lay_out.operator_context = 'INVOKE_REGION_WIN'
lay_out.separator()
- lay_out.menu("INFO_MT_mesh_vert_add",
+ lay_out.menu("VIEW3D_MT_mesh_vert_add",
text="Single Vert", icon="LAYER_ACTIVE")
lay_out.operator("mesh.primitive_round_cube_add",
text="Round Cube", icon="MOD_SUBSURF")
- lay_out.menu("INFO_MT_mesh_math_add",
+ lay_out.menu("VIEW3D_MT_mesh_math_add",
text="Math Function", icon="PACKAGE")
- lay_out.menu("INFO_MT_mesh_mech_add",
+ lay_out.menu("VIEW3D_MT_mesh_mech_add",
text="Mechanical", icon="SCRIPTWIN")
- lay_out.menu("INFO_MT_mesh_torus_add",
+ lay_out.menu("VIEW3D_MT_mesh_torus_add",
text="Torus Objects", icon="MESH_TORUS")
lay_out.separator()
lay_out.operator("mesh.generate_geodesic_dome",
@@ -371,7 +371,7 @@ def menu_func(self, context):
lay_out.operator("discombobulate.ops",
text="Discombobulator", icon="RETOPO")
lay_out.separator()
- lay_out.menu("INFO_MT_mesh_extras_add",
+ lay_out.menu("VIEW3D_MT_mesh_extras_add",
text="Extras", icon="MESH_DATA")
lay_out.separator()
lay_out.operator("object.parent_to_empty",
@@ -402,12 +402,12 @@ def register():
)
# Add "Extras" menu to the "Add Mesh" menu
- bpy.types.INFO_MT_mesh_add.append(menu_func)
+ bpy.types.VIEW3D_MT_mesh_add.append(menu_func)
def unregister():
# Remove "Extras" menu from the "Add Mesh" menu.
- bpy.types.INFO_MT_mesh_add.remove(menu_func)
+ bpy.types.VIEW3D_MT_mesh_add.remove(menu_func)
del bpy.types.Scene.discomb
del bpy.types.Scene.error_message
diff --git a/add_mesh_extra_objects/add_empty_as_parent.py b/add_mesh_extra_objects/add_empty_as_parent.py
index bcaecf64..a6ae69e6 100644
--- a/add_mesh_extra_objects/add_empty_as_parent.py
+++ b/add_mesh_extra_objects/add_empty_as_parent.py
@@ -78,18 +78,18 @@ class P2E(Operator):
bpy.ops.object.add(type='EMPTY', location=loc)
context.object.name = self.nombre
context.object.show_name = True
- context.object.show_x_ray = True
+ context.object.show_in_front = True
if self.grupo:
- bpy.ops.group.create(name=self.nombre)
- bpy.ops.group.objects_add_active()
+ bpy.ops.collection.create(name=self.nombre)
+ bpy.ops.collection.objects_add_active()
for o in objs:
o.select = True
if not o.parent:
bpy.ops.object.parent_set(type='OBJECT')
if self.grupo:
- bpy.ops.group.objects_add_active()
+ bpy.ops.collection.objects_add_active()
o.select = False
for o in objs:
if self.renom:
diff --git a/add_mesh_extra_objects/add_mesh_gears.py b/add_mesh_extra_objects/add_mesh_gears.py
index b042a6d5..6a7bafd9 100644
--- a/add_mesh_extra_objects/add_mesh_gears.py
+++ b/add_mesh_extra_objects/add_mesh_gears.py
@@ -666,10 +666,10 @@ class AddGear(Operator):
# XXX, supporting adding in editmode is move involved
if obj.mode != 'EDIT':
# Create vertex groups from stored vertices.
- tipGroup = obj.vertex_groups.new('Tips')
+ tipGroup = obj.vertex_groups.new(name='Tips')
tipGroup.add(verts_tip, 1.0, 'ADD')
- valleyGroup = obj.vertex_groups.new('Valleys')
+ valleyGroup = obj.vertex_groups.new(name='Valleys')
valleyGroup.add(verts_valley, 1.0, 'ADD')
return {'FINISHED'}
@@ -790,7 +790,7 @@ class AddWormGear(Operator):
# XXX, supporting adding in editmode is move involved
if obj.mode != 'EDIT':
# Create vertex groups from stored vertices.
- tipGroup = obj.vertex_groups.new('Tips')
+ tipGroup = obj.vertex_groups.new(name='Tips')
tipGroup.add(verts_tip, 1.0, 'ADD')
valleyGroup = obj.vertex_groups.new('Valleys')
diff --git a/ant_landscape/__init__.py b/ant_landscape/__init__.py
index 8fbfb79d..49432e81 100644
--- a/ant_landscape/__init__.py
+++ b/ant_landscape/__init__.py
@@ -938,14 +938,14 @@ class AntLandscapePropertiesGroup(bpy.types.PropertyGroup):
def register():
bpy.utils.register_module(__name__)
- bpy.types.INFO_MT_mesh_add.append(menu_func_landscape)
+ bpy.types.VIEW3D_MT_mesh_add.append(menu_func_landscape)
bpy.types.Object.ant_landscape = PointerProperty(type=AntLandscapePropertiesGroup, name="ANT_Landscape", description="Landscape properties")
bpy.types.VIEW3D_MT_paint_weight.append(menu_func_eroder)
def unregister():
bpy.utils.unregister_module(__name__)
- bpy.types.INFO_MT_mesh_add.remove(menu_func_landscape)
+ bpy.types.VIEW3D_MT_mesh_add.remove(menu_func_landscape)
bpy.types.VIEW3D_MT_paint_weight.remove(menu_func_eroder)
diff --git a/ant_landscape/ant_functions.py b/ant_landscape/ant_functions.py
index bee655c0..ca81ce6d 100644
--- a/ant_landscape/ant_functions.py
+++ b/ant_landscape/ant_functions.py
@@ -953,43 +953,43 @@ class Eroder(bpy.types.Operator):
try:
vg=ob.vertex_groups["rainmap"]
except:
- vg=ob.vertex_groups.new("rainmap")
+ vg=ob.vertex_groups.new(name="rainmap")
try:
vgscree=ob.vertex_groups["scree"]
except:
- vgscree=ob.vertex_groups.new("scree")
+ vgscree=ob.vertex_groups.new(name="scree")
try:
vgavalanced=ob.vertex_groups["avalanced"]
except:
- vgavalanced=ob.vertex_groups.new("avalanced")
+ vgavalanced=ob.vertex_groups.new(name="avalanced")
try:
vgw=ob.vertex_groups["water"]
except:
- vgw=ob.vertex_groups.new("water")
+ vgw=ob.vertex_groups.new(name="water")
try:
vgscour=ob.vertex_groups["scour"]
except:
- vgscour=ob.vertex_groups.new("scour")
+ vgscour=ob.vertex_groups.new(name="scour")
try:
vgdeposit=ob.vertex_groups["deposit"]
except:
- vgdeposit=ob.vertex_groups.new("deposit")
+ vgdeposit=ob.vertex_groups.new(name="deposit")
try:
vgflowrate=ob.vertex_groups["flowrate"]
except:
- vgflowrate=ob.vertex_groups.new("flowrate")
+ vgflowrate=ob.vertex_groups.new(name="flowrate")
try:
vgsediment=ob.vertex_groups["sediment"]
except:
- vgsediment=ob.vertex_groups.new("sediment")
+ vgsediment=ob.vertex_groups.new(name="sediment")
try:
vgsedimentpct=ob.vertex_groups["sedimentpct"]
except:
- vgsedimentpct=ob.vertex_groups.new("sedimentpct")
+ vgsedimentpct=ob.vertex_groups.new(name="sedimentpct")
try:
vgcapacity=ob.vertex_groups["capacity"]
except:
- vgcapacity=ob.vertex_groups.new("capacity")
+ vgcapacity=ob.vertex_groups.new(name="capacity")
g = Grid.fromBlenderMesh(me, vg, self.Ef)
diff --git a/archimesh/__init__.py b/archimesh/__init__.py
index cb22e157..07049ff2 100644
--- a/archimesh/__init__.py
+++ b/archimesh/__init__.py
@@ -53,7 +53,7 @@ if "bpy" in locals():
importlib.reload(achm_kitchen_maker)
importlib.reload(achm_shelves_maker)
importlib.reload(achm_books_maker)
- importlib.reload(achm_lamp_maker)
+ importlib.reload(achm_light_maker)
importlib.reload(achm_curtain_maker)
importlib.reload(achm_venetian_maker)
importlib.reload(achm_main_panel)
@@ -66,7 +66,7 @@ else:
from . import achm_venetian_maker
from . import achm_door_maker
from . import achm_kitchen_maker
- from . import achm_lamp_maker
+ from . import achm_light_maker
from . import achm_main_panel
from . import achm_roof_maker
from . import achm_room_maker
@@ -92,7 +92,7 @@ from bpy.types import (
AddonPreferences,
Menu,
Scene,
- INFO_MT_mesh_add,
+ VIEW3D_MT_mesh_add,
WindowManager,
)
@@ -102,13 +102,13 @@ from bpy.types import (
class AchmInfoMtMeshDecorationAdd(Menu):
- bl_idname = "INFO_MT_mesh_decoration_add"
+ bl_idname = "VIEW3D_MT_mesh_decoration_add"
bl_label = "Decoration assets"
# noinspection PyUnusedLocal
def draw(self, context):
self.layout.operator("mesh.archimesh_books", text="Add Books")
- self.layout.operator("mesh.archimesh_lamp", text="Add Lamp")
+ self.layout.operator("mesh.archimesh_light", text="Add Lamp")
self.layout.operator("mesh.archimesh_roller", text="Add Roller curtains")
self.layout.operator("mesh.archimesh_venetian", text="Add Venetian blind")
self.layout.operator("mesh.archimesh_japan", text="Add Japanese curtains")
@@ -119,7 +119,7 @@ class AchmInfoMtMeshDecorationAdd(Menu):
class AchmInfoMtMeshCustomMenuAdd(Menu):
- bl_idname = "INFO_MT_mesh_custom_menu_add"
+ bl_idname = "VIEW3D_MT_mesh_custom_menu_add"
bl_label = "Archimesh"
# noinspection PyUnusedLocal
@@ -134,7 +134,7 @@ class AchmInfoMtMeshCustomMenuAdd(Menu):
self.layout.operator("mesh.archimesh_column", text="Add Column")
self.layout.operator("mesh.archimesh_stairs", text="Add Stairs")
self.layout.operator("mesh.archimesh_roof", text="Add Roof")
- self.layout.menu("INFO_MT_mesh_decoration_add", text="Decoration props", icon="GROUP")
+ self.layout.menu("VIEW3D_MT_mesh_decoration_add", text="Decoration props", icon="GROUP")
# --------------------------------------------------------------
# Register all operators and panels
@@ -189,7 +189,7 @@ class Archi_Pref(AddonPreferences):
# Define menu
# noinspection PyUnusedLocal
def AchmMenu_func(self, context):
- self.layout.menu("INFO_MT_mesh_custom_menu_add", icon="GROUP")
+ self.layout.menu("VIEW3D_MT_mesh_custom_menu_add", icon="GROUP")
def register():
@@ -210,7 +210,7 @@ def register():
bpy.utils.register_class(achm_kitchen_maker.AchmExportInventory)
bpy.utils.register_class(achm_shelves_maker.AchmShelves)
bpy.utils.register_class(achm_books_maker.AchmBooks)
- bpy.utils.register_class(achm_lamp_maker.AchmLamp)
+ bpy.utils.register_class(achm_light_maker.AchmLamp)
bpy.utils.register_class(achm_curtain_maker.AchmRoller)
bpy.utils.register_class(achm_curtain_maker.AchmJapan)
bpy.utils.register_class(achm_venetian_maker.AchmVenetian)
@@ -222,7 +222,7 @@ def register():
bpy.utils.register_class(achm_window_panel.AchmWinPanel)
bpy.utils.register_class(achm_window_panel.AchmWindowEditPanel)
bpy.utils.register_class(Archi_Pref)
- INFO_MT_mesh_add.append(AchmMenu_func)
+ VIEW3D_MT_mesh_add.append(AchmMenu_func)
update_panel(None, bpy.context)
# Define properties
Scene.archimesh_select_only = BoolProperty(
@@ -321,7 +321,7 @@ def unregister():
bpy.utils.unregister_class(achm_kitchen_maker.AchmExportInventory)
bpy.utils.unregister_class(achm_shelves_maker.AchmShelves)
bpy.utils.unregister_class(achm_books_maker.AchmBooks)
- bpy.utils.unregister_class(achm_lamp_maker.AchmLamp)
+ bpy.utils.unregister_class(achm_light_maker.AchmLamp)
bpy.utils.unregister_class(achm_curtain_maker.AchmRoller)
bpy.utils.unregister_class(achm_curtain_maker.AchmJapan)
bpy.utils.unregister_class(achm_venetian_maker.AchmVenetian)
@@ -333,7 +333,7 @@ def unregister():
bpy.utils.unregister_class(achm_window_panel.AchmWinPanel)
bpy.utils.unregister_class(achm_window_panel.AchmWindowEditPanel)
bpy.utils.unregister_class(Archi_Pref)
- INFO_MT_mesh_add.remove(AchmMenu_func)
+ VIEW3D_MT_mesh_add.remove(AchmMenu_func)
# Remove properties
del Scene.archimesh_select_only
diff --git a/archimesh/achm_door_maker.py b/archimesh/achm_door_maker.py
index 6d048bfd..4ef99373 100644
--- a/archimesh/achm_door_maker.py
+++ b/archimesh/achm_door_maker.py
@@ -233,7 +233,7 @@ def shape_children(mainobject, update=False):
myctrl.location.x = 0
myctrl.location.y = -((mp.frame_thick * 3) / 2)
myctrl.location.z = -gap
- myctrl.draw_type = 'BOUNDS'
+ myctrl.display_type = 'BOUNDS'
myctrl.hide = False
myctrl.hide_render = True
if bpy.context.scene.render.engine == 'CYCLES':
@@ -256,7 +256,7 @@ def shape_children(mainobject, update=False):
myctrlbase.location.x = 0
myctrlbase.location.y = -0.15 - (mp.frame_thick / 3)
myctrlbase.location.z = -0.10
- myctrlbase.draw_type = 'BOUNDS'
+ myctrlbase.display_type = 'BOUNDS'
myctrlbase.hide = False
myctrlbase.hide_render = True
if bpy.context.scene.render.engine == 'CYCLES':
diff --git a/archimesh/achm_lamp_maker.py b/archimesh/achm_lamp_maker.py
index a6b15435..67fbfc1e 100644
--- a/archimesh/achm_lamp_maker.py
+++ b/archimesh/achm_lamp_maker.py
@@ -130,7 +130,7 @@ def set_preset(self):
# Lamps
# ------------------------------------------------------------------
class AchmLamp(Operator):
- bl_idname = "mesh.archimesh_lamp"
+ bl_idname = "mesh.archimesh_light"
bl_label = "Lamp"
bl_description = "Lamp Generator"
bl_category = 'Archimesh'
@@ -375,7 +375,7 @@ class AchmLamp(Operator):
self.oldpreset = self.preset
# Create lamp
- create_lamp_mesh(self)
+ create_light_mesh(self)
return {'FINISHED'}
else:
self.report({'WARNING'}, "Archimesh: Option only valid in Object mode")
@@ -386,13 +386,13 @@ class AchmLamp(Operator):
# Generate mesh data
# All custom values are passed using self container (self.myvariable)
# ------------------------------------------------------------------------------
-def create_lamp_mesh(self):
+def create_light_mesh(self):
# deactivate others
for o in bpy.data.objects:
if o.select is True:
o.select = False
bpy.ops.object.select_all(False)
- generate_lamp(self)
+ generate_light(self)
return
@@ -401,13 +401,13 @@ def create_lamp_mesh(self):
# Generate lamps
# All custom values are passed using self container (self.myvariable)
# ------------------------------------------------------------------------------
-def generate_lamp(self):
+def generate_light(self):
location = bpy.context.scene.cursor_location
myloc = copy(location) # copy location to keep 3D cursor position
# ---------------------
# Lamp base
# ---------------------
- mydata = create_lamp_base("Lamp_base", self.base_height,
+ mydata = create_light_base("Lamp_base", self.base_height,
myloc.x, myloc.y, myloc.z,
self.base_segments, self.base_rings,
[self.br01, self.br02, self.br03, self.br04, self.br05, self.br06,
@@ -429,7 +429,7 @@ def generate_lamp(self):
# ---------------------
# Lampholder
# ---------------------
- myholder = create_lampholder("Lampholder", self.holder,
+ myholder = create_lightholder("Lampholder", self.holder,
myloc.x, myloc.y, myloc.z,
self.crt_mat)
# refine
@@ -444,7 +444,7 @@ def generate_lamp(self):
# ---------------------
# Lamp strings
# ---------------------
- mystrings = create_lampholder_strings("Lampstrings", self.holder,
+ mystrings = create_lightholder_strings("Lampstrings", self.holder,
myloc.x, myloc.y, myloc.z,
self.tr02,
self.top_height,
@@ -460,7 +460,7 @@ def generate_lamp(self):
# ---------------------
# Lampshade
# ---------------------
- mytop = create_lampshade("Lampshade", self.top_height,
+ mytop = create_lightshade("Lampshade", self.top_height,
myloc.x, myloc.y, myloc.z,
self.top_segments,
self.tr01, self.tr02,
@@ -517,7 +517,7 @@ def generate_lamp(self):
# mat: Flag for creating materials
# objcol: Color
# ------------------------------------------------------------------------------
-def create_lamp_base(objname, height, px, py, pz, segments, rings, radios, ratios, subdivide, mat, objcol):
+def create_light_base(objname, height, px, py, pz, segments, rings, radios, ratios, subdivide, mat, objcol):
# Calculate heights
h = height / (rings - 1)
listheight = []
@@ -562,7 +562,7 @@ def create_lamp_base(objname, height, px, py, pz, segments, rings, radios, ratio
# pZ: position Z axis
# mat: Flag for creating materials
# ------------------------------------------------------------------------------
-def create_lampholder(objname, height, px, py, pz, mat):
+def create_lightholder(objname, height, px, py, pz, mat):
mydata = create_cylinder_data(16, [0, height, height + 0.005, height + 0.008, height + 0.05],
[0.005, 0.005, 0.010, 0.018, 0.018],
False, False, False, 0, False)
@@ -600,7 +600,7 @@ def create_lampholder(objname, height, px, py, pz, mat):
# shadeh: height of lampshader
# mat: Flag for creating materials
# ------------------------------------------------------------------------------
-def create_lampholder_strings(objname, height, px, py, pz, radio, shadeh, mat):
+def create_lightholder_strings(objname, height, px, py, pz, radio, shadeh, mat):
mydata = create_cylinder_data(32, [height + 0.005, height + 0.005, height + 0.006, height + 0.006],
[0.018, 0.025, 0.025, 0.018],
False, False, False, 0, False)
@@ -652,7 +652,7 @@ def create_lampholder_strings(objname, height, px, py, pz, radio, shadeh, mat):
# opacity: opacity factor
# mat: Flag for creating materials
# ------------------------------------------------------------------------------
-def create_lampshade(objname, height, px, py, pz, segments, radio1, radio2, pleats, pleatsize, opacity, mat):
+def create_lightshade(objname, height, px, py, pz, segments, radio1, radio2, pleats, pleatsize, opacity, mat):
gap = 0.002
radios = [radio1 - gap, radio1 - gap, radio1, radio2, radio2 - gap, radio2 - gap]
heights = [gap * 2, 0, 0, height, height, height - (gap * 2)]
diff --git a/archimesh/achm_main_panel.py b/archimesh/achm_main_panel.py
index 70312ac3..eb3d7748 100644
--- a/archimesh/achm_main_panel.py
+++ b/archimesh/achm_main_panel.py
@@ -462,10 +462,10 @@ class ArchimeshMainPanel(Panel):
# Prop Buttons
# ------------------------------
box = layout.box()
- box.label("Props", icon='LAMP_DATA')
+ box.label("Props", icon='LIGHT_DATA')
row = box.row()
row.operator("mesh.archimesh_books")
- row.operator("mesh.archimesh_lamp")
+ row.operator("mesh.archimesh_light")
row = box.row()
row.operator("mesh.archimesh_venetian")
row.operator("mesh.archimesh_roller")
diff --git a/archimesh/achm_window_maker.py b/archimesh/achm_window_maker.py
index b7eb8235..1e43c792 100644
--- a/archimesh/achm_window_maker.py
+++ b/archimesh/achm_window_maker.py
@@ -216,7 +216,7 @@ def shape_mesh_and_create_children(mainobject, tmp_mesh, update=False):
myctrl.location.x = 0
myctrl.location.y = -mp.depth * 3 / 2
myctrl.location.z = 0
- myctrl.draw_type = 'BOUNDS'
+ myctrl.display_type = 'BOUNDS'
myctrl.hide = False
myctrl.hide_render = True
if bpy.context.scene.render.engine == 'CYCLES':
diff --git a/archimesh/achm_window_panel.py b/archimesh/achm_window_panel.py
index dbeed892..16c9209b 100644
--- a/archimesh/achm_window_panel.py
+++ b/archimesh/achm_window_panel.py
@@ -394,7 +394,7 @@ def do_ctrl_box(myobject):
myctrl.location.x = 0
myctrl.location.y = 0
myctrl.location.z = 0
- myctrl.draw_type = 'WIRE'
+ myctrl.display_type = 'WIRE'
myctrl.hide = False
myctrl.hide_render = True
if bpy.context.scene.render.engine == 'CYCLES':
diff --git a/archipack/__init__.py b/archipack/__init__.py
index 0f5d3a86..6c30d218 100644
--- a/archipack/__init__.py
+++ b/archipack/__init__.py
@@ -31,8 +31,8 @@ bl_info = {
'author': 's-leger',
'license': 'GPL',
'deps': '',
- 'version': (1, 2, 8),
- 'blender': (2, 7, 8),
+ 'version': (1, 2, 81),
+ 'blender': (2, 80, 0),
'location': 'View3D > Tools > Create > Archipack',
'warning': '',
'wiki_url': 'https://github.com/s-leger/archipack/wiki',
@@ -46,7 +46,6 @@ import os
if "bpy" in locals():
import importlib as imp
- imp.reload(archipack_progressbar)
imp.reload(archipack_material)
imp.reload(archipack_snap)
imp.reload(archipack_manipulator)
@@ -65,7 +64,6 @@ if "bpy" in locals():
# print("archipack: reload ready")
else:
- from . import archipack_progressbar
from . import archipack_material
from . import archipack_snap
from . import archipack_manipulator
@@ -81,8 +79,7 @@ else:
from . import archipack_truss
from . import archipack_floor
from . import archipack_rendering
-
- # print("archipack: ready")
+ print("archipack: ready")
# noinspection PyUnresolvedReferences
import bpy
@@ -105,82 +102,110 @@ icons_collection = {}
# Addon preferences
# ----------------------------------------------------
-def update_panel(self, context):
- try:
- bpy.utils.unregister_class(TOOLS_PT_Archipack_Tools)
- bpy.utils.unregister_class(TOOLS_PT_Archipack_Create)
- except:
- pass
- prefs = context.user_preferences.addons[__name__].preferences
- TOOLS_PT_Archipack_Tools.bl_category = prefs.tools_category
- bpy.utils.register_class(TOOLS_PT_Archipack_Tools)
- TOOLS_PT_Archipack_Create.bl_category = prefs.create_category
- bpy.utils.register_class(TOOLS_PT_Archipack_Create)
-
class Archipack_Pref(AddonPreferences):
bl_idname = __name__
- tools_category = StringProperty(
- name="Tools",
- description="Choose a name for the category of the Tools panel",
- default="Tools",
- update=update_panel
- )
- create_category = StringProperty(
- name="Create",
- description="Choose a name for the category of the Create panel",
- default="Create",
- update=update_panel
- )
- create_submenu = BoolProperty(
+ create_submenu : BoolProperty(
name="Use Sub-menu",
description="Put Achipack's object into a sub menu (shift+a)",
default=True
)
- max_style_draw_tool = BoolProperty(
+ max_style_draw_tool : BoolProperty(
name="Draw a wall use 3dsmax style",
description="Reverse clic / release & drag cycle for Draw a wall",
default=True
)
# Arrow sizes (world units)
- arrow_size = FloatProperty(
+ arrow_size : FloatProperty(
name="Arrow",
description="Manipulators arrow size (blender units)",
default=0.05
)
# Handle area size (pixels)
- handle_size = IntProperty(
+ handle_size : IntProperty(
name="Handle",
description="Manipulators handle sensitive area size (pixels)",
min=2,
default=10
)
- matlib_path = StringProperty(
+ constant_handle_size: BoolProperty(
+ name="Constant handle size",
+ description="When checked, handle size on scree remains constant (handle size pixels)",
+ default=False
+ )
+ text_size: IntProperty(
+ name="Manipulators",
+ description="Manipulator font size (pixels)",
+ min=2,
+ default=14
+ )
+ handle_colour_selected: FloatVectorProperty(
+ name="Selected handle colour",
+ description="Handle color when selected",
+ subtype='COLOR_GAMMA',
+ default=(0.0, 0.0, 0.7, 1.0),
+ size=4,
+ min=0, max=1
+ )
+ handle_colour_inactive: FloatVectorProperty(
+ name="Inactive handle colour",
+ description="Handle color when disabled",
+ subtype='COLOR_GAMMA',
+ default=(0.3, 0.3, 0.3, 1.0),
+ size=4,
+ min=0, max=1
+ )
+ handle_colour_normal: FloatVectorProperty(
+ name="Handle colour normal",
+ description="Base handle color when not selected",
+ subtype='COLOR_GAMMA',
+ default=(1.0, 1.0, 1.0, 1.0),
+ size=4,
+ min=0, max=1
+ )
+ handle_colour_hover: FloatVectorProperty(
+ name="Handle colour hover",
+ description="Handle color when mouse hover",
+ subtype='COLOR_GAMMA',
+ default=(1.0, 1.0, 0.0, 1.0),
+ size=4,
+ min=0, max=1
+ )
+ handle_colour_active: FloatVectorProperty(
+ name="Handle colour active",
+ description="Handle colour when moving",
+ subtype='COLOR_GAMMA',
+ default=(1.0, 0.0, 0.0, 1.0),
+ size=4,
+ min=0, max=1
+ )
+ matlib_path : StringProperty(
name="Folder path",
description="absolute path to material library folder",
- default=""
+ default="",
+ subtype="DIR_PATH"
)
# Font sizes and basic colour scheme
- feedback_size_main = IntProperty(
+ feedback_size_main : IntProperty(
name="Main",
description="Main title font size (pixels)",
min=2,
default=16
)
- feedback_size_title = IntProperty(
+ feedback_size_title : IntProperty(
name="Title",
description="Tool name font size (pixels)",
min=2,
default=14
)
- feedback_size_shortcut = IntProperty(
+ feedback_size_shortcut : IntProperty(
name="Shortcut",
description="Shortcuts font size (pixels)",
min=2,
default=11
)
- feedback_shortcut_area = FloatVectorProperty(
+ feedback_shortcut_area : FloatVectorProperty(
name="Background Shortcut",
description="Shortcut area background color",
subtype='COLOR_GAMMA',
@@ -188,7 +213,7 @@ class Archipack_Pref(AddonPreferences):
size=4,
min=0, max=1
)
- feedback_title_area = FloatVectorProperty(
+ feedback_title_area : FloatVectorProperty(
name="Background Main",
description="Title area background color",
subtype='COLOR_GAMMA',
@@ -196,7 +221,7 @@ class Archipack_Pref(AddonPreferences):
size=4,
min=0, max=1
)
- feedback_colour_main = FloatVectorProperty(
+ feedback_colour_main : FloatVectorProperty(
name="Font Main",
description="Title color",
subtype='COLOR_GAMMA',
@@ -204,7 +229,7 @@ class Archipack_Pref(AddonPreferences):
size=4,
min=0, max=1
)
- feedback_colour_key = FloatVectorProperty(
+ feedback_colour_key : FloatVectorProperty(
name="Font Shortcut key",
description="KEY label color",
subtype='COLOR_GAMMA',
@@ -212,7 +237,7 @@ class Archipack_Pref(AddonPreferences):
size=4,
min=0, max=1
)
- feedback_colour_shortcut = FloatVectorProperty(
+ feedback_colour_shortcut : FloatVectorProperty(
name="Font Shortcut hint",
description="Shortcuts text color",
subtype='COLOR_GAMMA',
@@ -226,21 +251,22 @@ class Archipack_Pref(AddonPreferences):
box = layout.box()
row = box.row()
col = row.column()
- col.label(text="Tab Category:")
- col.prop(self, "tools_category")
- col.prop(self, "create_category")
- col.prop(self, "create_submenu")
- box = layout.box()
- box.label("Features")
- box.prop(self, "max_style_draw_tool")
+ col.label(text="Setup actions")
+ col.prop(self, "matlib_path", text="Material library")
+ box.label(text="Render presets Thumbnails")
+ box.operator("archipack.render_thumbs", icon='ERROR')
box = layout.box()
row = box.row()
col = row.column()
- col.label(text="Material library:")
- col.prop(self, "matlib_path")
+ col.label(text="Add menu:")
+ col.prop(self, "create_submenu")
+
+ box = layout.box()
+ box.label(text="Features")
+ box.prop(self, "max_style_draw_tool")
box = layout.box()
row = box.row()
- split = row.split(percentage=0.5)
+ split = row.split(factor=0.5)
col = split.column()
col.label(text="Colors:")
row = col.row(align=True)
@@ -253,6 +279,16 @@ class Archipack_Pref(AddonPreferences):
row.prop(self, "feedback_colour_key")
row = col.row(align=True)
row.prop(self, "feedback_colour_shortcut")
+ row = col.row(align=True)
+ row.prop(self, "handle_colour_normal")
+ row = col.row(align=True)
+ row.prop(self, "handle_colour_hover")
+ row = col.row(align=True)
+ row.prop(self, "handle_colour_active")
+ row = col.row(align=True)
+ row.prop(self, "handle_colour_selected")
+ row = col.row(align=True)
+ row.prop(self, "handle_colour_inactive")
col = split.column()
col.label(text="Font size:")
col.prop(self, "feedback_size_main")
@@ -261,46 +297,19 @@ class Archipack_Pref(AddonPreferences):
col.label(text="Manipulators:")
col.prop(self, "arrow_size")
col.prop(self, "handle_size")
- # layout.operator("archipack.render_thumbs")
+ col.prop(self, "text_size")
+ col.prop(self, "constant_handle_size")
# ----------------------------------------------------
-# Archipack panels
+# Archipack panel
# ----------------------------------------------------
-
-class TOOLS_PT_Archipack_Tools(Panel):
- bl_label = "Archipack Tools"
- bl_idname = "TOOLS_PT_Archipack_Tools"
- bl_space_type = "VIEW_3D"
- bl_region_type = "TOOLS"
- bl_category = "Tools"
- bl_context = "objectmode"
-
- @classmethod
- def poll(self, context):
- return True
-
- def draw(self, context):
- wm = context.window_manager
- layout = self.layout
- box = layout.box()
- box.label("Auto boolean")
- box.operator("archipack.auto_boolean", text="AutoBoolean", icon='AUTO').mode = 'HYBRID'
- box = layout.box()
- box.label("Rendering")
- box.prop(wm.archipack, 'render_type', text="")
- box.operator("archipack.render", icon='RENDER_STILL')
- box = layout.box()
- box.label("Render presets Thumbnails")
- box.operator("archipack.render_thumbs", icon='ERROR')
-
-
class TOOLS_PT_Archipack_Create(Panel):
- bl_label = "Add Archipack"
+ bl_label = "Archipack"
bl_idname = "TOOLS_PT_Archipack_Create"
bl_space_type = "VIEW_3D"
bl_region_type = "TOOLS"
- bl_category = "Create"
+ # bl_category = "Create"
bl_context = "objectmode"
@classmethod
@@ -312,9 +321,11 @@ class TOOLS_PT_Archipack_Create(Panel):
icons = icons_collection["main"]
layout = self.layout
+ box = layout.box()
+ box.operator("archipack.auto_boolean", text="Boolean", icon='AUTO').mode = 'HYBRID'
row = layout.row(align=True)
box = row.box()
- box.label("Objects")
+ box.label(text="Create")
row = box.row(align=True)
row.operator("archipack.window_preset_menu",
text="Window",
@@ -454,25 +465,6 @@ def menu_func(self, context):
draw_menu(self, context)
-# ----------------------------------------------------
-# Datablock to store global addon variables
-# ----------------------------------------------------
-
-
-class archipack_data(PropertyGroup):
- render_type = EnumProperty(
- items=(
- ('1', "Draw over", "Draw over last rendered image"),
- ('2', "OpenGL", ""),
- ('3', "Animation OpenGL", ""),
- ('4', "Image", "Render image and draw over"),
- ('5', "Animation", "Draw on each frame")
- ),
- name="Render type",
- description="Render method"
- )
-
-
def register():
global icons_collection
icons = previews.new()
@@ -481,8 +473,6 @@ def register():
name, ext = os.path.splitext(icon)
icons.load(name, os.path.join(icons_dir, icon), 'IMAGE')
icons_collection["main"] = icons
-
- archipack_progressbar.register()
archipack_material.register()
archipack_snap.register()
archipack_manipulator.register()
@@ -498,24 +488,18 @@ def register():
archipack_truss.register()
archipack_floor.register()
archipack_rendering.register()
-
- bpy.utils.register_class(archipack_data)
- WindowManager.archipack = PointerProperty(type=archipack_data)
bpy.utils.register_class(Archipack_Pref)
- update_panel(None, bpy.context)
+ bpy.utils.register_class(TOOLS_PT_Archipack_Create)
bpy.utils.register_class(ARCHIPACK_MT_create)
- bpy.types.INFO_MT_mesh_add.append(menu_func)
+ bpy.types.VIEW3D_MT_mesh_add.append(menu_func)
def unregister():
global icons_collection
- bpy.types.INFO_MT_mesh_add.remove(menu_func)
+ bpy.types.VIEW3D_MT_mesh_add.remove(menu_func)
bpy.utils.unregister_class(ARCHIPACK_MT_create)
-
- bpy.utils.unregister_class(TOOLS_PT_Archipack_Tools)
bpy.utils.unregister_class(TOOLS_PT_Archipack_Create)
bpy.utils.unregister_class(Archipack_Pref)
- archipack_progressbar.unregister()
archipack_material.unregister()
archipack_snap.unregister()
archipack_manipulator.unregister()
@@ -532,9 +516,6 @@ def unregister():
archipack_floor.unregister()
archipack_rendering.unregister()
- bpy.utils.unregister_class(archipack_data)
- del WindowManager.archipack
-
for icons in icons_collection.values():
previews.remove(icons)
icons_collection.clear()
diff --git a/archipack/archipack_2d.py b/archipack/archipack_2d.py
index 0078ae3c..e286e730 100644
--- a/archipack/archipack_2d.py
+++ b/archipack/archipack_2d.py
@@ -253,10 +253,10 @@ class Line(Projection):
t: param t of intersection on current line
"""
c = line.cross_z
- d = self.v * c
+ d = self.v.dot(c)
if d == 0:
return False, 0, 0
- t = (c * (line.p - self.p)) / d
+ t = c.dot(line.p - self.p) / d
return True, self.lerp(t), t
def intersect_ext(self, line):
@@ -264,13 +264,13 @@ class Line(Projection):
same as intersect, but return param t on both lines
"""
c = line.cross_z
- d = self.v * c
+ d = self.v.dot(c)
if d == 0:
return False, 0, 0, 0
dp = line.p - self.p
c2 = self.cross_z
- u = (c * dp) / d
- v = (c2 * dp) / d
+ u = c.dot(dp) / d
+ v = c2.dot(dp) / d
return u > 0 and v > 0 and u < 1 and v < 1, self.lerp(u), u, v
def point_sur_segment(self, pt):
@@ -284,7 +284,7 @@ class Line(Projection):
if dl == 0:
return dp.length < 0.00001, 0, 0
d = (self.v.x * dp.y - self.v.y * dp.x) / dl
- t = (self.v * dp) / (dl * dl)
+ t = self.v.dot(dp) / (dl * dl)
return t > 0 and t < 1, d, t
def steps(self, len):
@@ -328,7 +328,7 @@ class Line(Projection):
self.v = Matrix([
[ca, -sa],
[sa, ca]
- ]) * self.v
+ ]) @ self.v
return self
def scale(self, length):
@@ -354,8 +354,8 @@ class Line(Projection):
x, y, z = p
spline.points[i].co = (x, y, 0, 1)
curve_obj = bpy.data.objects.new('LINE', curve)
- context.scene.objects.link(curve_obj)
- curve_obj.select = True
+ context.scene.collection.objects.link(curve_obj)
+ curve_obj.select_set(state=True)
def make_offset(self, offset, last=None):
"""
@@ -399,13 +399,13 @@ class Line(Projection):
# intersect line / line
# 1 line -> 2 line
c = line.cross_z
- d = last.v * c
+ d = last.v.dot(c)
if d == 0:
return line
v = line.p - last.p
- t = (c * v) / d
+ t = c.dot(v) / d
c2 = last.cross_z
- u = (c2 * v) / d
+ u = c2.dot(v) / d
# intersect past this segment end
# or before last segment start
# print("u:%s t:%s" % (u, t))
@@ -431,9 +431,9 @@ class Circle(Projection):
def intersect(self, line):
v = line.p - self.c
- A = line.v * line.v
- B = 2 * v * line.v
- C = v * v - self.r2
+ A = line.v.dot(line.v)
+ B = 2 * v.dot(line.v)
+ C = v.dot(v) - self.r2
d = B * B - 4 * A * C
if A <= 0.0000001 or d < 0:
# dosent intersect, find closest point of line
@@ -549,7 +549,7 @@ class Arc(Circle):
u = self.p0 - self.p1
v = p0 - self.p1
scale, rM = self.scale_rot_matrix(u, v)
- self.c = self.p1 + rM * (self.c - self.p1)
+ self.c = self.p1 + rM @ (self.c - self.p1)
self.r *= scale
self.r2 = self.r * self.r
dp = p0 - self.c
@@ -566,7 +566,7 @@ class Arc(Circle):
v = p1 - p0
scale, rM = self.scale_rot_matrix(u, v)
- self.c = p0 + rM * (self.c - p0)
+ self.c = p0 + rM @ (self.c - p0)
self.r *= scale
self.r2 = self.r * self.r
dp = p0 - self.c
@@ -730,7 +730,7 @@ class Arc(Circle):
[sa, ca]
])
p0 = self.p0
- self.c = p0 + rM * (self.c - p0)
+ self.c = p0 + rM @ (self.c - p0)
dp = p0 - self.c
self.a0 = atan2(dp.y, dp.x)
return self
@@ -842,8 +842,8 @@ class Arc(Circle):
x, y = p
spline.points[i].co = (x, y, 0, 1)
curve_obj = bpy.data.objects.new('ARC', curve)
- context.scene.objects.link(curve_obj)
- curve_obj.select = True
+ context.scene.collection.objects.link(curve_obj)
+ curve_obj.select_set(state=True)
class Line3d(Line):
diff --git a/archipack/archipack_autoboolean.py b/archipack/archipack_autoboolean.py
index c7e60305..8e4df558 100644
--- a/archipack/archipack_autoboolean.py
+++ b/archipack/archipack_autoboolean.py
@@ -28,9 +28,9 @@ import bpy
from bpy.types import Operator
from bpy.props import EnumProperty
from mathutils import Vector
+from . archipack_object import ArchipackCollectionManager
-
-class ArchipackBoolManager():
+class ArchipackBoolManager(ArchipackCollectionManager):
"""
Handle three methods for booleans
- interactive: one modifier for each hole right on wall
@@ -38,12 +38,11 @@ class ArchipackBoolManager():
- mixed: merge holes with boolean and use result on wall
may be slow, but is robust
"""
- def __init__(self, mode, solver_mode='CARVE'):
+ def __init__(self, mode):
"""
mode in 'ROBUST', 'INTERACTIVE', 'HYBRID'
"""
self.mode = mode
- self.solver_mode = solver_mode
# internal variables
self.itM = None
self.min_x = 0
@@ -69,7 +68,7 @@ class ArchipackBoolManager():
self.min_z + 0.5 * (self.max_z - self.min_z)))
def _contains(self, pt):
- p = self.itM * pt
+ p = self.itM @ pt
return (p.x >= self.min_x and p.x <= self.max_x and
p.y >= self.min_y and p.y <= self.max_y and
p.z >= self.min_z and p.z <= self.max_z)
@@ -105,10 +104,10 @@ class ArchipackBoolManager():
hole.lock_location = (True, True, True)
hole.lock_rotation = (True, True, True)
hole.lock_scale = (True, True, True)
- hole.draw_type = 'WIRE'
+ hole.display_type = 'WIRE'
hole.hide_render = True
hole.hide_select = True
- hole.select = True
+ hole.select_set(state=True)
hole.cycles_visibility.camera = False
hole.cycles_visibility.diffuse = False
hole.cycles_visibility.glossy = False
@@ -135,7 +134,7 @@ class ArchipackBoolManager():
if d is not None:
if (self.itM is not None and (
self._contains(o.location) or
- self._contains(o.matrix_world * Vector((0, 0, 0.5 * d.z))))
+ self._contains(o.matrix_world @ Vector((0, 0, 0.5 * d.z))))
):
if self.mode != 'ROBUST':
hole = d.interactive_hole(context, o)
@@ -172,7 +171,7 @@ class ArchipackBoolManager():
sort hole from center to borders by distance from center
may improve nested booleans
"""
- center = wall.matrix_world * self.center
+ center = wall.matrix_world @ self.center
holes = [(o, (o.matrix_world.translation - center).length) for o in holes]
self.quicksort(holes)
return [o[0] for o in holes]
@@ -181,17 +180,12 @@ class ArchipackBoolManager():
# print("difference %s" % (hole.name))
m = basis.modifiers.new('AutoBoolean', 'BOOLEAN')
m.operation = 'DIFFERENCE'
- if solver is None:
- m.solver = self.solver_mode
- else:
- m.solver = solver
m.object = hole
def union(self, basis, hole):
# print("union %s" % (hole.name))
m = basis.modifiers.new('AutoMerge', 'BOOLEAN')
m.operation = 'UNION'
- m.solver = self.solver_mode
m.object = hole
def remove_modif_and_object(self, context, o, to_delete):
@@ -202,7 +196,7 @@ class ArchipackBoolManager():
m.object = None
o.modifiers.remove(m)
if h is not None:
- context.scene.objects.unlink(h)
+ self.unlink_object_from_scene(h)
bpy.data.objects.remove(h, do_unlink=True)
# Mixed
@@ -210,7 +204,7 @@ class ArchipackBoolManager():
# print("create_merge_basis")
h = bpy.data.meshes.new("AutoBoolean")
hole_obj = bpy.data.objects.new("AutoBoolean", h)
- context.scene.objects.link(hole_obj)
+ self.link_object_to_scene(context, hole_obj)
hole_obj['archipack_hybridhole'] = True
if wall.parent is not None:
hole_obj.parent = wall.parent
@@ -254,7 +248,6 @@ class ArchipackBoolManager():
m = wall.modifiers.get("AutoMixedBoolean")
if m is None:
m = wall.modifiers.new('AutoMixedBoolean', 'BOOLEAN')
- m.solver = self.solver_mode
m.operation = 'DIFFERENCE'
if m.object is None:
@@ -306,7 +299,7 @@ class ArchipackBoolManager():
to_delete.append([m, h])
# remove modifier and holes not found in new list
self.remove_modif_and_object(context, hole_obj, to_delete)
- context.scene.objects.unlink(hole_obj)
+ self.unlink_object_from_scene(hole_obj)
bpy.data.objects.remove(hole_obj, do_unlink=True)
to_delete = []
@@ -388,12 +381,12 @@ class ArchipackBoolManager():
# to support "revival" of applied modifiers
m = wall.modifiers.get("Wall")
if m is None:
- wall.select = True
- context.scene.objects.active = wall
+ wall.select_set(state=True)
+ context.view_layer.objects.active = wall
wall.data.archipack_wall2[0].update(context)
bpy.ops.object.select_all(action='DESELECT')
- context.scene.objects.active = None
+ context.view_layer.objects.active = None
childs = []
holes = []
# get wall bounds to find what's inside
@@ -429,19 +422,19 @@ class ArchipackBoolManager():
# parenting childs to wall reference point
if wall.parent is None:
x, y, z = wall.bound_box[0]
- context.scene.cursor_location = wall.matrix_world * Vector((x, y, z))
+ context.scene.cursor_location = wall.matrix_world @ Vector((x, y, z))
# fix issue #9
- context.scene.objects.active = wall
+ context.view_layer.objects.active = wall
bpy.ops.archipack.reference_point()
else:
- wall.parent.select = True
- context.scene.objects.active = wall.parent
+ wall.parent.select_set(state=True)
+ context.view_layer.objects.active = wall.parent
- wall.select = True
+ wall.select_set(state=True)
for o in childs:
if 'archipack_robusthole' in o:
o.hide_select = False
- o.select = True
+ o.select_set(state=True)
bpy.ops.archipack.parent_to_reference()
@@ -499,7 +492,6 @@ class ArchipackBoolManager():
if m is None:
m = wall.modifiers.new('AutoMixedBoolean', 'BOOLEAN')
m.operation = 'DIFFERENCE'
- m.solver = self.solver_mode
if m.object is None:
hole_obj = self.create_merge_basis(context, wall)
@@ -513,21 +505,21 @@ class ArchipackBoolManager():
# parenting childs to wall reference point
if wall.parent is None:
x, y, z = wall.bound_box[0]
- context.scene.cursor_location = wall.matrix_world * Vector((x, y, z))
+ context.scene.cursor_location = wall.matrix_world @ Vector((x, y, z))
# fix issue #9
- context.scene.objects.active = wall
+ context.view_layer.objects.active = wall
bpy.ops.archipack.reference_point()
else:
- context.scene.objects.active = wall.parent
+ context.view_layer.objects.active = wall.parent
if hole_obj is not None:
- hole_obj.select = True
+ hole_obj.select_set(state=True)
- wall.select = True
- o.select = True
+ wall.select_set(state=True)
+ o.select_set(state=True)
bpy.ops.archipack.parent_to_reference()
- wall.select = True
- context.scene.objects.active = wall
+ wall.select_set(state=True)
+ context.view_layer.objects.active = wall
if "archipack_wall2" in wall.data:
d = wall.data.archipack_wall2[0]
g = d.get_generator()
@@ -545,7 +537,7 @@ class ARCHIPACK_OT_single_boolean(Operator):
bl_description = "Add single boolean for doors and windows"
bl_category = 'Archipack'
bl_options = {'REGISTER', 'UNDO'}
- mode = EnumProperty(
+ mode : EnumProperty(
name="Mode",
items=(
('INTERACTIVE', 'INTERACTIVE', 'Interactive, fast but may fail', 0),
@@ -554,14 +546,6 @@ class ARCHIPACK_OT_single_boolean(Operator):
),
default='HYBRID'
)
- solver_mode = EnumProperty(
- name="Solver",
- items=(
- ('CARVE', 'CARVE', 'Slow but robust (could be slow in hybrid mode with many holes)', 0),
- ('BMESH', 'BMESH', 'Fast but more prone to errors', 1)
- ),
- default='BMESH'
- )
"""
Wall must be active object
window or door must be selected
@@ -583,14 +567,14 @@ class ARCHIPACK_OT_single_boolean(Operator):
def execute(self, context):
if context.mode == "OBJECT":
wall = context.active_object
- manager = ArchipackBoolManager(mode=self.mode, solver_mode=self.solver_mode)
+ manager = ArchipackBoolManager(mode=self.mode)
for o in context.selected_objects:
if o != wall:
manager.singleboolean(context, wall, o)
+ o.select_set(state=False)
break
- o.select = False
- wall.select = True
- context.scene.objects.active = wall
+ wall.select_set(state=True)
+ context.view_layer.objects.active = wall
return {'FINISHED'}
else:
self.report({'WARNING'}, "Archipack: Option only valid in Object mode")
@@ -603,7 +587,7 @@ class ARCHIPACK_OT_auto_boolean(Operator):
bl_description = "Automatic boolean for doors and windows"
bl_category = 'Archipack'
bl_options = {'REGISTER', 'UNDO'}
- mode = EnumProperty(
+ mode : EnumProperty(
name="Mode",
items=(
('INTERACTIVE', 'INTERACTIVE', 'Interactive, fast but may fail', 0),
@@ -612,39 +596,30 @@ class ARCHIPACK_OT_auto_boolean(Operator):
),
default='HYBRID'
)
- solver_mode = EnumProperty(
- name="Solver",
- items=(
- ('CARVE', 'CARVE', 'Slow but robust (could be slow in hybrid mode with many holes)', 0),
- ('BMESH', 'BMESH', 'Fast but more prone to errors', 1)
- ),
- default='BMESH'
- )
def draw(self, context):
layout = self.layout
row = layout.row()
row.prop(self, 'mode')
- row.prop(self, 'solver_mode')
def execute(self, context):
if context.mode == "OBJECT":
- manager = ArchipackBoolManager(mode=self.mode, solver_mode=self.solver_mode)
- active = context.scene.objects.active
+ manager = ArchipackBoolManager(mode=self.mode)
+ active = context.view_layer.objects.active
walls = [wall for wall in context.selected_objects if not manager.filter_wall(wall)]
bpy.ops.object.select_all(action='DESELECT')
for wall in walls:
manager.autoboolean(context, wall)
bpy.ops.object.select_all(action='DESELECT')
- wall.select = True
- context.scene.objects.active = wall
+ wall.select_set(state=True)
+ context.view_layer.objects.active = wall
if wall.data is not None and 'archipack_wall2' in wall.data:
bpy.ops.archipack.wall2_manipulate('EXEC_DEFAULT')
# reselect walls
bpy.ops.object.select_all(action='DESELECT')
for wall in walls:
- wall.select = True
- context.scene.objects.active = active
+ wall.select_set(state=True)
+ context.view_layer.objects.active = active
return {'FINISHED'}
else:
self.report({'WARNING'}, "Archipack: Option only valid in Object mode")
@@ -668,13 +643,13 @@ class ARCHIPACK_OT_generate_hole(Operator):
self.report({'WARNING'}, "Archipack: active object must be a door, a window or a roof")
return {'CANCELLED'}
bpy.ops.object.select_all(action='DESELECT')
- o.select = True
- context.scene.objects.active = o
+ o.select_set(state=True)
+ context.view_layer.objects.active = o
hole = manager._generate_hole(context, o)
manager.prepare_hole(hole)
- hole.select = False
- o.select = True
- context.scene.objects.active = o
+ hole.select_set(state=False)
+ o.select_set(state=True)
+ context.view_layer.objects.active = o
return {'FINISHED'}
else:
self.report({'WARNING'}, "Archipack: Option only valid in Object mode")
diff --git a/archipack/archipack_cutter.py b/archipack/archipack_cutter.py
index 2ce37d4c..3b9f424d 100644
--- a/archipack/archipack_cutter.py
+++ b/archipack/archipack_cutter.py
@@ -160,7 +160,7 @@ class CutterGenerator():
"""
move shape fromTM into toTM coordsys
"""
- dp = (toTM.inverted() * fromTM.translation).to_2d()
+ dp = (toTM.inverted() @ fromTM.translation).to_2d()
da = toTM.row[1].to_2d().angle_signed(fromTM.row[1].to_2d())
ca = cos(da)
sa = sin(da)
@@ -169,7 +169,7 @@ class CutterGenerator():
[sa, ca]
])
for s in self.segs:
- tp = (rM * s.p0) - s.p0 + dp
+ tp = (rM @ s.p0) - s.p0 + dp
s.rotate(da)
s.translate(tp)
@@ -519,7 +519,7 @@ class CutAbleGenerator():
f.calc_center_median().to_2d(),
segs=segs)]
if len(f_geom) > 0:
- bmesh.ops.delete(bm, geom=f_geom, context=5)
+ bmesh.ops.delete(bm, geom=f_geom, context='FACES')
def cut_boundary(self, bm, cutable, offset={'DEFAULT': 0}):
o_keys = offset.keys()
@@ -545,7 +545,7 @@ class CutAbleGenerator():
f_geom = [f for f in bm.faces
if not cutable.inside(f.calc_center_median().to_2d())]
if len(f_geom) > 0:
- bmesh.ops.delete(bm, geom=f_geom, context=5)
+ bmesh.ops.delete(bm, geom=f_geom, context='FACES')
def update_hole(self, context):
@@ -562,14 +562,14 @@ class ArchipackCutterPart():
Childs MUST define
-type EnumProperty
"""
- length = FloatProperty(
+ length : FloatProperty(
name="Length",
min=0.01,
max=1000.0,
default=2.0,
update=update_hole
)
- a0 = FloatProperty(
+ a0 : FloatProperty(
name="Angle",
min=-2 * pi,
max=2 * pi,
@@ -577,7 +577,7 @@ class ArchipackCutterPart():
subtype='ANGLE', unit='ROTATION',
update=update_hole
)
- offset = FloatProperty(
+ offset : FloatProperty(
name="Offset",
min=0,
default=0,
@@ -617,28 +617,28 @@ def update_manipulators(self, context):
class ArchipackCutter():
- n_parts = IntProperty(
+ n_parts : IntProperty(
name="Parts",
min=1,
default=1, update=update_manipulators
)
- z = FloatProperty(
+ z : FloatProperty(
name="dumb z",
description="Dumb z for manipulator placeholder",
default=0.01,
options={'SKIP_SAVE'}
)
- user_defined_path = StringProperty(
+ user_defined_path : StringProperty(
name="User defined",
update=update_path
)
- user_defined_resolution = IntProperty(
+ user_defined_resolution : IntProperty(
name="Resolution",
min=1,
max=128,
default=12, update=update_path
)
- operation = EnumProperty(
+ operation : EnumProperty(
items=(
('DIFFERENCE', 'Difference', 'Cut inside part', 0),
('INTERSECTION', 'Intersection', 'Keep inside part', 1)
@@ -646,20 +646,17 @@ class ArchipackCutter():
default='DIFFERENCE',
update=update_operation
)
- auto_update = BoolProperty(
+ auto_update : BoolProperty(
options={'SKIP_SAVE'},
default=True,
update=update_manipulators
)
# UI layout related
- parts_expand = BoolProperty(
+ parts_expand : BoolProperty(
default=False
)
- closed = BoolProperty(
- description="keep closed to be wall snap manipulator compatible",
- options={'SKIP_SAVE'},
- default=True
- )
+
+ closed = True
def draw(self, layout, context):
box = layout.box()
@@ -731,18 +728,18 @@ class ArchipackCutter():
# since this can lower points count by a resolution factor
# use normalized to handle non linear t
if resolution == 0:
- pts.append(wM * p0.co.to_3d())
+ pts.append(wM @ p0.co.to_3d())
else:
v = (p1.co - p0.co).normalized()
d1 = (p0.handle_right - p0.co).normalized()
d2 = (p1.co - p1.handle_left).normalized()
if d1 == v and d2 == v:
- pts.append(wM * p0.co.to_3d())
+ pts.append(wM @ p0.co.to_3d())
else:
- seg = interpolate_bezier(wM * p0.co,
- wM * p0.handle_right,
- wM * p1.handle_left,
- wM * p1.co,
+ seg = interpolate_bezier(wM @ p0.co,
+ wM @ p0.handle_right,
+ wM @ p1.handle_left,
+ wM @ p1.co,
resolution + 1)
for i in range(resolution):
pts.append(seg[i].to_3d())
@@ -768,7 +765,7 @@ class ArchipackCutter():
def from_spline(self, context, wM, resolution, spline):
pts = []
if spline.type == 'POLY':
- pts = [wM * p.co.to_3d() for p in spline.points]
+ pts = [wM @ p.co.to_3d() for p in spline.points]
if spline.use_cyclic_u:
pts.append(pts[0])
elif spline.type == 'BEZIER':
@@ -783,21 +780,16 @@ class ArchipackCutter():
self.interpolate_bezier(pts, wM, p0, p1, resolution)
pts.append(pts[0])
else:
- pts.append(wM * points[-1].co)
+ pts.append(wM @ points[-1].co)
if self.is_cw(pts) == (self.operation == 'INTERSECTION'):
pts = list(reversed(pts))
- pt = wM.inverted() * pts[0]
+ pt = wM.inverted() @ pts[0]
# pretranslate
o = self.find_in_selection(context, self.auto_update)
- o.matrix_world = wM * Matrix([
- [1, 0, 0, pt.x],
- [0, 1, 0, pt.y],
- [0, 0, 1, pt.z],
- [0, 0, 0, 1]
- ])
+ o.matrix_world = wM @ Matrix.Translation(pt)
self.auto_update = False
self.from_points(pts)
self.auto_update = True
@@ -854,7 +846,8 @@ class ArchipackCutter():
self.update_parent(context, o)
def update_path(self, context):
- user_def_path = context.scene.objects.get(self.user_defined_path)
+
+ user_def_path = context.scene.objects.get(self.user_defined_path.strip())
if user_def_path is not None and user_def_path.type == 'CURVE':
self.from_spline(context,
user_def_path.matrix_world,
@@ -912,7 +905,7 @@ class ArchipackCutter():
def manipulable_setup(self, context):
self.manipulable_disable(context)
- o = context.active_object
+ o = context.object
n_parts = self.n_parts + 1
diff --git a/archipack/archipack_door.py b/archipack/archipack_door.py
index 3e85cf31..03e3d5e1 100644
--- a/archipack/archipack_door.py
+++ b/archipack/archipack_door.py
@@ -40,7 +40,7 @@ from .panel import Panel as DoorPanel
from .archipack_handle import create_handle, door_handle_horizontal_01
from .archipack_manipulator import Manipulable
from .archipack_preset import ArchipackPreset, PresetMenuOperator
-from .archipack_object import ArchipackObject, ArchipackCreateTool, ArchpackDrawTool
+from .archipack_object import ArchipackObject, ArchipackCreateTool, ArchipackDrawTool, ArchipackCollectionManager
from .archipack_gl import FeedbackPanel
from .archipack_keymaps import Keymaps
@@ -60,83 +60,83 @@ def update_childs(self, context):
class archipack_door_panel(ArchipackObject, PropertyGroup):
- x = FloatProperty(
+ x : FloatProperty(
name='Width',
min=0.25,
default=100.0, precision=2,
unit='LENGTH', subtype='DISTANCE',
description='Width'
)
- y = FloatProperty(
+ y : FloatProperty(
name='Depth',
min=0.001,
default=0.02, precision=2,
unit='LENGTH', subtype='DISTANCE',
description='depth'
)
- z = FloatProperty(
+ z : FloatProperty(
name='Height',
min=0.1,
default=2.0, precision=2,
unit='LENGTH', subtype='DISTANCE',
description='height'
)
- direction = IntProperty(
+ direction : IntProperty(
name="Direction",
min=0,
max=1,
description="open direction"
)
- model = IntProperty(
+ model : IntProperty(
name="Model",
min=0,
max=3,
default=0,
description="Model"
)
- chanfer = FloatProperty(
+ chanfer : FloatProperty(
name='Bevel',
min=0.001,
default=0.005, precision=3,
unit='LENGTH', subtype='DISTANCE',
description='chanfer'
)
- panel_spacing = FloatProperty(
+ panel_spacing : FloatProperty(
name='Spacing',
min=0.001,
default=0.1, precision=2,
unit='LENGTH', subtype='DISTANCE',
description='distance between panels'
)
- panel_bottom = FloatProperty(
+ panel_bottom : FloatProperty(
name='Bottom',
min=0.0,
default=0.0, precision=2,
unit='LENGTH', subtype='DISTANCE',
description='distance from bottom'
)
- panel_border = FloatProperty(
+ panel_border : FloatProperty(
name='Border',
min=0.001,
default=0.2, precision=2,
unit='LENGTH', subtype='DISTANCE',
description='distance from border'
)
- panels_x = IntProperty(
+ panels_x : IntProperty(
name="# h",
min=1,
max=50,
default=1,
description="panels h"
)
- panels_y = IntProperty(
+ panels_y : IntProperty(
name="# v",
min=1,
max=50,
default=1,
description="panels v"
)
- panels_distrib = EnumProperty(
+ panels_distrib : EnumProperty(
name='distribution',
items=(
('REGULAR', 'Regular', '', 0),
@@ -144,7 +144,7 @@ class archipack_door_panel(ArchipackObject, PropertyGroup):
),
default='REGULAR'
)
- handle = EnumProperty(
+ handle : EnumProperty(
name='Shape',
items=(
('NONE', 'No handle', '', 0),
@@ -559,7 +559,7 @@ class archipack_door_panel(ArchipackObject, PropertyGroup):
def remove_handle(self, context, o):
handle = self.find_handle(o)
if handle is not None:
- context.scene.objects.unlink(handle)
+ self.unlink_object_from_scene(handle)
bpy.data.objects.remove(handle, do_unlink=True)
def update(self, context):
@@ -584,7 +584,7 @@ class ARCHIPACK_PT_door_panel(Panel):
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
# bl_context = 'object'
- bl_category = 'ArchiPack'
+ bl_category = 'Archipack'
@classmethod
def poll(cls, context):
@@ -600,88 +600,88 @@ class ARCHIPACK_PT_door_panel(Panel):
# ------------------------------------------------------------------
-class ARCHIPACK_OT_door_panel(Operator):
+class ARCHIPACK_OT_door_panel(ArchipackCollectionManager, Operator):
bl_idname = "archipack.door_panel"
bl_label = "Door model 1"
bl_description = "Door model 1"
bl_category = 'Archipack'
bl_options = {'REGISTER', 'UNDO'}
- x = FloatProperty(
+ x : FloatProperty(
name='Width',
min=0.1,
default=0.80, precision=2,
unit='LENGTH', subtype='DISTANCE',
description='Width'
)
- z = FloatProperty(
+ z : FloatProperty(
name='Height',
min=0.1,
default=2.0, precision=2,
unit='LENGTH', subtype='DISTANCE',
description='height'
)
- y = FloatProperty(
+ y : FloatProperty(
name='Depth',
min=0.001,
default=0.02, precision=2,
unit='LENGTH', subtype='DISTANCE',
description='Depth'
)
- direction = IntProperty(
+ direction : IntProperty(
name="Direction",
min=0,
max=1,
description="open direction"
)
- model = IntProperty(
+ model : IntProperty(
name="Model",
min=0,
max=3,
description="panel type"
)
- chanfer = FloatProperty(
+ chanfer : FloatProperty(
name='Bevel',
min=0.001,
default=0.005, precision=3,
unit='LENGTH', subtype='DISTANCE',
description='chanfer'
)
- panel_spacing = FloatProperty(
+ panel_spacing : FloatProperty(
name='Spacing',
min=0.001,
default=0.1, precision=2,
unit='LENGTH', subtype='DISTANCE',
description='distance between panels'
)
- panel_bottom = FloatProperty(
+ panel_bottom : FloatProperty(
name='Bottom',
min=0.0,
default=0.0, precision=2,
unit='LENGTH', subtype='DISTANCE',
description='distance from bottom'
)
- panel_border = FloatProperty(
+ panel_border : FloatProperty(
name='Border',
min=0.001,
default=0.2, precision=2,
unit='LENGTH', subtype='DISTANCE',
description='distance from border'
)
- panels_x = IntProperty(
+ panels_x : IntProperty(
name="# h",
min=1,
max=50,
default=1,
description="panels h"
)
- panels_y = IntProperty(
+ panels_y : IntProperty(
name="# v",
min=1,
max=50,
default=1,
description="panels v"
)
- panels_distrib = EnumProperty(
+ panels_distrib : EnumProperty(
name='Distribution',
items=(
('REGULAR', 'Regular', '', 0),
@@ -689,7 +689,7 @@ class ARCHIPACK_OT_door_panel(Operator):
),
default='REGULAR'
)
- handle = EnumProperty(
+ handle : EnumProperty(
name='Shape',
items=(
('NONE', 'No handle', '', 0),
@@ -697,14 +697,14 @@ class ARCHIPACK_OT_door_panel(Operator):
),
default='BOTH'
)
- material = StringProperty(
+ material : StringProperty(
default=""
)
def draw(self, context):
layout = self.layout
row = layout.row()
- row.label("Use Properties panel (N) to define parms", icon='INFO')
+ row.label(text="Use Properties panel (N) to define parms", icon='INFO')
def create(self, context):
"""
@@ -727,7 +727,7 @@ class ARCHIPACK_OT_door_panel(Operator):
d.panels_x = self.panels_x
d.panels_y = self.panels_y
d.handle = self.handle
- context.scene.objects.link(o)
+ self.link_object_to_scene(context, o)
o.lock_location[0] = True
o.lock_location[1] = True
o.lock_location[2] = True
@@ -736,8 +736,8 @@ class ARCHIPACK_OT_door_panel(Operator):
o.lock_scale[0] = True
o.lock_scale[1] = True
o.lock_scale[2] = True
- o.select = True
- context.scene.objects.active = o
+ o.select_set(state=True)
+ context.view_layer.objects.active = o
m = o.archipack_material.add()
m.category = "door"
m.material = self.material
@@ -751,8 +751,8 @@ class ARCHIPACK_OT_door_panel(Operator):
if context.mode == "OBJECT":
bpy.ops.object.select_all(action="DESELECT")
o = self.create(context)
- o.select = True
- context.scene.objects.active = o
+ o.select_set(state=True)
+ context.view_layer.objects.active = o
return {'FINISHED'}
else:
self.report({'WARNING'}, "Archipack: Option only valid in Object mode")
@@ -769,14 +769,14 @@ class ARCHIPACK_OT_select_parent(Operator):
def draw(self, context):
layout = self.layout
row = layout.row()
- row.label("Use Properties panel (N) to define parms", icon='INFO')
+ row.label(text="Use Properties panel (N) to define parms", icon='INFO')
def execute(self, context):
if context.mode == "OBJECT":
if context.active_object is not None and context.active_object.parent is not None:
bpy.ops.object.select_all(action="DESELECT")
- context.active_object.parent.select = True
- context.scene.objects.active = context.active_object.parent
+ context.active_object.parent.select_set(state=True)
+ context.view_layer.objects.active = context.active_object.parent
return {'FINISHED'}
else:
self.report({'WARNING'}, "Archipack: Option only valid in Object mode")
@@ -789,117 +789,117 @@ class archipack_door(ArchipackObject, Manipulable, PropertyGroup):
parent parametric object
create/remove/update her own childs
"""
- x = FloatProperty(
+ x : FloatProperty(
name='Width',
min=0.25,
default=100.0, precision=2, step=1,
unit='LENGTH', subtype='DISTANCE',
description='Width', update=update,
)
- y = FloatProperty(
+ y : FloatProperty(
name='Depth',
min=0.1,
default=0.20, precision=2, step=1,
unit='LENGTH', subtype='DISTANCE',
description='Depth', update=update,
)
- z = FloatProperty(
+ z : FloatProperty(
name='Height',
min=0.1,
default=2.0, precision=2, step=1,
unit='LENGTH', subtype='DISTANCE',
description='height', update=update,
)
- frame_x = FloatProperty(
+ frame_x : FloatProperty(
name='Width',
min=0,
default=0.1, precision=2, step=1,
unit='LENGTH', subtype='DISTANCE',
description='frame width', update=update,
)
- frame_y = FloatProperty(
+ frame_y : FloatProperty(
name='Depth',
default=0.03, precision=2, step=1,
unit='LENGTH', subtype='DISTANCE',
description='frame depth', update=update,
)
- direction = IntProperty(
+ direction : IntProperty(
name="Direction",
min=0,
max=1,
description="open direction", update=update,
)
- door_y = FloatProperty(
+ door_y : FloatProperty(
name='Depth',
min=0.001,
default=0.02, precision=2, step=1,
unit='LENGTH', subtype='DISTANCE',
description='depth', update=update,
)
- door_offset = FloatProperty(
+ door_offset : FloatProperty(
name='Offset',
min=0,
default=0, precision=2, step=1,
unit='LENGTH', subtype='DISTANCE',
description='offset', update=update,
)
- model = IntProperty(
+ model : IntProperty(
name="Model",
min=0,
max=3,
default=0,
description="Model", update=update,
)
- n_panels = IntProperty(
+ n_panels : IntProperty(
name="Panels",
min=1,
max=2,
default=1,
description="number of panels", update=update
)
- chanfer = FloatProperty(
+ chanfer : FloatProperty(
name='Bevel',
min=0.001,
default=0.005, precision=3, step=0.01,
unit='LENGTH', subtype='DISTANCE',
description='chanfer', update=update_childs,
)
- panel_spacing = FloatProperty(
+ panel_spacing : FloatProperty(
name='Spacing',
min=0.001,
default=0.1, precision=2, step=1,
unit='LENGTH', subtype='DISTANCE',
description='distance between panels', update=update_childs,
)
- panel_bottom = FloatProperty(
+ panel_bottom : FloatProperty(
name='Bottom',
min=0.0,
default=0.0, precision=2, step=1,
unit='LENGTH', subtype='DISTANCE',
description='distance from bottom', update=update_childs,
)
- panel_border = FloatProperty(
+ panel_border : FloatProperty(
name='Border',
min=0.001,
default=0.2, precision=2, step=1,
unit='LENGTH', subtype='DISTANCE',
description='distance from border', update=update_childs,
)
- panels_x = IntProperty(
+ panels_x : IntProperty(
name="# h",
min=1,
max=50,
default=1,
description="panels h", update=update_childs,
)
- panels_y = IntProperty(
+ panels_y : IntProperty(
name="# v",
min=1,
max=50,
default=1,
description="panels v", update=update_childs,
)
- panels_distrib = EnumProperty(
+ panels_distrib : EnumProperty(
name='Distribution',
items=(
('REGULAR', 'Regular', '', 0),
@@ -907,7 +907,7 @@ class archipack_door(ArchipackObject, Manipulable, PropertyGroup):
),
default='REGULAR', update=update_childs,
)
- handle = EnumProperty(
+ handle : EnumProperty(
name='Handle',
items=(
('NONE', 'No handle', '', 0),
@@ -915,19 +915,19 @@ class archipack_door(ArchipackObject, Manipulable, PropertyGroup):
),
default='BOTH', update=update_childs,
)
- hole_margin = FloatProperty(
+ hole_margin : FloatProperty(
name='Hole margin',
min=0.0,
default=0.1, precision=2, step=1,
unit='LENGTH', subtype='DISTANCE',
description='how much hole surround wall'
)
- flip = BoolProperty(
+ flip : BoolProperty(
default=False,
update=update,
description='flip outside and outside material of hole'
)
- auto_update = BoolProperty(
+ auto_update : BoolProperty(
options={'SKIP_SAVE'},
default=True,
update=update
@@ -1032,13 +1032,13 @@ class archipack_door(ArchipackObject, Manipulable, PropertyGroup):
if archipack_door_panel.filter(child):
self.remove_handle(context, child)
to_remove -= 1
- context.scene.objects.unlink(child)
+ self.unlink_object_from_scene(child)
bpy.data.objects.remove(child, do_unlink=True)
def remove_handle(self, context, o):
handle = self.find_handle(o)
if handle is not None:
- context.scene.objects.unlink(handle)
+ self.unlink_object_from_scene(handle)
bpy.data.objects.remove(handle, do_unlink=True)
def create_childs(self, context, o):
@@ -1112,7 +1112,7 @@ class archipack_door(ArchipackObject, Manipulable, PropertyGroup):
id = c_names.index(c.data.name)
except:
self.remove_handle(context, c)
- context.scene.objects.unlink(c)
+ self.unlink_object_from_scene(c)
bpy.data.objects.remove(c, do_unlink=True)
# children ordering may not be the same, so get the right l_childs order
@@ -1130,7 +1130,7 @@ class archipack_door(ArchipackObject, Manipulable, PropertyGroup):
for i, child in enumerate(childs):
if order[i] < 0:
p = bpy.data.objects.new("DoorPanel", child.data)
- context.scene.objects.link(p)
+ self.link_object_to_scene(context, p)
p.lock_location[0] = True
p.lock_location[1] = True
p.lock_location[2] = True
@@ -1158,7 +1158,7 @@ class archipack_door(ArchipackObject, Manipulable, PropertyGroup):
# MaterialUtils.add_handle_materials(h)
h.location = handle.location.copy()
elif h is not None:
- context.scene.objects.unlink(h)
+ self.unlink_object_from_scene(h)
bpy.data.objects.remove(h, do_unlink=True)
def _synch_hole(self, context, linked, hole):
@@ -1166,7 +1166,7 @@ class archipack_door(ArchipackObject, Manipulable, PropertyGroup):
if l_hole is None:
l_hole = bpy.data.objects.new("hole", hole.data)
l_hole['archipack_hole'] = True
- context.scene.objects.link(l_hole)
+ self.link_object_to_scene(context, l_hole)
l_hole.parent = linked
l_hole.matrix_world = linked.matrix_world.copy()
l_hole.location = hole.location.copy()
@@ -1178,8 +1178,8 @@ class archipack_door(ArchipackObject, Manipulable, PropertyGroup):
synch childs nodes of linked objects
"""
bpy.ops.object.select_all(action='DESELECT')
- o.select = True
- context.scene.objects.active = o
+ o.select_set(state=True)
+ context.view_layer.objects.active = o
childs = self.get_childs_panels(context, o)
hole = self.find_hole(o)
bpy.ops.object.select_linked(type='OBDATA')
@@ -1253,8 +1253,8 @@ class archipack_door(ArchipackObject, Manipulable, PropertyGroup):
child.matrix_world = o.matrix_world.copy()
else:
child = childs[child_n - 1]
- child.select = True
- context.scene.objects.active = child
+ child.select_set(state=True)
+ context.view_layer.objects.active = child
props = archipack_door_panel.datablock(child)
if props is not None:
props.x = x
@@ -1314,7 +1314,7 @@ class archipack_door(ArchipackObject, Manipulable, PropertyGroup):
if hole_obj is None:
m = bpy.data.meshes.new("hole")
hole_obj = bpy.data.objects.new("hole", m)
- context.scene.objects.link(hole_obj)
+ self.link_object_to_scene(context, hole_obj)
hole_obj['archipack_hole'] = True
hole_obj.parent = o
hole_obj.matrix_world = o.matrix_world.copy()
@@ -1339,19 +1339,19 @@ class archipack_door(ArchipackObject, Manipulable, PropertyGroup):
m = bpy.data.meshes.new("hole")
o = bpy.data.objects.new("hole", m)
o['archipack_robusthole'] = True
- context.scene.objects.link(o)
+ self.link_object_to_scene(context, o)
v = Vector((0, 0, 0))
offset = Vector((0, -0.001, 0))
size = Vector((self.x + 2 * self.frame_x, self.z + self.frame_x + 0.001, self.y))
verts = hole.vertices(16, offset, v, v, size, v, 0, 0, shape_z=None, path_type='RECTANGLE')
- verts = [tM * Vector(v) for v in verts]
+ verts = [tM @ Vector(v) for v in verts]
faces = hole.faces(16, path_type='RECTANGLE')
matids = hole.mat(16, 0, 1, path_type='RECTANGLE')
uvs = hole.uv(16, v, v, size, v, 0, 0, 0, 0, path_type='RECTANGLE')
bmed.buildmesh(context, o, verts, faces, matids=matids, uvs=uvs)
- o.select = True
- context.scene.objects.active = o
+ o.select_set(state=True)
+ context.view_layer.objects.active = o
return o
@@ -1360,7 +1360,7 @@ class ARCHIPACK_PT_door(Panel):
bl_label = "Door"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
- bl_category = 'ArchiPack'
+ bl_category = 'Archipack'
@classmethod
def poll(cls, context):
@@ -1371,7 +1371,7 @@ class ARCHIPACK_PT_door(Panel):
if not archipack_door.filter(o):
return
layout = self.layout
- layout.operator('archipack.door_manipulate', icon='HAND')
+ layout.operator('archipack.door_manipulate', icon='VIEW_PAN')
props = archipack_door.datablock(o)
row = layout.row(align=True)
row.operator('archipack.door', text="Refresh", icon='FILE_REFRESH').mode = 'REFRESH'
@@ -1382,8 +1382,8 @@ class ARCHIPACK_PT_door(Panel):
# box.label(text="Styles")
row = box.row(align=True)
row.operator("archipack.door_preset_menu", text=bpy.types.ARCHIPACK_OT_door_preset_menu.bl_label)
- row.operator("archipack.door_preset", text="", icon='ZOOMIN')
- row.operator("archipack.door_preset", text="", icon='ZOOMOUT').remove_active = True
+ row.operator("archipack.door_preset", text="", icon='ADD')
+ row.operator("archipack.door_preset", text="", icon='REMOVE').remove_active = True
row = layout.row()
box = row.box()
box.label(text="Size")
@@ -1434,82 +1434,82 @@ class ARCHIPACK_OT_door(ArchipackCreateTool, Operator):
bl_description = "Door"
bl_category = 'Archipack'
bl_options = {'REGISTER', 'UNDO'}
- x = FloatProperty(
+ x : FloatProperty(
name='width',
min=0.1,
default=0.80, precision=2,
unit='LENGTH', subtype='DISTANCE',
description='Width'
)
- y = FloatProperty(
+ y : FloatProperty(
name='depth',
min=0.1,
default=0.20, precision=2,
unit='LENGTH', subtype='DISTANCE',
description='Depth'
)
- z = FloatProperty(
+ z : FloatProperty(
name='height',
min=0.1,
default=2.0, precision=2,
unit='LENGTH', subtype='DISTANCE',
description='height'
)
- direction = IntProperty(
+ direction : IntProperty(
name="direction",
min=0,
max=1,
description="open direction"
)
- n_panels = IntProperty(
+ n_panels : IntProperty(
name="panels",
min=1,
max=2,
default=1,
description="number of panels"
)
- chanfer = FloatProperty(
+ chanfer : FloatProperty(
name='chanfer',
min=0.001,
default=0.005, precision=3,
unit='LENGTH', subtype='DISTANCE',
description='chanfer'
)
- panel_spacing = FloatProperty(
+ panel_spacing : FloatProperty(
name='spacing',
min=0.001,
default=0.1, precision=2,
unit='LENGTH', subtype='DISTANCE',
description='distance between panels'
)
- panel_bottom = FloatProperty(
+ panel_bottom : FloatProperty(
name='bottom',
default=0.0, precision=2,
unit='LENGTH', subtype='DISTANCE',
description='distance from bottom'
)
- panel_border = FloatProperty(
+ panel_border : FloatProperty(
name='border',
min=0.001,
default=0.2, precision=2,
unit='LENGTH', subtype='DISTANCE',
description='distance from border'
)
- panels_x = IntProperty(
+ panels_x : IntProperty(
name="panels h",
min=1,
max=50,
default=1,
description="panels h"
)
- panels_y = IntProperty(
+ panels_y : IntProperty(
name="panels v",
min=1,
max=50,
default=1,
description="panels v"
)
- panels_distrib = EnumProperty(
+ panels_distrib : EnumProperty(
name='distribution',
items=(
('REGULAR', 'Regular', '', 0),
@@ -1517,7 +1517,7 @@ class ARCHIPACK_OT_door(ArchipackCreateTool, Operator):
),
default='REGULAR'
)
- handle = EnumProperty(
+ handle : EnumProperty(
name='Shape',
items=(
('NONE', 'No handle', '', 0),
@@ -1525,7 +1525,7 @@ class ARCHIPACK_OT_door(ArchipackCreateTool, Operator):
),
default='BOTH'
)
- mode = EnumProperty(
+ mode : EnumProperty(
items=(
('CREATE', 'Create', '', 0),
('DELETE', 'Delete', '', 1),
@@ -1556,13 +1556,13 @@ class ARCHIPACK_OT_door(ArchipackCreateTool, Operator):
d.panels_x = self.panels_x
d.panels_y = self.panels_y
d.handle = self.handle
- context.scene.objects.link(o)
- o.select = True
- context.scene.objects.active = o
+ self.link_object_to_scene(context, o)
+ o.select_set(state=True)
+ context.view_layer.objects.active = o
self.add_material(o)
self.load_preset(d)
- o.select = True
- context.scene.objects.active = o
+ o.select_set(state=True)
+ context.view_layer.objects.active = o
return o
def delete(self, context):
@@ -1571,16 +1571,16 @@ class ARCHIPACK_OT_door(ArchipackCreateTool, Operator):
bpy.ops.archipack.disable_manipulate()
for child in o.children:
if 'archipack_hole' in child:
- context.scene.objects.unlink(child)
+ self.unlink_object_from_scene(child)
bpy.data.objects.remove(child, do_unlink=True)
elif child.data is not None and 'archipack_door_panel' in child.data:
for handle in child.children:
if 'archipack_handle' in handle:
- context.scene.objects.unlink(handle)
+ self.unlink_object_from_scene(handle)
bpy.data.objects.remove(handle, do_unlink=True)
- context.scene.objects.unlink(child)
+ self.unlink_object_from_scene(child)
bpy.data.objects.remove(child, do_unlink=True)
- context.scene.objects.unlink(o)
+ self.unlink_object_from_scene(o)
bpy.data.objects.remove(o, do_unlink=True)
def update(self, context):
@@ -1593,8 +1593,8 @@ class ARCHIPACK_OT_door(ArchipackCreateTool, Operator):
if linked != o:
archipack_door.datablock(linked).update(context)
bpy.ops.object.select_all(action="DESELECT")
- o.select = True
- context.scene.objects.active = o
+ o.select_set(state=True)
+ context.view_layer.objects.active = o
def unique(self, context):
act = context.active_object
@@ -1602,12 +1602,12 @@ class ARCHIPACK_OT_door(ArchipackCreateTool, Operator):
bpy.ops.object.select_all(action="DESELECT")
for o in sel:
if archipack_door.filter(o):
- o.select = True
+ o.select_set(state=True)
for child in o.children:
if 'archipack_hole' in child or (child.data is not None and
'archipack_door_panel' in child.data):
child.hide_select = False
- child.select = True
+ child.select_set(state=True)
if len(context.selected_objects) > 0:
bpy.ops.object.make_single_user(type='SELECTED_OBJECTS', object=True,
obdata=True, material=False, texture=False, animation=False)
@@ -1615,9 +1615,9 @@ class ARCHIPACK_OT_door(ArchipackCreateTool, Operator):
if 'archipack_hole' in child:
child.hide_select = True
bpy.ops.object.select_all(action="DESELECT")
- context.scene.objects.active = act
+ context.view_layer.objects.active = act
for o in sel:
- o.select = True
+ o.select_set(state=True)
def execute(self, context):
if context.mode == "OBJECT":
@@ -1625,8 +1625,8 @@ class ARCHIPACK_OT_door(ArchipackCreateTool, Operator):
bpy.ops.object.select_all(action="DESELECT")
o = self.create(context)
o.location = bpy.context.scene.cursor_location
- o.select = True
- context.scene.objects.active = o
+ o.select_set(state=True)
+ context.view_layer.objects.active = o
self.manipulate()
elif self.mode == 'DELETE':
self.delete(context)
@@ -1640,14 +1640,14 @@ class ARCHIPACK_OT_door(ArchipackCreateTool, Operator):
return {'CANCELLED'}
-class ARCHIPACK_OT_door_draw(ArchpackDrawTool, Operator):
+class ARCHIPACK_OT_door_draw(ArchipackDrawTool, Operator):
bl_idname = "archipack.door_draw"
bl_label = "Draw Doors"
bl_description = "Draw Doors over walls"
bl_category = 'Archipack'
bl_options = {'REGISTER', 'UNDO'}
- filepath = StringProperty(default="")
+ filepath : StringProperty(default="")
feedback = None
stack = []
object_name = ""
@@ -1659,7 +1659,7 @@ class ARCHIPACK_OT_door_draw(ArchpackDrawTool, Operator):
def draw(self, context):
layout = self.layout
row = layout.row()
- row.label("Use Properties panel (N) to define parms", icon='INFO')
+ row.label(text="Use Properties panel (N) to define parms", icon='INFO')
def draw_callback(self, _self, context):
self.feedback.draw(context)
@@ -1670,32 +1670,32 @@ class ARCHIPACK_OT_door_draw(ArchpackDrawTool, Operator):
if archipack_door.filter(o):
- o.select = True
- context.scene.objects.active = o
+ o.select_set(state=True)
+ context.view_layer.objects.active = o
if event.shift:
bpy.ops.archipack.door(mode="UNIQUE")
new_w = o.copy()
new_w.data = o.data
- context.scene.objects.link(new_w)
+ self.link_object_to_scene(context, new_w)
# instance subs
for child in o.children:
if "archipack_hole" not in child:
new_c = child.copy()
new_c.data = child.data
new_c.parent = new_w
- context.scene.objects.link(new_c)
+ self.link_object_to_scene(context, new_c)
# dup handle if any
for c in child.children:
new_h = c.copy()
new_h.data = c.data
new_h.parent = new_c
- context.scene.objects.link(new_h)
+ self.link_object_to_scene(context, new_h)
o = new_w
- o.select = True
- context.scene.objects.active = o
+ o.select_set(state=True)
+ context.view_layer.objects.active = o
else:
bpy.ops.archipack.door(auto_manipulate=False, filepath=self.filepath)
@@ -1704,13 +1704,13 @@ class ARCHIPACK_OT_door_draw(ArchpackDrawTool, Operator):
self.object_name = o.name
bpy.ops.archipack.generate_hole('INVOKE_DEFAULT')
- o.select = True
- context.scene.objects.active = o
+ o.select_set(state=True)
+ context.view_layer.objects.active = o
def modal(self, context, event):
context.area.tag_redraw()
- o = context.scene.objects.get(self.object_name)
+ o = context.scene.objects.get(self.object_name.strip())
if o is None:
return {'FINISHED'}
@@ -1722,14 +1722,14 @@ class ARCHIPACK_OT_door_draw(ArchpackDrawTool, Operator):
# hide hole from raycast
if hole is not None:
- o.hide = True
- hole.hide = True
+ o.hide_viewport = True
+ hole.hide_viewport = True
- res, tM, wall, y = self.mouse_hover_wall(context, event)
+ res, tM, wall, width, y, z_offset = self.mouse_hover_wall(context, event)
if hole is not None:
- o.hide = False
- hole.hide = False
+ o.hide_viewport = False
+ hole.hide_viewport = False
if res and d is not None:
o.matrix_world = tM
@@ -1749,14 +1749,14 @@ class ARCHIPACK_OT_door_draw(ArchpackDrawTool, Operator):
if event.type in {'LEFTMOUSE', 'RET', 'NUMPAD_ENTER', 'SPACE'}:
if wall is not None:
- context.scene.objects.active = wall
- wall.select = True
+ context.view_layer.objects.active = wall
+ wall.select_set(state=True)
if bpy.ops.archipack.single_boolean.poll():
bpy.ops.archipack.single_boolean()
- wall.select = False
+ wall.select_set(state=False)
# o must be a door here
if d is not None:
- context.scene.objects.active = o
+ context.view_layer.objects.active = o
self.stack.append(o)
self.add_object(context, event)
context.active_object.matrix_world = tM
@@ -1771,9 +1771,9 @@ class ARCHIPACK_OT_door_draw(ArchpackDrawTool, Operator):
):
if len(self.stack) > 0:
last = self.stack.pop()
- context.scene.objects.active = last
+ context.view_layer.objects.active = last
bpy.ops.archipack.door(mode="DELETE")
- context.scene.objects.active = o
+ context.view_layer.objects.active = o
return {'RUNNING_MODAL'}
if event.value == 'RELEASE':
@@ -1797,11 +1797,11 @@ class ARCHIPACK_OT_door_draw(ArchpackDrawTool, Operator):
# invoke with alt pressed will use current object as basis for linked copy
if self.filepath == '' and archipack_door.filter(context.active_object):
o = context.active_object
- context.scene.objects.active = None
+ context.view_layer.objects.active = None
bpy.ops.object.select_all(action="DESELECT")
if o is not None:
- o.select = True
- context.scene.objects.active = o
+ o.select_set(state=True)
+ context.view_layer.objects.active = o
self.add_object(context, event)
self.feedback = FeedbackPanel()
self.feedback.instructions(context, "Draw a door", "Click & Drag over a wall", [
diff --git a/archipack/archipack_fence.py b/archipack/archipack_fence.py
index c2989be2..5f987779 100644
--- a/archipack/archipack_fence.py
+++ b/archipack/archipack_fence.py
@@ -268,7 +268,7 @@ class FenceGenerator():
co.z *= self.user_defined_post_scale.z
if 'Slope' in g:
co.z += co.y * slope
- verts.append(tM * co)
+ verts.append(tM @ co)
matids += self.user_defined_mat
faces += [tuple([i + f for i in p.vertices]) for p in m.polygons]
uvs += self.user_defined_uvs
@@ -341,7 +341,7 @@ class FenceGenerator():
if s < n_sections:
v1 = subs[s + 1][0].v.normalized()
dir = (v0 + v1).normalized()
- scale = 1 / cos(0.5 * acos(min(1, max(-1, v0 * v1))))
+ scale = 1 / cos(0.5 * acos(min(1, max(-1, v0.dot(v1)))))
for p in profile:
x, y = n.p + scale * p.x * dir
z = zl + p.y + altitude
@@ -556,7 +556,7 @@ class FenceGenerator():
if s < n_sections:
v1 = sections[s + 1][0].v.normalized()
dir = (v0 + v1).normalized()
- scale = min(10, 1 / cos(0.5 * acos(min(1, max(-1, v0 * v1)))))
+ scale = min(10, 1 / cos(0.5 * acos(min(1, max(-1, v0.dot(v1) )))))
for p in profile:
# x, y = n.p + scale * (x_offset + p.x) * dir
x, y = n.p + scale * p.x * dir
@@ -673,7 +673,7 @@ materials_enum = (
class archipack_fence_material(PropertyGroup):
- index = EnumProperty(
+ index : EnumProperty(
items=materials_enum,
default='0',
update=update
@@ -684,7 +684,7 @@ class archipack_fence_material(PropertyGroup):
find witch selected object this instance belongs to
provide support for "copy to selected"
"""
- selected = [o for o in context.selected_objects]
+ selected = context.selected_objects[:]
for o in selected:
props = archipack_fence.datablock(o)
if props:
@@ -700,7 +700,7 @@ class archipack_fence_material(PropertyGroup):
class archipack_fence_part(PropertyGroup):
- type = EnumProperty(
+ type : EnumProperty(
items=(
('S_FENCE', 'Straight fence', '', 0),
('C_FENCE', 'Curved fence', '', 1),
@@ -708,21 +708,21 @@ class archipack_fence_part(PropertyGroup):
default='S_FENCE',
update=update_type
)
- length = FloatProperty(
+ length : FloatProperty(
name="Length",
min=0.01,
default=2.0,
unit='LENGTH', subtype='DISTANCE',
update=update
)
- radius = FloatProperty(
+ radius : FloatProperty(
name="Radius",
min=0.01,
default=0.7,
unit='LENGTH', subtype='DISTANCE',
update=update
)
- da = FloatProperty(
+ da : FloatProperty(
name="Angle",
min=-pi,
max=pi,
@@ -730,7 +730,7 @@ class archipack_fence_part(PropertyGroup):
subtype='ANGLE', unit='ROTATION',
update=update
)
- a0 = FloatProperty(
+ a0 : FloatProperty(
name="Start angle",
min=-2 * pi,
max=2 * pi,
@@ -738,20 +738,20 @@ class archipack_fence_part(PropertyGroup):
subtype='ANGLE', unit='ROTATION',
update=update
)
- dz = FloatProperty(
+ dz : FloatProperty(
name="delta z",
default=0,
unit='LENGTH', subtype='DISTANCE'
)
- manipulators = CollectionProperty(type=archipack_manipulator)
+ manipulators : CollectionProperty(type=archipack_manipulator)
def find_datablock_in_selection(self, context):
"""
find witch selected object this instance belongs to
provide support for "copy to selected"
"""
- selected = [o for o in context.selected_objects]
+ selected = context.selected_objects[:]
for o in selected:
props = archipack_fence.datablock(o)
if props is not None:
@@ -783,43 +783,43 @@ class archipack_fence_part(PropertyGroup):
class archipack_fence(ArchipackObject, Manipulable, PropertyGroup):
- parts = CollectionProperty(type=archipack_fence_part)
- user_defined_path = StringProperty(
+ parts : CollectionProperty(type=archipack_fence_part)
+ user_defined_path : StringProperty(
name="User defined",
update=update_path
)
- user_defined_spline = IntProperty(
+ user_defined_spline : IntProperty(
name="Spline index",
min=0,
default=0,
update=update_path
)
- user_defined_resolution = IntProperty(
+ user_defined_resolution : IntProperty(
name="Resolution",
min=1,
max=128,
default=12, update=update_path
)
- n_parts = IntProperty(
+ n_parts : IntProperty(
name="Parts",
min=1,
default=1, update=update_manipulators
)
- x_offset = FloatProperty(
+ x_offset : FloatProperty(
name="Offset",
default=0.0, precision=2, step=1,
unit='LENGTH', subtype='DISTANCE',
update=update
)
- radius = FloatProperty(
+ radius : FloatProperty(
name="Radius",
min=0.01,
default=0.7,
unit='LENGTH', subtype='DISTANCE',
update=update
)
- da = FloatProperty(
+ da : FloatProperty(
name="Angle",
min=-pi,
max=pi,
@@ -827,7 +827,7 @@ class archipack_fence(ArchipackObject, Manipulable, PropertyGroup):
subtype='ANGLE', unit='ROTATION',
update=update
)
- angle_limit = FloatProperty(
+ angle_limit : FloatProperty(
name="Angle",
min=0,
max=2 * pi,
@@ -835,7 +835,7 @@ class archipack_fence(ArchipackObject, Manipulable, PropertyGroup):
subtype='ANGLE', unit='ROTATION',
update=update_manipulators
)
- shape = EnumProperty(
+ shape : EnumProperty(
items=(
('RECTANGLE', 'Straight', '', 0),
('CIRCLE', 'Curved ', '', 1)
@@ -843,106 +843,106 @@ class archipack_fence(ArchipackObject, Manipulable, PropertyGroup):
default='RECTANGLE',
update=update
)
- post = BoolProperty(
+ post : BoolProperty(
name='Enable',
default=True,
update=update
)
- post_spacing = FloatProperty(
+ post_spacing : FloatProperty(
name="Spacing",
min=0.1,
default=1.0, precision=2, step=1,
unit='LENGTH', subtype='DISTANCE',
update=update
)
- post_x = FloatProperty(
+ post_x : FloatProperty(
name="Width",
min=0.001,
default=0.04, precision=2, step=1,
unit='LENGTH', subtype='DISTANCE',
update=update
)
- post_y = FloatProperty(
+ post_y : FloatProperty(
name="Length",
min=0.001, max=1000,
default=0.04, precision=2, step=1,
unit='LENGTH', subtype='DISTANCE',
update=update
)
- post_z = FloatProperty(
+ post_z : FloatProperty(
name="Height",
min=0.001,
default=1, precision=2, step=1,
unit='LENGTH', subtype='DISTANCE',
update=update
)
- post_alt = FloatProperty(
+ post_alt : FloatProperty(
name="Altitude",
default=0, precision=2, step=1,
unit='LENGTH', subtype='DISTANCE',
update=update
)
- user_defined_post_enable = BoolProperty(
+ user_defined_post_enable : BoolProperty(
name="User",
update=update,
default=True
)
- user_defined_post = StringProperty(
+ user_defined_post : StringProperty(
name="User defined",
update=update
)
- idmat_post = EnumProperty(
+ idmat_post : EnumProperty(
name="Post",
items=materials_enum,
default='1',
update=update
)
- subs = BoolProperty(
+ subs : BoolProperty(
name='Enable',
default=False,
update=update
)
- subs_spacing = FloatProperty(
+ subs_spacing : FloatProperty(
name="Spacing",
min=0.05,
default=0.10, precision=2, step=1,
unit='LENGTH', subtype='DISTANCE',
update=update
)
- subs_x = FloatProperty(
+ subs_x : FloatProperty(
name="Width",
min=0.001,
default=0.02, precision=2, step=1,
unit='LENGTH', subtype='DISTANCE',
update=update
)
- subs_y = FloatProperty(
+ subs_y : FloatProperty(
name="Length",
min=0.001,
default=0.02, precision=2, step=1,
unit='LENGTH', subtype='DISTANCE',
update=update
)
- subs_z = FloatProperty(
+ subs_z : FloatProperty(
name="Height",
min=0.001,
default=1, precision=2, step=1,
unit='LENGTH', subtype='DISTANCE',
update=update
)
- subs_alt = FloatProperty(
+ subs_alt : FloatProperty(
name="Altitude",
default=0, precision=2, step=1,
unit='LENGTH', subtype='DISTANCE',
update=update
)
- subs_offset_x = FloatProperty(
+ subs_offset_x : FloatProperty(
name="Offset",
default=0.0, precision=2, step=1,
unit='LENGTH', subtype='DISTANCE',
update=update
)
- subs_bottom = EnumProperty(
+ subs_bottom : EnumProperty(
name="Bottom",
items=(
('STEP', 'Follow step', '', 0),
@@ -951,78 +951,78 @@ class archipack_fence(ArchipackObject, Manipulable, PropertyGroup):
default='STEP',
update=update
)
- user_defined_subs_enable = BoolProperty(
+ user_defined_subs_enable : BoolProperty(
name="User",
update=update,
default=True
)
- user_defined_subs = StringProperty(
+ user_defined_subs : StringProperty(
name="User defined",
update=update
)
- idmat_subs = EnumProperty(
+ idmat_subs : EnumProperty(
name="Subs",
items=materials_enum,
default='1',
update=update
)
- panel = BoolProperty(
+ panel : BoolProperty(
name='Enable',
default=True,
update=update
)
- panel_alt = FloatProperty(
+ panel_alt : FloatProperty(
name="Altitude",
default=0.25, precision=2, step=1,
unit='LENGTH', subtype='DISTANCE',
update=update
)
- panel_x = FloatProperty(
+ panel_x : FloatProperty(
name="Width",
min=0.001,
default=0.01, precision=2, step=1,
unit='LENGTH', subtype='DISTANCE',
update=update
)
- panel_z = FloatProperty(
+ panel_z : FloatProperty(
name="Height",
min=0.001,
default=0.6, precision=2, step=1,
unit='LENGTH', subtype='DISTANCE',
update=update
)
- panel_dist = FloatProperty(
+ panel_dist : FloatProperty(
name="Spacing",
min=0.001,
default=0.05, precision=2, step=1,
unit='LENGTH', subtype='DISTANCE',
update=update
)
- panel_offset_x = FloatProperty(
+ panel_offset_x : FloatProperty(
name="Offset",
default=0.0, precision=2, step=1,
unit='LENGTH', subtype='DISTANCE',
update=update
)
- idmat_panel = EnumProperty(
+ idmat_panel : EnumProperty(
name="Panels",
items=materials_enum,
default='2',
update=update
)
- rail = BoolProperty(
+ rail : BoolProperty(
name="Enable",
update=update,
default=False
)
- rail_n = IntProperty(
+ rail_n : IntProperty(
name="#",
default=1,
min=0,
max=31,
update=update
)
- rail_x = FloatVectorProperty(
+ rail_x : FloatVectorProperty(
name="Width",
default=[
0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05,
@@ -1036,7 +1036,7 @@ class archipack_fence(ArchipackObject, Manipulable, PropertyGroup):
unit='LENGTH',
update=update
)
- rail_z = FloatVectorProperty(
+ rail_z : FloatVectorProperty(
name="Height",
default=[
0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05,
@@ -1050,7 +1050,7 @@ class archipack_fence(ArchipackObject, Manipulable, PropertyGroup):
unit='LENGTH',
update=update
)
- rail_offset = FloatVectorProperty(
+ rail_offset : FloatVectorProperty(
name="Offset",
default=[
0, 0, 0, 0, 0, 0, 0, 0,
@@ -1063,7 +1063,7 @@ class archipack_fence(ArchipackObject, Manipulable, PropertyGroup):
unit='LENGTH',
update=update
)
- rail_alt = FloatVectorProperty(
+ rail_alt : FloatVectorProperty(
name="Altitude",
default=[
1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,
@@ -1076,43 +1076,43 @@ class archipack_fence(ArchipackObject, Manipulable, PropertyGroup):
unit='LENGTH',
update=update
)
- rail_mat = CollectionProperty(type=archipack_fence_material)
+ rail_mat : CollectionProperty(type=archipack_fence_material)
- handrail = BoolProperty(
+ handrail : BoolProperty(
name="Enable",
update=update,
default=True
)
- handrail_offset = FloatProperty(
+ handrail_offset : FloatProperty(
name="Offset",
default=0.0, precision=2, step=1,
unit='LENGTH', subtype='DISTANCE',
update=update
)
- handrail_alt = FloatProperty(
+ handrail_alt : FloatProperty(
name="Altitude",
default=1.0, precision=2, step=1,
unit='LENGTH', subtype='DISTANCE',
update=update
)
- handrail_extend = FloatProperty(
+ handrail_extend : FloatProperty(
name="Extend",
min=0,
default=0.1, precision=2, step=1,
unit='LENGTH', subtype='DISTANCE',
update=update
)
- handrail_slice = BoolProperty(
+ handrail_slice : BoolProperty(
name='Slice',
default=True,
update=update
)
- handrail_slice_right = BoolProperty(
+ handrail_slice_right : BoolProperty(
name='Slice',
default=True,
update=update
)
- handrail_profil = EnumProperty(
+ handrail_profil : EnumProperty(
name="Profil",
items=(
('SQUARE', 'Square', '', 0),
@@ -1122,28 +1122,28 @@ class archipack_fence(ArchipackObject, Manipulable, PropertyGroup):
default='SQUARE',
update=update
)
- handrail_x = FloatProperty(
+ handrail_x : FloatProperty(
name="Width",
min=0.001,
default=0.04, precision=2, step=1,
unit='LENGTH', subtype='DISTANCE',
update=update
)
- handrail_y = FloatProperty(
+ handrail_y : FloatProperty(
name="Height",
min=0.001,
default=0.04, precision=2, step=1,
unit='LENGTH', subtype='DISTANCE',
update=update
)
- handrail_radius = FloatProperty(
+ handrail_radius : FloatProperty(
name="Radius",
min=0.001,
default=0.02, precision=2, step=1,
unit='LENGTH', subtype='DISTANCE',
update=update
)
- idmat_handrail = EnumProperty(
+ idmat_handrail : EnumProperty(
name="Handrail",
items=materials_enum,
default='0',
@@ -1151,25 +1151,25 @@ class archipack_fence(ArchipackObject, Manipulable, PropertyGroup):
)
# UI layout related
- parts_expand = BoolProperty(
+ parts_expand : BoolProperty(
default=False
)
- rail_expand = BoolProperty(
+ rail_expand : BoolProperty(
default=False
)
- idmats_expand = BoolProperty(
+ idmats_expand : BoolProperty(
default=False
)
- handrail_expand = BoolProperty(
+ handrail_expand : BoolProperty(
default=False
)
- post_expand = BoolProperty(
+ post_expand : BoolProperty(
default=False
)
- panel_expand = BoolProperty(
+ panel_expand : BoolProperty(
default=False
)
- subs_expand = BoolProperty(
+ subs_expand : BoolProperty(
default=False
)
@@ -1178,7 +1178,7 @@ class archipack_fence(ArchipackObject, Manipulable, PropertyGroup):
# .auto_update = False
# bulk changes
# .auto_update = True
- auto_update = BoolProperty(
+ auto_update : BoolProperty(
options={'SKIP_SAVE'},
default=True,
update=update_manipulators
@@ -1226,7 +1226,6 @@ class archipack_fence(ArchipackObject, Manipulable, PropertyGroup):
# add parts
for i in range(len(self.parts), self.n_parts):
self.parts.add()
-
self.setup_manipulators()
def interpolate_bezier(self, pts, wM, p0, p1, resolution):
@@ -1234,18 +1233,18 @@ class archipack_fence(ArchipackObject, Manipulable, PropertyGroup):
# since this can lower points count by a resolution factor
# use normalized to handle non linear t
if resolution == 0:
- pts.append(wM * p0.co.to_3d())
+ pts.append(wM @ p0.co.to_3d())
else:
v = (p1.co - p0.co).normalized()
d1 = (p0.handle_right - p0.co).normalized()
d2 = (p1.co - p1.handle_left).normalized()
if d1 == v and d2 == v:
- pts.append(wM * p0.co.to_3d())
+ pts.append(wM @ p0.co.to_3d())
else:
- seg = interpolate_bezier(wM * p0.co,
- wM * p0.handle_right,
- wM * p1.handle_left,
- wM * p1.co,
+ seg = interpolate_bezier(wM @ p0.co,
+ wM @ p0.handle_right,
+ wM @ p1.handle_left,
+ wM @ p1.co,
resolution + 1)
for i in range(resolution):
pts.append(seg[i].to_3d())
@@ -1264,7 +1263,7 @@ class archipack_fence(ArchipackObject, Manipulable, PropertyGroup):
pts = []
if spline.type == 'POLY':
pt = spline.points[0].co
- pts = [wM * p.co.to_3d() for p in spline.points]
+ pts = [wM @ p.co.to_3d() for p in spline.points]
if spline.use_cyclic_u:
pts.append(pts[0])
elif spline.type == 'BEZIER':
@@ -1280,7 +1279,7 @@ class archipack_fence(ArchipackObject, Manipulable, PropertyGroup):
self.interpolate_bezier(pts, wM, p0, p1, resolution)
pts.append(pts[0])
else:
- pts.append(wM * points[-1].co)
+ pts.append(wM @ points[-1].co)
auto_update = self.auto_update
self.auto_update = False
@@ -1305,15 +1304,10 @@ class archipack_fence(ArchipackObject, Manipulable, PropertyGroup):
self.auto_update = auto_update
- o.matrix_world = tM * Matrix([
- [1, 0, 0, pt.x],
- [0, 1, 0, pt.y],
- [0, 0, 1, pt.z],
- [0, 0, 0, 1]
- ])
+ o.matrix_world = tM @ Matrix.Translation(pt)
def update_path(self, context):
- path = context.scene.objects.get(self.user_defined_path)
+ path = context.scene.objects.get(self.user_defined_path.strip())
if path is not None and path.type == 'CURVE':
splines = path.data.splines
if len(splines) > self.user_defined_spline:
@@ -1335,7 +1329,6 @@ class archipack_fence(ArchipackObject, Manipulable, PropertyGroup):
return g
def update(self, context, manipulable_refresh=False):
-
o = self.find_in_selection(context, self.auto_update)
if o is None:
@@ -1359,7 +1352,7 @@ class archipack_fence(ArchipackObject, Manipulable, PropertyGroup):
if self.user_defined_post_enable:
# user defined posts
- user_def_post = context.scene.objects.get(self.user_defined_post)
+ user_def_post = context.scene.objects.get(self.user_defined_post.strip())
if user_def_post is not None and user_def_post.type == 'MESH':
g.setup_user_defined_post(user_def_post, self.post_x, self.post_y, self.post_z)
@@ -1373,7 +1366,7 @@ class archipack_fence(ArchipackObject, Manipulable, PropertyGroup):
# user defined subs
if self.user_defined_subs_enable:
- user_def_subs = context.scene.objects.get(self.user_defined_subs)
+ user_def_subs = context.scene.objects.get(self.user_defined_subs.strip())
if user_def_subs is not None and user_def_subs.type == 'MESH':
g.setup_user_defined_post(user_def_subs, self.subs_x, self.subs_y, self.subs_z)
@@ -1465,7 +1458,7 @@ class ARCHIPACK_PT_fence(Panel):
bl_label = "Fence"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
- bl_category = 'ArchiPack'
+ bl_category = 'Archipack'
@classmethod
def poll(cls, context):
@@ -1478,13 +1471,13 @@ class ARCHIPACK_PT_fence(Panel):
scene = context.scene
layout = self.layout
row = layout.row(align=True)
- row.operator('archipack.fence_manipulate', icon='HAND')
+ row.operator('archipack.fence_manipulate', icon='VIEW_PAN')
box = layout.box()
# box.label(text="Styles")
row = box.row(align=True)
row.operator("archipack.fence_preset_menu", text=bpy.types.ARCHIPACK_OT_fence_preset_menu.bl_label)
- row.operator("archipack.fence_preset", text="", icon='ZOOMIN')
- row.operator("archipack.fence_preset", text="", icon='ZOOMOUT').remove_active = True
+ row.operator("archipack.fence_preset", text="", icon='ADD')
+ row.operator("archipack.fence_preset", text="", icon='REMOVE').remove_active = True
box = layout.box()
row = box.row(align=True)
row.operator("archipack.fence_curve_update", text="", icon='FILE_REFRESH')
@@ -1619,15 +1612,18 @@ class ARCHIPACK_OT_fence(ArchipackCreateTool, Operator):
bl_options = {'REGISTER', 'UNDO'}
def create(self, context):
+
m = bpy.data.meshes.new("Fence")
o = bpy.data.objects.new("Fence", m)
d = m.archipack_fence.add()
# make manipulators selectable
+
d.manipulable_selectable = True
- context.scene.objects.link(o)
- o.select = True
- context.scene.objects.active = o
+ self.link_object_to_scene(context, o)
+ o.select_set(state=True)
+ context.view_layer.objects.active = o
self.load_preset(d)
+
self.add_material(o)
return o
@@ -1635,9 +1631,9 @@ class ARCHIPACK_OT_fence(ArchipackCreateTool, Operator):
if context.mode == "OBJECT":
bpy.ops.object.select_all(action="DESELECT")
o = self.create(context)
- o.location = bpy.context.scene.cursor_location
- o.select = True
- context.scene.objects.active = o
+ o.location = context.scene.cursor_location
+ o.select_set(state=True)
+ context.view_layer.objects.active = o
self.manipulate()
return {'FINISHED'}
else:
@@ -1663,7 +1659,7 @@ class ARCHIPACK_OT_fence_curve_update(Operator):
def draw(self, context):
layout = self.layout
row = layout.row()
- row.label("Use Properties panel (N) to define parms", icon='INFO')
+ row.label(text="Use Properties panel (N) to define parms", icon='INFO')
def execute(self, context):
if context.mode == "OBJECT":
@@ -1689,7 +1685,7 @@ class ARCHIPACK_OT_fence_from_curve(ArchipackCreateTool, Operator):
def draw(self, context):
layout = self.layout
row = layout.row()
- row.label("Use Properties panel (N) to define parms", icon='INFO')
+ row.label(text="Use Properties panel (N) to define parms", icon='INFO')
def create(self, context):
o = None
@@ -1709,8 +1705,8 @@ class ARCHIPACK_OT_fence_from_curve(ArchipackCreateTool, Operator):
bpy.ops.object.select_all(action="DESELECT")
o = self.create(context)
if o is not None:
- o.select = True
- context.scene.objects.active = o
+ o.select_set(state=True)
+ context.view_layer.objects.active = o
# self.manipulate()
return {'FINISHED'}
else:
diff --git a/archipack/archipack_floor.py b/archipack/archipack_floor.py
index ed52fd8f..82c1f000 100644
--- a/archipack/archipack_floor.py
+++ b/archipack/archipack_floor.py
@@ -225,7 +225,7 @@ class FloorGenerator(CutAblePolygon, CutAbleGenerator):
# rotate seg
seg.rotate(a)
# rotate delta from rotation center to segment start
- dp = rM * (seg.p0 - p0)
+ dp = rM @ (seg.p0 - p0)
seg.translate(dp)
def translate(self, idx_from, dp):
@@ -305,7 +305,7 @@ class FloorGenerator(CutAblePolygon, CutAbleGenerator):
use_dissolve_boundaries=False,
verts=bm.verts,
edges=bm.edges,
- delimit=1)
+ delimit={'MATERIAL'})
bm.verts.ensure_lookup_table()
@@ -333,7 +333,7 @@ class FloorGenerator(CutAblePolygon, CutAbleGenerator):
bmesh.ops.bevel(bm,
geom=geom,
offset=d.bevel_amount,
- offset_type=0,
+ offset_type='OFFSET',
segments=1, # d.bevel_res
profile=0.5,
vertex_only=False,
@@ -366,7 +366,7 @@ class FloorGenerator(CutAblePolygon, CutAbleGenerator):
use_dissolve_boundaries=False,
verts=bm.verts,
edges=bm.edges,
- delimit=1)
+ delimit={'MATERIAL'})
bm.verts.ensure_lookup_table()
@@ -925,7 +925,7 @@ class archipack_floor_part(PropertyGroup):
A single manipulable polyline like segment
polyline like segment line or arc based
"""
- type = EnumProperty(
+ type : EnumProperty(
items=(
('S_SEG', 'Straight', '', 0),
('C_SEG', 'Curved', '', 1),
@@ -933,19 +933,19 @@ class archipack_floor_part(PropertyGroup):
default='S_SEG',
update=update_type
)
- length = FloatProperty(
+ length : FloatProperty(
name="Length",
min=0.01,
default=2.0,
update=update
)
- radius = FloatProperty(
+ radius : FloatProperty(
name="Radius",
min=0.5,
default=0.7,
update=update
)
- da = FloatProperty(
+ da : FloatProperty(
name="Angle",
min=-pi,
max=pi,
@@ -953,7 +953,7 @@ class archipack_floor_part(PropertyGroup):
subtype='ANGLE', unit='ROTATION',
update=update
)
- a0 = FloatProperty(
+ a0 : FloatProperty(
name="Start angle",
min=-2 * pi,
max=2 * pi,
@@ -961,21 +961,21 @@ class archipack_floor_part(PropertyGroup):
subtype='ANGLE', unit='ROTATION',
update=update
)
- offset = FloatProperty(
+ offset : FloatProperty(
name="Offset",
description="Side offset of segment",
default=0,
unit='LENGTH', subtype='DISTANCE',
update=update
)
- manipulators = CollectionProperty(type=archipack_manipulator)
+ manipulators : CollectionProperty(type=archipack_manipulator)
def find_in_selection(self, context):
"""
find witch selected object this instance belongs to
provide support for "copy to selected"
"""
- selected = [o for o in context.selected_objects]
+ selected = context.selected_objects[:]
for o in selected:
props = archipack_floor.datablock(o)
if props:
@@ -1002,35 +1002,35 @@ class archipack_floor_part(PropertyGroup):
class archipack_floor(ArchipackObject, Manipulable, PropertyGroup):
- n_parts = IntProperty(
+ n_parts : IntProperty(
name="Parts",
min=1,
default=1, update=update_manipulators
)
- parts = CollectionProperty(type=archipack_floor_part)
- user_defined_path = StringProperty(
+ parts : CollectionProperty(type=archipack_floor_part)
+ user_defined_path : StringProperty(
name="User defined",
update=update_path
)
- user_defined_resolution = IntProperty(
+ user_defined_resolution : IntProperty(
name="Resolution",
min=1,
max=128,
default=12, update=update_path
)
- closed = BoolProperty(
+ closed : BoolProperty(
default=True,
name="Close",
options={'SKIP_SAVE'},
update=update_manipulators
)
# UI layout related
- parts_expand = BoolProperty(
+ parts_expand : BoolProperty(
options={'SKIP_SAVE'},
default=False
)
- pattern = EnumProperty(
+ pattern : EnumProperty(
name='Floor Pattern',
items=(("boards", "Boards", ""),
("square_parquet", "Square Parquet", ""),
@@ -1044,7 +1044,7 @@ class archipack_floor(ArchipackObject, Manipulable, PropertyGroup):
default="boards",
update=update
)
- spacing = FloatProperty(
+ spacing : FloatProperty(
name='Spacing',
description='The amount of space between boards or tiles in both directions',
unit='LENGTH', subtype='DISTANCE',
@@ -1053,7 +1053,7 @@ class archipack_floor(ArchipackObject, Manipulable, PropertyGroup):
precision=2,
update=update
)
- thickness = FloatProperty(
+ thickness : FloatProperty(
name='Thickness',
description='Thickness',
unit='LENGTH', subtype='DISTANCE',
@@ -1062,13 +1062,13 @@ class archipack_floor(ArchipackObject, Manipulable, PropertyGroup):
precision=2,
update=update
)
- vary_thickness = BoolProperty(
+ vary_thickness : BoolProperty(
name='Random Thickness',
description='Vary thickness',
default=False,
update=update
)
- thickness_variance = FloatProperty(
+ thickness_variance : FloatProperty(
name='Variance',
description='How much vary by',
min=0, max=100,
@@ -1078,7 +1078,7 @@ class archipack_floor(ArchipackObject, Manipulable, PropertyGroup):
update=update
)
- board_width = FloatProperty(
+ board_width : FloatProperty(
name='Width',
description='The width',
unit='LENGTH', subtype='DISTANCE',
@@ -1087,13 +1087,13 @@ class archipack_floor(ArchipackObject, Manipulable, PropertyGroup):
precision=2,
update=update
)
- vary_width = BoolProperty(
+ vary_width : BoolProperty(
name='Random Width',
description='Vary width',
default=False,
update=update
)
- width_variance = FloatProperty(
+ width_variance : FloatProperty(
name='Variance',
description='How much vary by',
subtype='PERCENTAGE',
@@ -1101,7 +1101,7 @@ class archipack_floor(ArchipackObject, Manipulable, PropertyGroup):
precision=2,
update=update
)
- width_spacing = FloatProperty(
+ width_spacing : FloatProperty(
name='Width Spacing',
description='The amount of space between boards in the width direction',
unit='LENGTH', subtype='DISTANCE',
@@ -1111,7 +1111,7 @@ class archipack_floor(ArchipackObject, Manipulable, PropertyGroup):
update=update
)
- board_length = FloatProperty(
+ board_length : FloatProperty(
name='Length',
description='The length of the boards',
unit='LENGTH', subtype='DISTANCE',
@@ -1120,7 +1120,7 @@ class archipack_floor(ArchipackObject, Manipulable, PropertyGroup):
default=2,
update=update
)
- short_board_length = FloatProperty(
+ short_board_length : FloatProperty(
name='Length',
description='The length of the boards',
unit='LENGTH', subtype='DISTANCE',
@@ -1129,27 +1129,27 @@ class archipack_floor(ArchipackObject, Manipulable, PropertyGroup):
default=2,
update=update
)
- vary_length = BoolProperty(
+ vary_length : BoolProperty(
name='Random Length',
description='Vary board length',
default=False,
update=update
)
- length_variance = FloatProperty(
+ length_variance : FloatProperty(
name='Variance',
description='How much board length can vary by',
subtype='PERCENTAGE',
min=1, max=100, default=50,
precision=2, update=update
)
- max_boards = IntProperty(
+ max_boards : IntProperty(
name='Max Boards',
description='Max number of boards in one row',
min=1,
default=20,
update=update
)
- length_spacing = FloatProperty(
+ length_spacing : FloatProperty(
name='Length Spacing',
description='The amount of space between boards in the length direction',
unit='LENGTH', subtype='DISTANCE',
@@ -1160,7 +1160,7 @@ class archipack_floor(ArchipackObject, Manipulable, PropertyGroup):
)
# parquet specific
- boards_in_group = IntProperty(
+ boards_in_group : IntProperty(
name='Boards in Group',
description='Number of boards in a group',
min=1, default=4,
@@ -1168,7 +1168,7 @@ class archipack_floor(ArchipackObject, Manipulable, PropertyGroup):
)
# tile specific
- tile_width = FloatProperty(
+ tile_width : FloatProperty(
name='Width',
description='Width of the tiles',
unit='LENGTH', subtype='DISTANCE',
@@ -1177,7 +1177,7 @@ class archipack_floor(ArchipackObject, Manipulable, PropertyGroup):
precision=2,
update=update
)
- tile_length = FloatProperty(
+ tile_length : FloatProperty(
name='Length',
description='Length of the tiles',
unit='LENGTH', subtype='DISTANCE',
@@ -1188,13 +1188,13 @@ class archipack_floor(ArchipackObject, Manipulable, PropertyGroup):
)
# grout
- add_grout = BoolProperty(
+ add_grout : BoolProperty(
name='Add Grout',
description='Add grout',
default=False,
update=update
)
- mortar_depth = FloatProperty(
+ mortar_depth : FloatProperty(
name='Depth',
description='The depth of the mortar from the surface of the tile',
unit='LENGTH', subtype='DISTANCE',
@@ -1206,19 +1206,19 @@ class archipack_floor(ArchipackObject, Manipulable, PropertyGroup):
)
# regular tile
- random_offset = BoolProperty(
+ random_offset : BoolProperty(
name='Random Offset',
description='Random amount of offset for each row of tiles',
update=update, default=False
)
- offset = FloatProperty(
+ offset : FloatProperty(
name='Offset',
description='How much to offset each row of tiles',
min=0, max=100, default=0,
precision=2,
update=update
)
- offset_variance = FloatProperty(
+ offset_variance : FloatProperty(
name='Variance',
description='How much to vary the offset each row of tiles',
min=0.001, max=100, default=50,
@@ -1227,13 +1227,13 @@ class archipack_floor(ArchipackObject, Manipulable, PropertyGroup):
)
# bevel
- bevel = BoolProperty(
+ bevel : BoolProperty(
name='Bevel',
update=update,
default=False,
description='Bevel upper faces'
)
- bevel_amount = FloatProperty(
+ bevel_amount : FloatProperty(
name='Bevel',
description='Bevel amount',
unit='LENGTH', subtype='DISTANCE',
@@ -1241,29 +1241,29 @@ class archipack_floor(ArchipackObject, Manipulable, PropertyGroup):
precision=2, step=0.05,
update=update
)
- solidify = BoolProperty(
+ solidify : BoolProperty(
name="Solidify",
default=True,
update=update
)
- vary_materials = BoolProperty(
+ vary_materials : BoolProperty(
name="Random Material",
default=True,
description="Vary Material indexes",
update=update)
- matid = IntProperty(
+ matid : IntProperty(
name="#variations",
min=1,
max=10,
default=7,
description="Material index maxi",
update=update)
- auto_update = BoolProperty(
+ auto_update : BoolProperty(
options={'SKIP_SAVE'},
default=True,
update=update_manipulators
)
- z = FloatProperty(
+ z : FloatProperty(
name="dumb z",
description="Dumb z for manipulator placeholder",
default=0.01,
@@ -1324,18 +1324,18 @@ class archipack_floor(ArchipackObject, Manipulable, PropertyGroup):
# since this can lower points count by a resolution factor
# use normalized to handle non linear t
if resolution == 0:
- pts.append(wM * p0.co.to_3d())
+ pts.append(wM @ p0.co.to_3d())
else:
v = (p1.co - p0.co).normalized()
d1 = (p0.handle_right - p0.co).normalized()
d2 = (p1.co - p1.handle_left).normalized()
if d1 == v and d2 == v:
- pts.append(wM * p0.co.to_3d())
+ pts.append(wM @ p0.co.to_3d())
else:
- seg = interpolate_bezier(wM * p0.co,
- wM * p0.handle_right,
- wM * p1.handle_left,
- wM * p1.co,
+ seg = interpolate_bezier(wM @ p0.co,
+ wM @ p0.handle_right,
+ wM @ p1.handle_left,
+ wM @ p1.co,
resolution + 1)
for i in range(resolution):
pts.append(seg[i].to_3d())
@@ -1343,7 +1343,7 @@ class archipack_floor(ArchipackObject, Manipulable, PropertyGroup):
def from_spline(self, context, wM, resolution, spline):
pts = []
if spline.type == 'POLY':
- pts = [wM * p.co.to_3d() for p in spline.points]
+ pts = [wM @ p.co.to_3d() for p in spline.points]
if spline.use_cyclic_u:
pts.append(pts[0])
elif spline.type == 'BEZIER':
@@ -1358,18 +1358,13 @@ class archipack_floor(ArchipackObject, Manipulable, PropertyGroup):
self.interpolate_bezier(pts, wM, p0, p1, resolution)
pts.append(pts[0])
else:
- pts.append(wM * points[-1].co)
+ pts.append(wM @ points[-1].co)
- pt = wM.inverted() * pts[0]
+ pt = wM.inverted() @ pts[0]
# pretranslate
o = self.find_in_selection(context, self.auto_update)
- o.matrix_world = wM * Matrix([
- [1, 0, 0, pt.x],
- [0, 1, 0, pt.y],
- [0, 0, 1, pt.z],
- [0, 0, 0, 1]
- ])
+ o.matrix_world = wM @ Matrix.Translation(pt)
self.from_points(pts)
def from_points(self, pts):
@@ -1405,7 +1400,7 @@ class archipack_floor(ArchipackObject, Manipulable, PropertyGroup):
self.auto_update = True
def update_path(self, context):
- user_def_path = context.scene.objects.get(self.user_defined_path)
+ user_def_path = context.scene.objects.get(self.user_defined_path.strip())
if user_def_path is not None and user_def_path.type == 'CURVE':
self.from_spline(
context,
@@ -1565,8 +1560,8 @@ def update_operation(self, context):
class archipack_floor_cutter_segment(ArchipackCutterPart, PropertyGroup):
- manipulators = CollectionProperty(type=archipack_manipulator)
- type = EnumProperty(
+ manipulators : CollectionProperty(type=archipack_manipulator)
+ type : EnumProperty(
name="Type",
items=(
('DEFAULT', 'Side', 'Side with rake', 0),
@@ -1581,7 +1576,7 @@ class archipack_floor_cutter_segment(ArchipackCutterPart, PropertyGroup):
)
def find_in_selection(self, context):
- selected = [o for o in context.selected_objects]
+ selected = context.selected_objects[:]
for o in selected:
d = archipack_floor_cutter.datablock(o)
if d:
@@ -1599,7 +1594,7 @@ class archipack_floor_cutter_segment(ArchipackCutterPart, PropertyGroup):
class archipack_floor_cutter(ArchipackCutter, ArchipackObject, Manipulable, PropertyGroup):
- parts = CollectionProperty(type=archipack_floor_cutter_segment)
+ parts : CollectionProperty(type=archipack_floor_cutter_segment)
def update_points(self, context, o, pts, update_parent=False):
"""
@@ -1615,11 +1610,11 @@ class archipack_floor_cutter(ArchipackCutter, ArchipackObject, Manipulable, Prop
d = archipack_floor.datablock(o.parent)
if d is not None:
- o.parent.select = True
- context.scene.objects.active = o.parent
+ o.parent.select_set(state=True)
+ context.view_layer.objects.active = o.parent
d.update(context)
- o.parent.select = False
- context.scene.objects.active = o
+ o.parent.select_set(state=False)
+ context.view_layer.objects.active = o
# ------------------------------------------------------------------
@@ -1632,7 +1627,7 @@ class ARCHIPACK_PT_floor(Panel):
bl_label = "Flooring"
bl_space_type = "VIEW_3D"
bl_region_type = "UI"
- bl_category = "Archipack"
+ bl_category = 'Archipack'
@classmethod
def poll(cls, context):
@@ -1648,7 +1643,7 @@ class ARCHIPACK_PT_floor(Panel):
# retrieve datablock of your object
props = archipack_floor.datablock(o)
# manipulate
- layout.operator("archipack.floor_manipulate", icon="HAND")
+ layout.operator("archipack.floor_manipulate", icon="VIEW_PAN")
layout.separator()
box = layout.box()
row = box.row(align=True)
@@ -1658,10 +1653,10 @@ class ARCHIPACK_PT_floor(Panel):
text=bpy.types.ARCHIPACK_OT_floor_preset_menu.bl_label)
row.operator("archipack.floor_preset",
text="",
- icon='ZOOMIN')
+ icon='ADD')
row.operator("archipack.floor_preset",
text="",
- icon='ZOOMOUT').remove_active = True
+ icon='REMOVE').remove_active = True
box = layout.box()
box.operator('archipack.floor_cutter').parent = o.name
@@ -1759,7 +1754,7 @@ class ARCHIPACK_PT_floor_cutter(Panel):
bl_label = "Floor Cutter"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
- bl_category = 'ArchiPack'
+ bl_category = 'Archipack'
@classmethod
def poll(cls, context):
@@ -1772,7 +1767,7 @@ class ARCHIPACK_PT_floor_cutter(Panel):
layout = self.layout
scene = context.scene
box = layout.box()
- box.operator('archipack.floor_cutter_manipulate', icon='HAND')
+ box.operator('archipack.floor_cutter_manipulate', icon='VIEW_PAN')
box.prop(prop, 'operation', text="")
box = layout.box()
box.label(text="From curve")
@@ -1824,9 +1819,9 @@ class ARCHIPACK_OT_floor(ArchipackCreateTool, Operator):
p.a0 = angle_90
p.length = x
d.n_parts = 4
- context.scene.objects.link(o)
- o.select = True
- context.scene.objects.active = o
+ self.link_object_to_scene(context, o)
+ o.select_set(state=True)
+ context.view_layer.objects.active = o
self.load_preset(d)
self.add_material(o)
return o
@@ -1837,8 +1832,8 @@ class ARCHIPACK_OT_floor(ArchipackCreateTool, Operator):
o = self.create(context)
o.location = context.scene.cursor_location
# activate manipulators at creation time
- o.select = True
- context.scene.objects.active = o
+ o.select_set(state=True)
+ context.view_layer.objects.active = o
self.manipulate()
return {'FINISHED'}
else:
@@ -1864,7 +1859,7 @@ class ARCHIPACK_OT_floor_from_curve(ArchipackCreateTool, Operator):
def draw(self, context):
layout = self.layout
row = layout.row()
- row.label("Use Properties panel (N) to define parms", icon='INFO')
+ row.label(text="Use Properties panel (N) to define parms", icon='INFO')
def create(self, context):
curve = context.active_object
@@ -1903,7 +1898,7 @@ class ARCHIPACK_OT_floor_from_wall(ArchipackCreateTool, Operator):
wall = context.active_object
wd = wall.data.archipack_wall2[0]
bpy.ops.archipack.floor(auto_manipulate=False, filepath=self.filepath)
- o = context.scene.objects.active
+ o = context.view_layer.objects.active
d = archipack_floor.datablock(o)
d.auto_update = False
d.closed = True
@@ -1931,8 +1926,8 @@ class ARCHIPACK_OT_floor_from_wall(ArchipackCreateTool, Operator):
if context.mode == "OBJECT":
bpy.ops.object.select_all(action="DESELECT")
o = self.create(context)
- o.select = True
- context.scene.objects.active = o
+ o.select_set(state=True)
+ context.view_layer.objects.active = o
if self.auto_manipulate:
bpy.ops.archipack.floor_manipulate('INVOKE_DEFAULT')
return {'FINISHED'}
@@ -1948,13 +1943,13 @@ class ARCHIPACK_OT_floor_cutter(ArchipackCreateTool, Operator):
bl_category = 'Archipack'
bl_options = {'REGISTER', 'UNDO'}
- parent = StringProperty("")
+ parent : StringProperty("")
def create(self, context):
m = bpy.data.meshes.new("Floor Cutter")
o = bpy.data.objects.new("Floor Cutter", m)
d = m.archipack_floor_cutter.add()
- parent = context.scene.objects.get(self.parent)
+ parent = context.scene.objects.get(self.parent.strip())
if parent is not None:
o.parent = parent
bbox = parent.bound_box
@@ -1963,7 +1958,7 @@ class ARCHIPACK_OT_floor_cutter(ArchipackCreateTool, Operator):
x1, y1, z = bbox[6]
x = 0.2 * (x1 - x0)
y = 0.2 * (y1 - y0)
- o.matrix_world = parent.matrix_world * Matrix([
+ o.matrix_world = parent.matrix_world @ Matrix([
[1, 0, 0, -3 * x],
[0, 1, 0, 0],
[0, 0, 1, 0],
@@ -1986,9 +1981,9 @@ class ARCHIPACK_OT_floor_cutter(ArchipackCreateTool, Operator):
o.location = context.scene.cursor_location
# make manipulators selectable
d.manipulable_selectable = True
- context.scene.objects.link(o)
- o.select = True
- context.scene.objects.active = o
+ self.link_object_to_scene(context, o)
+ o.select_set(state=True)
+ context.view_layer.objects.active = o
# self.add_material(o)
self.load_preset(d)
update_operation(d, context)
@@ -2001,8 +1996,8 @@ class ARCHIPACK_OT_floor_cutter(ArchipackCreateTool, Operator):
if context.mode == "OBJECT":
bpy.ops.object.select_all(action="DESELECT")
o = self.create(context)
- o.select = True
- context.scene.objects.active = o
+ o.select_set(state=True)
+ context.view_layer.objects.active = o
self.manipulate()
return {'FINISHED'}
else:
diff --git a/archipack/archipack_gl.py b/archipack/archipack_gl.py
index a85a3080..578098f9 100644
--- a/archipack/archipack_gl.py
+++ b/archipack/archipack_gl.py
@@ -24,10 +24,12 @@
# Author: Stephen Leger (s-leger)
#
# ----------------------------------------------------------
-
import bgl
import blf
+import gpu
+from gpu_extras.batch import batch_for_shader
import bpy
+import numpy as np
from math import sin, cos, atan2, pi
from mathutils import Vector, Matrix
from bpy_extras import view3d_utils, object_utils
@@ -44,6 +46,7 @@ class DefaultColorScheme:
default to this when not found in addon prefs
Colors are FloatVectorProperty of size 4 and type COLOR_GAMMA
"""
+ text_size = 14
feedback_size_main = 16
feedback_size_title = 14
feedback_size_shortcut = 11
@@ -52,6 +55,167 @@ class DefaultColorScheme:
feedback_colour_shortcut = (0.51, 0.51, 0.51, 1.0)
feedback_shortcut_area = (0, 0.4, 0.6, 0.2)
feedback_title_area = (0, 0.4, 0.6, 0.5)
+ handle_colour_normal = (1.0, 1.0, 1.0, 1.0)
+ handle_colour_hover = (1.0, 1.0, 0.0, 1.0)
+ handle_colour_active = (1.0, 0.0, 0.0, 1.0)
+ handle_colour_selected = (0.0, 0.0, 0.7, 1.0)
+ handle_colour_inactive = (0.3, 0.3, 0.3, 1.0)
+
+
+def get_prefs(context):
+ global __name__
+ try:
+ addon_name = __name__.split('.')[0]
+ prefs = context.user_preferences.addons[addon_name].preferences
+ except:
+ prefs = DefaultColorScheme
+ pass
+ return prefs
+
+
+g_poly = None
+g_image = None
+g_line = None
+
+
+line_vertSrc = '''
+uniform mat4 ModelViewProjectionMatrix;
+
+in vec2 pos;
+
+void main()
+{
+ gl_Position = ModelViewProjectionMatrix * vec4(pos, 0.0, 1.0);
+}
+
+'''
+
+line_fragSrc = '''
+uniform vec4 color;
+
+out vec4 fragColor;
+
+void main()
+{
+ fragColor = color;
+}
+'''
+
+"""
+# TESTS
+_format = gpu.types.GPUVertFormat()
+_pos_id = _format.attr_add(
+ id="pos",
+ comp_type="F32",
+ len=2,
+ fetch_mode="FLOAT")
+coords = [(0,0), (200,100)]
+vbo = gpu.types.GPUVertBuf(len=len(coords), format=_format)
+"""
+
+
+class GPU_Line:
+ def __init__(self):
+ # never call this in -b mode
+ if bpy.app.background:
+ return
+ self._format = gpu.types.GPUVertFormat()
+ self._pos_id = self._format.attr_add(
+ id="pos",
+ comp_type="F32",
+ len=2,
+ fetch_mode="FLOAT")
+ self.shader = gpu.types.GPUShader(line_vertSrc, line_fragSrc)
+ self.unif_color = self.shader.uniform_from_name("color")
+ self.color = np.array([0.0, 0.0, 0.0, 1.0], 'f')
+
+ def batch_line_strip_create(self, coords):
+ vbo = gpu.types.GPUVertBuf(len=len(coords), format=self._format)
+ vbo.attr_fill(id=self._pos_id, data=coords)
+ batch_lines = gpu.types.GPUBatch(type="LINE_STRIP", buf=vbo)
+ batch_lines.program_set(self.shader)
+ return batch_lines
+
+ def draw(self, color, list_verts_co):
+ if list_verts_co:
+ batch = self.batch_line_strip_create(list_verts_co)
+ self.color[0:4] = color[0:4]
+ self.shader.uniform_vector_float(self.unif_color, self.color, 4)
+ batch.draw()
+ del batch
+
+
+class GPU_Poly:
+
+ def __init__(self):
+ if bpy.app.background:
+ return
+ self._format = gpu.types.GPUVertFormat()
+ self._pos_id = self._format.attr_add(
+ id="pos",
+ comp_type="F32",
+ len=2,
+ fetch_mode="FLOAT")
+ self.shader = gpu.types.GPUShader(line_vertSrc, line_fragSrc)
+ self.unif_color = self.shader.uniform_from_name("color")
+ self.color = np.array([0.0, 0.0, 0.0, 0.5], 'f')
+
+ def batch_create(self, coords):
+ vbo = gpu.types.GPUVertBuf(len=len(coords), format=self._format)
+ vbo.attr_fill(id=self._pos_id, data=coords)
+ batch = gpu.types.GPUBatch(type="TRI_FAN", buf=vbo)
+ # batch.program_set(self.shader)
+ # batch = batch_for_shader(self.shader, 'TRI_FAN', {"position": coords})
+ return batch
+
+ def draw(self, color, list_verts_co):
+ if list_verts_co:
+ self.shader.bind()
+ batch = self.batch_create(list_verts_co)
+ self.color[0:4] = color[0:4]
+ self.shader.uniform_vector_float(self.unif_color, self.color, 4)
+ batch.draw(self.shader)
+
+
+class GPU_Image:
+ uvs = [(0, 0), (1, 0), (0, 1), (1, 1)]
+ indices = [(0, 1, 2), (2, 1, 3)]
+
+ def __init__(self):
+ if bpy.app.background:
+ return
+ self._format = gpu.types.GPUVertFormat()
+ self._pos_id = self._format.attr_add(
+ id="pos",
+ comp_type="F32",
+ len=2,
+ fetch_mode="FLOAT")
+ self.shader = gpu.shader.from_builtin('2D_IMAGE')
+
+ def batch_create(self, coords):
+ batch = batch_for_shader(self.shader, 'TRIS',
+ {"pos": coords,
+ "texCoord": self.uvs},
+ indices=self.indices)
+ return batch
+
+ def draw(self, texture_id, list_verts_co):
+ if list_verts_co:
+ batch = self.batch_create(list_verts_co)
+
+ # in case someone disabled it before
+ bgl.glEnable(bgl.GL_TEXTURE_2D)
+
+ # bind texture to image unit 0
+ bgl.glActiveTexture(bgl.GL_TEXTURE0)
+ bgl.glBindTexture(bgl.GL_TEXTURE_2D, texture_id)
+
+ self.shader.bind()
+ # tell shader to use the image that is bound to image unit 0
+ self.shader.uniform_int("image", 0)
+ batch.draw(self.shader)
+
+ bgl.glDisable(bgl.GL_TEXTURE_2D)
class Gl():
@@ -61,9 +225,22 @@ class Gl():
3 to convert pos from 3d
2 to keep pos as 2d absolute screen position
"""
+
def __init__(self,
- d=3,
- colour=(0.0, 0.0, 0.0, 1.0)):
+ d=3,
+ colour=(0.0, 0.0, 0.0, 1.0)):
+
+ global g_poly, g_image, g_line
+
+ if g_poly is None:
+ g_poly = GPU_Poly()
+
+ if g_image is None:
+ g_image = GPU_Image()
+
+ if g_line is None:
+ g_line = GPU_Line()
+
# nth dimensions of input coords 3=word coords 2=pixel screen coords
self.d = d
self.pos_2d = Vector((0, 0))
@@ -77,13 +254,13 @@ class Gl():
""" coord given in local input coordsys
"""
if self.d == 2:
- return coord
+ return Vector(coord)
if render:
return self.get_render_location(context, coord)
region = context.region
rv3d = context.region_data
loc = view3d_utils.location_3d_to_region_2d(region, rv3d, coord, self.pos_2d)
- return loc
+ return Vector(loc)
def get_render_location(self, context, coord):
scene = context.scene
@@ -92,42 +269,32 @@ class Gl():
render_scale = scene.render.resolution_percentage / 100
render_size = (int(scene.render.resolution_x * render_scale),
int(scene.render.resolution_y * render_scale))
- return [round(co_2d.x * render_size[0]), round(co_2d.y * render_size[1])]
-
- def _end(self):
-
- # print("_end")
- bgl.glEnd()
- bgl.glPopAttrib()
- bgl.glLineWidth(1)
- bgl.glDisable(bgl.GL_BLEND)
- bgl.glColor4f(0.0, 0.0, 0.0, 1.0)
- # print("_end %s" % (type(self).__name__))
+ return Vector(round(co_2d.x * render_size[0]), round(co_2d.y * render_size[1]))
class GlText(Gl):
def __init__(self,
- d=3,
- label="",
- value=None,
- precision=2,
- unit_mode='AUTO',
- unit_type='SIZE',
- dimension=1,
- angle=0,
- font_size=12,
- colour=(1, 1, 1, 1),
- z_axis=Vector((0, 0, 1))):
+ d=3,
+ label="",
+ value=None,
+ precision=4,
+ unit_mode='AUTO',
+ unit_type='SIZE',
+ dimension=1,
+ angle=0,
+ font_size=None,
+ colour=(1, 1, 1, 1),
+ z_axis=Vector((0, 0, 1))):
"""
d: [2|3] coords type: 2 for coords in screen pixels, 3 for 3d world location
label : string label
value : float value (will add unit according following settings)
precision : integer rounding for values
dimension : [1 - 3] nth dimension of unit (single, square, cubic)
- unit_mode : ['AUTO','METER','CENTIMETER','MILIMETER','FEET','INCH','RADIANS','DEGREE']
+ unit_mode : ['ADAPTIVE','METER','CENTIMETER','MILIMETER','FEET','INCH','RADIANS','DEGREE']
unit type to use to postfix values
- auto use scene units setup
+ ADAPTIVE use scene units setup
unit_type : ['SIZE','ANGLE']
unit type to add to value
angle : angle to rotate text
@@ -142,13 +309,17 @@ class GlText(Gl):
self.dimension = dimension
self.unit_type = unit_type
self.unit_mode = unit_mode
-
- self.font_size = font_size
+ if font_size is None:
+ prefs = get_prefs(bpy.context)
+ self.font_size = prefs.text_size
+ else:
+ self.font_size = font_size
self.angle = angle
Gl.__init__(self, d)
self.colour_inactive = colour
# store text with units
self._text = ""
+ self.cbuff = bgl.Buffer(bgl.GL_FLOAT, 4)
def text_size(self, context):
"""
@@ -177,20 +348,25 @@ class GlText(Gl):
def add_units(self, context):
if self.value is None:
return ""
+ system = context.scene.unit_settings.system
if self.unit_type == 'ANGLE':
scale = 1
+ mode = 'ADAPTIVE'
else:
scale = context.scene.unit_settings.scale_length
+ mode = self.unit_mode
+ if mode == 'AUTO':
+ mode = context.scene.unit_settings.length_unit.upper()
val = self.value * scale
- mode = self.unit_mode
- if mode == 'AUTO':
+
+ if mode == 'ADAPTIVE':
if self.unit_type == 'ANGLE':
mode = context.scene.unit_settings.system_rotation
else:
- if context.scene.unit_settings.system == "IMPERIAL":
+ if system == "IMPERIAL":
if round(val * (3.2808399 ** self.dimension), 2) >= 1.0:
- mode = 'FEET'
+ mode = 'FOOT'
else:
mode = 'INCH'
elif context.scene.unit_settings.system == "METRIC":
@@ -201,7 +377,10 @@ class GlText(Gl):
mode = 'CENTIMETER'
else:
mode = 'MILIMETER'
+
+ # TODO: support for separate units (through 2.8 api)
# convert values
+ unit = ""
if mode == 'METER':
unit = "m"
elif mode == 'CENTIMETER':
@@ -210,26 +389,38 @@ class GlText(Gl):
elif mode == 'MILIMETER':
val *= (1000 ** self.dimension)
unit = 'mm'
+ elif mode in {'FOOT', 'FEET'}:
+ val *= (3.2808399 ** self.dimension)
+ unit = "ft"
elif mode == 'INCH':
val *= (39.3700787 ** self.dimension)
unit = "in"
- elif mode == 'FEET':
- val *= (3.2808399 ** self.dimension)
- unit = "ft"
elif mode == 'RADIANS':
unit = ""
elif mode == 'DEGREES':
val = self.value / pi * 180
unit = "°"
- else:
- unit = ""
- if self.dimension == 2:
- unit += "\u00b2" # Superscript two
- elif self.dimension == 3:
- unit += "\u00b3" # Superscript three
-
- fmt = "%1." + str(self.precision) + "f " + unit
- return fmt % val
+ if system == 'IMPERIAL':
+ if self.dimension == 2:
+ unit = "sq " + unit
+ elif self.dimension == 3:
+ unit = "cu " + unit
+ elif system == 'METRIC':
+ if self.dimension == 2:
+ unit += "\u00b2" # Superscript two
+ elif self.dimension == 3:
+ unit += "\u00b3" # Superscript three
+
+ fmt = "%1." + str(self.precision) + "f"
+ # remove trailing zeros
+ res = fmt % val
+ while res[-1] == '0':
+ res = res[:-1]
+
+ if res[-1] == ".":
+ res = res + '0'
+
+ return "{} {}".format(res, unit)
def set_pos(self, context, value, pos_3d, direction, angle=0, normal=Vector((0, 0, 1))):
self.up_axis = direction.normalized()
@@ -243,66 +434,68 @@ class GlText(Gl):
# print("draw_text %s %s" % (self.text, type(self).__name__))
self.render = render
- x, y = self.position_2d_from_coord(context, self.pts[0], render)
+ p = self.position_2d_from_coord(context, self.pts[0], render)
+
# dirty fast assignment
dpi, font_id = context.user_preferences.system.dpi, 0
- bgl.glColor4f(*self.colour)
+
+ # self.cbuff[0:4] = self.colour
+
+ # bgl.glEnableClientState(bgl.GL_COLOR_ARRAY)
+ # bgl.glColorPointer(4, bgl.GL_FLOAT, 0, self.cbuff)
+ blf.color(0, *self.colour)
if self.angle != 0:
blf.enable(font_id, blf.ROTATION)
blf.rotation(font_id, self.angle)
blf.size(font_id, self.font_size, dpi)
- blf.position(font_id, x, y, 0)
+ blf.position(font_id, p.x, p.y, 0)
blf.draw(font_id, self.text)
if self.angle != 0:
blf.disable(font_id, blf.ROTATION)
+ # bgl.glDisableClientState(bgl.GL_COLOR_ARRAY)
+
class GlBaseLine(Gl):
def __init__(self,
- d=3,
- width=1,
- style=bgl.GL_LINE,
- closed=False):
+ d=3,
+ width=1,
+ style=bgl.GL_LINE,
+ closed=False,
+ n_pts=2):
Gl.__init__(self, d)
# default line width
self.width = width
# default line style
self.style = style
# allow closed lines
- self.closed = False
+ self.closed = closed
+
+ self.n_pts = n_pts
def draw(self, context, render=False):
"""
render flag when rendering
"""
-
- # print("draw_line %s" % (type(self).__name__))
- bgl.glPushAttrib(bgl.GL_ENABLE_BIT)
- if self.style == bgl.GL_LINE_STIPPLE:
- bgl.glLineStipple(1, 0x9999)
- bgl.glEnable(self.style)
+ self.render = render
bgl.glEnable(bgl.GL_BLEND)
- if render:
- # enable anti-alias on lines
- bgl.glEnable(bgl.GL_LINE_SMOOTH)
- bgl.glColor4f(*self.colour)
bgl.glLineWidth(self.width)
+ list_verts_co = [
+ tuple(self.position_2d_from_coord(context, pt, render)[0:2])
+ for i, pt in enumerate(self.pts)]
if self.closed:
- bgl.glBegin(bgl.GL_LINE_LOOP)
- else:
- bgl.glBegin(bgl.GL_LINE_STRIP)
-
- for pt in self.pts:
- x, y = self.position_2d_from_coord(context, pt, render)
- bgl.glVertex2f(x, y)
- self._end()
+ list_verts_co.append(list_verts_co[0])
+ g_line.draw(self.colour, list_verts_co)
+ bgl.glLineWidth(1.0)
+ bgl.glDisable(bgl.GL_BLEND)
class GlLine(GlBaseLine):
"""
2d/3d Line
"""
+
def __init__(self, d=3, p=None, v=None, p0=None, p1=None, z_axis=None):
"""
d=3 use 3d coords, d=2 use 2d pixels coords
@@ -322,13 +515,13 @@ class GlLine(GlBaseLine):
self.p = Vector(p0)
self.v = Vector(p1) - self.p
else:
- self.p = Vector((0, 0, 0))
- self.v = Vector((0, 0, 0))
+ self.p = Vector()
+ self.v = Vector()
if z_axis is not None:
self.z_axis = z_axis
else:
self.z_axis = Vector((0, 0, 1))
- GlBaseLine.__init__(self, d)
+ GlBaseLine.__init__(self, d, n_pts=2)
@property
def p0(self):
@@ -391,7 +584,7 @@ class GlLine(GlBaseLine):
def sized_normal(self, t, size):
"""
GlLine perpendicular on plane defined by z_axis and of given size
- positioned at t in current line
+ positionned at t in current line
lie on the right side
p1
|--x
@@ -425,7 +618,7 @@ class GlLine(GlBaseLine):
v2d = self.v.to_2d()
dl = v2d.length
d = (self.v.x * dp.y - self.v.y * dp.x) / dl
- t = (v2d * dp) / (dl * dl)
+ t = v2d.dot(dp) / (dl * dl)
return t > 0 and t < 1, d, t
@property
@@ -436,10 +629,10 @@ class GlLine(GlBaseLine):
class GlCircle(GlBaseLine):
def __init__(self,
- d=3,
- radius=0,
- center=Vector((0, 0, 0)),
- z_axis=Vector((0, 0, 1))):
+ d=3,
+ radius=0,
+ center=Vector((0, 0, 0)),
+ z_axis=Vector((0, 0, 1))):
self.r = radius
self.c = center
@@ -460,18 +653,19 @@ class GlCircle(GlBaseLine):
self.z_axis = z
self.a0 = 0
self.da = 2 * pi
- GlBaseLine.__init__(self, d)
+ GlBaseLine.__init__(self, d, n_pts=60)
def lerp(self, t):
"""
Linear interpolation
"""
a = self.a0 + t * self.da
- return self.c + self.rM * Vector((self.r * cos(a), self.r * sin(a), 0))
+ return self.c + self.rM @ Vector((self.r * cos(a), self.r * sin(a), 0))
@property
def pts(self):
n_pts = max(1, int(round(abs(self.da) / pi * 30, 0)))
+ self.n_pts = n_pts
t_step = 1 / n_pts
return [self.lerp(i * t_step) for i in range(n_pts + 1)]
@@ -479,12 +673,12 @@ class GlCircle(GlBaseLine):
class GlArc(GlCircle):
def __init__(self,
- d=3,
- radius=0,
- center=Vector((0, 0, 0)),
- z_axis=Vector((0, 0, 1)),
- a0=0,
- da=0):
+ d=3,
+ radius=0,
+ center=Vector((0, 0, 0)),
+ z_axis=Vector((0, 0, 1)),
+ a0=0,
+ da=0):
"""
a0 and da arguments are in radians
a0 = 0 on the x+ axis side
@@ -526,8 +720,8 @@ class GlArc(GlCircle):
ca = cos(a)
sa = sin(a)
n = GlLine(d=self.d, z_axis=self.z_axis)
- n.p = self.c + self.rM * Vector((self.r * ca, self.r * sa, 0))
- n.v = self.rM * Vector((length * sa, -length * ca, 0))
+ n.p = self.c + self.rM @ Vector((self.r * ca, self.r * sa, 0))
+ n.v = self.rM @ Vector((length * sa, -length * ca, 0))
if self.da > 0:
n.v = -n.v
return n
@@ -541,22 +735,23 @@ class GlArc(GlCircle):
else:
radius = self.r - offset
return GlArc(d=self.d,
- radius=radius,
- center=self.c,
- a0=self.a0,
- da=self.da,
- z_axis=self.z_axis)
+ radius=radius,
+ center=self.c,
+ a0=self.a0,
+ da=self.da,
+ z_axis=self.z_axis)
class GlPolygon(Gl):
def __init__(self,
- colour=(0.0, 0.0, 0.0, 1.0),
- d=3):
-
+ colour=(0.3, 0.3, 0.3, 1.0),
+ d=3, n_pts=60):
self.pts_3d = []
Gl.__init__(self, d, colour)
+ self.n_pts = min(n_pts, 60)
+
def set_pos(self, pts_3d):
self.pts_3d = pts_3d
@@ -568,52 +763,53 @@ class GlPolygon(Gl):
"""
render flag when rendering
"""
+ # return
- # print("draw_polygon")
self.render = render
- bgl.glPushAttrib(bgl.GL_ENABLE_BIT)
- bgl.glEnable(bgl.GL_BLEND)
- if render:
- # enable anti-alias on polygons
- bgl.glEnable(bgl.GL_POLYGON_SMOOTH)
- bgl.glColor4f(*self.colour)
- bgl.glBegin(bgl.GL_POLYGON)
+ if self.n_pts == 0:
+ return
+
+ pts = self.pts
- for pt in self.pts:
- x, y = self.position_2d_from_coord(context, pt, render)
- bgl.glVertex2f(x, y)
- self._end()
+ g_vertices = [
+ tuple(self.position_2d_from_coord(context, pt, render)[0:2])
+ for i, pt in enumerate(pts)]
+ bgl.glEnable(bgl.GL_BLEND)
+ g_poly.draw(self.colour, g_vertices)
+ bgl.glDisable(bgl.GL_BLEND)
class GlRect(GlPolygon):
def __init__(self,
- colour=(0.0, 0.0, 0.0, 1.0),
- d=2):
- GlPolygon.__init__(self, colour, d)
+ colour=(0.0, 0.0, 0.0, 1.0),
+ d=2):
+ GlPolygon.__init__(self, colour, d, n_pts=4)
- def draw(self, context, render=False):
+ @property
+ def pts(self):
+ return self.pts_2d
- self.render = render
- bgl.glPushAttrib(bgl.GL_ENABLE_BIT)
- bgl.glEnable(bgl.GL_BLEND)
- if render:
- # enable anti-alias on polygons
- bgl.glEnable(bgl.GL_POLYGON_SMOOTH)
- bgl.glColor4f(*self.colour)
- p0 = self.pts[0]
- p1 = self.pts[1]
- bgl.glRectf(p0.x, p0.y, p1.x, p1.y)
- self._end()
+ def draw(self, context, render=False):
+ pts = [
+ self.position_2d_from_coord(context, pt, render)
+ for pt in self.pts_3d
+ ]
+ x0, y0 = pts[0]
+ x1, y1 = pts[1]
+ self.pts_2d = [Vector((x, y)) for x, y in [(x0, y0), (x0, y1), (x1, y1), (x1, y0)]]
+ GlPolygon.draw(self, context, render)
class GlImage(Gl):
def __init__(self,
- d=2,
- image=None):
+ d=2,
+ image=None):
+ # GImage bindcode[0]
self.image = image
self.colour_inactive = (1, 1, 1, 1)
Gl.__init__(self, d)
self.pts_2d = [Vector((0, 0)), Vector((10, 10))]
+ self.n_pts = 4
def set_pos(self, pts):
self.pts_2d = pts
@@ -625,38 +821,17 @@ class GlImage(Gl):
def draw(self, context, render=False):
if self.image is None:
return
- bgl.glPushAttrib(bgl.GL_ENABLE_BIT)
+
p0 = self.pts[0]
p1 = self.pts[1]
- bgl.glEnable(bgl.GL_BLEND)
- bgl.glColor4f(*self.colour)
- bgl.glRectf(p0.x, p0.y, p1.x, p1.y)
- self.image.gl_load()
- bgl.glEnable(bgl.GL_BLEND)
- bgl.glBindTexture(bgl.GL_TEXTURE_2D, self.image.bindcode[0])
- bgl.glTexParameteri(bgl.GL_TEXTURE_2D, bgl.GL_TEXTURE_MIN_FILTER, bgl.GL_NEAREST)
- bgl.glTexParameteri(bgl.GL_TEXTURE_2D, bgl.GL_TEXTURE_MAG_FILTER, bgl.GL_NEAREST)
- bgl.glEnable(bgl.GL_TEXTURE_2D)
- bgl.glBlendFunc(bgl.GL_SRC_ALPHA, bgl.GL_ONE_MINUS_SRC_ALPHA)
- # bgl.glColor4f(1, 1, 1, 1)
- bgl.glBegin(bgl.GL_QUADS)
- bgl.glTexCoord2d(0, 0)
- bgl.glVertex2d(p0.x, p0.y)
- bgl.glTexCoord2d(0, 1)
- bgl.glVertex2d(p0.x, p1.y)
- bgl.glTexCoord2d(1, 1)
- bgl.glVertex2d(p1.x, p1.y)
- bgl.glTexCoord2d(1, 0)
- bgl.glVertex2d(p1.x, p0.y)
- bgl.glEnd()
- self.image.gl_free()
- bgl.glDisable(bgl.GL_TEXTURE_2D)
+ coords = [(p0.x, p0.y), (p1.x, p0.y), (p0.x, p1.y), (p1.x, p1.y)]
+ g_image.draw(self.image.bindcode, coords)
class GlPolyline(GlBaseLine):
- def __init__(self, colour, d=3):
+ def __init__(self, colour, d=3, n_pts=60):
self.pts_3d = []
- GlBaseLine.__init__(self, d)
+ GlBaseLine.__init__(self, d, n_pts=n_pts)
self.colour_inactive = colour
def set_pos(self, pts_3d):
@@ -670,17 +845,25 @@ class GlPolyline(GlBaseLine):
class GlHandle(GlPolygon):
- def __init__(self, sensor_size, size, draggable=False, selectable=False, d=3):
+ def __init__(self, sensor_size, size, draggable=False, selectable=False, d=3, n_pts=4):
"""
sensor_size : 2d size in pixels of sensor area
size : 3d size of handle
"""
- GlPolygon.__init__(self, d=d)
- self.colour_active = (1.0, 0.0, 0.0, 1.0)
- self.colour_hover = (1.0, 1.0, 0.0, 1.0)
- self.colour_normal = (1.0, 1.0, 1.0, 1.0)
- self.colour_selected = (0.0, 0.0, 0.7, 1.0)
+ GlPolygon.__init__(self, d=d, n_pts=n_pts)
+ prefs = get_prefs(bpy.context)
+ self.colour_active = prefs.handle_colour_active
+ self.colour_hover = prefs.handle_colour_hover
+ self.colour_normal = prefs.handle_colour_normal
+ self.colour_selected = prefs.handle_colour_selected
+ self.colour_inactive = prefs.handle_colour_inactive
+ # variable symbol size in world coords
self.size = size
+ # constant symbol size in pixels
+ self.symbol_size = sensor_size
+ # scale factor for constant symbol pixel size
+ self.scale = 1
+ # sensor size in pixels
self.sensor_width = sensor_size
self.sensor_height = sensor_size
self.pos_3d = Vector((0, 0, 0))
@@ -692,11 +875,38 @@ class GlHandle(GlPolygon):
self.selectable = selectable
self.selected = False
+ def scale_factor(self, context, pts):
+ """
+ Compute screen scale factor for symbol
+ given 2 points in symbol direction (eg start and end of arrow)
+ """
+ prefs = get_prefs(context)
+ if not prefs.constant_handle_size:
+ return 1
+
+ p2d = []
+ for pt in pts:
+ p2d.append(self.position_2d_from_coord(context, pt))
+ sx = [p.x for p in p2d]
+ sy = [p.y for p in p2d]
+ x = max(sx) - min(sx)
+ y = max(sy) - min(sy)
+ s = max(x, y)
+ if s > 0:
+ fac = self.symbol_size / s
+ else:
+ fac = 1
+ return fac
+
def set_pos(self, context, pos_3d, direction, normal=Vector((0, 0, 1))):
self.up_axis = direction.normalized()
self.c_axis = self.up_axis.cross(normal)
self.pos_3d = pos_3d
self.pos_2d = self.position_2d_from_coord(context, self.sensor_center)
+ x = self.up_axis * 0.5 * self.size
+ y = self.c_axis * 0.5 * self.size
+ pts = [self.sensor_center + v for v in [-x, x, -y, y]]
+ self.scale = self.scale_factor(context, pts)
def check_hover(self, pos_2d):
if self.draggable:
@@ -737,7 +947,7 @@ class GlHandle(GlPolygon):
class SquareHandle(GlHandle):
def __init__(self, sensor_size, size, draggable=False, selectable=False):
- GlHandle.__init__(self, sensor_size, size, draggable, selectable)
+ GlHandle.__init__(self, sensor_size, size, draggable, selectable, n_pts=4)
@property
def pts(self):
@@ -747,15 +957,15 @@ class SquareHandle(GlHandle):
scale = 1
else:
scale = 0.5
- x = n * self.size * scale
- y = c * self.size * scale
+ x = n * self.scale * self.size * scale
+ y = c * self.scale * self.size * scale
return [self.pos_3d - x - y, self.pos_3d + x - y, self.pos_3d + x + y, self.pos_3d - x + y]
class TriHandle(GlHandle):
def __init__(self, sensor_size, size, draggable=False, selectable=False):
- GlHandle.__init__(self, sensor_size, size, draggable, selectable)
+ GlHandle.__init__(self, sensor_size, size, draggable, selectable, n_pts=3)
@property
def pts(self):
@@ -767,11 +977,110 @@ class TriHandle(GlHandle):
scale = 1
# else:
# scale = 0.5
- x = n * self.size * 4 * scale
- y = c * self.size * scale
+ x = n * self.scale * self.size * 4 * scale
+ y = c * self.scale * self.size * scale
return [self.pos_3d - x + y, self.pos_3d - x - y, self.pos_3d]
+class CruxHandle(GlHandle):
+
+ def __init__(self, sensor_size, size, draggable=True, selectable=False):
+ GlHandle.__init__(self, sensor_size, size, draggable, selectable, n_pts=0)
+ self.branch_0 = GlPolygon((1, 1, 1, 1), d=3, n_pts=4)
+ self.branch_1 = GlPolygon((1, 1, 1, 1), d=3, n_pts=4)
+
+ def set_pos(self, context, pos_3d, direction, normal=Vector((0, 0, 1))):
+ self.pos_3d = pos_3d
+ self.pos_2d = self.position_2d_from_coord(context, self.sensor_center)
+ o = self.pos_3d
+ d = 0.5 * self.size
+ x = direction.normalized()
+ y = x.cross(normal)
+
+ sx = x * 0.5 * self.size
+ sy = y * 0.5 * self.size
+ pts = [o + v for v in [-sx, sx, -sy, sy]]
+ self.scale = self.scale_factor(context, pts)
+
+ c = self.scale * d / 1.4242
+ w = self.scale * self.size
+ s = w - c
+
+ xs = x * s
+ xw = x * w
+ ys = y * s
+ yw = y * w
+ p0 = o + xs + yw
+ p1 = o + xw + ys
+ p2 = o - xs - yw
+ p3 = o - xw - ys
+ p4 = o - xs + yw
+ p5 = o + xw - ys
+ p6 = o + xs - yw
+ p7 = o - xw + ys
+
+ self.branch_0.set_pos([p0, p1, p2, p3])
+ self.branch_1.set_pos([p4, p5, p6, p7])
+
+ @property
+ def pts(self):
+ return [self.pos_3d]
+
+ def draw(self, context, render=False):
+ self.render = render
+ self.branch_0.colour_inactive = self.colour
+ self.branch_1.colour_inactive = self.colour
+ self.branch_0.draw(context)
+ self.branch_1.draw(context)
+
+
+class PlusHandle(GlHandle):
+
+ def __init__(self, sensor_size, size, draggable=True, selectable=False):
+ GlHandle.__init__(self, sensor_size, size, draggable, selectable, n_pts=0)
+ self.branch_0 = GlPolygon((1, 1, 1, 1), d=3, n_pts=4)
+ self.branch_1 = GlPolygon((1, 1, 1, 1), d=3, n_pts=4)
+
+ def set_pos(self, context, pos_3d, direction, normal=Vector((0, 0, 1))):
+ self.pos_3d = pos_3d
+ self.pos_2d = self.position_2d_from_coord(context, self.sensor_center)
+ o = self.pos_3d
+ x = direction.normalized()
+ y = x.cross(normal)
+ sx = x * 0.5 * self.size
+ sy = y * 0.5 * self.size
+ pts = [o + v for v in [-sx, sx, -sy, sy]]
+ self.scale = self.scale_factor(context, pts)
+ w = self.scale * self.size
+ s = self.scale * 0.25 * w
+
+ xs = x * s
+ xw = x * w
+ ys = y * s
+ yw = y * w
+ p0 = o - xw + ys
+ p1 = o + xw + ys
+ p2 = o + xw - ys
+ p3 = o - xw - ys
+ p4 = o - xs + yw
+ p5 = o + xs + yw
+ p6 = o + xs - yw
+ p7 = o - xs - yw
+ self.branch_0.set_pos([p0, p1, p2, p3])
+ self.branch_1.set_pos([p4, p5, p6, p7])
+
+ @property
+ def pts(self):
+ return [self.pos_3d]
+
+ def draw(self, context, render=False):
+ self.render = render
+ self.branch_0.colour_inactive = self.colour
+ self.branch_1.colour_inactive = self.colour
+ self.branch_0.draw(context)
+ self.branch_1.draw(context)
+
+
class EditableText(GlText, GlHandle):
def __init__(self, sensor_size, size, draggable=False, selectable=False):
GlHandle.__init__(self, sensor_size, size, draggable, selectable)
@@ -783,10 +1092,10 @@ class EditableText(GlText, GlHandle):
self.pos_3d = pos_3d
self.value = value
self._text = self.add_units(context)
- x, y = self.text_size(context)
+ ts = self.text_size(context)
self.pos_2d = self.position_2d_from_coord(context, pos_3d)
- self.pos_2d.x += 0.5 * x
- self.sensor_width, self.sensor_height = 0.5 * x, y
+ self.pos_2d.x += 0.5 * ts.x
+ self.sensor_width, self.sensor_height = 0.5 * ts.x, ts.y
@property
def sensor_center(self):
@@ -796,10 +1105,10 @@ class EditableText(GlText, GlHandle):
class ThumbHandle(GlHandle):
def __init__(self, size_2d, label, image=None, draggable=False, selectable=False, d=2):
- GlHandle.__init__(self, size_2d, size_2d, draggable, selectable, d)
+ GlHandle.__init__(self, size_2d, size_2d, draggable, selectable, d, n_pts=4)
self.image = GlImage(image=image)
self.label = GlText(d=2, label=label.replace("_", " ").capitalize())
- self.frame = GlPolyline((1, 1, 1, 1), d=2)
+ self.frame = GlPolyline((1, 1, 1, 1), d=2, n_pts=4)
self.frame.closed = True
self.size_2d = size_2d
self.sensor_width = 0.5 * size_2d.x
@@ -830,7 +1139,7 @@ class ThumbHandle(GlHandle):
def draw(self, context, render=False):
self.render = render
self.image.colour_inactive = self.colour
- GlHandle.draw(self, context, render=False)
+ # GlHandle.draw(self, context, render=False)
self.image.draw(context, render=False)
self.label.draw(context, render=False)
self.frame.draw(context, render=False)
@@ -849,10 +1158,9 @@ class Screen():
y_max = h - self.margin
x_min = self.margin
x_max = w - self.margin
- if (system.use_region_overlap and
- system.window_draw_method in {'TRIPLE_BUFFER', 'AUTOMATIC'}):
+ if system.use_region_overlap:
+ # system.window_draw_method in {'TRIPLE_BUFFER', 'ADAPTIVEMATIC'}):
area = context.area
-
for r in area.regions:
if r.type == 'TOOLS':
x_min += r.width
@@ -866,29 +1174,30 @@ class FeedbackPanel():
Feed-back panel
inspired by np_station
"""
+
def __init__(self, title='Archipack'):
- prefs = self.get_prefs(bpy.context)
+ prefs = get_prefs(bpy.context)
self.main_title = GlText(d=2,
- label=title + " : ",
- font_size=prefs.feedback_size_main,
- colour=prefs.feedback_colour_main
- )
+ label=title + " : ",
+ font_size=prefs.feedback_size_main,
+ colour=prefs.feedback_colour_main
+ )
self.title = GlText(d=2,
- font_size=prefs.feedback_size_title,
- colour=prefs.feedback_colour_main
- )
+ font_size=prefs.feedback_size_title,
+ colour=prefs.feedback_colour_main
+ )
self.spacing = Vector((
0.5 * prefs.feedback_size_shortcut,
0.5 * prefs.feedback_size_shortcut))
self.margin = 50
self.explanation = GlText(d=2,
- font_size=prefs.feedback_size_shortcut,
- colour=prefs.feedback_colour_main
- )
- self.shortcut_area = GlPolygon(colour=prefs.feedback_shortcut_area, d=2)
- self.title_area = GlPolygon(colour=prefs.feedback_title_area, d=2)
+ font_size=prefs.feedback_size_shortcut,
+ colour=prefs.feedback_colour_main
+ )
+ self.shortcut_area = GlPolygon(colour=prefs.feedback_shortcut_area, d=2, n_pts=4)
+ self.title_area = GlPolygon(colour=prefs.feedback_title_area, d=2, n_pts=4)
self.shortcuts = []
self.on = False
self.show_title = True
@@ -903,22 +1212,11 @@ class FeedbackPanel():
def enable(self):
self.on = True
- def get_prefs(self, context):
- global __name__
- try:
- # retrieve addon name from imports
- addon_name = __name__.split('.')[0]
- prefs = context.user_preferences.addons[addon_name].preferences
- except:
- prefs = DefaultColorScheme
- pass
- return prefs
-
def instructions(self, context, title, explanation, shortcuts):
"""
position from bottom to top
"""
- prefs = self.get_prefs(context)
+ prefs = get_prefs(context)
self.explanation.label = explanation
self.title.label = title
@@ -927,16 +1225,17 @@ class FeedbackPanel():
for key, label in shortcuts:
key = GlText(d=2, label=key,
- font_size=prefs.feedback_size_shortcut,
- colour=prefs.feedback_colour_key)
+ font_size=prefs.feedback_size_shortcut,
+ colour=prefs.feedback_colour_key)
label = GlText(d=2, label=' : ' + label,
- font_size=prefs.feedback_size_shortcut,
- colour=prefs.feedback_colour_shortcut)
+ font_size=prefs.feedback_size_shortcut,
+ colour=prefs.feedback_colour_shortcut)
ks = key.text_size(context)
ls = label.text_size(context)
self.shortcuts.append([key, ks, label, ls])
def draw(self, context, render=False):
+
if self.on:
"""
draw from bottom to top
@@ -992,7 +1291,7 @@ class FeedbackPanel():
(x_max, self.margin),
(x_max, pos.y),
(x_min, pos.y)
- ]
+ ]
# small space between shortcut area and main title bar
if n_shortcuts > 0:
@@ -1003,7 +1302,7 @@ class FeedbackPanel():
(x_max, pos.y),
(x_max, pos.y + main_title_size.y + 2 * self.spacing.y),
(x_min, pos.y + main_title_size.y + 2 * self.spacing.y)
- ]
+ ]
pos.y += self.spacing.y
title_size = self.title.text_size(context)
@@ -1047,12 +1346,13 @@ class GlCursorFence():
"""
Cursor crossing Fence
"""
- def __init__(self, width=1, colour=(1.0, 1.0, 1.0, 0.5), style=bgl.GL_LINE_STIPPLE):
- self.line_x = GlLine(d=2)
+
+ def __init__(self, width=1, colour=(1.0, 1.0, 1.0, 0.5), style=2852):
+ self.line_x = GlLine(d=2, n_pts=2)
self.line_x.style = style
self.line_x.width = width
self.line_x.colour_inactive = colour
- self.line_y = GlLine(d=2)
+ self.line_y = GlLine(d=2, n_pts=2)
self.line_y.style = style
self.line_y.width = width
self.line_y.colour_inactive = colour
@@ -1061,7 +1361,8 @@ class GlCursorFence():
def set_location(self, context, location):
w = context.region.width
h = context.region.height
- x, y = location
+ p = Vector(location)
+ x, y = p.x, p.y
self.line_x.p = Vector((0, y))
self.line_x.v = Vector((w, 0))
self.line_y.p = Vector((x, 0))
@@ -1081,27 +1382,29 @@ class GlCursorFence():
class GlCursorArea():
def __init__(self,
- width=1,
- bordercolour=(1.0, 1.0, 1.0, 0.5),
- areacolour=(0.5, 0.5, 0.5, 0.08),
- style=bgl.GL_LINE_STIPPLE):
+ width=1,
+ bordercolour=(1.0, 1.0, 1.0, 0.5),
+ areacolour=(0.5, 0.5, 0.5, 0.08),
+ style=2852):
- self.border = GlPolyline(bordercolour, d=2)
+ self.border = GlPolyline(bordercolour, d=2, n_pts=4)
self.border.style = style
self.border.width = width
self.border.closed = True
- self.area = GlPolygon(areacolour, d=2)
+ self.area = GlPolygon(areacolour, d=2, n_pts=4)
self.min = Vector((0, 0))
self.max = Vector((0, 0))
self.on = False
def in_area(self, pt):
return (self.min.x <= pt.x and self.max.x >= pt.x and
- self.min.y <= pt.y and self.max.y >= pt.y)
+ self.min.y <= pt.y and self.max.y >= pt.y)
def set_location(self, context, p0, p1):
- x0, y0 = p0
- x1, y1 = p1
+ p = Vector(p0)
+ x0, y0 = p.x, p.y
+ p = Vector(p1)
+ x1, y1 = p.x, p.y
if x0 > x1:
x1, x0 = x0, x1
if y0 > y1:
diff --git a/archipack/archipack_handle.py b/archipack/archipack_handle.py
index fcdb570e..27da6b0b 100644
--- a/archipack/archipack_handle.py
+++ b/archipack/archipack_handle.py
@@ -26,23 +26,24 @@
# ----------------------------------------------------------
import bpy
+from .archipack_object import ArchipackCollectionManager
def create_handle(context, parent, mesh):
old = context.active_object
handle = bpy.data.objects.new("Handle", mesh)
handle['archipack_handle'] = True
- context.scene.objects.link(handle)
+ ArchipackCollectionManager.link_object_to_scene(context, handle)
modif = handle.modifiers.new('Subsurf', 'SUBSURF')
modif.render_levels = 4
modif.levels = 1
handle.parent = parent
handle.matrix_world = parent.matrix_world.copy()
- context.scene.objects.active = handle
+ context.view_layer.objects.active = handle
m = handle.archipack_material.add()
m.category = 'handle'
m.material = 'DEFAULT'
- context.scene.objects.active = old
+ context.view_layer.objects.active = old
return handle
diff --git a/archipack/archipack_keymaps.py b/archipack/archipack_keymaps.py
index c4c8176e..537833ee 100644
--- a/archipack/archipack_keymaps.py
+++ b/archipack/archipack_keymaps.py
@@ -24,6 +24,7 @@
# Author: Stephen Leger (s-leger)
#
# ----------------------------------------------------------
+import bpy
class Keymaps:
@@ -51,7 +52,9 @@ class Keymaps:
"""
# provide abstration between user and addon
# with different select mouse side
- mouse_right = context.user_preferences.inputs.select_mouse
+ wm = context.window_manager
+ keyconfig = wm.keyconfigs.active
+ mouse_right = getattr(keyconfig.preferences, "select_mouse", "LEFT")
if mouse_right == 'LEFT':
mouse_left = 'RIGHT'
mouse_right_side = 'Left'
@@ -79,6 +82,10 @@ class Keymaps:
event: simple event signature to compare like :
if event == keymap.undo.event:
"""
+ # Headless mode fails without this check
+ if bpy.app.background:
+ return {'type': None, 'event':(False, False, False, False, None, None)}
+
ev = context.window_manager.keyconfigs.user.keymaps[keyconfig].keymap_items[keymap_item]
key = ev.type
if ev.ctrl:
@@ -89,6 +96,7 @@ class Keymaps:
key += '+SHIFT'
return {'type': key, 'name': ev.name, 'event': (ev.alt, ev.ctrl, ev.shift, ev.type, ev.value)}
+
def dump_keys(self, context, filename="c:\\tmp\\keymap.txt"):
"""
Utility for developers :
diff --git a/archipack/archipack_manipulator.py b/archipack/archipack_manipulator.py
index 4e916240..be6c2ef2 100644
--- a/archipack/archipack_manipulator.py
+++ b/archipack/archipack_manipulator.py
@@ -30,7 +30,13 @@ from mathutils import Vector, Matrix
from mathutils.geometry import intersect_line_plane, intersect_point_line, intersect_line_sphere
from bpy_extras import view3d_utils
from bpy.types import PropertyGroup, Operator
-from bpy.props import FloatVectorProperty, StringProperty, CollectionProperty, BoolProperty
+from bpy.props import (
+ FloatVectorProperty,
+ StringProperty,
+ CollectionProperty,
+ BoolProperty
+)
+
from bpy.app.handlers import persistent
from .archipack_snap import snap_point
from .archipack_keymaps import Keymaps
@@ -67,14 +73,16 @@ from .archipack_gl import (
# (manips[key].manipulable.manip_stack)
# Must investigate for a way to handle unselect after drag done.
+
+import logging
+logger = logging.getLogger("archipack")
+
+
"""
- @TODO:
- Last modal running wins.
- Manipulateurs without snap and thus not running own modal,
- may loose events events caught by select mode of last
- manipulable enabled
+ Change object location when moving 1 point
+ When False, change data.origin instead
"""
-
+USE_MOVE_OBJECT = True
# Arrow sizes (world units)
arrow_size = 0.05
# Handle area size (pixels)
@@ -445,7 +453,7 @@ class Manipulator():
ret = False
self.keyboard_cancel(context, event)
pass
- context.area.header_text_set("")
+ context.area.header_text_set(None)
self.keyboard_input_active = False
self.feedback.disable()
return ret
@@ -469,7 +477,7 @@ class Manipulator():
view_vector_mouse = view3d_utils.region_2d_to_vector_3d(region, rv3d, self.mouse_pos)
ray_origin_mouse = view3d_utils.region_2d_to_origin_3d(region, rv3d, self.mouse_pos)
pt = intersect_line_plane(ray_origin_mouse, ray_origin_mouse + view_vector_mouse,
- self.origin, rM * self.manipulator.normal, False)
+ self.origin, rM @ self.manipulator.normal, False)
# fix issue with parallel plane
if pt is None:
pt = intersect_line_plane(ray_origin_mouse, ray_origin_mouse + view_vector_mouse,
@@ -495,18 +503,19 @@ class Manipulator():
"""
try:
if self.get_value(data, attr, index) != value:
+ o = self.o
# switch context so unselected object may be manipulable too
- old = context.active_object
- state = self.o.select
- self.o.select = True
- context.scene.objects.active = self.o
+ old = context.object
+ state = o.select_get()
+ o.select_set(state=True)
+ context.view_layer.objects.active = o
if index > -1:
getattr(data, attr)[index] = value
else:
setattr(data, attr, value)
- self.o.select = state
- old.select = True
- context.scene.objects.active = old
+ o.select_set(state=state)
+ old.select_set(state=True)
+ context.view_layer.objects.active = old
except:
pass
@@ -516,11 +525,7 @@ class Manipulator():
tM Matrix source
vec Vector translation
"""
- return tM * Matrix([
- [1, 0, 0, vec.x],
- [0, 1, 0, vec.y],
- [0, 0, 1, vec.z],
- [0, 0, 0, 1]])
+ return tM @ Matrix.Translation(vec)
def _move(self, o, axis, value):
if axis == 'x':
@@ -538,15 +543,15 @@ class Manipulator():
"""
old = context.active_object
bpy.ops.object.select_all(action='DESELECT')
- self.o.select = True
- context.scene.objects.active = self.o
+ self.o.select_set(state=True)
+ context.view_layer.objects.active = self.o
bpy.ops.object.select_linked(type='OBDATA')
for o in context.selected_objects:
if o != self.o:
self._move(o, axis, value)
bpy.ops.object.select_all(action='DESELECT')
- old.select = True
- context.scene.objects.active = old
+ old.select_set(state=True)
+ context.view_layer.objects.active = old
def move(self, context, axis, value):
"""
@@ -555,68 +560,6 @@ class Manipulator():
self._move(self.o, axis, value)
-# OUT OF ORDER
-class SnapPointManipulator(Manipulator):
- """
- np_station based snap manipulator
- dosent update anything by itself.
- NOTE : currently out of order
- and disabled in __init__
- """
- def __init__(self, context, o, datablock, manipulator, handle_size, snap_callback=None):
-
- raise NotImplementedError
-
- self.handle = SquareHandle(handle_size, 1.2 * arrow_size, draggable=True)
- Manipulator.__init__(self, context, o, datablock, manipulator, snap_callback)
-
- def check_hover(self):
- self.handle.check_hover(self.mouse_pos)
-
- def mouse_press(self, context, event):
- if self.handle.hover:
- self.handle.hover = False
- self.handle.active = True
- self.o.select = True
- # takeloc = self.o.matrix_world * self.manipulator.p0
- # print("Invoke sp_point_move %s" % (takeloc))
- # @TODO:
- # implement and add draw and callbacks
- # snap_point(takeloc, draw, callback)
- return True
- return False
-
- def mouse_release(self, context, event):
- self.check_hover()
- self.handle.active = False
- # False to callback manipulable_release
- return False
-
- def update(self, context, event):
- # NOTE:
- # dosent set anything internally
- return
-
- def mouse_move(self, context, event):
- """
-
- """
- self.mouse_position(event)
- if self.handle.active:
- # self.handle.active = np_snap.is_running
- # self.update(context)
- # True here to callback manipulable_manipulate
- return True
- else:
- self.check_hover()
- return False
-
- def draw_callback(self, _self, context, render=False):
- left, right, side, normal = self.manipulator.get_pts(self.o.matrix_world)
- self.handle.set_pos(context, left, Vector((1, 0, 0)), normal=normal)
- self.handle.draw(context, render)
-
-
# Generic snap tool for line based archipack objects (fence, wall, maybe stair too)
gl_pts3d = []
@@ -682,7 +625,7 @@ class WallSnapManipulator(Manipulator):
])
self.feedback.enable()
self.handle.hover = False
- self.o.select = True
+ self.o.select_set(state=True)
takeloc, right, side, dz = self.manipulator.get_pts(self.o.matrix_world)
dx = (right - takeloc).normalized()
dy = dz.cross(dx)
@@ -712,20 +655,20 @@ class WallSnapManipulator(Manipulator):
np station callback on moving, place, or cancel
"""
global gl_pts3d
+ logger.debug("WallSnapManipulator.sp_callback")
if state == 'SUCCESS':
-
- self.o.select = True
+ o = self.o
+ o.select_set(state=True)
+ context.view_layer.objects.active = o
# apply changes to wall
d = self.datablock
- d.auto_update = False
-
g = d.get_generator()
# rotation relative to object
- rM = self.o.matrix_world.inverted().to_3x3()
- delta = (rM * sp.delta).to_2d()
- # x_axis = (rM * Vector((1, 0, 0))).to_2d()
+ rM = o.matrix_world.inverted().to_3x3()
+ delta =rM @ sp.delta
+ # x_axis = (rM @ Vector((1, 0, 0))).to_2d()
# update generator
idx = 0
@@ -734,7 +677,7 @@ class WallSnapManipulator(Manipulator):
if selected:
# new location in object space
- pt = g.segs[idx].lerp(0) + delta
+ pt = g.segs[idx].lerp(0) + delta.to_2d()
# move last point of segment before current
if idx > 0:
@@ -747,6 +690,7 @@ class WallSnapManipulator(Manipulator):
# update properties from generator
idx = 0
+ d.auto_update = False
for p0, p1, selected in gl_pts3d:
if selected:
@@ -759,7 +703,7 @@ class WallSnapManipulator(Manipulator):
if idx > 1:
part.a0 = w.delta_angle(g.segs[idx - 2])
else:
- part.a0 = w.straight(1, 0).angle
+ part.a0 = w.a0
if "C_" in part.type:
part.radius = w.r
@@ -773,10 +717,14 @@ class WallSnapManipulator(Manipulator):
if idx > 0:
part.a0 = w.delta_angle(g.segs[idx - 1])
else:
- part.a0 = w.straight(1, 0).angle
+ part.a0 = w.a0
# move object when point 0
- self.o.location += sp.delta
- self.o.matrix_world.translation += sp.delta
+ if USE_MOVE_OBJECT:
+ d.move_object(o, o.matrix_world.translation + sp.delta)
+ # self.o.location += sp.delta
+ # self.o.matrix_world.translation += sp.delta
+ else:
+ d.origin += sp.delta
if "C_" in part.type:
part.radius = w.r
@@ -790,15 +738,20 @@ class WallSnapManipulator(Manipulator):
idx += 1
self.mouse_release(context, event)
+ if hasattr(d, "relocate_childs"):
+ d.relocate_childs(context, o, g)
d.auto_update = True
+ d.update(context)
if state == 'CANCEL':
self.mouse_release(context, event)
+ logger.debug("WallSnapManipulator.sp_callback done")
return
def sp_draw(self, sp, context):
# draw wall placeholders
+ logger.debug("WallSnapManipulator.sp_draw")
global gl_pts3d
@@ -842,6 +795,7 @@ class WallSnapManipulator(Manipulator):
self.label.set_pos(context, self.line.length, self.line.lerp(0.5), self.line.v, normal=Vector((0, 0, 1)))
self.line.draw(context, render=False)
self.label.draw(context, render=False)
+ logger.debug("WallSnapManipulator.sp_draw done")
def mouse_move(self, context, event):
self.mouse_position(event)
@@ -1002,7 +956,6 @@ class SizeManipulator(Manipulator):
])
gl_pts3d = [left, right]
snap_point(takemat=takemat,
- draw=self.sp_draw,
callback=self.sp_callback,
constraint_axis=(True, False, False))
self.handle_right.active = True
@@ -1085,32 +1038,31 @@ class SizeManipulator(Manipulator):
self.label.draw(context, render)
self.feedback.draw(context, render)
- def sp_draw(self, sp, context):
+ def sp_callback(self, context, event, state, sp):
+ logger.debug("SizeManipulator.sp_callback")
global gl_pts3d
- if self.o is None:
- return
+
p0 = gl_pts3d[0].copy()
p1 = gl_pts3d[1].copy()
- p1 += sp.delta
- self.sp_update(context, p0, p1)
- return
- def sp_callback(self, context, event, state, sp):
+ if state != 'CANCEL':
+ p1 += sp.delta
- if state == 'SUCCESS':
- self.sp_draw(sp, context)
- self.mouse_release(context, event)
+ length = (p0 - p1).length
- if state == 'CANCEL':
- p0 = gl_pts3d[0].copy()
- p1 = gl_pts3d[1].copy()
- self.sp_update(context, p0, p1)
- self.mouse_release(context, event)
+ if state != 'CANCEL' and event.alt:
+ if event.shift:
+ length = round(length, 2)
+ else:
+ length = round(length, 1)
- def sp_update(self, context, p0, p1):
- length = (p0 - p1).length
self.set_value(context, self.datablock, self.manipulator.prop1_name, length)
+ if state != 'RUNNING':
+ self.mouse_release(context, event)
+
+ logger.debug("SizeManipulator.sp_callback done")
+
class SizeLocationManipulator(SizeManipulator):
"""
@@ -1190,7 +1142,7 @@ class SizeLocationManipulator(SizeManipulator):
self.mouse_release(context, event)
# must move back to original location
itM = self.o.matrix_world.inverted()
- dl = self.get_value(itM * self.original_location, self.manipulator.prop2_name)
+ dl = self.get_value(itM @ self.original_location, self.manipulator.prop2_name)
self.move(context, self.manipulator.prop2_name, dl)
self.set_value(context, self.datablock, self.manipulator.prop1_name, self.original_size)
@@ -1263,7 +1215,6 @@ class SnapSizeLocationManipulator(SizeLocationManipulator):
])
gl_pts3d = [left, right]
snap_point(takemat=takemat,
- draw=self.sp_draw,
callback=self.sp_callback,
constraint_axis=(True, False, False))
@@ -1290,7 +1241,6 @@ class SnapSizeLocationManipulator(SizeLocationManipulator):
])
gl_pts3d = [left, right]
snap_point(takemat=takemat,
- draw=self.sp_draw,
callback=self.sp_callback,
constraint_axis=(True, False, False))
self.handle_left.active = True
@@ -1305,50 +1255,50 @@ class SnapSizeLocationManipulator(SizeLocationManipulator):
return False
- def sp_draw(self, sp, context):
+ def sp_callback(self, context, event, state, sp):
+ logger.debug("SnapSizeLocationManipulator.sp_callback")
global gl_pts3d
- if self.o is None:
- return
p0 = gl_pts3d[0].copy()
p1 = gl_pts3d[1].copy()
- if self.handle_right.active:
- p1 += sp.delta
- else:
- p0 += sp.delta
- self.sp_update(context, p0, p1)
-
- # snapping child objects may require base object update
- # eg manipulating windows requiring wall update
- if self.snap_callback is not None:
- snap_helper = context.active_object
- self.snap_callback(context, o=self.o, manipulator=self)
- context.scene.objects.active = snap_helper
-
- return
-
- def sp_callback(self, context, event, state, sp):
- if state == 'SUCCESS':
- self.sp_draw(sp, context)
- self.mouse_release(context, event)
-
- if state == 'CANCEL':
- p0 = gl_pts3d[0].copy()
- p1 = gl_pts3d[1].copy()
- self.sp_update(context, p0, p1)
- self.mouse_release(context, event)
+ if state != 'CANCEL':
+ if self.handle_right.active:
+ p1 += sp.delta
+ else:
+ p0 += sp.delta
- def sp_update(self, context, p0, p1):
l0 = self.get_value(self.datablock, self.manipulator.prop1_name)
length = (p0 - p1).length
+
+ if state != 'CANCEL' and event.alt:
+ if event.shift:
+ length = round(length, 2)
+ else:
+ length = round(length, 1)
+
dp = length - l0
+
if self.handle_left.active:
dp = -dp
dl = 0.5 * dp
+
+ # snap_helper = context.object
self.move(context, self.manipulator.prop2_name, dl)
self.set_value(context, self.datablock, self.manipulator.prop1_name, length)
self.move_linked(context, self.manipulator.prop2_name, dl)
+ # snapping child objects may require base object update
+ # eg manipulating windows requiring wall update
+ if self.snap_callback is not None:
+ snap_helper = context.active_object
+ self.snap_callback(context, o=self.o, manipulator=self)
+ snap_helper.select_set(state=True)
+
+ if state != 'RUNNING':
+ self.mouse_release(context, event)
+
+ logger.debug("SnapSizeLocationManipulator.sp_callback done")
+
class DeltaLocationManipulator(SizeManipulator):
"""
@@ -1388,7 +1338,6 @@ class DeltaLocationManipulator(SizeManipulator):
])
gl_pts3d = [p0]
snap_point(takemat=takemat,
- draw=self.sp_draw,
callback=self.sp_callback,
constraint_axis=(
self.manipulator.prop1_name == 'x',
@@ -1413,40 +1362,37 @@ class DeltaLocationManipulator(SizeManipulator):
self.check_hover()
return False
- def sp_draw(self, sp, context):
- global gl_pts3d
- if self.o is None:
- return
- p0 = gl_pts3d[0].copy()
- p1 = p0 + sp.delta
- itM = self.o.matrix_world.inverted()
- dl = self.get_value(itM * p1, self.manipulator.prop1_name)
- self.move(context, self.manipulator.prop1_name, dl)
-
- # snapping child objects may require base object update
- # eg manipulating windows requiring wall update
- if self.snap_callback is not None:
- snap_helper = context.active_object
- self.snap_callback(context, o=self.o, manipulator=self)
- context.scene.objects.active = snap_helper
-
- return
-
def sp_callback(self, context, event, state, sp):
-
- if state == 'SUCCESS':
- self.sp_draw(sp, context)
- self.mouse_release(context, event)
+ logger.debug("DeltaLocationManipulator.sp_callback")
if state == 'CANCEL':
self.cancel(context, event)
+ else:
+ global gl_pts3d
+ p0 = gl_pts3d[0].copy()
+ p1 = p0 + sp.delta
+ itM = self.o.matrix_world.inverted()
+ dl = self.get_value(itM @ p1, self.manipulator.prop1_name)
+ self.move(context, self.manipulator.prop1_name, dl)
+
+ # snapping child objects may require base object update
+ # eg manipulating windows requiring wall update
+ if self.snap_callback is not None:
+ snap_helper = context.active_object
+ self.snap_callback(context, o=self.o, manipulator=self)
+ snap_helper.select_set(state=True)
+
+ if state == 'SUCCESS':
+ self.mouse_release(context, event)
+
+ logger.debug("DeltaLocationManipulator.sp_callback done")
def cancel(self, context, event):
if self.active:
self.mouse_release(context, event)
# must move back to original location
itM = self.o.matrix_world.inverted()
- dl = self.get_value(itM * self.original_location, self.manipulator.prop1_name)
+ dl = self.get_value(itM @ self.original_location, self.manipulator.prop1_name)
self.move(context, self.manipulator.prop1_name, dl)
def draw_callback(self, _self, context, render=False):
@@ -1963,22 +1909,22 @@ class archipack_manipulator(PropertyGroup):
p0, p1, p2 3d Vectors as base points to represent manipulators on screen
normal Vector normal of plane on with draw manipulator
"""
- type_key = StringProperty(default='SIZE')
+ type_key : StringProperty(default='SIZE')
# How 3d points are stored in manipulators ?
# SIZE = 2 absolute positioned and a scaling vector
# RADIUS = 1 absolute positioned (center) and 2 relatives (sides)
# POLYGON = 2 absolute positioned and a relative vector (for rect polygons)
- pts_mode = StringProperty(default='SIZE')
- prop1_name = StringProperty()
- prop2_name = StringProperty()
- p0 = FloatVectorProperty(subtype='XYZ')
- p1 = FloatVectorProperty(subtype='XYZ')
- p2 = FloatVectorProperty(subtype='XYZ')
+ pts_mode : StringProperty(default='SIZE')
+ prop1_name : StringProperty()
+ prop2_name : StringProperty()
+ p0 : FloatVectorProperty(subtype='XYZ')
+ p1 : FloatVectorProperty(subtype='XYZ')
+ p2 : FloatVectorProperty(subtype='XYZ')
# allow orientation of manipulators by default on xy plane,
# but may be used to constrain heights on local object space
- normal = FloatVectorProperty(subtype='XYZ', default=(0, 0, 1))
+ normal : FloatVectorProperty(subtype='XYZ', default=(0, 0, 1))
def set_pts(self, pts, normal=None):
"""
@@ -1999,9 +1945,9 @@ class archipack_manipulator(PropertyGroup):
"""
rM = tM.to_3x3()
if self.pts_mode in ['SIZE', 'POLYGON']:
- return tM * self.p0, tM * self.p1, self.p2, rM * self.normal
+ return tM @ self.p0, tM @ self.p1, self.p2, rM @ self.normal
else:
- return tM * self.p0, rM * self.p1, rM * self.p2, rM * self.normal
+ return tM @ self.p0, rM @ self.p1, rM @ self.p2, rM @ self.normal
def get_prefs(self, context):
global __name__
@@ -2053,7 +1999,7 @@ class ARCHIPACK_OT_manipulate(Operator):
bl_description = "Manipulate"
bl_options = {'REGISTER', 'UNDO'}
- object_name = StringProperty(default="")
+ object_name : StringProperty(default="")
@classmethod
def poll(self, context):
@@ -2123,38 +2069,39 @@ class Manipulable():
Beware : prevent crash calling manipulable_disable()
before changing manipulated data structure
"""
- manipulators = CollectionProperty(
+ manipulators : CollectionProperty(
type=archipack_manipulator,
# options={'SKIP_SAVE'},
# options={'HIDDEN'},
description="store 3d points to draw gl manipulators"
)
- manipulable_refresh = BoolProperty(
+
+ # TODO: make simple instance vars
+ manipulable_refresh : BoolProperty(
default=False,
options={'SKIP_SAVE'},
description="Flag enable to rebuild manipulators when data model change"
)
- manipulate_mode = BoolProperty(
+ manipulate_mode : BoolProperty(
default=False,
options={'SKIP_SAVE'},
description="Flag manipulation state so we are able to toggle"
)
- select_mode = BoolProperty(
+ select_mode : BoolProperty(
default=False,
options={'SKIP_SAVE'},
description="Flag select state so we are able to toggle"
)
- manipulable_selectable = BoolProperty(
+ manipulable_selectable : BoolProperty(
default=False,
options={'SKIP_SAVE'},
description="Flag make manipulators selectable"
)
- keymap = None
- # selectable manipulators
- manipulable_area = GlCursorArea()
- manipulable_start_point = Vector((0, 0))
- manipulable_end_point = Vector((0, 0))
+ keymap = None
+ manipulable_area = None
+ manipulable_start_point = None
+ manipulable_end_point = None
manipulable_draw_handler = None
def setup_manipulators(self):
@@ -2171,6 +2118,13 @@ class Manipulable():
"""
disable gl draw handlers
"""
+
+ if self.keymap is None:
+ self.keymap = Keymaps(context)
+ self.manipulable_area = GlCursorArea()
+ self.manipulable_start_point = Vector((0, 0))
+ self.manipulable_end_point = Vector((0, 0))
+
o = context.active_object
if o is not None:
self.manipulable_exit_selectmode(context)
@@ -2259,6 +2213,7 @@ class Manipulable():
as it provide all needed
functionality out of the box
"""
+
# setup again when manipulators type change
if self.manipulable_refresh:
# print("manipulable_refresh")
diff --git a/archipack/archipack_material.py b/archipack/archipack_material.py
index ddb44009..cbec80cd 100644
--- a/archipack/archipack_material.py
+++ b/archipack/archipack_material.py
@@ -181,7 +181,7 @@ class MatlibsManager():
def apply(self, context, slot_index, name, link=False):
o = context.active_object
- o.select = True
+ o.select_set(state=True)
# material with same name exist in scene
mat = self.from_data(name)
@@ -225,6 +225,8 @@ class MaterialSetManager():
self.objects = {}
# hold reference of dynamic enumerator
self.enums = {}
+ self.default_enum = [('DEFAULT', 'Default', '', 0)]
+
def get_filename(self, object_type):
@@ -262,9 +264,9 @@ class MaterialSetManager():
material_sets = {}
# create file object, and set open mode
- if os.path.exists(filename):
- try:
- f = open(filename, 'r')
+
+ try:
+ with open(filename, 'r') as f:
lines = f.readlines()
for line in lines:
@@ -272,17 +274,15 @@ class MaterialSetManager():
if str(s_key) not in material_sets.keys():
material_sets[s_key] = []
material_sets[s_key].append(mat_name.strip())
- except:
- print("Archipack: An error occurred while loading {}".format(filename))
- pass
- finally:
- f.close()
+ except:
+ print("Archipack: material preset for {} not found".format(object_type))
+ pass
- s_keys = material_sets.keys()
- for s_key in s_keys:
- self.register_set(object_type, s_key, material_sets[s_key])
+ s_keys = material_sets.keys()
+ for s_key in s_keys:
+ self.register_set(object_type, s_key, material_sets[s_key])
- self.make_enum(object_type, s_keys)
+ self.make_enum(object_type, s_keys)
def save(self, object_type):
# always save in user prefs
@@ -330,17 +330,15 @@ class MaterialSetManager():
if object_type not in self.objects.keys():
self.load(object_type)
if object_type not in self.objects.keys():
- print("Archipack: Unknown object type {}".format(object_type))
+ # print("Archipack: Unknown object type {}".format(object_type))
return None
if set_name not in self.objects[object_type].keys():
- print("Archipack: set {} not found".format(set_name))
+ # print("Archipack: set {} not found".format(set_name))
return None
return self.objects[object_type][set_name]
def make_enum(self, object_type, s_keys):
- if len(s_keys) < 1:
- self.enums[object_type] = [('DEFAULT', 'Default', '', 0)]
- else:
+ if len(s_keys) > 0:
self.enums[object_type] = [(s.upper(), s.capitalize(), '', i) for i, s in enumerate(s_keys)]
def get_enum(self, object_type):
@@ -351,7 +349,10 @@ class MaterialSetManager():
if object_type not in self.objects.keys():
self.objects[object_type] = {}
- return self.enums[object_type]
+ if object_type in self.enums:
+ return self.enums[object_type]
+
+ return self.default_enum
def material_enum(self, context):
@@ -367,12 +368,12 @@ def update(self, context):
class archipack_material(PropertyGroup):
- category = StringProperty(
+ category : StringProperty(
name="Category",
description="Archipack object name",
default=""
)
- material = EnumProperty(
+ material : EnumProperty(
name="Material",
description="Material Set name",
items=material_enum,
@@ -411,17 +412,17 @@ class archipack_material(PropertyGroup):
mats = setman.get_materials(self.category, self.material)
- if mats is None:
+ if mats is None or len(mats) < 1:
return False
for ob in sel:
- context.scene.objects.active = ob
+ context.view_layer.objects.active = ob
for slot_index, mat_name in enumerate(mats):
if slot_index >= len(ob.material_slots):
bpy.ops.object.material_slot_add()
self.apply_material(context, slot_index, mat_name)
- context.scene.objects.active = o
+ context.view_layer.objects.active = o
return True
@@ -431,7 +432,7 @@ class ARCHIPACK_PT_material(Panel):
bl_label = "Archipack Material"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
- # bl_category = 'ArchiPack'
+ bl_category = 'Archipack'
@classmethod
def poll(cls, context):
@@ -442,8 +443,8 @@ class ARCHIPACK_PT_material(Panel):
props = context.active_object.archipack_material[0]
row = layout.row(align=True)
row.prop(props, 'material', text="")
- row.operator('archipack.material_add', icon="ZOOMIN", text="")
- row.operator('archipack.material_remove', icon="ZOOMOUT", text="")
+ row.operator('archipack.material_add', icon="ADD", text="")
+ row.operator('archipack.material_remove', icon="REMOVE", text="")
class ARCHIPACK_OT_material(Operator):
@@ -452,12 +453,12 @@ class ARCHIPACK_OT_material(Operator):
bl_description = "Add archipack material"
bl_options = {'REGISTER', 'UNDO'}
- category = StringProperty(
+ category : StringProperty(
name="Category",
description="Archipack object name",
default=""
)
- material = StringProperty(
+ material : StringProperty(
name="Material",
description="Material Set name",
default=""
@@ -484,13 +485,11 @@ class ARCHIPACK_OT_material(Operator):
res = False
pass
- if res:
- # print("ARCHIPACK_OT_material.apply {} {}".format(self.category, self.material))
- return {'FINISHED'}
- else:
+ if not res:
print("Archipack: unable to add material {} for {}".format(self.material, self.category))
- self.report({'WARNING'}, 'Material {} for {} not found'.format(self.material, self.category))
- return {'CANCELLED'}
+ # self.report({'WARNING'}, 'Material {} for {} not found'.format(self.material, self.category))
+
+ return {'FINISHED'}
class ARCHIPACK_OT_material_add(Operator):
@@ -499,7 +498,7 @@ class ARCHIPACK_OT_material_add(Operator):
bl_description = "Add a set of archipack material"
bl_options = {'REGISTER', 'UNDO'}
- material = StringProperty(
+ material : StringProperty(
name="Material",
description="Material Set name",
default=""
@@ -603,5 +602,5 @@ def unregister():
bpy.utils.unregister_class(ARCHIPACK_OT_material_add)
bpy.utils.unregister_class(ARCHIPACK_OT_material_remove)
bpy.utils.unregister_class(ARCHIPACK_OT_material_library)
- bpy.utils.unregister_class(archipack_material)
del Object.archipack_material
+ bpy.utils.unregister_class(archipack_material)
diff --git a/archipack/archipack_object.py b/archipack/archipack_object.py
index b479ea30..8c908214 100644
--- a/archipack/archipack_object.py
+++ b/archipack/archipack_object.py
@@ -38,7 +38,23 @@ from bpy_extras.view3d_utils import (
)
-class ArchipackObject():
+class ArchipackCollectionManager():
+
+ @staticmethod
+ def link_object_to_scene(context, o):
+ coll_main = context.scene.collection.children.get("Archipack")
+ if coll_main is None:
+ coll_main = bpy.data.collections.new(name="Archipack")
+ context.scene.collection.children.link(coll_main)
+ coll_main.objects.link(o)
+
+ @staticmethod
+ def unlink_object_from_scene(o):
+ for coll in o.users_collection:
+ coll.objects.unlink(o)
+
+
+class ArchipackObject(ArchipackCollectionManager):
"""
Shared property of archipack's objects PropertyGroup
provide basic support for copy to selected
@@ -100,6 +116,7 @@ class ArchipackObject():
selected = context.selected_objects[:]
for o in selected:
+
if self.__class__.datablock(o) == self:
self.previously_selected = selected
self.previously_active = active
@@ -113,27 +130,41 @@ class ArchipackObject():
try:
for o in self.previously_selected:
- o.select = True
+ o.select_set(state=True)
except:
pass
if self.previously_active is not None:
- self.previously_active.select = True
- context.scene.objects.active = self.previously_active
+ self.previously_active.select_set(state=True)
+ context.view_layer.objects.active = self.previously_active
self.previously_selected = None
self.previously_active = None
+ def move_object(self, o, p):
+ """
+ When firstpoint is moving we must move object according
+ p is new x, y location in world coordsys
+ """
+ p = Vector((p.x, p.y, o.matrix_world.translation.z))
+ # p is in o coordsys
+ if o.parent:
+ o.location = p @ o.parent.matrix_world.inverted()
+ o.matrix_world.translation = p
+ else:
+ o.location = p
+ o.matrix_world.translation = p
+
-class ArchipackCreateTool():
+class ArchipackCreateTool(ArchipackCollectionManager):
"""
Shared property of archipack's create tool Operator
"""
- auto_manipulate = BoolProperty(
+ auto_manipulate : BoolProperty(
name="Auto manipulate",
description="Enable object's manipulators after create",
options={'SKIP_SAVE'},
default=True
)
- filepath = StringProperty(
+ filepath : StringProperty(
options={'SKIP_SAVE'},
name="Preset",
description="Full filename of python preset to load at create time",
@@ -164,7 +195,10 @@ class ArchipackCreateTool():
if fallback:
# fallback to load preset on background process
try:
- exec(compile(open(self.filepath).read(), self.filepath, 'exec'))
+ with open(self.filepath) as f:
+ lines = f.read()
+ cmp = compile(lines, self.filepath, 'exec')
+ exec(cmp)
except:
print("Archipack unable to load preset file : %s" % (self.filepath))
pass
@@ -194,7 +228,7 @@ class ArchipackCreateTool():
pass
-class ArchpackDrawTool():
+class ArchipackDrawTool(ArchipackCollectionManager):
"""
Draw tools
"""
@@ -225,8 +259,9 @@ class ArchpackDrawTool():
view_vector_mouse = region_2d_to_vector_3d(region, rv3d, co2d)
ray_origin_mouse = region_2d_to_origin_3d(region, rv3d, co2d)
res, pos, normal, face_index, object, matrix_world = context.scene.ray_cast(
- ray_origin_mouse,
- view_vector_mouse)
+ view_layer=context.view_layer,
+ origin=ray_origin_mouse,
+ direction=view_vector_mouse)
return res, pos, normal, face_index, object, matrix_world
def mouse_hover_wall(self, context, event):
@@ -245,5 +280,5 @@ class ArchpackDrawTool():
[x.y, y.y, z.y, pt.y],
[x.z, y.z, z.z, o.matrix_world.translation.z],
[0, 0, 0, 1]
- ]), o, y
- return False, Matrix(), None, Vector()
+ ]), o, d.width, y, 0 # d.z_offset
+ return False, Matrix(), None, 0, Vector(), 0
diff --git a/archipack/archipack_preset.py b/archipack/archipack_preset.py
index dcaa7970..8f1468b2 100644
--- a/archipack/archipack_preset.py
+++ b/archipack/archipack_preset.py
@@ -170,7 +170,9 @@ class PresetMenuItem():
def __init__(self, thumbsize, preset, image=None):
name = bpy.path.display_name_from_filepath(preset)
self.preset = preset
- self.handle = ThumbHandle(thumbsize, name, image, draggable=True)
+ self.image = image
+ self.image.gl_load()
+ self.handle = ThumbHandle(thumbsize, name, self.image, draggable=True)
self.enable = True
def filter(self, keywords):
@@ -179,6 +181,11 @@ class PresetMenuItem():
return False
return True
+ def cleanup(self):
+ if self.image:
+ self.image.gl_free()
+ # bpy.data.images.remove(self.image)
+
def set_pos(self, context, pos):
self.handle.set_pos(context, pos)
@@ -223,7 +230,6 @@ class PresetMenu():
self.border = GlPolyline((0.7, 0.7, 0.7, 1), d=2)
self.keywords = SeekBox()
self.keywords.colour_normal = (1, 1, 1, 1)
-
self.border.closed = True
self.set_pos(context)
@@ -268,12 +274,15 @@ class PresetMenu():
return file_list
def clearImages(self):
+ for item in self.menuItems:
+ item.cleanup()
for image in bpy.data.images:
if image.filepath_raw in self.imageList:
# image.user_clear()
bpy.data.images.remove(image, do_unlink=True)
self.imageList.clear()
+
def make_menuitem(self, filepath):
"""
@TODO:
@@ -377,7 +386,7 @@ class PresetMenu():
class PresetMenuOperator():
- preset_operator = StringProperty(
+ preset_operator : StringProperty(
options={'SKIP_SAVE'},
default="script.execute_preset"
)
@@ -414,7 +423,11 @@ class PresetMenuOperator():
if self.preset_operator == 'script.execute_preset':
# call from preset menu
# ensure right active_object class
- d = getattr(bpy.types, self.preset_subdir).datablock(context.active_object)
+ o = context.active_object
+ if o.data and self.preset_subdir in o.data:
+ d = getattr(o.data, self.preset_subdir)[0]
+ elif self.preset_subdir in o:
+ d = getattr(o, self.preset_subdir)[0]
if d is not None:
d.auto_update = False
# print("Archipack execute_preset loading auto_update:%s" % d.auto_update)
@@ -525,17 +538,22 @@ class ArchipackPreset(AddPresetBase):
def background_render(self, context, cls, preset):
generator = os.path.dirname(os.path.realpath(__file__)) + os.path.sep + "archipack_thumbs.py"
+ addon_name = __name__.split('.')[0]
+ matlib_path = context.user_preferences.addons[addon_name].preferences.matlib_path
# Run external instance of blender like the original thumbnail generator.
cmd = [
bpy.app.binary_path,
"--background",
+ "--factory-startup",
"-noaudio",
+ # "--addons", addon_name,
"--python", generator,
"--",
+ "addon:" + addon_name,
+ "matlib:" + matlib_path,
"cls:" + cls,
"preset:" + preset
- ]
- # print(repr(cmd))
+ ]
subprocess.Popen(cmd)
@@ -555,7 +573,7 @@ class ArchipackPreset(AddPresetBase):
preset = os.path.join(target_path, filename) + ".py"
cls = self.preset_subdir[10:]
- print("post cb cls:%s preset:%s" % (cls, preset))
+ # print("post cb cls:%s preset:%s" % (cls, preset))
self.background_render(context, cls, preset)
return
diff --git a/archipack/archipack_reference_point.py b/archipack/archipack_reference_point.py
index 2f58c088..0b0c0348 100644
--- a/archipack/archipack_reference_point.py
+++ b/archipack/archipack_reference_point.py
@@ -34,6 +34,7 @@ from bpy.props import (
)
from mathutils import Vector
from .bmesh_utils import BmeshEdit as bmed
+from .archipack_object import ArchipackCollectionManager
def update(self, context):
@@ -41,22 +42,22 @@ def update(self, context):
class archipack_reference_point(PropertyGroup):
- location_2d = FloatVectorProperty(
+ location_2d : FloatVectorProperty(
subtype='XYZ',
name="position 2d",
default=Vector((0, 0, 0))
)
- location_3d = FloatVectorProperty(
+ location_3d : FloatVectorProperty(
subtype='XYZ',
name="position 3d",
default=Vector((0, 0, 0))
)
- symbol_scale = FloatProperty(
+ symbol_scale : FloatProperty(
name="Screen scale",
default=1,
min=0.01,
update=update)
- symbol_type = EnumProperty(
+ symbol_type : EnumProperty(
name="Symbol type",
default='WALL',
items=(
@@ -177,7 +178,7 @@ class ARCHIPACK_PT_reference_point(Panel):
bl_label = "Reference point"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
- bl_category = 'ArchiPack'
+ bl_category = 'Archipack'
@classmethod
def poll(cls, context):
@@ -199,19 +200,19 @@ class ARCHIPACK_PT_reference_point(Panel):
layout.operator('archipack.apply_holes')
-class ARCHIPACK_OT_reference_point(Operator):
+class ARCHIPACK_OT_reference_point(ArchipackCollectionManager, Operator):
"""Add reference point"""
bl_idname = "archipack.reference_point"
bl_label = "Reference point"
bl_description = "Add reference point"
bl_category = 'Archipack'
bl_options = {'REGISTER', 'UNDO'}
- location_3d = FloatVectorProperty(
+ location_3d : FloatVectorProperty(
subtype='XYZ',
name="position 3d",
default=Vector((0, 0, 0))
)
- symbol_type = EnumProperty(
+ symbol_type : EnumProperty(
name="Symbol type",
default='WALL',
items=(
@@ -226,7 +227,7 @@ class ARCHIPACK_OT_reference_point(Operator):
def draw(self, context):
layout = self.layout
row = layout.row()
- row.label("Use Properties panel (N) to define parms", icon='INFO')
+ row.label(text="Use Properties panel (N) to define parms", icon='INFO')
def create(self, context):
x, y, z = context.scene.cursor_location
@@ -234,21 +235,21 @@ class ARCHIPACK_OT_reference_point(Operator):
m = bpy.data.meshes.new(name="Reference")
o = bpy.data.objects.new("Reference", m)
o.location = Vector((x, y, 0))
- context.scene.objects.link(o)
+ self.link_object_to_scene(context, o)
d = o.archipack_reference_point.add()
d.location_2d = Vector((x, y, 0))
d.location_3d = self.location_3d
d.symbol_type = self.symbol_type
- o.select = True
- context.scene.objects.active = o
+ o.select_set(state=True)
+ context.view_layer.objects.active = o
d.update(context)
return o
def execute(self, context):
if context.mode == "OBJECT":
o = self.create(context)
- o.select = True
- context.scene.objects.active = o
+ o.select_set(state=True)
+ context.view_layer.objects.active = o
return {'FINISHED'}
else:
self.report({'WARNING'}, "Archipack: Option only valid in Object mode")
@@ -298,7 +299,7 @@ class ARCHIPACK_OT_apply_holes(Operator):
ctx['modifier'] = mod
try:
bpy.ops.object.modifier_apply(ctx, apply_as='DATA',
- modifier=ctx['modifier'].name)
+ modifier=mod.name)
except:
pass
@@ -327,12 +328,12 @@ class ARCHIPACK_OT_apply_holes(Operator):
bpy.ops.object.select_all(action="DESELECT")
for r in to_remove:
r.hide_select = False
- r.select = True
- context.scene.objects.active = r
+ r.select_set(state=True)
+ context.view_layer.objects.active = r
bpy.ops.object.delete(use_global=False)
- o.select = True
- context.scene.objects.active = o
+ o.select_set(state=True)
+ context.view_layer.objects.active = o
return {'FINISHED'}
else:
@@ -410,9 +411,9 @@ class ARCHIPACK_OT_move_2d_reference_to_cursor(Operator):
bpy.ops.object.select_all(action="DESELECT")
bpy.ops.archipack.reference_point(location_3d=props.location_3d)
for child in o.children:
- child.select = True
+ child.select_set(state=True)
bpy.ops.archipack.parent_to_reference()
- context.scene.objects.unlink(o)
+ self.unlink_object_from_scene(o)
return {'FINISHED'}
else:
self.report({'WARNING'}, "Archipack: Option only valid in Object mode")
@@ -441,7 +442,7 @@ class ARCHIPACK_OT_parent_to_reference(Operator):
# print("parent_to_reference parenting:%s objects" % (len(sel)))
for child in sel:
rs = child.matrix_world.to_3x3().to_4x4()
- loc = itM * child.matrix_world.translation
+ loc = itM @ child.matrix_world.translation
child.parent = None
child.matrix_parent_inverse.identity()
child.location = Vector((0, 0, 0))
diff --git a/archipack/archipack_rendering.py b/archipack/archipack_rendering.py
index 8ea06ef9..533afffc 100644
--- a/archipack/archipack_rendering.py
+++ b/archipack/archipack_rendering.py
@@ -31,15 +31,14 @@ import bpy
# noinspection PyUnresolvedReferences
import bgl
from shutil import copyfile
-from os import path, remove, listdir
-from sys import exc_info
+from os import path, listdir
import subprocess
# noinspection PyUnresolvedReferences
import bpy_extras.image_utils as img_utils
# noinspection PyUnresolvedReferences
from math import ceil
from bpy.types import Operator
-from bl_ui import properties_render
+# from bl_ui import properties_render
class ARCHIPACK_OT_render_thumbs(Operator):
@@ -51,8 +50,8 @@ class ARCHIPACK_OT_render_thumbs(Operator):
@classmethod
def poll(cls, context):
# Ensure CYCLES engine is available
- return context.scene.archipack_progress < 0 and \
- 'CYCLES' in properties_render.RENDER_PT_render.COMPAT_ENGINES
+ # hasattr(context.scene, 'cycles')
+ return context.scene
def background_render(self, context, cls, preset):
generator = path.dirname(path.realpath(__file__)) + path.sep + "archipack_thumbs.py"
@@ -64,6 +63,7 @@ class ARCHIPACK_OT_render_thumbs(Operator):
"--background",
"--factory-startup",
"-noaudio",
+ # "--addons", addon_name,
"--python", generator,
"--",
"addon:" + addon_name,
@@ -71,6 +71,9 @@ class ARCHIPACK_OT_render_thumbs(Operator):
"cls:" + cls,
"preset:" + preset
]
+
+ # print(" ".join(cmd))
+
popen = subprocess.Popen(cmd, stdout=subprocess.PIPE, universal_newlines=True)
for stdout_line in iter(popen.stdout.readline, ""):
yield stdout_line
@@ -158,12 +161,16 @@ class ARCHIPACK_OT_render_thumbs(Operator):
for i, preset in enumerate(file_list):
dir, file = preset
cls = dir[10:]
- context.scene.archipack_progress = (100 * i / ttl)
+ # context.scene.archipack_progress = (100 * i / ttl)
+ log_all = False
for l in self.background_render(context, cls, file + ".py"):
if "[log]" in l:
print(l[5:].strip())
- # elif not "Fra:1" in l:
- # print(l.strip())
+ elif "blender.crash" in l:
+ print("Unexpected error")
+ log_all = True
+ if log_all:
+ print(l.strip())
def invoke(self, context, event):
addon_name = __name__.split('.')[0]
@@ -174,504 +181,16 @@ class ARCHIPACK_OT_render_thumbs(Operator):
return context.window_manager.invoke_confirm(self, event)
def execute(self, context):
- context.scene.archipack_progress_text = 'Generating thumbs'
- context.scene.archipack_progress = 0
+ # context.scene.archipack_progress_text = 'Generating thumbs'
+ # context.scene.archipack_progress = 0
self.rebuild_thumbs(context)
- context.scene.archipack_progress = -1
+ # context.scene.archipack_progress = -1
return {'FINISHED'}
-# -------------------------------------------------------------
-# Defines button for render
-#
-# -------------------------------------------------------------
-class ARCHIPACK_OT_render(Operator):
- bl_idname = "archipack.render"
- bl_label = "Render"
- bl_category = 'Archipack'
- bl_description = "Create a render image with measures. Use UV/Image editor to view image generated"
- bl_category = 'Archipack'
-
- # --------------------------------------------------------------------
- # Get the final render image and return as image object
- #
- # return None if no render available
- # --------------------------------------------------------------------
-
- def get_render_image(self, outpath):
- saved = False
- # noinspection PyBroadException
- try:
- # noinspection PyBroadException
- try:
- result = bpy.data.images['Render Result']
- if result.has_data is False:
- # this save produce to fill data image
- result.save_render(outpath)
- saved = True
- except:
- print("No render image found")
- return None
-
- # Save and reload
- if saved is False:
- result.save_render(outpath)
-
- img = img_utils.load_image(outpath)
-
- return img
- except:
- print("Unexpected render image error")
- return None
-
- # -------------------------------------
- # Save image to file
- # -------------------------------------
-
- def save_image(self, filepath, myimage):
- # noinspection PyBroadException
- try:
-
- # Save old info
- settings = bpy.context.scene.render.image_settings
- myformat = settings.file_format
- mode = settings.color_mode
- depth = settings.color_depth
-
- # Apply new info and save
- settings.file_format = 'PNG'
- settings.color_mode = "RGBA"
- settings.color_depth = '8'
- myimage.save_render(filepath)
- print("Archipack: Image " + filepath + " saved")
-
- # Restore old info
- settings.file_format = myformat
- settings.color_mode = mode
- settings.color_depth = depth
- except:
- print("Unexpected error:" + str(exc_info()))
- self.report({'ERROR'}, "Archipack: Unable to save render image")
- return
-
- # -------------------------------------------------------------
- # Render image main entry point
- #
- # -------------------------------------------------------------
-
- def render_main(self, context, objlist, animation=False):
- # noinspection PyBroadException,PyBroadException
- # Save old info
- scene = context.scene
- render = scene.render
- settings = render.image_settings
- depth = settings.color_depth
- settings.color_depth = '8'
- # noinspection PyBroadException
- try:
-
- # Get visible layers
- layers = []
- for x in range(0, 20):
- if scene.layers[x] is True:
- layers.extend([x])
-
- # --------------------
- # Get resolution
- # --------------------
- render_scale = render.resolution_percentage / 100
-
- width = int(render.resolution_x * render_scale)
- height = int(render.resolution_y * render_scale)
- # ---------------------------------------
- # Get output path
- # ---------------------------------------
- temp_path = path.realpath(bpy.app.tempdir)
- if len(temp_path) > 0:
- outpath = path.join(temp_path, "archipack_tmp_render.png")
- else:
- self.report({'ERROR'},
- "Archipack: Unable to save temporary render image. Define a valid temp path")
- settings.color_depth = depth
- return False
-
- # Get Render Image
- img = self.get_render_image(outpath)
- if img is None:
- self.report({'ERROR'},
- "Archipack: Unable to save temporary render image. Define a valid temp path")
- settings.color_depth = depth
- return False
-
- # -----------------------------
- # Calculate rows and columns
- # -----------------------------
- tile_x = 240
- tile_y = 216
- row_num = ceil(height / tile_y)
- col_num = ceil(width / tile_x)
- print("Archipack: Image divided in " + str(row_num) + "x" + str(col_num) + " tiles")
-
- # pixels out of visible area
- cut4 = (col_num * tile_x * 4) - width * 4 # pixels aout of drawing area
- totpixel4 = width * height * 4 # total pixels RGBA
-
- viewport_info = bgl.Buffer(bgl.GL_INT, 4)
- bgl.glGetIntegerv(bgl.GL_VIEWPORT, viewport_info)
-
- # Load image on memory
- img.gl_load(0, bgl.GL_NEAREST, bgl.GL_NEAREST)
-
- # 2.77 API change
- if bpy.app.version >= (2, 77, 0):
- tex = img.bindcode[0]
- else:
- tex = img.bindcode
-
- # --------------------------------------------
- # Create output image (to apply texture)
- # --------------------------------------------
- if "archipack_output" in bpy.data.images:
- out_img = bpy.data.images["archipack_output"]
- if out_img is not None:
- # out_img.user_clear()
- bpy.data.images.remove(out_img, do_unlink=True)
-
- out = bpy.data.images.new("archipack_output", width, height)
- tmp_pixels = [1] * totpixel4
-
- # --------------------------------
- # Loop for all tiles
- # --------------------------------
- for row in range(0, row_num):
- for col in range(0, col_num):
- buffer = bgl.Buffer(bgl.GL_FLOAT, width * height * 4)
- bgl.glDisable(bgl.GL_SCISSOR_TEST) # if remove this line, get blender screenshot not image
- bgl.glViewport(0, 0, tile_x, tile_y)
-
- bgl.glMatrixMode(bgl.GL_PROJECTION)
- bgl.glLoadIdentity()
-
- # defines ortographic view for single tile
- x1 = tile_x * col
- y1 = tile_y * row
- bgl.gluOrtho2D(x1, x1 + tile_x, y1, y1 + tile_y)
-
- # Clear
- bgl.glClearColor(0.0, 0.0, 0.0, 0.0)
- bgl.glClear(bgl.GL_COLOR_BUFFER_BIT | bgl.GL_DEPTH_BUFFER_BIT)
-
- bgl.glEnable(bgl.GL_TEXTURE_2D)
- bgl.glBindTexture(bgl.GL_TEXTURE_2D, tex)
-
- # defines drawing area
- bgl.glBegin(bgl.GL_QUADS)
-
- bgl.glColor3f(1.0, 1.0, 1.0)
- bgl.glTexCoord2f(0.0, 0.0)
- bgl.glVertex2f(0.0, 0.0)
-
- bgl.glTexCoord2f(1.0, 0.0)
- bgl.glVertex2f(width, 0.0)
-
- bgl.glTexCoord2f(1.0, 1.0)
- bgl.glVertex2f(width, height)
-
- bgl.glTexCoord2f(0.0, 1.0)
- bgl.glVertex2f(0.0, height)
-
- bgl.glEnd()
-
- # -----------------------------
- # Loop to draw all lines
- # -----------------------------
- for o, d in objlist:
- if o.hide is False:
- # verify visible layer
- for x in range(0, 20):
- if o.layers[x] is True:
- if x in layers:
- context.scene.objects.active = o
- # print("%s: %s" % (o.name, d.manip_stack))
- manipulators = d.manip_stack
- if manipulators is not None:
- for m in manipulators:
- if m is not None:
- m.draw_callback(m, context, render=True)
- break
-
- # -----------------------------
- # Loop to draw all debug
- # -----------------------------
- """
- if scene.archipack_debug is True:
- selobj = bpy.context.selected_objects
- for myobj in selobj:
- if scene.archipack_debug_vertices is True:
- draw_vertices(context, myobj, None, None)
- if scene.archipack_debug_faces is True or scene.archipack_debug_normals is True:
- draw_faces(context, myobj, None, None)
- """
- """
- if scene.archipack_rf is True:
- bgl.glColor3f(1.0, 1.0, 1.0)
- rfcolor = scene.archipack_rf_color
- rfborder = scene.archipack_rf_border
- rfline = scene.archipack_rf_line
-
- bgl.glLineWidth(rfline)
- bgl.glColor4f(rfcolor[0], rfcolor[1], rfcolor[2], rfcolor[3])
-
- x1 = rfborder
- x2 = width - rfborder
- y1 = int(ceil(rfborder / (width / height)))
- y2 = height - y1
- draw_rectangle((x1, y1), (x2, y2))
- """
- # --------------------------------
- # copy pixels to temporary area
- # --------------------------------
- bgl.glFinish()
- bgl.glReadPixels(0, 0, width, height, bgl.GL_RGBA, bgl.GL_FLOAT, buffer) # read image data
- for y in range(0, tile_y):
- # final image pixels position
- p1 = (y * width * 4) + (row * tile_y * width * 4) + (col * tile_x * 4)
- p2 = p1 + (tile_x * 4)
- # buffer pixels position
- b1 = y * width * 4
- b2 = b1 + (tile_x * 4)
-
- if p1 < totpixel4: # avoid pixel row out of area
- if col == col_num - 1: # avoid pixel columns out of area
- p2 -= cut4
- b2 -= cut4
-
- tmp_pixels[p1:p2] = buffer[b1:b2]
-
- # -----------------------
- # Copy temporary to final
- # -----------------------
- out.pixels = tmp_pixels[:] # Assign image data
- img.gl_free() # free opengl image memory
-
- # delete image
- # img.user_clear()
- bpy.data.images.remove(img, do_unlink=True)
- # remove temp file
- remove(outpath)
- # reset
- bgl.glEnable(bgl.GL_SCISSOR_TEST)
- # -----------------------
- # restore opengl defaults
- # -----------------------
- bgl.glLineWidth(1)
- bgl.glDisable(bgl.GL_BLEND)
- bgl.glColor4f(0.0, 0.0, 0.0, 1.0)
- # Saves image
- if out is not None:
- # and (scene.archipack_render is True or animation is True):
- ren_path = bpy.context.scene.render.filepath
- filename = "ap_frame"
- if len(ren_path) > 0:
- if ren_path.endswith(path.sep):
- initpath = path.realpath(ren_path) + path.sep
- else:
- (initpath, filename) = path.split(ren_path)
-
- ftxt = "%04d" % scene.frame_current
- outpath = path.realpath(path.join(initpath, filename + ftxt + ".png"))
-
- self.save_image(outpath, out)
-
- settings.color_depth = depth
- return True
-
- except:
- settings.color_depth = depth
- print("Unexpected error:" + str(exc_info()))
- self.report(
- {'ERROR'},
- "Archipack: Unable to create render image. Be sure the output render path is correct"
- )
- return False
-
- def get_objlist(self, context):
- """
- Get objects with gl manipulators
- """
- objlist = []
- for o in context.scene.objects:
- if o.data is not None:
- d = None
- if 'archipack_window' in o.data:
- d = o.data.archipack_window[0]
- elif 'archipack_door' in o.data:
- d = o.data.archipack_door[0]
- elif 'archipack_wall2' in o.data:
- d = o.data.archipack_wall2[0]
- elif 'archipack_stair' in o.data:
- d = o.data.archipack_stair[0]
- elif 'archipack_fence' in o.data:
- d = o.data.archipack_fence[0]
- elif 'archipack_floor' in o.data:
- d = o.data.archipack_floor[0]
- elif 'archipack_roof' in o.data:
- d = o.data.archipack_roof[0]
- if d is not None:
- objlist.append((o, d))
- return objlist
-
- def draw_gl(self, context):
- objlist = self.get_objlist(context)
- for o, d in objlist:
- context.scene.objects.active = o
- d.manipulable_disable(context)
- d.manipulable_invoke(context)
- return objlist
-
- def hide_gl(self, context, objlist):
- for o, d in objlist:
- context.scene.objects.active = o
- d.manipulable_disable(context)
-
- # ------------------------------
- # Execute button action
- # ------------------------------
- # noinspection PyMethodMayBeStatic,PyUnusedLocal
- def execute(self, context):
- scene = context.scene
- wm = context.window_manager
- msg = "New image created with measures. Open it in UV/image editor"
- camera_msg = "Unable to render. No camera found"
-
- # -----------------------------
- # Check camera
- # -----------------------------
- if scene.camera is None:
- self.report({'ERROR'}, camera_msg)
- return {'FINISHED'}
-
- objlist = self.draw_gl(context)
-
- # -----------------------------
- # Use current rendered image
- # -----------------------------
- if wm.archipack.render_type == "1":
- # noinspection PyBroadException
- try:
- result = bpy.data.images['Render Result']
- if result.has_data is False:
- bpy.ops.render.render()
- except:
- bpy.ops.render.render()
-
- print("Archipack: Using current render image on buffer")
- if self.render_main(context, objlist) is True:
- self.report({'INFO'}, msg)
-
- # -----------------------------
- # OpenGL image
- # -----------------------------
- elif wm.archipack.render_type == "2":
- self.set_camera_view()
- self.set_only_render(True)
-
- print("Archipack: Rendering opengl image")
- bpy.ops.render.opengl()
- if self.render_main(context, objlist) is True:
- self.report({'INFO'}, msg)
-
- self.set_only_render(False)
-
- # -----------------------------
- # OpenGL Animation
- # -----------------------------
- elif wm.archipack.render_type == "3":
- oldframe = scene.frame_current
- self.set_camera_view()
- self.set_only_render(True)
- flag = False
- # loop frames
- for frm in range(scene.frame_start, scene.frame_end + 1):
- scene.frame_set(frm)
- print("Archipack: Rendering opengl frame %04d" % frm)
- bpy.ops.render.opengl()
- flag = self.render_main(context, objlist, True)
- if flag is False:
- break
-
- self.set_only_render(False)
- scene.frame_current = oldframe
- if flag is True:
- self.report({'INFO'}, msg)
-
- # -----------------------------
- # Image
- # -----------------------------
- elif wm.archipack.render_type == "4":
- print("Archipack: Rendering image")
- bpy.ops.render.render()
- if self.render_main(context, objlist) is True:
- self.report({'INFO'}, msg)
-
- # -----------------------------
- # Animation
- # -----------------------------
- elif wm.archipack.render_type == "5":
- oldframe = scene.frame_current
- flag = False
- # loop frames
- for frm in range(scene.frame_start, scene.frame_end + 1):
- scene.frame_set(frm)
- print("Archipack: Rendering frame %04d" % frm)
- bpy.ops.render.render()
- flag = self.render_main(context, objlist, True)
- if flag is False:
- break
-
- scene.frame_current = oldframe
- if flag is True:
- self.report({'INFO'}, msg)
-
- self.hide_gl(context, objlist)
-
- return {'FINISHED'}
-
- # ---------------------
- # Set cameraView
- # ---------------------
- # noinspection PyMethodMayBeStatic
- def set_camera_view(self):
- for area in bpy.context.screen.areas:
- if area.type == 'VIEW_3D':
- area.spaces[0].region_3d.view_perspective = 'CAMERA'
-
- # -------------------------------------
- # Set only render status
- # -------------------------------------
- # noinspection PyMethodMayBeStatic
- def set_only_render(self, status):
- screen = bpy.context.screen
-
- v3d = False
- s = None
- # get spaceview_3d in current screen
- for a in screen.areas:
- if a.type == 'VIEW_3D':
- for s in a.spaces:
- if s.type == 'VIEW_3D':
- v3d = s
- break
-
- if v3d is not False:
- s.show_only_render = status
-
-
def register():
- bpy.utils.register_class(ARCHIPACK_OT_render)
bpy.utils.register_class(ARCHIPACK_OT_render_thumbs)
def unregister():
- bpy.utils.unregister_class(ARCHIPACK_OT_render)
bpy.utils.unregister_class(ARCHIPACK_OT_render_thumbs)
diff --git a/archipack/archipack_roof.py b/archipack/archipack_roof.py
index 024dccb7..02bb01e8 100644
--- a/archipack/archipack_roof.py
+++ b/archipack/archipack_roof.py
@@ -1564,7 +1564,7 @@ class RoofGenerator(CutAbleGenerator):
use_dissolve_boundaries=False,
verts=bm.verts,
edges=bm.edges,
- delimit=1)
+ delimit={'MATERIAL'})
geom = bm.faces[:]
verts = bm.verts[:]
@@ -1595,7 +1595,7 @@ class RoofGenerator(CutAbleGenerator):
BEVEL_AMT_PERCENT
};
"""
- offset_type = 3
+ offset_type = 'PERCENT'
if d.tile_offset > 0:
offset = - d.tile_offset / 100
@@ -1694,8 +1694,8 @@ class RoofGenerator(CutAbleGenerator):
step = 100 / ttl
- if d.quick_edit:
- context.scene.archipack_progress_text = "Build tiles:"
+ # if d.quick_edit:
+ # context.scene.archipack_progress_text = "Build tiles:"
for i, pan in enumerate(self.pans):
@@ -1742,8 +1742,8 @@ class RoofGenerator(CutAbleGenerator):
progress = step * i + substep * k
# print("progress %s" % (progress))
- if d.quick_edit:
- context.scene.archipack_progress = progress
+ # if d.quick_edit:
+ # context.scene.archipack_progress = progress
y = k * dy
@@ -1759,7 +1759,7 @@ class RoofGenerator(CutAbleGenerator):
for j in range(nx):
x = x0 + j * dx
- lM = tM * Matrix([
+ lM = tM @ Matrix([
[sx, 0, 0, x],
[0, sy, 0, -y],
[0, 0, sz, 0],
@@ -1768,7 +1768,7 @@ class RoofGenerator(CutAbleGenerator):
v = len(verts)
- verts.extend([lM * p for p in t_pts])
+ verts.extend([lM @ p for p in t_pts])
faces.extend([tuple(i + v for i in f) for f in t_faces])
mid = randint(idmat, idmat + rand)
t_mats = [mid for i in range(n_faces)]
@@ -1847,7 +1847,7 @@ class RoofGenerator(CutAbleGenerator):
segs[-1].p1 = p
f_geom = [f for f in bm.faces if not pan.inside(f.calc_center_median().to_2d(), segs)]
if len(f_geom) > 0:
- bmesh.ops.delete(bm, geom=f_geom, context=5)
+ bmesh.ops.delete(bm, geom=f_geom, context="FACES")
self.cut_holes(bm, pan)
@@ -1856,7 +1856,7 @@ class RoofGenerator(CutAbleGenerator):
use_dissolve_boundaries=False,
verts=bm.verts[:],
edges=bm.edges[:],
- delimit=1)
+ delimit={'MATERIAL'})
if d.tile_bevel:
geom = bm.verts[:]
@@ -1881,8 +1881,8 @@ class RoofGenerator(CutAbleGenerator):
bmed.bmesh_join(context, o, [bm], normal_update=True)
bpy.ops.object.mode_set(mode='OBJECT')
- if d.quick_edit:
- context.scene.archipack_progress = -1
+ # if d.quick_edit:
+ # context.scene.archipack_progress = -1
def _bargeboard(self, s, i, boundary, pan,
width, height, altitude, offset, idmat,
@@ -2774,7 +2774,7 @@ class RoofGenerator(CutAbleGenerator):
use_dissolve_boundaries=False,
verts=bm.verts,
edges=bm.edges,
- delimit=1)
+ delimit={'MATERIAL'})
geom = bm.faces[:]
verts = bm.verts[:]
@@ -3040,7 +3040,7 @@ class RoofGenerator(CutAbleGenerator):
t_verts[i].z += dz * t_verts[i].y
for k in range(n_x):
- lM = tM * Matrix([
+ lM = tM @ Matrix([
[1, 0, 0, x0 + k * dx],
[0, -1, 0, 0],
[0, 0, 1, 0],
@@ -3048,7 +3048,7 @@ class RoofGenerator(CutAbleGenerator):
])
f = len(verts)
- verts.extend([lM * p for p in t_verts])
+ verts.extend([lM @ p for p in t_verts])
faces.extend([tuple(i + f for i in p) for p in t_faces])
matids.extend(t_idmats)
uvs.extend(t_uvs)
@@ -3132,14 +3132,14 @@ class RoofGenerator(CutAbleGenerator):
t_verts[i].z -= t_verts[i].y * (pan.slope - d.tile_size_z / d.tile_size_y)
for k in range(n_obj):
- lM = tM * Matrix([
+ lM = tM @ Matrix([
[1, 0, 0, x0 + k * dx],
[0, -1, 0, 0],
[0, 0, 1, 0],
[0, 0, 0, 1]
])
v = len(verts)
- verts.extend([lM * p for p in t_verts])
+ verts.extend([lM @ p for p in t_verts])
faces.extend([tuple(i + v for i in f) for f in t_faces])
matids.extend(t_idmats)
uvs.extend(t_uvs)
@@ -3243,19 +3243,19 @@ class RoofGenerator(CutAbleGenerator):
pts = [p.to_3d() for p in pts]
if hole_obj is None:
- context.scene.objects.active = o.parent
+ context.view_layer.objects.active = o.parent
bpy.ops.archipack.roof_cutter(parent=d.t_parent, auto_manipulate=False)
hole_obj = context.active_object
else:
- context.scene.objects.active = hole_obj
+ context.view_layer.objects.active = hole_obj
- hole_obj.select = True
+ hole_obj.select_set(state=True)
if d.parts[0].a0 < 0:
y = -d.t_dist_y
else:
y = d.t_dist_y
- hole_obj.matrix_world = o.matrix_world * Matrix([
+ hole_obj.matrix_world = o.matrix_world @ Matrix([
[1, 0, 0, 0],
[0, 1, 0, y],
[0, 0, 1, 0],
@@ -3265,15 +3265,15 @@ class RoofGenerator(CutAbleGenerator):
hd = archipack_roof_cutter.datablock(hole_obj)
hd.boundary = o.name
hd.update_points(context, hole_obj, pts, update_parent=update_parent)
- hole_obj.select = False
+ hole_obj.select_set(state=False)
- context.scene.objects.active = o
+ context.view_layer.objects.active = o
def change_coordsys(self, fromTM, toTM):
"""
move shape fromTM into toTM coordsys
"""
- dp = (toTM.inverted() * fromTM.translation).to_2d()
+ dp = (toTM.inverted() @ fromTM.translation).to_2d()
da = toTM.row[1].to_2d().angle_signed(fromTM.row[1].to_2d())
ca = cos(da)
sa = sin(da)
@@ -3282,7 +3282,7 @@ class RoofGenerator(CutAbleGenerator):
[sa, ca]
])
for s in self.segs:
- tp = (rM * s.p0) - s.p0 + dp
+ tp = (rM @ s.p0) - s.p0 + dp
s.rotate(da)
s.translate(tp)
@@ -3357,9 +3357,9 @@ class RoofGenerator(CutAbleGenerator):
wall_t[widx].append((0, z, 0))
old = context.active_object
- old_sel = wall.select
- wall.select = True
- context.scene.objects.active = wall
+ old_sel = wall.select_get()
+ wall.select_set(state=True)
+ context.view_layer.objects.active = wall
wd.auto_update = False
# setup splits count and first split to 0
@@ -3412,8 +3412,8 @@ class RoofGenerator(CutAbleGenerator):
for s in wg.segs:
s.as_curve(context)
"""
- wall.select = old_sel
- context.scene.objects.active = old
+ wall.select_set(state=old_sel)
+ context.view_layer.objects.active = old
def boundary(self, context, o):
"""
@@ -3494,14 +3494,14 @@ def update_components(self, context):
class ArchipackSegment():
- length = FloatProperty(
+ length : FloatProperty(
name="Length",
min=0.01,
max=1000.0,
default=4.0,
update=update
)
- a0 = FloatProperty(
+ a0 : FloatProperty(
name="Angle",
min=-2 * pi,
max=2 * pi,
@@ -3509,17 +3509,17 @@ class ArchipackSegment():
subtype='ANGLE', unit='ROTATION',
update=update_cutter
)
- manipulators = CollectionProperty(type=archipack_manipulator)
+ manipulators : CollectionProperty(type=archipack_manipulator)
class ArchipackLines():
- n_parts = IntProperty(
+ n_parts : IntProperty(
name="Parts",
min=1,
default=1, update=update_manipulators
)
# UI layout related
- parts_expand = BoolProperty(
+ parts_expand : BoolProperty(
default=False
)
@@ -3580,37 +3580,37 @@ class ArchipackLines():
class archipack_roof_segment(ArchipackSegment, PropertyGroup):
- bound_idx = IntProperty(
+ bound_idx : IntProperty(
name="Link to",
default=0,
min=0,
update=update_manipulators
)
- width_left = FloatProperty(
+ width_left : FloatProperty(
name="L Width",
min=0.01,
default=3.0,
update=update_cutter
)
- width_right = FloatProperty(
+ width_right : FloatProperty(
name="R Width",
min=0.01,
default=3.0,
update=update_cutter
)
- slope_left = FloatProperty(
+ slope_left : FloatProperty(
name="L slope",
min=0.0,
default=0.3,
update=update_cutter
)
- slope_right = FloatProperty(
+ slope_right : FloatProperty(
name="R slope",
min=0.0,
default=0.3,
update=update_cutter
)
- auto_left = EnumProperty(
+ auto_left : EnumProperty(
description="Left mode",
name="Left",
items=(
@@ -3622,7 +3622,7 @@ class archipack_roof_segment(ArchipackSegment, PropertyGroup):
default="AUTO",
update=update_manipulators
)
- auto_right = EnumProperty(
+ auto_right : EnumProperty(
description="Right mode",
name="Right",
items=(
@@ -3634,19 +3634,19 @@ class archipack_roof_segment(ArchipackSegment, PropertyGroup):
default="AUTO",
update=update_manipulators
)
- triangular_end = BoolProperty(
+ triangular_end : BoolProperty(
name="Triangular end",
default=False,
update=update
)
- take_precedence = BoolProperty(
+ take_precedence : BoolProperty(
name="Take precedence",
description="On T segment take width precedence",
default=False,
update=update
)
- constraint_type = EnumProperty(
+ constraint_type : EnumProperty(
items=(
('HORIZONTAL', 'Horizontal', '', 0),
('SLOPE', 'Slope', '', 1)
@@ -3655,7 +3655,7 @@ class archipack_roof_segment(ArchipackSegment, PropertyGroup):
update=update_manipulators
)
- enforce_part = EnumProperty(
+ enforce_part : EnumProperty(
name="Enforce part",
items=(
('AUTO', 'Auto', '', 0),
@@ -3671,7 +3671,7 @@ class archipack_roof_segment(ArchipackSegment, PropertyGroup):
find witch selected object this instance belongs to
provide support for "copy to selected"
"""
- selected = [o for o in context.selected_objects]
+ selected = context.selected_objects[:]
for o in selected:
d = archipack_roof.datablock(o)
if d:
@@ -3687,7 +3687,7 @@ class archipack_roof_segment(ArchipackSegment, PropertyGroup):
if self.constraint_type == 'SLOPE':
box.prop(self, "enforce_part", text="")
else:
- box.label("Part 1:")
+ box.label(text="Part 1:")
box.prop(self, "length")
box.prop(self, "a0")
@@ -3720,63 +3720,63 @@ class archipack_roof_segment(ArchipackSegment, PropertyGroup):
class archipack_roof(ArchipackLines, ArchipackObject, Manipulable, PropertyGroup):
- parts = CollectionProperty(type=archipack_roof_segment)
- z = FloatProperty(
+ parts : CollectionProperty(type=archipack_roof_segment)
+ z : FloatProperty(
name="Altitude",
default=3, precision=2, step=1,
unit='LENGTH', subtype='DISTANCE',
update=update_childs
)
- slope_left = FloatProperty(
+ slope_left : FloatProperty(
name="L slope",
default=0.5, precision=2, step=1,
update=update_childs
)
- slope_right = FloatProperty(
+ slope_right : FloatProperty(
name="R slope",
default=0.5, precision=2, step=1,
update=update_childs
)
- width_left = FloatProperty(
+ width_left : FloatProperty(
name="L width",
default=3, precision=2, step=1,
unit='LENGTH', subtype='DISTANCE',
update=update_cutter
)
- width_right = FloatProperty(
+ width_right : FloatProperty(
name="R width",
default=3, precision=2, step=1,
unit='LENGTH', subtype='DISTANCE',
update=update_cutter
)
- draft = BoolProperty(
+ draft : BoolProperty(
options={'SKIP_SAVE'},
name="Draft mode",
default=False,
update=update_manipulators
)
- auto_update = BoolProperty(
+ auto_update : BoolProperty(
options={'SKIP_SAVE'},
default=True,
update=update_manipulators
)
- quick_edit = BoolProperty(
+ quick_edit : BoolProperty(
options={'SKIP_SAVE'},
name="Quick Edit",
- default=True
+ default=False
)
- tile_enable = BoolProperty(
+ tile_enable : BoolProperty(
name="Enable",
default=True,
update=update_components
)
- tile_solidify = BoolProperty(
+ tile_solidify : BoolProperty(
name="Solidify",
default=True,
update=update_components
)
- tile_height = FloatProperty(
+ tile_height : FloatProperty(
name="Height",
description="Amount for solidify",
min=0,
@@ -3784,12 +3784,12 @@ class archipack_roof(ArchipackLines, ArchipackObject, Manipulable, PropertyGroup
unit='LENGTH', subtype='DISTANCE',
update=update_components
)
- tile_bevel = BoolProperty(
+ tile_bevel : BoolProperty(
name="Bevel",
default=False,
update=update_components
)
- tile_bevel_amt = FloatProperty(
+ tile_bevel_amt : FloatProperty(
name="Amount",
description="Amount for bevel",
min=0,
@@ -3797,19 +3797,19 @@ class archipack_roof(ArchipackLines, ArchipackObject, Manipulable, PropertyGroup
unit='LENGTH', subtype='DISTANCE',
update=update_components
)
- tile_bevel_segs = IntProperty(
+ tile_bevel_segs : IntProperty(
name="Segs",
description="Bevel Segs",
min=1,
default=2,
update=update_components
)
- tile_alternate = BoolProperty(
+ tile_alternate : BoolProperty(
name="Alternate",
default=False,
update=update_components
)
- tile_offset = FloatProperty(
+ tile_offset : FloatProperty(
name="Offset",
description="Offset from start",
min=0,
@@ -3817,14 +3817,14 @@ class archipack_roof(ArchipackLines, ArchipackObject, Manipulable, PropertyGroup
subtype="PERCENTAGE",
update=update_components
)
- tile_altitude = FloatProperty(
+ tile_altitude : FloatProperty(
name="Altitude",
description="Altitude from roof",
default=0.1,
unit='LENGTH', subtype='DISTANCE',
update=update_components
)
- tile_size_x = FloatProperty(
+ tile_size_x : FloatProperty(
name="Width",
description="Size of tiles on x axis",
min=0.01,
@@ -3832,7 +3832,7 @@ class archipack_roof(ArchipackLines, ArchipackObject, Manipulable, PropertyGroup
unit='LENGTH', subtype='DISTANCE',
update=update_components
)
- tile_size_y = FloatProperty(
+ tile_size_y : FloatProperty(
name="Length",
description="Size of tiles on y axis",
min=0.01,
@@ -3840,7 +3840,7 @@ class archipack_roof(ArchipackLines, ArchipackObject, Manipulable, PropertyGroup
unit='LENGTH', subtype='DISTANCE',
update=update_components
)
- tile_size_z = FloatProperty(
+ tile_size_z : FloatProperty(
name="Thickness",
description="Size of tiles on z axis",
min=0.0,
@@ -3848,7 +3848,7 @@ class archipack_roof(ArchipackLines, ArchipackObject, Manipulable, PropertyGroup
unit='LENGTH', subtype='DISTANCE',
update=update_components
)
- tile_space_x = FloatProperty(
+ tile_space_x : FloatProperty(
name="Width",
description="Space between tiles on x axis",
min=0.01,
@@ -3856,7 +3856,7 @@ class archipack_roof(ArchipackLines, ArchipackObject, Manipulable, PropertyGroup
unit='LENGTH', subtype='DISTANCE',
update=update_components
)
- tile_space_y = FloatProperty(
+ tile_space_y : FloatProperty(
name="Length",
description="Space between tiles on y axis",
min=0.01,
@@ -3864,25 +3864,25 @@ class archipack_roof(ArchipackLines, ArchipackObject, Manipulable, PropertyGroup
unit='LENGTH', subtype='DISTANCE',
update=update_components
)
- tile_fit_x = BoolProperty(
+ tile_fit_x : BoolProperty(
name="Fit x",
description="Fit roof on x axis",
default=True,
update=update_components
)
- tile_fit_y = BoolProperty(
+ tile_fit_y : BoolProperty(
name="Fit y",
description="Fit roof on y axis",
default=True,
update=update_components
)
- tile_expand = BoolProperty(
+ tile_expand : BoolProperty(
options={'SKIP_SAVE'},
name="Tiles",
description="Expand tiles panel",
default=False
)
- tile_model = EnumProperty(
+ tile_model : EnumProperty(
name="Model",
items=(
('BRAAS1', 'Braas 1', '', 0),
@@ -3899,14 +3899,14 @@ class archipack_roof(ArchipackLines, ArchipackObject, Manipulable, PropertyGroup
default="BRAAS2",
update=update_components
)
- tile_side = FloatProperty(
+ tile_side : FloatProperty(
name="Side",
description="Space on side",
default=0,
unit='LENGTH', subtype='DISTANCE',
update=update_components
)
- tile_couloir = FloatProperty(
+ tile_couloir : FloatProperty(
name="Valley",
description="Space between tiles on valley",
min=0,
@@ -3914,7 +3914,7 @@ class archipack_roof(ArchipackLines, ArchipackObject, Manipulable, PropertyGroup
unit='LENGTH', subtype='DISTANCE',
update=update_components
)
- tile_border = FloatProperty(
+ tile_border : FloatProperty(
name="Bottom",
description="Tiles offset from bottom",
default=0,
@@ -3922,25 +3922,25 @@ class archipack_roof(ArchipackLines, ArchipackObject, Manipulable, PropertyGroup
update=update_components
)
- gutter_expand = BoolProperty(
+ gutter_expand : BoolProperty(
options={'SKIP_SAVE'},
name="Gutter",
description="Expand gutter panel",
default=False
)
- gutter_enable = BoolProperty(
+ gutter_enable : BoolProperty(
name="Enable",
default=True,
update=update_components
)
- gutter_alt = FloatProperty(
+ gutter_alt : FloatProperty(
name="Altitude",
description="altitude",
default=0,
unit='LENGTH', subtype='DISTANCE',
update=update_components
)
- gutter_width = FloatProperty(
+ gutter_width : FloatProperty(
name="Width",
description="Width",
min=0.01,
@@ -3948,7 +3948,7 @@ class archipack_roof(ArchipackLines, ArchipackObject, Manipulable, PropertyGroup
unit='LENGTH', subtype='DISTANCE',
update=update_components
)
- gutter_dist = FloatProperty(
+ gutter_dist : FloatProperty(
name="Spacing",
description="Spacing",
min=0,
@@ -3956,7 +3956,7 @@ class archipack_roof(ArchipackLines, ArchipackObject, Manipulable, PropertyGroup
unit='LENGTH', subtype='DISTANCE',
update=update_components
)
- gutter_boudin = FloatProperty(
+ gutter_boudin : FloatProperty(
name="Small width",
description="Small width",
min=0,
@@ -3964,25 +3964,25 @@ class archipack_roof(ArchipackLines, ArchipackObject, Manipulable, PropertyGroup
unit='LENGTH', subtype='DISTANCE',
update=update_components
)
- gutter_segs = IntProperty(
+ gutter_segs : IntProperty(
default=6,
min=1,
name="Segs",
update=update_components
)
- beam_expand = BoolProperty(
+ beam_expand : BoolProperty(
options={'SKIP_SAVE'},
name="Beam",
description="Expand beam panel",
default=False
)
- beam_enable = BoolProperty(
+ beam_enable : BoolProperty(
name="Ridge pole",
default=True,
update=update_components
)
- beam_width = FloatProperty(
+ beam_width : FloatProperty(
name="Width",
description="Width",
min=0.01,
@@ -3990,7 +3990,7 @@ class archipack_roof(ArchipackLines, ArchipackObject, Manipulable, PropertyGroup
unit='LENGTH', subtype='DISTANCE',
update=update_components
)
- beam_height = FloatProperty(
+ beam_height : FloatProperty(
name="Height",
description="Height",
min=0.01,
@@ -3998,26 +3998,26 @@ class archipack_roof(ArchipackLines, ArchipackObject, Manipulable, PropertyGroup
unit='LENGTH', subtype='DISTANCE',
update=update_components
)
- beam_offset = FloatProperty(
+ beam_offset : FloatProperty(
name="Offset",
description="Distance from roof border",
default=0.02,
unit='LENGTH', subtype='DISTANCE',
update=update_components
)
- beam_alt = FloatProperty(
+ beam_alt : FloatProperty(
name="Altitude",
description="Altitude from roof",
default=-0.15,
unit='LENGTH', subtype='DISTANCE',
update=update_components
)
- beam_sec_enable = BoolProperty(
+ beam_sec_enable : BoolProperty(
name="Hip rafter",
default=True,
update=update_components
)
- beam_sec_width = FloatProperty(
+ beam_sec_width : FloatProperty(
name="Width",
description="Width",
min=0.01,
@@ -4025,7 +4025,7 @@ class archipack_roof(ArchipackLines, ArchipackObject, Manipulable, PropertyGroup
unit='LENGTH', subtype='DISTANCE',
update=update_components
)
- beam_sec_height = FloatProperty(
+ beam_sec_height : FloatProperty(
name="Height",
description="Height",
min=0.01,
@@ -4033,19 +4033,19 @@ class archipack_roof(ArchipackLines, ArchipackObject, Manipulable, PropertyGroup
unit='LENGTH', subtype='DISTANCE',
update=update_components
)
- beam_sec_alt = FloatProperty(
+ beam_sec_alt : FloatProperty(
name="Altitude",
description="Distance from roof",
default=-0.1,
unit='LENGTH', subtype='DISTANCE',
update=update_components
)
- rafter_enable = BoolProperty(
+ rafter_enable : BoolProperty(
name="Rafter",
default=True,
update=update_components
)
- rafter_width = FloatProperty(
+ rafter_width : FloatProperty(
name="Width",
description="Width",
min=0.01,
@@ -4053,7 +4053,7 @@ class archipack_roof(ArchipackLines, ArchipackObject, Manipulable, PropertyGroup
unit='LENGTH', subtype='DISTANCE',
update=update_components
)
- rafter_height = FloatProperty(
+ rafter_height : FloatProperty(
name="Height",
description="Height",
min=0.01,
@@ -4061,7 +4061,7 @@ class archipack_roof(ArchipackLines, ArchipackObject, Manipulable, PropertyGroup
unit='LENGTH', subtype='DISTANCE',
update=update_components
)
- rafter_spacing = FloatProperty(
+ rafter_spacing : FloatProperty(
name="Spacing",
description="Spacing",
min=0.1,
@@ -4069,7 +4069,7 @@ class archipack_roof(ArchipackLines, ArchipackObject, Manipulable, PropertyGroup
unit='LENGTH', subtype='DISTANCE',
update=update_components
)
- rafter_start = FloatProperty(
+ rafter_start : FloatProperty(
name="Offset",
description="Spacing from roof border",
min=0,
@@ -4077,7 +4077,7 @@ class archipack_roof(ArchipackLines, ArchipackObject, Manipulable, PropertyGroup
unit='LENGTH', subtype='DISTANCE',
update=update_components
)
- rafter_alt = FloatProperty(
+ rafter_alt : FloatProperty(
name="Altitude",
description="Altitude from roof",
max=-0.0001,
@@ -4086,25 +4086,25 @@ class archipack_roof(ArchipackLines, ArchipackObject, Manipulable, PropertyGroup
update=update_components
)
- hip_enable = BoolProperty(
+ hip_enable : BoolProperty(
name="Enable",
default=True,
update=update_components
)
- hip_expand = BoolProperty(
+ hip_expand : BoolProperty(
options={'SKIP_SAVE'},
name="Hips",
description="Expand hips panel",
default=False
)
- hip_alt = FloatProperty(
+ hip_alt : FloatProperty(
name="Altitude",
description="Hip altitude from roof",
default=0.1,
unit='LENGTH', subtype='DISTANCE',
update=update_components
)
- hip_space_x = FloatProperty(
+ hip_space_x : FloatProperty(
name="Spacing",
description="Space between hips",
min=0.01,
@@ -4112,7 +4112,7 @@ class archipack_roof(ArchipackLines, ArchipackObject, Manipulable, PropertyGroup
unit='LENGTH', subtype='DISTANCE',
update=update_components
)
- hip_size_x = FloatProperty(
+ hip_size_x : FloatProperty(
name="Length",
description="Length of hip",
min=0.01,
@@ -4120,7 +4120,7 @@ class archipack_roof(ArchipackLines, ArchipackObject, Manipulable, PropertyGroup
unit='LENGTH', subtype='DISTANCE',
update=update_components
)
- hip_size_y = FloatProperty(
+ hip_size_y : FloatProperty(
name="Width",
description="Width of hip",
min=0.01,
@@ -4128,7 +4128,7 @@ class archipack_roof(ArchipackLines, ArchipackObject, Manipulable, PropertyGroup
unit='LENGTH', subtype='DISTANCE',
update=update_components
)
- hip_size_z = FloatProperty(
+ hip_size_z : FloatProperty(
name="Height",
description="Height of hip",
min=0.0,
@@ -4136,7 +4136,7 @@ class archipack_roof(ArchipackLines, ArchipackObject, Manipulable, PropertyGroup
unit='LENGTH', subtype='DISTANCE',
update=update_components
)
- hip_model = EnumProperty(
+ hip_model : EnumProperty(
name="Model",
items=(
('ROUND', 'Round', '', 0),
@@ -4146,32 +4146,32 @@ class archipack_roof(ArchipackLines, ArchipackObject, Manipulable, PropertyGroup
default="ROUND",
update=update_components
)
- valley_altitude = FloatProperty(
+ valley_altitude : FloatProperty(
name="Altitude",
description="Valley altitude from roof",
default=0.1,
unit='LENGTH', subtype='DISTANCE',
update=update_components
)
- valley_enable = BoolProperty(
+ valley_enable : BoolProperty(
name="Valley",
default=True,
update=update_components
)
- fascia_enable = BoolProperty(
+ fascia_enable : BoolProperty(
name="Enable",
description="Enable Fascia",
default=True,
update=update_components
)
- fascia_expand = BoolProperty(
+ fascia_expand : BoolProperty(
options={'SKIP_SAVE'},
name="Fascia",
description="Expand fascia panel",
default=False
)
- fascia_height = FloatProperty(
+ fascia_height : FloatProperty(
name="Height",
description="Height",
min=0.01,
@@ -4179,7 +4179,7 @@ class archipack_roof(ArchipackLines, ArchipackObject, Manipulable, PropertyGroup
unit='LENGTH', subtype='DISTANCE',
update=update_components
)
- fascia_width = FloatProperty(
+ fascia_width : FloatProperty(
name="Width",
description="Width",
min=0.01,
@@ -4187,14 +4187,14 @@ class archipack_roof(ArchipackLines, ArchipackObject, Manipulable, PropertyGroup
unit='LENGTH', subtype='DISTANCE',
update=update_components
)
- fascia_offset = FloatProperty(
+ fascia_offset : FloatProperty(
name="Offset",
description="Offset from roof border",
default=0,
unit='LENGTH', subtype='DISTANCE',
update=update_components
)
- fascia_altitude = FloatProperty(
+ fascia_altitude : FloatProperty(
name="Altitude",
description="Fascia altitude from roof",
default=0.1,
@@ -4202,19 +4202,19 @@ class archipack_roof(ArchipackLines, ArchipackObject, Manipulable, PropertyGroup
update=update_components
)
- bargeboard_enable = BoolProperty(
+ bargeboard_enable : BoolProperty(
name="Enable",
description="Enable Bargeboard",
default=True,
update=update_components
)
- bargeboard_expand = BoolProperty(
+ bargeboard_expand : BoolProperty(
options={'SKIP_SAVE'},
name="Bargeboard",
description="Expand Bargeboard panel",
default=False
)
- bargeboard_height = FloatProperty(
+ bargeboard_height : FloatProperty(
name="Height",
description="Height",
min=0.01,
@@ -4222,7 +4222,7 @@ class archipack_roof(ArchipackLines, ArchipackObject, Manipulable, PropertyGroup
unit='LENGTH', subtype='DISTANCE',
update=update_components
)
- bargeboard_width = FloatProperty(
+ bargeboard_width : FloatProperty(
name="Width",
description="Width",
min=0.01,
@@ -4230,14 +4230,14 @@ class archipack_roof(ArchipackLines, ArchipackObject, Manipulable, PropertyGroup
unit='LENGTH', subtype='DISTANCE',
update=update_components
)
- bargeboard_offset = FloatProperty(
+ bargeboard_offset : FloatProperty(
name="Offset",
description="Offset from roof border",
default=0.001,
unit='LENGTH', subtype='DISTANCE',
update=update_components
)
- bargeboard_altitude = FloatProperty(
+ bargeboard_altitude : FloatProperty(
name="Altitude",
description="Fascia altitude from roof",
default=0.1,
@@ -4245,25 +4245,25 @@ class archipack_roof(ArchipackLines, ArchipackObject, Manipulable, PropertyGroup
update=update_components
)
- t_parent = StringProperty(
+ t_parent : StringProperty(
name="Parent",
default="",
update=update_parent
)
- t_part = IntProperty(
+ t_part : IntProperty(
name="Part",
description="Parent part index",
default=0,
min=0,
update=update_cutter
)
- t_dist_x = FloatProperty(
+ t_dist_x : FloatProperty(
name="Dist x",
description="Location on axis ",
default=0,
update=update_cutter
)
- t_dist_y = FloatProperty(
+ t_dist_y : FloatProperty(
name="Dist y",
description="Lateral distance from axis",
min=0.0001,
@@ -4271,21 +4271,21 @@ class archipack_roof(ArchipackLines, ArchipackObject, Manipulable, PropertyGroup
update=update_cutter
)
- hole_offset_left = FloatProperty(
+ hole_offset_left : FloatProperty(
name="Left",
description="Left distance from border",
min=0,
default=0,
update=update_cutter
)
- hole_offset_right = FloatProperty(
+ hole_offset_right : FloatProperty(
name="Right",
description="Right distance from border",
min=0,
default=0,
update=update_cutter
)
- hole_offset_front = FloatProperty(
+ hole_offset_front : FloatProperty(
name="Front",
description="Front distance from border",
default=0,
@@ -4387,7 +4387,7 @@ class archipack_roof(ArchipackLines, ArchipackObject, Manipulable, PropertyGroup
bm.free()
def find_parent(self, context):
- o = context.scene.objects.get(self.t_parent)
+ o = context.scene.objects.get(self.t_parent.strip())
return o, archipack_roof.datablock(o)
def intersection_angle(self, t_slope, t_width, p_slope, angle):
@@ -4420,7 +4420,7 @@ class archipack_roof(ArchipackLines, ArchipackObject, Manipulable, PropertyGroup
t = (d.t_dist_x / seg.length)
x, y, z = seg.lerp(t).to_3d()
dy = -seg.v.normalized()
- child.matrix_world = o.matrix_world * Matrix([
+ child.matrix_world = o.matrix_world @ Matrix([
[dy.x, -dy.y, 0, x],
[dy.y, dy.x, 0, y],
[0, 0, 1, z],
@@ -4438,13 +4438,13 @@ class archipack_roof(ArchipackLines, ArchipackObject, Manipulable, PropertyGroup
d = archipack_roof.datablock(child)
if d is not None and d.t_parent == o.name:
# print("upate_childs(%s)" % (child.name))
- child.select = True
- context.scene.objects.active = child
+ child.select_set(state=True)
+ context.view_layer.objects.active = child
# regenerate hole
d.update(context, update_hole=True, update_parent=False)
- child.select = False
- o.select = True
- context.scene.objects.active = o
+ child.select_set(state=False)
+ o.select_set(state=True)
+ context.view_layer.objects.active = o
def update(self,
context,
@@ -4726,8 +4726,8 @@ def update_operation(self, context):
class archipack_roof_cutter_segment(ArchipackCutterPart, PropertyGroup):
- manipulators = CollectionProperty(type=archipack_manipulator)
- type = EnumProperty(
+ manipulators : CollectionProperty(type=archipack_manipulator)
+ type : EnumProperty(
name="Type",
items=(
('SIDE', 'Side', 'Side with bargeboard', 0),
@@ -4742,7 +4742,7 @@ class archipack_roof_cutter_segment(ArchipackCutterPart, PropertyGroup):
)
def find_in_selection(self, context):
- selected = [o for o in context.selected_objects]
+ selected = context.selected_objects[:]
for o in selected:
d = archipack_roof_cutter.datablock(o)
if d:
@@ -4754,8 +4754,8 @@ class archipack_roof_cutter_segment(ArchipackCutterPart, PropertyGroup):
class archipack_roof_cutter(ArchipackCutter, ArchipackObject, Manipulable, PropertyGroup):
# boundary
- parts = CollectionProperty(type=archipack_roof_cutter_segment)
- boundary = StringProperty(
+ parts : CollectionProperty(type=archipack_roof_cutter_segment)
+ boundary : StringProperty(
default="",
name="Boundary",
description="Boundary of t child to cut parent"
@@ -4778,11 +4778,11 @@ class archipack_roof_cutter(ArchipackCutter, ArchipackObject, Manipulable, Prope
d = archipack_roof.datablock(o.parent)
if d is not None:
- o.parent.select = True
- context.scene.objects.active = o.parent
+ o.parent.select_set(state=True)
+ context.view_layer.objects.active = o.parent
d.update(context, update_childs=False, update_hole=False)
- o.parent.select = False
- context.scene.objects.active = o
+ o.parent.select_set(state=False)
+ context.view_layer.objects.active = o
# print("update_parent")
@@ -4791,7 +4791,7 @@ class ARCHIPACK_PT_roof_cutter(Panel):
bl_label = "Roof Cutter"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
- bl_category = 'ArchiPack'
+ bl_category = 'Archipack'
@classmethod
def poll(cls, context):
@@ -4808,7 +4808,7 @@ class ARCHIPACK_PT_roof_cutter(Panel):
box.label(text="Auto Cutter:")
box.label(text=prop.boundary)
else:
- box.operator('archipack.roof_cutter_manipulate', icon='HAND')
+ box.operator('archipack.roof_cutter_manipulate', icon='VIEW_PAN')
box.prop(prop, 'operation', text="")
box = layout.box()
box.label(text="From curve")
@@ -4828,7 +4828,7 @@ class ARCHIPACK_PT_roof(Panel):
bl_label = "Roof"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
- bl_category = 'ArchiPack'
+ bl_category = 'Archipack'
@classmethod
def poll(cls, context):
@@ -4842,13 +4842,13 @@ class ARCHIPACK_PT_roof(Panel):
scene = context.scene
layout = self.layout
row = layout.row(align=True)
- row.operator('archipack.roof_manipulate', icon='HAND')
+ row.operator('archipack.roof_manipulate', icon='VIEW_PAN')
box = layout.box()
row = box.row(align=True)
row.operator("archipack.roof_preset_menu", text=bpy.types.ARCHIPACK_OT_roof_preset_menu.bl_label)
- row.operator("archipack.roof_preset", text="", icon='ZOOMIN')
- row.operator("archipack.roof_preset", text="", icon='ZOOMOUT').remove_active = True
+ row.operator("archipack.roof_preset", text="", icon='ADD')
+ row.operator("archipack.roof_preset", text="", icon='REMOVE').remove_active = True
box = layout.box()
box.prop_search(prop, "t_parent", scene, "objects", text="Parent", icon='OBJECT_DATA')
layout.operator('archipack.roof_cutter').parent = o.name
@@ -5029,9 +5029,9 @@ class ARCHIPACK_OT_roof(ArchipackCreateTool, Operator):
d = m.archipack_roof.add()
# make manipulators selectable
d.manipulable_selectable = True
- context.scene.objects.link(o)
- o.select = True
- context.scene.objects.active = o
+ self.link_object_to_scene(context, o)
+ o.select_set(state=True)
+ context.view_layer.objects.active = o
self.add_material(o)
# disable progress bar when
@@ -5050,8 +5050,8 @@ class ARCHIPACK_OT_roof(ArchipackCreateTool, Operator):
bpy.ops.object.select_all(action="DESELECT")
o = self.create(context)
o.location = context.scene.cursor_location
- o.select = True
- context.scene.objects.active = o
+ o.select_set(state=True)
+ context.view_layer.objects.active = o
self.manipulate()
return {'FINISHED'}
else:
@@ -5066,13 +5066,13 @@ class ARCHIPACK_OT_roof_cutter(ArchipackCreateTool, Operator):
bl_category = 'Archipack'
bl_options = {'REGISTER', 'UNDO'}
- parent = StringProperty("")
+ parent : StringProperty("")
def create(self, context):
m = bpy.data.meshes.new("Roof Cutter")
o = bpy.data.objects.new("Roof Cutter", m)
d = m.archipack_roof_cutter.add()
- parent = context.scene.objects.get(self.parent)
+ parent = context.scene.objects.get(self.parent.strip())
if parent is not None:
o.parent = parent
bbox = parent.bound_box
@@ -5081,7 +5081,7 @@ class ARCHIPACK_OT_roof_cutter(ArchipackCreateTool, Operator):
x1, y1, z = bbox[6]
x = 0.2 * (x1 - x0)
y = 0.2 * (y1 - y0)
- o.matrix_world = parent.matrix_world * Matrix([
+ o.matrix_world = parent.matrix_world @ Matrix([
[1, 0, 0, -3 * x],
[0, 1, 0, 0],
[0, 0, 1, 0],
@@ -5104,9 +5104,9 @@ class ARCHIPACK_OT_roof_cutter(ArchipackCreateTool, Operator):
o.location = context.scene.cursor_location
# make manipulators selectable
d.manipulable_selectable = True
- context.scene.objects.link(o)
- o.select = True
- context.scene.objects.active = o
+ self.link_object_to_scene(context, o)
+ o.select_set(state=True)
+ context.view_layer.objects.active = o
self.add_material(o)
self.load_preset(d)
update_operation(d, context)
@@ -5119,8 +5119,8 @@ class ARCHIPACK_OT_roof_cutter(ArchipackCreateTool, Operator):
if context.mode == "OBJECT":
bpy.ops.object.select_all(action="DESELECT")
o = self.create(context)
- o.select = True
- context.scene.objects.active = o
+ o.select_set(state=True)
+ context.view_layer.objects.active = o
self.manipulate()
return {'FINISHED'}
else:
@@ -5133,14 +5133,14 @@ class ARCHIPACK_OT_roof_cutter(ArchipackCreateTool, Operator):
# ------------------------------------------------------------------
-class ARCHIPACK_OT_roof_from_curve(Operator):
+class ARCHIPACK_OT_roof_from_curve(ArchipackCreateTool, Operator):
bl_idname = "archipack.roof_from_curve"
bl_label = "Roof curve"
bl_description = "Create a roof from a curve"
bl_category = 'Archipack'
bl_options = {'REGISTER', 'UNDO'}
- auto_manipulate = BoolProperty(default=True)
+ auto_manipulate : BoolProperty(default=True)
@classmethod
def poll(self, context):
@@ -5149,7 +5149,7 @@ class ARCHIPACK_OT_roof_from_curve(Operator):
def draw(self, context):
layout = self.layout
row = layout.row()
- row.label("Use Properties panel (N) to define parms", icon='INFO')
+ row.label(text="Use Properties panel (N) to define parms", icon='INFO')
def create(self, context):
curve = context.active_object
@@ -5159,9 +5159,9 @@ class ARCHIPACK_OT_roof_from_curve(Operator):
# make manipulators selectable
d.manipulable_selectable = True
d.user_defined_path = curve.name
- context.scene.objects.link(o)
- o.select = True
- context.scene.objects.active = o
+ self.link_object_to_scene(context, o)
+ o.select_set(state=True)
+ context.view_layer.objects.active = o
d.update_path(context)
spline = curve.data.splines[0]
@@ -5172,14 +5172,14 @@ class ARCHIPACK_OT_roof_from_curve(Operator):
else:
pt = Vector((0, 0, 0))
# pretranslate
- o.matrix_world = curve.matrix_world * Matrix([
+ o.matrix_world = curve.matrix_world @ Matrix([
[1, 0, 0, pt.x],
[0, 1, 0, pt.y],
[0, 0, 1, pt.z],
[0, 0, 0, 1]
])
- o.select = True
- context.scene.objects.active = o
+ o.select_set(state=True)
+ context.view_layer.objects.active = o
return o
# -----------------------------------------------------
@@ -5250,7 +5250,7 @@ class ArchipackThrottleHandler():
def start_timer(self, context):
self.start = time.time()
- self._timer = context.window_manager.event_timer_add(self.delay, context.window)
+ self._timer = context.window_manager.event_timer_add(self.delay, window=context.window)
def stop_timer(self, context):
if self._timer is not None:
@@ -5300,7 +5300,7 @@ class ARCHIPACK_OT_roof_throttle_update(Operator):
bl_idname = "archipack.roof_throttle_update"
bl_label = "Update childs with a delay"
- name = StringProperty()
+ name : StringProperty()
def kill_handler(self, context, name):
if name in throttle_handlers.keys():
@@ -5318,19 +5318,19 @@ class ARCHIPACK_OT_roof_throttle_update(Operator):
if self.name in throttle_handlers.keys():
if throttle_handlers[self.name].modal(context, event):
act = context.active_object
- o = context.scene.objects.get(self.name)
+ o = context.scene.objects.get(self.name.strip())
# print("delay update of %s" % (self.name))
if o is not None:
- selected = o.select
- o.select = True
- context.scene.objects.active = o
+ selected = o.select_get()
+ o.select_set(state=True)
+ context.view_layer.objects.active = o
d = o.data.archipack_roof[0]
d.update(context,
force_update=True,
update_parent=False)
# skip_parent_update=self.skip_parent_update)
- o.select = selected
- context.scene.objects.active = act
+ o.select_set(state=selected)
+ context.view_layer.objects.active = act
del throttle_handlers[self.name]
return {'FINISHED'}
else:
diff --git a/archipack/archipack_slab.py b/archipack/archipack_slab.py
index a6cc9bf6..38150094 100644
--- a/archipack/archipack_slab.py
+++ b/archipack/archipack_slab.py
@@ -220,7 +220,7 @@ class SlabGenerator(CutAblePolygon, CutAbleGenerator):
# rotate seg
seg.rotate(a)
# rotate delta from rotation center to segment start
- dp = rM * (seg.p0 - p0)
+ dp = rM @ (seg.p0 - p0)
seg.translate(dp)
def translate(self, idx_from, dp):
@@ -282,7 +282,7 @@ class SlabGenerator(CutAblePolygon, CutAbleGenerator):
use_dissolve_boundaries=False,
verts=bm.verts,
edges=bm.edges,
- delimit=1)
+ delimit={'MATERIAL'})
bm.to_mesh(o.data)
bm.free()
@@ -319,7 +319,7 @@ materials_enum = (
class archipack_slab_material(PropertyGroup):
- index = EnumProperty(
+ index : EnumProperty(
items=materials_enum,
default='4',
update=update
@@ -330,7 +330,7 @@ class archipack_slab_material(PropertyGroup):
find witch selected object this instance belongs to
provide support for "copy to selected"
"""
- selected = [o for o in context.selected_objects]
+ selected = context.selected_objects[:]
for o in selected:
props = archipack_slab.datablock(o)
if props:
@@ -349,12 +349,12 @@ class archipack_slab_child(PropertyGroup):
"""
Store child fences to be able to sync
"""
- child_name = StringProperty()
- idx = IntProperty()
+ child_name : StringProperty()
+ idx : IntProperty()
def get_child(self, context):
d = None
- child = context.scene.objects.get(self.child_name)
+ child = context.scene.objects.get(self.child_name.strip())
if child is not None and child.data is not None:
if 'archipack_fence' in child.data:
d = child.data.archipack_fence[0]
@@ -439,7 +439,7 @@ class ArchipackSegment():
@TODO: share this base class with
stair, wall, fence, slab
"""
- type = EnumProperty(
+ type : EnumProperty(
items=(
('S_SEG', 'Straight', '', 0),
('C_SEG', 'Curved', '', 1),
@@ -447,19 +447,19 @@ class ArchipackSegment():
default='S_SEG',
update=update_type
)
- length = FloatProperty(
+ length : FloatProperty(
name="Length",
min=0.01,
default=2.0,
update=update
)
- radius = FloatProperty(
+ radius : FloatProperty(
name="Radius",
min=0.5,
default=0.7,
update=update
)
- da = FloatProperty(
+ da : FloatProperty(
name="Angle",
min=-pi,
max=pi,
@@ -467,7 +467,7 @@ class ArchipackSegment():
subtype='ANGLE', unit='ROTATION',
update=update
)
- a0 = FloatProperty(
+ a0 : FloatProperty(
name="Start angle",
min=-2 * pi,
max=2 * pi,
@@ -475,21 +475,21 @@ class ArchipackSegment():
subtype='ANGLE', unit='ROTATION',
update=update
)
- offset = FloatProperty(
+ offset : FloatProperty(
name="Offset",
description="Add to current segment offset",
default=0,
unit='LENGTH', subtype='DISTANCE',
update=update
)
- linked_idx = IntProperty(default=-1)
+ linked_idx : IntProperty(default=-1)
# @TODO:
# flag to handle wall's x_offset
# when set add wall offset value to segment offset
# pay attention at allowing per wall segment offset
- manipulators = CollectionProperty(type=archipack_manipulator)
+ manipulators : CollectionProperty(type=archipack_manipulator)
def find_in_selection(self, context):
raise NotImplementedError
@@ -531,7 +531,7 @@ class archipack_slab_part(ArchipackSegment, PropertyGroup):
find witch selected object this instance belongs to
provide support for "copy to selected"
"""
- selected = [o for o in context.selected_objects]
+ selected = context.selected_objects[:]
for o in selected:
props = archipack_slab.datablock(o)
if props:
@@ -543,38 +543,38 @@ class archipack_slab_part(ArchipackSegment, PropertyGroup):
class archipack_slab(ArchipackObject, Manipulable, PropertyGroup):
# boundary
- n_parts = IntProperty(
+ n_parts : IntProperty(
name="Parts",
min=1,
default=1, update=update_manipulators
)
- parts = CollectionProperty(type=archipack_slab_part)
- closed = BoolProperty(
+ parts : CollectionProperty(type=archipack_slab_part)
+ closed : BoolProperty(
default=True,
name="Close",
options={'SKIP_SAVE'},
update=update_manipulators
)
# UI layout related
- parts_expand = BoolProperty(
+ parts_expand : BoolProperty(
options={'SKIP_SAVE'},
default=False
)
- x_offset = FloatProperty(
+ x_offset : FloatProperty(
name="Offset",
min=-1000, max=1000,
default=0.0, precision=2, step=1,
unit='LENGTH', subtype='DISTANCE',
update=update
)
- z = FloatProperty(
+ z : FloatProperty(
name="Thickness",
default=0.3, precision=2, step=1,
unit='LENGTH', subtype='DISTANCE',
update=update
)
- auto_synch = BoolProperty(
+ auto_synch : BoolProperty(
name="Auto-Synch",
description="Keep wall in synch when editing",
default=True,
@@ -584,13 +584,13 @@ class archipack_slab(ArchipackObject, Manipulable, PropertyGroup):
# Global slab offset
# will only affect slab parts sharing a wall
- childs = CollectionProperty(type=archipack_slab_child)
+ childs : CollectionProperty(type=archipack_slab_child)
# Flag to prevent mesh update while making bulk changes over variables
# use :
# .auto_update = False
# bulk changes
# .auto_update = True
- auto_update = BoolProperty(
+ auto_update : BoolProperty(
options={'SKIP_SAVE'},
default=True,
update=update_manipulators
@@ -696,17 +696,17 @@ class archipack_slab(ArchipackObject, Manipulable, PropertyGroup):
o = context.active_object
bpy.ops.archipack.fence(auto_manipulate=False)
c = context.active_object
- c.select = True
+ c.select_set(state=True)
c.data.archipack_fence[0].n_parts = 3
- c.select = False
+ c.select_set(state=False)
# link to o
c.location = Vector((0, 0, 0))
c.parent = o
c.location = g.segs[where + 1].p0.to_3d()
self.add_child(c.name, where + 1)
# c.matrix_world.translation = g.segs[where].p1.to_3d()
- o.select = True
- context.scene.objects.active = o
+ o.select_set(state=True)
+ context.view_layer.objects.active = o
self.relocate_childs(context, o, g)
def add_part(self, context, length):
@@ -736,7 +736,7 @@ class archipack_slab(ArchipackObject, Manipulable, PropertyGroup):
dmax = 0.2
for c in o.children:
if (c.data and 'archipack_fence' in c.data):
- pt = (itM * c.matrix_world.translation).to_2d()
+ pt = (itM @ c.matrix_world.translation).to_2d()
for idx, seg in enumerate(g.segs):
# may be optimized with a bound check
res, d, t = seg.point_sur_segment(pt)
@@ -874,14 +874,14 @@ class archipack_slab(ArchipackObject, Manipulable, PropertyGroup):
else:
d.parts[i].length = max(0.01, seg.length)
- wall.select = True
- context.scene.objects.active = wall
+ wall.select_set(state=True)
+ context.view_layer.objects.active = wall
d.auto_update = True
- wall.select = False
+ wall.select_set(state=False)
- o.select = True
- context.scene.objects.active = o
+ o.select_set(state=True)
+ context.view_layer.objects.active = o
wall.matrix_world = o.matrix_world.copy()
@@ -897,11 +897,11 @@ class archipack_slab(ArchipackObject, Manipulable, PropertyGroup):
ca = cos(a)
if d is not None:
- c.select = True
+ c.select_set(state=True)
# auto_update need object to be active to
# setup manipulators on the right object
- context.scene.objects.active = c
+ context.view_layer.objects.active = c
d.auto_update = False
for i, part in enumerate(d.parts):
@@ -915,11 +915,11 @@ class archipack_slab(ArchipackObject, Manipulable, PropertyGroup):
part.radius = self.parts[i + child.idx].radius
d.parts[0].a0 = pi / 2
d.auto_update = True
- c.select = False
+ c.select_set(state=False)
- context.scene.objects.active = o
+ context.view_layer.objects.active = o
# preTranslate
- c.matrix_world = tM * Matrix([
+ c.matrix_world = tM @ Matrix([
[sa, ca, 0, x],
[-ca, sa, 0, y],
[0, 0, 1, 0],
@@ -1034,18 +1034,18 @@ class archipack_slab(ArchipackObject, Manipulable, PropertyGroup):
# since this can lower points count by a resolution factor
# use normalized to handle non linear t
if resolution == 0:
- pts.append(wM * p0.co.to_3d())
+ pts.append(wM @ p0.co.to_3d())
else:
v = (p1.co - p0.co).normalized()
d1 = (p0.handle_right - p0.co).normalized()
d2 = (p1.co - p1.handle_left).normalized()
if d1 == v and d2 == v:
- pts.append(wM * p0.co.to_3d())
+ pts.append(wM @ p0.co.to_3d())
else:
- seg = interpolate_bezier(wM * p0.co,
- wM * p0.handle_right,
- wM * p1.handle_left,
- wM * p1.co,
+ seg = interpolate_bezier(wM @ p0.co,
+ wM @ p0.handle_right,
+ wM @ p1.handle_left,
+ wM @ p1.co,
resolution + 1)
for i in range(resolution):
pts.append(seg[i].to_3d())
@@ -1053,7 +1053,7 @@ class archipack_slab(ArchipackObject, Manipulable, PropertyGroup):
def from_spline(self, wM, resolution, spline):
pts = []
if spline.type == 'POLY':
- pts = [wM * p.co.to_3d() for p in spline.points]
+ pts = [wM @ p.co.to_3d() for p in spline.points]
if spline.use_cyclic_u:
pts.append(pts[0])
elif spline.type == 'BEZIER':
@@ -1068,7 +1068,7 @@ class archipack_slab(ArchipackObject, Manipulable, PropertyGroup):
self.interpolate_bezier(pts, wM, p0, p1, resolution)
pts.append(pts[0])
else:
- pts.append(wM * points[-1].co)
+ pts.append(wM @ points[-1].co)
self.from_points(pts, spline.use_cyclic_u)
@@ -1147,8 +1147,8 @@ class archipack_slab(ArchipackObject, Manipulable, PropertyGroup):
# relocate before cutting segs
self.relocate_childs(context, o, g)
- o.select = True
- context.scene.objects.active = o
+ o.select_set(state=True)
+ context.view_layer.objects.active = o
g.cut(context, o)
@@ -1244,8 +1244,8 @@ def update_operation(self, context):
class archipack_slab_cutter_segment(ArchipackCutterPart, PropertyGroup):
- manipulators = CollectionProperty(type=archipack_manipulator)
- type = EnumProperty(
+ manipulators : CollectionProperty(type=archipack_manipulator)
+ type : EnumProperty(
name="Type",
items=(
('DEFAULT', 'Side', 'Side with rake', 0),
@@ -1260,7 +1260,7 @@ class archipack_slab_cutter_segment(ArchipackCutterPart, PropertyGroup):
)
def find_in_selection(self, context):
- selected = [o for o in context.selected_objects]
+ selected = context.selected_objects[:]
for o in selected:
d = archipack_slab_cutter.datablock(o)
if d:
@@ -1278,7 +1278,7 @@ class archipack_slab_cutter_segment(ArchipackCutterPart, PropertyGroup):
class archipack_slab_cutter(ArchipackCutter, ArchipackObject, Manipulable, PropertyGroup):
- parts = CollectionProperty(type=archipack_slab_cutter_segment)
+ parts : CollectionProperty(type=archipack_slab_cutter_segment)
def update_points(self, context, o, pts, update_parent=False):
self.auto_update = False
@@ -1291,11 +1291,11 @@ class archipack_slab_cutter(ArchipackCutter, ArchipackObject, Manipulable, Prope
d = archipack_slab.datablock(o.parent)
if d is not None:
- o.parent.select = True
- context.scene.objects.active = o.parent
+ o.parent.select_set(state=True)
+ context.view_layer.objects.active = o.parent
d.update(context)
- o.parent.select = False
- context.scene.objects.active = o
+ o.parent.select_set(state=False)
+ context.view_layer.objects.active = o
class ARCHIPACK_PT_slab(Panel):
@@ -1305,7 +1305,7 @@ class ARCHIPACK_PT_slab(Panel):
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
# bl_context = 'object'
- bl_category = 'ArchiPack'
+ bl_category = 'Archipack'
@classmethod
def poll(cls, context):
@@ -1317,7 +1317,7 @@ class ARCHIPACK_PT_slab(Panel):
if prop is None:
return
layout = self.layout
- layout.operator('archipack.slab_manipulate', icon='HAND')
+ layout.operator('archipack.slab_manipulate', icon='VIEW_PAN')
box = layout.box()
box.operator('archipack.slab_cutter').parent = o.name
box = layout.box()
@@ -1341,7 +1341,7 @@ class ARCHIPACK_PT_slab_cutter(Panel):
bl_label = "Slab Cutter"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
- bl_category = 'ArchiPack'
+ bl_category = 'Archipack'
@classmethod
def poll(cls, context):
@@ -1354,7 +1354,7 @@ class ARCHIPACK_PT_slab_cutter(Panel):
layout = self.layout
scene = context.scene
box = layout.box()
- box.operator('archipack.slab_cutter_manipulate', icon='HAND')
+ box.operator('archipack.slab_cutter_manipulate', icon='VIEW_PAN')
box.prop(prop, 'operation', text="")
box = layout.box()
box.label(text="From curve")
@@ -1380,7 +1380,7 @@ class ARCHIPACK_OT_slab_insert(Operator):
bl_description = "Insert part"
bl_category = 'Archipack'
bl_options = {'REGISTER', 'UNDO'}
- index = IntProperty(default=0)
+ index : IntProperty(default=0)
def execute(self, context):
if context.mode == "OBJECT":
@@ -1400,7 +1400,7 @@ class ARCHIPACK_OT_slab_balcony(Operator):
bl_description = "Insert part"
bl_category = 'Archipack'
bl_options = {'REGISTER', 'UNDO'}
- index = IntProperty(default=0)
+ index : IntProperty(default=0)
def execute(self, context):
if context.mode == "OBJECT":
@@ -1420,7 +1420,7 @@ class ARCHIPACK_OT_slab_remove(Operator):
bl_description = "Remove part"
bl_category = 'Archipack'
bl_options = {'REGISTER', 'UNDO'}
- index = IntProperty(default=0)
+ index : IntProperty(default=0)
def execute(self, context):
if context.mode == "OBJECT":
@@ -1452,9 +1452,9 @@ class ARCHIPACK_OT_slab(ArchipackCreateTool, Operator):
d = m.archipack_slab.add()
# make manipulators selectable
d.manipulable_selectable = True
- context.scene.objects.link(o)
- o.select = True
- context.scene.objects.active = o
+ self.link_object_to_scene(context, o)
+ o.select_set(state=True)
+ context.view_layer.objects.active = o
self.load_preset(d)
self.add_material(o)
return o
@@ -1467,8 +1467,8 @@ class ARCHIPACK_OT_slab(ArchipackCreateTool, Operator):
bpy.ops.object.select_all(action="DESELECT")
o = self.create(context)
o.location = bpy.context.scene.cursor_location
- o.select = True
- context.scene.objects.active = o
+ o.select_set(state=True)
+ context.view_layer.objects.active = o
self.manipulate()
return {'FINISHED'}
else:
@@ -1483,7 +1483,7 @@ class ARCHIPACK_OT_slab_from_curve(Operator):
bl_category = 'Archipack'
bl_options = {'REGISTER', 'UNDO'}
- auto_manipulate = BoolProperty(default=True)
+ auto_manipulate : BoolProperty(default=True)
@classmethod
def poll(self, context):
@@ -1496,12 +1496,12 @@ class ARCHIPACK_OT_slab_from_curve(Operator):
def draw(self, context):
layout = self.layout
row = layout.row()
- row.label("Use Properties panel (N) to define parms", icon='INFO')
+ row.label(text="Use Properties panel (N) to define parms", icon='INFO')
def create(self, context):
curve = context.active_object
bpy.ops.archipack.slab(auto_manipulate=self.auto_manipulate)
- o = context.scene.objects.active
+ o = context.view_layer.objects.active
d = archipack_slab.datablock(o)
spline = curve.data.splines[0]
d.from_spline(curve.matrix_world, 12, spline)
@@ -1512,12 +1512,7 @@ class ARCHIPACK_OT_slab_from_curve(Operator):
else:
pt = Vector((0, 0, 0))
# pretranslate
- o.matrix_world = curve.matrix_world * Matrix([
- [1, 0, 0, pt.x],
- [0, 1, 0, pt.y],
- [0, 0, 1, pt.z],
- [0, 0, 0, 1]
- ])
+ o.matrix_world = curve.matrix_world @ Matrix.Translation(pt)
return o
# -----------------------------------------------------
@@ -1540,8 +1535,8 @@ class ARCHIPACK_OT_slab_from_wall(Operator):
bl_category = 'Archipack'
bl_options = {'REGISTER', 'UNDO'}
- auto_manipulate = BoolProperty(default=True)
- ceiling = BoolProperty(default=False)
+ auto_manipulate : BoolProperty(default=True)
+ ceiling : BoolProperty(default=False)
@classmethod
def poll(self, context):
@@ -1552,7 +1547,7 @@ class ARCHIPACK_OT_slab_from_wall(Operator):
wall = context.active_object
wd = wall.data.archipack_wall2[0]
bpy.ops.archipack.slab(auto_manipulate=False)
- o = context.scene.objects.active
+ o = context.view_layer.objects.active
d = archipack_slab.datablock(o)
d.auto_update = False
d.closed = True
@@ -1576,24 +1571,24 @@ class ARCHIPACK_OT_slab_from_wall(Operator):
[0, 1, 0, 0],
[0, 0, 1, wd.z + d.z],
[0, 0, 0, 1],
- ]) * wall.matrix_world
+ ]) @ wall.matrix_world
else:
o.matrix_world = wall.matrix_world.copy()
bpy.ops.object.select_all(action='DESELECT')
# parenting childs to wall reference point
if wall.parent is None:
x, y, z = wall.bound_box[0]
- context.scene.cursor_location = wall.matrix_world * Vector((x, y, z))
+ context.scene.cursor_location = wall.matrix_world @ Vector((x, y, z))
# fix issue #9
- context.scene.objects.active = wall
+ context.view_layer.objects.active = wall
bpy.ops.archipack.reference_point()
else:
- wall.parent.select = True
- context.scene.objects.active = wall.parent
- wall.select = True
- o.select = True
+ wall.parent.select_set(state=True)
+ context.view_layer.objects.active = wall.parent
+ wall.select_set(state=True)
+ o.select_set(state=True)
bpy.ops.archipack.parent_to_reference()
- wall.parent.select = False
+ wall.parent.select_set(state=False)
return o
@@ -1604,8 +1599,8 @@ class ARCHIPACK_OT_slab_from_wall(Operator):
if context.mode == "OBJECT":
bpy.ops.object.select_all(action="DESELECT")
o = self.create(context)
- o.select = True
- context.scene.objects.active = o
+ o.select_set(state=True)
+ context.view_layer.objects.active = o
if self.auto_manipulate:
bpy.ops.archipack.slab_manipulate('INVOKE_DEFAULT')
return {'FINISHED'}
@@ -1621,13 +1616,13 @@ class ARCHIPACK_OT_slab_cutter(ArchipackCreateTool, Operator):
bl_category = 'Archipack'
bl_options = {'REGISTER', 'UNDO'}
- parent = StringProperty("")
+ parent : StringProperty("")
def create(self, context):
m = bpy.data.meshes.new("Slab Cutter")
o = bpy.data.objects.new("Slab Cutter", m)
d = m.archipack_slab_cutter.add()
- parent = context.scene.objects.get(self.parent)
+ parent = context.scene.objects.get(self.parent.strip())
if parent is not None:
o.parent = parent
bbox = parent.bound_box
@@ -1636,7 +1631,7 @@ class ARCHIPACK_OT_slab_cutter(ArchipackCreateTool, Operator):
x1, y1, z = bbox[6]
x = 0.2 * (x1 - x0)
y = 0.2 * (y1 - y0)
- o.matrix_world = parent.matrix_world * Matrix([
+ o.matrix_world = parent.matrix_world @ Matrix([
[1, 0, 0, -3 * x],
[0, 1, 0, 0],
[0, 0, 1, 0],
@@ -1659,9 +1654,9 @@ class ARCHIPACK_OT_slab_cutter(ArchipackCreateTool, Operator):
o.location = context.scene.cursor_location
# make manipulators selectable
d.manipulable_selectable = True
- context.scene.objects.link(o)
- o.select = True
- context.scene.objects.active = o
+ self.link_object_to_scene(context, o)
+ o.select_set(state=True)
+ context.view_layer.objects.active = o
# self.add_material(o)
self.load_preset(d)
update_operation(d, context)
@@ -1674,8 +1669,8 @@ class ARCHIPACK_OT_slab_cutter(ArchipackCreateTool, Operator):
if context.mode == "OBJECT":
bpy.ops.object.select_all(action="DESELECT")
o = self.create(context)
- o.select = True
- context.scene.objects.active = o
+ o.select_set(state=True)
+ context.view_layer.objects.active = o
self.manipulate()
return {'FINISHED'}
else:
diff --git a/archipack/archipack_snap.py b/archipack/archipack_snap.py
index 92e09595..92d6ae24 100644
--- a/archipack/archipack_snap.py
+++ b/archipack/archipack_snap.py
@@ -43,7 +43,7 @@
sp.delta
action_callback(context, event, state, sp)
- state in {'SUCCESS', 'CANCEL'}
+ state in {'RUNNING', 'SUCCESS', 'CANCEL'}
sp.takeloc
sp.placeloc
sp.delta
@@ -64,6 +64,9 @@
import bpy
from bpy.types import Operator
from mathutils import Vector, Matrix
+import logging
+
+logger = logging.getLogger("archipack")
def dumb_callback(context, event, state, sp):
@@ -81,8 +84,8 @@ class SnapStore:
callback = None
draw = None
helper = None
- takeloc = Vector((0, 0, 0))
- placeloc = Vector((0, 0, 0))
+ takeloc = Vector()
+ placeloc = Vector()
constraint_axis = (True, True, False)
helper_matrix = Matrix()
transform_orientation = 'GLOBAL'
@@ -93,20 +96,20 @@ class SnapStore:
act = None
sel = []
use_snap = False
- snap_element = None
+ snap_elements = None
snap_target = None
pivot_point = None
trans_orientation = None
def snap_point(takeloc=None,
- draw=dumb_draw,
- callback=dumb_callback,
- takemat=None,
- constraint_axis=(True, True, False),
- transform_orientation='GLOBAL',
- mode='OBJECT',
- release_confirm=True):
+ draw=None,
+ callback=dumb_callback,
+ takemat=None,
+ constraint_axis=(True, True, False),
+ transform_orientation='GLOBAL',
+ mode='OBJECT',
+ release_confirm=True):
"""
Invoke op from outside world
in a convenient importable function
@@ -126,16 +129,19 @@ def snap_point(takeloc=None,
SnapStore.callback = callback
SnapStore.constraint_axis = constraint_axis
SnapStore.release_confirm = release_confirm
+
if takemat is not None:
SnapStore.helper_matrix = takemat
- takeloc = takemat.translation
+ takeloc = takemat.translation.copy()
transform_orientation = 'LOCAL'
elif takeloc is not None:
- SnapStore.helper_matrix = Matrix().Translation(takeloc)
+ SnapStore.helper_matrix = Matrix.Translation(takeloc)
else:
raise ValueError("ArchipackSnap: Either takeloc or takemat must be defined")
+
SnapStore.takeloc = takeloc
- SnapStore.placeloc = takeloc
+ SnapStore.placeloc = takeloc.copy()
+
SnapStore.transform_orientation = transform_orientation
# @NOTE: unused mode var to switch between OBJECT and EDIT mode
@@ -160,53 +166,56 @@ class ArchipackSnapBase():
- takeloc
- placeloc
"""
+
def __init__(self):
self._draw_handler = None
def init(self, context, event):
# Store context data
- if SnapStore.instances_running < 1:
- SnapStore.sel = [o for o in context.selected_objects]
- SnapStore.act = context.active_object
- bpy.ops.object.select_all(action="DESELECT")
- SnapStore.use_snap = context.tool_settings.use_snap
- SnapStore.snap_element = context.tool_settings.snap_element
- SnapStore.snap_target = context.tool_settings.snap_target
- SnapStore.pivot_point = context.space_data.pivot_point
- SnapStore.trans_orientation = context.space_data.transform_orientation
+ # if SnapStore.instances_running < 1:
+ SnapStore.sel = context.selected_objects[:]
+ SnapStore.act = context.object
+ bpy.ops.object.select_all(action="DESELECT")
+ ts = context.tool_settings
+ SnapStore.use_snap = ts.use_snap
+ SnapStore.snap_elements = ts.snap_elements
+ SnapStore.snap_target = ts.snap_target
+ SnapStore.pivot_point = ts.transform_pivot_point
+ SnapStore.trans_orientation = context.scene.transform_orientation
self.create_helper(context)
- SnapStore.instances_running += 1
- # print("ArchipackSnapBase init: %s" % (SnapStore.instances_running))
- self.set_transform_orientation(context)
- args = (self, context)
- self._draw_handler = bpy.types.SpaceView3D.draw_handler_add(SnapStore.draw, args, 'WINDOW', 'POST_PIXEL')
+ # Use a timer to broadcast a TIMER event while transform.translate is running
+ self._timer = context.window_manager.event_timer_add(0.1, window=context.window)
+
+ if SnapStore.draw is not None:
+ args = (self, context)
+ self._draw_handler = bpy.types.SpaceView3D.draw_handler_add(SnapStore.draw, args, 'WINDOW', 'POST_PIXEL')
+
+ def remove_timer(self, context):
+ if self._timer is not None:
+ context.window_manager.event_timer_remove(self._timer)
def exit(self, context):
- bpy.types.SpaceView3D.draw_handler_remove(self._draw_handler, 'WINDOW')
- # trick to allow launch 2nd instance
- # via callback, preserve context as it
- SnapStore.instances_running -= 1
- # print("ArchipackSnapBase exit: %s" % (SnapStore.instances_running))
- if SnapStore.instances_running > 0:
- return
- self.destroy_helper(context)
+ self.remove_timer(context)
+
+ if self._draw_handler is not None:
+ bpy.types.SpaceView3D.draw_handler_remove(self._draw_handler, 'WINDOW')
+
# Restore original context
- context.tool_settings.use_snap = SnapStore.use_snap
- context.tool_settings.snap_element = SnapStore.snap_element
- context.tool_settings.snap_target = SnapStore.snap_target
- context.space_data.pivot_point = SnapStore.pivot_point
- context.space_data.transform_orientation = SnapStore.trans_orientation
+ if hasattr(context, "tool_settings"):
+ ts = context.tool_settings
+ ts.use_snap = SnapStore.use_snap
+ ts.snap_elements = SnapStore.snap_elements
+ ts.snap_target = SnapStore.snap_target
+ ts.transform_pivot_point = SnapStore.pivot_point
+ context.scene.transform_orientation = SnapStore.trans_orientation
for o in SnapStore.sel:
- o.select = True
+ o.select_set(state=True)
if SnapStore.act is not None:
- context.scene.objects.active = SnapStore.act
-
- def set_transform_orientation(self, context):
- """
- Allow local constraint orientation to be set
- """
- context.space_data.transform_orientation = SnapStore.transform_orientation
+ SnapStore.act.select_set(state=True)
+ context.view_layer.objects.active = SnapStore.act
+ self.destroy_helper(context)
+ logger.debug("Snap.exit %s", context.object.name)
def create_helper(self, context):
"""
@@ -217,23 +226,25 @@ class ArchipackSnapBase():
Do target helper be linked to scene in order to work ?
"""
-
- helper_idx = bpy.data.objects.find('Archipack_snap_helper')
- if helper_idx > -1:
- helper = bpy.data.objects[helper_idx]
- if context.scene.objects.find('Archipack_snap_helper') < 0:
- context.scene.objects.link(helper)
+ helper = bpy.data.objects.get('Archipack_snap_helper')
+ if helper is not None:
+ print("helper found")
+ if context.scene.objects.get('Archipack_snap_helper') is None:
+ print("link helper")
+ # self.link_object_to_scene(context, helper)
+ context.scene.collection.objects.link(helper)
else:
- bpy.ops.object.add(type='MESH')
- helper = context.active_object
- helper.name = 'Archipack_snap_helper'
+ print("create helper")
+ m = bpy.data.meshes.new("Archipack_snap_helper")
+ m.vertices.add(count=1)
+ helper = bpy.data.objects.new("Archipack_snap_helper", m)
+ context.scene.collection.objects.link(helper)
helper.use_fake_user = True
helper.data.use_fake_user = True
- # hide snap helper
- # helper.hide = True
+
helper.matrix_world = SnapStore.helper_matrix
- helper.select = True
- context.scene.objects.active = helper
+ helper.select_set(state=True)
+ context.view_layer.objects.active = helper
SnapStore.helper = helper
def destroy_helper(self, context):
@@ -242,7 +253,8 @@ class ArchipackSnapBase():
currently only support OBJECT mode
"""
if SnapStore.helper is not None:
- context.scene.objects.unlink(SnapStore.helper)
+ # @TODO: Fix this
+ # self.unlink_object_from_scene(context, SnapStore.helper)
SnapStore.helper = None
@property
@@ -266,35 +278,61 @@ class ArchipackSnapBase():
class ARCHIPACK_OT_snap(ArchipackSnapBase, Operator):
bl_idname = 'archipack.snap'
bl_label = 'Archipack snap'
- bl_options = {'UNDO'}
+ bl_options = {'INTERNAL'} # , 'UNDO'
def modal(self, context, event):
- # print("Snap.modal event %s %s" % (event.type, event.value))
+
+ if SnapStore.helper is not None:
+ logger.debug("Snap.modal event %s %s location:%s",
+ event.type,
+ event.value,
+ SnapStore.helper.location)
+
context.area.tag_redraw()
- # NOTE: this part only run after transform LEFTMOUSE RELEASE
- # or with ESC and RIGHTMOUSE
- if event.type not in {'ESC', 'RIGHTMOUSE', 'LEFTMOUSE', 'MOUSEMOVE'}:
- # print("Snap.modal skip unknown event %s %s" % (event.type, event.value))
- # self.report({'WARNING'}, "ARCHIPACK_OT_snap unknown event")
- return{'PASS_THROUGH'}
+
+ if event.type in ('TIMER', 'NOTHING'):
+ SnapStore.callback(context, event, 'RUNNING', self)
+ return {'PASS_THROUGH'}
+
+ if event.type not in ('ESC', 'RIGHTMOUSE', 'LEFTMOUSE', 'MOUSEMOVE', 'INBETWEEN_MOUSEMOVE'):
+ return {'PASS_THROUGH'}
+
if event.type in ('ESC', 'RIGHTMOUSE'):
SnapStore.callback(context, event, 'CANCEL', self)
else:
SnapStore.placeloc = SnapStore.helper.location
- SnapStore.callback(context, event, 'SUCCESS', self)
+ # on tt modal exit with right click, the delta is 0 so exit
+ if self.delta.length == 0:
+ SnapStore.callback(context, event, 'CANCEL', self)
+ else:
+ SnapStore.callback(context, event, 'SUCCESS', self)
+
self.exit(context)
# self.report({'INFO'}, "ARCHIPACK_OT_snap exit")
- return{'FINISHED'}
+ return {'FINISHED'}
def invoke(self, context, event):
if context.area.type == 'VIEW_3D':
- # print("Snap.invoke event %s %s" % (event.type, event.value))
+
+ if event.type in ('ESC', 'RIGHTMOUSE'):
+ return {'FINISHED'}
+
self.init(context, event)
+
+ logger.debug("Snap.invoke event %s %s location:%s act:%s",
+ event.type,
+ event.value,
+ SnapStore.helper.location, context.object.name)
+
context.window_manager.modal_handler_add(self)
+
bpy.ops.transform.translate('INVOKE_DEFAULT',
- constraint_axis=SnapStore.constraint_axis,
- constraint_orientation=SnapStore.transform_orientation,
- release_confirm=SnapStore.release_confirm)
+ constraint_axis=SnapStore.constraint_axis,
+ constraint_orientation=SnapStore.transform_orientation,
+ release_confirm=SnapStore.release_confirm)
+
+ logger.debug("Snap.invoke transform.translate done")
+
return {'RUNNING_MODAL'}
else:
self.report({'WARNING'}, "View3D not found, cannot run operator")
diff --git a/archipack/archipack_stair.py b/archipack/archipack_stair.py
index 6e3d6212..c0d75317 100644
--- a/archipack/archipack_stair.py
+++ b/archipack/archipack_stair.py
@@ -234,16 +234,16 @@ class Stair():
def project_uv(self, rM, uvs, verts, indexes, up_axis='Z'):
if up_axis == 'Z':
- uvs.append([(rM * Vector(verts[i])).to_2d() for i in indexes])
+ uvs.append([(rM @ Vector(verts[i])).to_2d() for i in indexes])
elif up_axis == 'Y':
- uvs.append([(x, z) for x, y, z in [(rM * Vector(verts[i])) for i in indexes]])
+ uvs.append([(x, z) for x, y, z in [(rM @ Vector(verts[i])) for i in indexes]])
else:
- uvs.append([(y, z) for x, y, z in [(rM * Vector(verts[i])) for i in indexes]])
+ uvs.append([(y, z) for x, y, z in [(rM @ Vector(verts[i])) for i in indexes]])
def get_proj_matrix(self, part, t, nose_y):
# a matrix to project verts
# into uv space for horizontal parts of this step
- # so uv = (rM * vertex).to_2d()
+ # so uv = (rM @ vertex).to_2d()
tl = t - nose_y / self.get_length("LEFT")
tr = t - nose_y / self.get_length("RIGHT")
t2, part, dz, shape = self.get_part(tl, "LEFT")
@@ -264,7 +264,7 @@ class Stair():
# a matrix to project verts
# into uv space for horizontal parts of this step
- # so uv = (rM * vertex).to_2d()
+ # so uv = (rM @ vertex).to_2d()
rM = self.get_proj_matrix(self, t, nose_y)
if self.z_mode == 'LINEAR':
@@ -1024,7 +1024,7 @@ class StairGenerator():
co.z += z1
if 'Slope' in g:
co.z += co.y * slope
- verts.append(tM * co)
+ verts.append(tM @ co)
matids += self.user_defined_mat
faces += [tuple([i + f for i in p.vertices]) for p in m.polygons]
uvs += self.user_defined_uvs
@@ -1106,7 +1106,7 @@ class StairGenerator():
if s < n_sections:
v1 = subs[s + 1][0].v.normalized()
dir = (v0 + v1).normalized()
- scale = 1 / cos(0.5 * acos(min(1, max(-1, v0 * v1))))
+ scale = 1 / cos(0.5 * acos(min(1, max(-1, v0.dot(v1)))))
for p in profile:
x, y = n.p + scale * p.x * dir
z = zl + p.y + altitude
@@ -1463,7 +1463,7 @@ class StairGenerator():
if s < n_sections:
v1 = cur_sect[s + 1][0].v.normalized()
dir = (v0 + v1).normalized()
- scale = 1 / cos(0.5 * acos(min(1, max(-1, v0 * v1))))
+ scale = 1 / cos(0.5 * acos(min(1, max(-1, v0.dot(v1)))))
for p in profile:
x, y = n.p + scale * p.x * dir
z = zl + p.y + z_offset
@@ -1546,7 +1546,7 @@ materials_enum = (
class archipack_stair_material(PropertyGroup):
- index = EnumProperty(
+ index : EnumProperty(
items=materials_enum,
default='4',
update=update
@@ -1557,7 +1557,7 @@ class archipack_stair_material(PropertyGroup):
find witch selected object this instance belongs to
provide support for "copy to selected"
"""
- selected = [o for o in context.selected_objects]
+ selected = context.selected_objects[:]
for o in selected:
props = archipack_stair.datablock(o)
if props:
@@ -1573,7 +1573,7 @@ class archipack_stair_material(PropertyGroup):
class archipack_stair_part(PropertyGroup):
- type = EnumProperty(
+ type : EnumProperty(
items=(
('S_STAIR', 'Straight stair', '', 0),
('C_STAIR', 'Curved stair', '', 1),
@@ -1585,21 +1585,21 @@ class archipack_stair_part(PropertyGroup):
default='S_STAIR',
update=update_manipulators
)
- length = FloatProperty(
+ length : FloatProperty(
name="Length",
min=0.01,
default=2.0,
unit='LENGTH', subtype='DISTANCE',
update=update
)
- radius = FloatProperty(
+ radius : FloatProperty(
name="Radius",
min=0.01,
default=0.7,
unit='LENGTH', subtype='DISTANCE',
update=update
)
- da = FloatProperty(
+ da : FloatProperty(
name="Angle",
min=-pi,
max=pi,
@@ -1607,7 +1607,7 @@ class archipack_stair_part(PropertyGroup):
subtype='ANGLE', unit='ROTATION',
update=update
)
- left_shape = EnumProperty(
+ left_shape : EnumProperty(
items=(
('RECTANGLE', 'Straight', '', 0),
('CIRCLE', 'Curved ', '', 1)
@@ -1615,7 +1615,7 @@ class archipack_stair_part(PropertyGroup):
default='RECTANGLE',
update=update
)
- right_shape = EnumProperty(
+ right_shape : EnumProperty(
items=(
('RECTANGLE', 'Straight', '', 0),
('CIRCLE', 'Curved ', '', 1)
@@ -1623,14 +1623,14 @@ class archipack_stair_part(PropertyGroup):
default='RECTANGLE',
update=update
)
- manipulators = CollectionProperty(type=archipack_manipulator)
+ manipulators : CollectionProperty(type=archipack_manipulator)
def find_datablock_in_selection(self, context):
"""
find witch selected object this instance belongs to
provide support for "copy to selected"
"""
- selected = [o for o in context.selected_objects]
+ selected = context.selected_objects[:]
for o in selected:
props = archipack_stair.datablock(o)
if props:
@@ -1670,69 +1670,69 @@ class archipack_stair_part(PropertyGroup):
class archipack_stair(ArchipackObject, Manipulable, PropertyGroup):
- parts = CollectionProperty(type=archipack_stair_part)
- n_parts = IntProperty(
+ parts : CollectionProperty(type=archipack_stair_part)
+ n_parts : IntProperty(
name="Parts",
min=1,
max=32,
default=1, update=update_manipulators
)
- step_depth = FloatProperty(
+ step_depth : FloatProperty(
name="Going",
min=0.2,
default=0.25,
unit='LENGTH', subtype='DISTANCE',
update=update
)
- width = FloatProperty(
+ width : FloatProperty(
name="Width",
min=0.01,
default=1.2,
unit='LENGTH', subtype='DISTANCE',
update=update
)
- height = FloatProperty(
+ height : FloatProperty(
name="Height",
min=0.1,
default=2.4, precision=2, step=1,
unit='LENGTH', subtype='DISTANCE',
update=update
)
- nose_y = FloatProperty(
+ nose_y : FloatProperty(
name="Depth",
min=0.0,
default=0.02, precision=2, step=1,
unit='LENGTH', subtype='DISTANCE',
update=update
)
- x_offset = FloatProperty(
+ x_offset : FloatProperty(
name="Offset",
default=0.0, precision=2, step=1,
unit='LENGTH', subtype='DISTANCE',
update=update
)
- nose_z = FloatProperty(
+ nose_z : FloatProperty(
name="Height",
min=0.001,
default=0.03, precision=2, step=1,
unit='LENGTH', subtype='DISTANCE',
update=update
)
- bottom_z = FloatProperty(
+ bottom_z : FloatProperty(
name="Thickness",
min=0.001,
default=0.03, precision=2, step=1,
unit='LENGTH', subtype='DISTANCE',
update=update
)
- radius = FloatProperty(
+ radius : FloatProperty(
name="Radius",
min=0.5,
default=0.7,
unit='LENGTH', subtype='DISTANCE',
update=update
)
- da = FloatProperty(
+ da : FloatProperty(
name="Angle",
min=-pi,
max=pi,
@@ -1740,7 +1740,7 @@ class archipack_stair(ArchipackObject, Manipulable, PropertyGroup):
subtype='ANGLE', unit='ROTATION',
update=update
)
- total_angle = FloatProperty(
+ total_angle : FloatProperty(
name="Angle",
min=-50 * pi,
max=50 * pi,
@@ -1748,7 +1748,7 @@ class archipack_stair(ArchipackObject, Manipulable, PropertyGroup):
subtype='ANGLE', unit='ROTATION',
update=update
)
- steps_type = EnumProperty(
+ steps_type : EnumProperty(
name="Steps",
items=(
('CLOSED', 'Closed', '', 0),
@@ -1758,7 +1758,7 @@ class archipack_stair(ArchipackObject, Manipulable, PropertyGroup):
default='CLOSED',
update=update
)
- nose_type = EnumProperty(
+ nose_type : EnumProperty(
name="Nosing",
items=(
('STRAIGHT', 'Straight', '', 0),
@@ -1767,7 +1767,7 @@ class archipack_stair(ArchipackObject, Manipulable, PropertyGroup):
default='STRAIGHT',
update=update
)
- left_shape = EnumProperty(
+ left_shape : EnumProperty(
items=(
('RECTANGLE', 'Straight', '', 0),
('CIRCLE', 'Curved ', '', 1)
@@ -1775,7 +1775,7 @@ class archipack_stair(ArchipackObject, Manipulable, PropertyGroup):
default='RECTANGLE',
update=update
)
- right_shape = EnumProperty(
+ right_shape : EnumProperty(
items=(
('RECTANGLE', 'Straight', '', 0),
('CIRCLE', 'Curved ', '', 1)
@@ -1783,7 +1783,7 @@ class archipack_stair(ArchipackObject, Manipulable, PropertyGroup):
default='RECTANGLE',
update=update
)
- z_mode = EnumProperty(
+ z_mode : EnumProperty(
name="Interp z",
items=(
('STANDARD', 'Standard', '', 0),
@@ -1793,7 +1793,7 @@ class archipack_stair(ArchipackObject, Manipulable, PropertyGroup):
default='STANDARD',
update=update
)
- presets = EnumProperty(
+ presets : EnumProperty(
items=(
('STAIR_I', 'I stair', '', 0),
('STAIR_L', 'L stair', '', 1),
@@ -1803,131 +1803,131 @@ class archipack_stair(ArchipackObject, Manipulable, PropertyGroup):
),
default='STAIR_I', update=update_preset
)
- left_post = BoolProperty(
+ left_post : BoolProperty(
name='left',
default=True,
update=update
)
- right_post = BoolProperty(
+ right_post : BoolProperty(
name='right',
default=True,
update=update
)
- post_spacing = FloatProperty(
+ post_spacing : FloatProperty(
name="Spacing",
min=0.1,
default=1.0, precision=2, step=1,
unit='LENGTH', subtype='DISTANCE',
update=update
)
- post_x = FloatProperty(
+ post_x : FloatProperty(
name="Width",
min=0.001,
default=0.04, precision=2, step=1,
unit='LENGTH', subtype='DISTANCE',
update=update
)
- post_y = FloatProperty(
+ post_y : FloatProperty(
name="Length",
min=0.001,
default=0.04, precision=2, step=1,
unit='LENGTH', subtype='DISTANCE',
update=update
)
- post_z = FloatProperty(
+ post_z : FloatProperty(
name="Height",
min=0.001,
default=1, precision=2, step=1,
unit='LENGTH', subtype='DISTANCE',
update=update
)
- post_alt = FloatProperty(
+ post_alt : FloatProperty(
name="Altitude",
min=-100,
default=0, precision=2, step=1,
unit='LENGTH', subtype='DISTANCE',
update=update
)
- post_offset_x = FloatProperty(
+ post_offset_x : FloatProperty(
name="Offset",
min=-100.0, max=100,
default=0.02, precision=2, step=1,
unit='LENGTH', subtype='DISTANCE',
update=update
)
- post_corners = BoolProperty(
+ post_corners : BoolProperty(
name="Only on edges",
update=update,
default=False
)
- user_defined_post_enable = BoolProperty(
+ user_defined_post_enable : BoolProperty(
name="User",
update=update,
default=True
)
- user_defined_post = StringProperty(
+ user_defined_post : StringProperty(
name="User defined",
update=update
)
- idmat_post = EnumProperty(
+ idmat_post : EnumProperty(
name="Post",
items=materials_enum,
default='4',
update=update
)
- left_subs = BoolProperty(
+ left_subs : BoolProperty(
name='left',
default=False,
update=update
)
- right_subs = BoolProperty(
+ right_subs : BoolProperty(
name='right',
default=False,
update=update
)
- subs_spacing = FloatProperty(
+ subs_spacing : FloatProperty(
name="Spacing",
min=0.05,
default=0.10, precision=2, step=1,
unit='LENGTH', subtype='DISTANCE',
update=update
)
- subs_x = FloatProperty(
+ subs_x : FloatProperty(
name="Width",
min=0.001,
default=0.02, precision=2, step=1,
unit='LENGTH', subtype='DISTANCE',
update=update
)
- subs_y = FloatProperty(
+ subs_y : FloatProperty(
name="Length",
min=0.001,
default=0.02, precision=2, step=1,
unit='LENGTH', subtype='DISTANCE',
update=update
)
- subs_z = FloatProperty(
+ subs_z : FloatProperty(
name="Height",
min=0.001,
default=1, precision=2, step=1,
unit='LENGTH', subtype='DISTANCE',
update=update
)
- subs_alt = FloatProperty(
+ subs_alt : FloatProperty(
name="Altitude",
min=-100,
default=0, precision=2, step=1,
unit='LENGTH', subtype='DISTANCE',
update=update
)
- subs_offset_x = FloatProperty(
+ subs_offset_x : FloatProperty(
name="Offset",
min=-100.0, max=100,
default=0.0, precision=2, step=1,
unit='LENGTH', subtype='DISTANCE',
update=update
)
- subs_bottom = EnumProperty(
+ subs_bottom : EnumProperty(
name="Bottom",
items=(
('STEP', 'Follow step', '', 0),
@@ -1936,88 +1936,88 @@ class archipack_stair(ArchipackObject, Manipulable, PropertyGroup):
default='STEP',
update=update
)
- user_defined_subs_enable = BoolProperty(
+ user_defined_subs_enable : BoolProperty(
name="User",
update=update,
default=True
)
- user_defined_subs = StringProperty(
+ user_defined_subs : StringProperty(
name="User defined",
update=update
)
- idmat_subs = EnumProperty(
+ idmat_subs : EnumProperty(
name="Subs",
items=materials_enum,
default='4',
update=update
)
- left_panel = BoolProperty(
+ left_panel : BoolProperty(
name='left',
default=True,
update=update
)
- right_panel = BoolProperty(
+ right_panel : BoolProperty(
name='right',
default=True,
update=update
)
- panel_alt = FloatProperty(
+ panel_alt : FloatProperty(
name="Altitude",
default=0.25, precision=2, step=1,
unit='LENGTH', subtype='DISTANCE',
update=update
)
- panel_x = FloatProperty(
+ panel_x : FloatProperty(
name="Width",
min=0.001,
default=0.01, precision=2, step=1,
unit='LENGTH', subtype='DISTANCE',
update=update
)
- panel_z = FloatProperty(
+ panel_z : FloatProperty(
name="Height",
min=0.001,
default=0.6, precision=2, step=1,
unit='LENGTH', subtype='DISTANCE',
update=update
)
- panel_dist = FloatProperty(
+ panel_dist : FloatProperty(
name="Spacing",
min=0.001,
default=0.05, precision=2, step=1,
unit='LENGTH', subtype='DISTANCE',
update=update
)
- panel_offset_x = FloatProperty(
+ panel_offset_x : FloatProperty(
name="Offset",
default=0.0, precision=2, step=1,
unit='LENGTH', subtype='DISTANCE',
update=update
)
- idmat_panel = EnumProperty(
+ idmat_panel : EnumProperty(
name="Panels",
items=materials_enum,
default='5',
update=update
)
- left_rail = BoolProperty(
+ left_rail : BoolProperty(
name="left",
update=update,
default=False
)
- right_rail = BoolProperty(
+ right_rail : BoolProperty(
name="right",
update=update,
default=False
)
- rail_n = IntProperty(
+ rail_n : IntProperty(
name="#",
default=1,
min=0,
max=31,
update=update
)
- rail_x = FloatVectorProperty(
+ rail_x : FloatVectorProperty(
name="Width",
default=[
0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05,
@@ -2031,7 +2031,7 @@ class archipack_stair(ArchipackObject, Manipulable, PropertyGroup):
unit='LENGTH',
update=update
)
- rail_z = FloatVectorProperty(
+ rail_z : FloatVectorProperty(
name="Height",
default=[
0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05,
@@ -2045,7 +2045,7 @@ class archipack_stair(ArchipackObject, Manipulable, PropertyGroup):
unit='LENGTH',
update=update
)
- rail_offset = FloatVectorProperty(
+ rail_offset : FloatVectorProperty(
name="Offset",
default=[
0, 0, 0, 0, 0, 0, 0, 0,
@@ -2058,7 +2058,7 @@ class archipack_stair(ArchipackObject, Manipulable, PropertyGroup):
unit='LENGTH',
update=update
)
- rail_alt = FloatVectorProperty(
+ rail_alt : FloatVectorProperty(
name="Altitude",
default=[
1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,
@@ -2071,47 +2071,47 @@ class archipack_stair(ArchipackObject, Manipulable, PropertyGroup):
unit='LENGTH',
update=update
)
- rail_mat = CollectionProperty(type=archipack_stair_material)
+ rail_mat : CollectionProperty(type=archipack_stair_material)
- left_handrail = BoolProperty(
+ left_handrail : BoolProperty(
name="left",
update=update,
default=True
)
- right_handrail = BoolProperty(
+ right_handrail : BoolProperty(
name="right",
update=update,
default=True
)
- handrail_offset = FloatProperty(
+ handrail_offset : FloatProperty(
name="Offset",
default=0.0, precision=2, step=1,
unit='LENGTH', subtype='DISTANCE',
update=update
)
- handrail_alt = FloatProperty(
+ handrail_alt : FloatProperty(
name="Altitude",
default=1.0, precision=2, step=1,
unit='LENGTH', subtype='DISTANCE',
update=update
)
- handrail_extend = FloatProperty(
+ handrail_extend : FloatProperty(
name="Extend",
default=0.1, precision=2, step=1,
unit='LENGTH', subtype='DISTANCE',
update=update
)
- handrail_slice_left = BoolProperty(
+ handrail_slice_left : BoolProperty(
name='Slice',
default=True,
update=update
)
- handrail_slice_right = BoolProperty(
+ handrail_slice_right : BoolProperty(
name='Slice',
default=True,
update=update
)
- handrail_profil = EnumProperty(
+ handrail_profil : EnumProperty(
name="Profil",
items=(
('SQUARE', 'Square', '', 0),
@@ -2121,21 +2121,21 @@ class archipack_stair(ArchipackObject, Manipulable, PropertyGroup):
default='SQUARE',
update=update
)
- handrail_x = FloatProperty(
+ handrail_x : FloatProperty(
name="Width",
min=0.001,
default=0.04, precision=2, step=1,
unit='LENGTH', subtype='DISTANCE',
update=update
)
- handrail_y = FloatProperty(
+ handrail_y : FloatProperty(
name="Height",
min=0.001,
default=0.04, precision=2, step=1,
unit='LENGTH', subtype='DISTANCE',
update=update
)
- handrail_radius = FloatProperty(
+ handrail_radius : FloatProperty(
name="Radius",
min=0.001,
default=0.02, precision=2, step=1,
@@ -2143,85 +2143,85 @@ class archipack_stair(ArchipackObject, Manipulable, PropertyGroup):
update=update
)
- left_string = BoolProperty(
+ left_string : BoolProperty(
name="left",
update=update,
default=False
)
- right_string = BoolProperty(
+ right_string : BoolProperty(
name="right",
update=update,
default=False
)
- string_x = FloatProperty(
+ string_x : FloatProperty(
name="Width",
min=-100.0,
default=0.02, precision=2, step=1,
unit='LENGTH', subtype='DISTANCE',
update=update
)
- string_z = FloatProperty(
+ string_z : FloatProperty(
name="Height",
default=0.3, precision=2, step=1,
unit='LENGTH', subtype='DISTANCE',
update=update
)
- string_offset = FloatProperty(
+ string_offset : FloatProperty(
name="Offset",
default=0.0, precision=2, step=1,
unit='LENGTH', subtype='DISTANCE',
update=update
)
- string_alt = FloatProperty(
+ string_alt : FloatProperty(
name="Altitude",
default=-0.04, precision=2, step=1,
unit='LENGTH', subtype='DISTANCE',
update=update
)
- idmat_bottom = EnumProperty(
+ idmat_bottom : EnumProperty(
name="Bottom",
items=materials_enum,
default='1',
update=update
)
- idmat_raise = EnumProperty(
+ idmat_raise : EnumProperty(
name="Raise",
items=materials_enum,
default='1',
update=update
)
- idmat_step_front = EnumProperty(
+ idmat_step_front : EnumProperty(
name="Step front",
items=materials_enum,
default='3',
update=update
)
- idmat_top = EnumProperty(
+ idmat_top : EnumProperty(
name="Top",
items=materials_enum,
default='3',
update=update
)
- idmat_side = EnumProperty(
+ idmat_side : EnumProperty(
name="Side",
items=materials_enum,
default='1',
update=update
)
- idmat_step_side = EnumProperty(
+ idmat_step_side : EnumProperty(
name="Step Side",
items=materials_enum,
default='3',
update=update
)
- idmat_handrail = EnumProperty(
+ idmat_handrail : EnumProperty(
name="Handrail",
items=materials_enum,
default='3',
update=update
)
- idmat_string = EnumProperty(
+ idmat_string : EnumProperty(
name="String",
items=materials_enum,
default='3',
@@ -2229,35 +2229,35 @@ class archipack_stair(ArchipackObject, Manipulable, PropertyGroup):
)
# UI layout related
- parts_expand = BoolProperty(
+ parts_expand : BoolProperty(
default=False
)
- steps_expand = BoolProperty(
+ steps_expand : BoolProperty(
default=False
)
- rail_expand = BoolProperty(
+ rail_expand : BoolProperty(
default=False
)
- idmats_expand = BoolProperty(
+ idmats_expand : BoolProperty(
default=False
)
- handrail_expand = BoolProperty(
+ handrail_expand : BoolProperty(
default=False
)
- string_expand = BoolProperty(
+ string_expand : BoolProperty(
default=False
)
- post_expand = BoolProperty(
+ post_expand : BoolProperty(
default=False
)
- panel_expand = BoolProperty(
+ panel_expand : BoolProperty(
default=False
)
- subs_expand = BoolProperty(
+ subs_expand : BoolProperty(
default=False
)
- auto_update = BoolProperty(
+ auto_update : BoolProperty(
options={'SKIP_SAVE'},
default=True,
update=update_manipulators
@@ -2389,7 +2389,7 @@ class archipack_stair(ArchipackObject, Manipulable, PropertyGroup):
if self.user_defined_post_enable:
# user defined posts
- user_def_post = context.scene.objects.get(self.user_defined_post)
+ user_def_post = context.scene.objects.get(self.user_defined_post.strip())
if user_def_post is not None and user_def_post.type == 'MESH':
g.setup_user_defined_post(user_def_post, self.post_x, self.post_y, self.post_z)
@@ -2408,7 +2408,7 @@ class archipack_stair(ArchipackObject, Manipulable, PropertyGroup):
# user defined subs
if self.user_defined_subs_enable:
- user_def_subs = context.scene.objects.get(self.user_defined_subs)
+ user_def_subs = context.scene.objects.get(self.user_defined_subs.strip())
if user_def_subs is not None and user_def_subs.type == 'MESH':
g.setup_user_defined_post(user_def_subs, self.subs_x, self.subs_y, self.subs_z)
@@ -2539,7 +2539,7 @@ class ARCHIPACK_PT_stair(Panel):
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
# bl_context = 'object'
- bl_category = 'ArchiPack'
+ bl_category = 'Archipack'
@classmethod
def poll(cls, context):
@@ -2552,7 +2552,7 @@ class ARCHIPACK_PT_stair(Panel):
scene = context.scene
layout = self.layout
row = layout.row(align=True)
- row.operator('archipack.stair_manipulate', icon='HAND')
+ row.operator('archipack.stair_manipulate', icon='VIEW_PAN')
row = layout.row(align=True)
row.prop(prop, 'presets', text="")
box = layout.box()
@@ -2560,8 +2560,8 @@ class ARCHIPACK_PT_stair(Panel):
row = box.row(align=True)
# row.menu("ARCHIPACK_MT_stair_preset", text=bpy.types.ARCHIPACK_MT_stair_preset.bl_label)
row.operator("archipack.stair_preset_menu", text=bpy.types.ARCHIPACK_OT_stair_preset_menu.bl_label)
- row.operator("archipack.stair_preset", text="", icon='ZOOMIN')
- row.operator("archipack.stair_preset", text="", icon='ZOOMOUT').remove_active = True
+ row.operator("archipack.stair_preset", text="", icon='ADD')
+ row.operator("archipack.stair_preset", text="", icon='REMOVE').remove_active = True
box = layout.box()
box.prop(prop, 'width')
box.prop(prop, 'height')
@@ -2753,9 +2753,9 @@ class ARCHIPACK_OT_stair(ArchipackCreateTool, Operator):
m = bpy.data.meshes.new("Stair")
o = bpy.data.objects.new("Stair", m)
d = m.archipack_stair.add()
- context.scene.objects.link(o)
- o.select = True
- context.scene.objects.active = o
+ self.link_object_to_scene(context, o)
+ o.select_set(state=True)
+ context.view_layer.objects.active = o
self.load_preset(d)
self.add_material(o)
m.auto_smooth_angle = 0.20944
@@ -2769,8 +2769,8 @@ class ARCHIPACK_OT_stair(ArchipackCreateTool, Operator):
bpy.ops.object.select_all(action="DESELECT")
o = self.create(context)
o.location = context.scene.cursor_location
- o.select = True
- context.scene.objects.active = o
+ o.select_set(state=True)
+ context.view_layer.objects.active = o
self.manipulate()
return {'FINISHED'}
else:
diff --git a/archipack/archipack_thumbs.py b/archipack/archipack_thumbs.py
index 8f652ab2..4ae4d020 100644
--- a/archipack/archipack_thumbs.py
+++ b/archipack/archipack_thumbs.py
@@ -33,38 +33,30 @@ def log(s):
print("[log]" + s)
-def generateThumb(context, cls, preset):
- log("### RENDER THUMB ############################")
- log("Start generating: " + cls)
+def create_lamp(context, loc):
+ bpy.ops.object.light_add(
+ type='POINT',
+ radius=1,
+ view_align=False,
+ location=loc)
+ lamp = context.active_object
+ lamp.data.use_nodes = True
+ tree = lamp.data.node_tree
+ return tree, tree.nodes, lamp.data
- # engine settings
- context.scene.render.engine = 'CYCLES'
- render = context.scene.cycles
- render.progressive = 'PATH'
- render.samples = 24
- try:
- render.use_square_samples = True
- except:
- pass
- render.preview_samples = 24
- render.aa_samples = 24
- render.transparent_max_bounces = 8
- render.transparent_min_bounces = 8
- render.transmission_bounces = 8
- render.max_bounces = 8
- render.min_bounces = 6
- render.caustics_refractive = False
- render.caustics_reflective = False
- render.use_transparent_shadows = True
- render.diffuse_bounces = 1
- render.glossy_bounces = 4
- bpy.ops.object.select_all(action="SELECT")
- bpy.ops.object.delete()
- # create object, loading preset
- getattr(bpy.ops.archipack, cls)('INVOKE_DEFAULT', filepath=preset, auto_manipulate=False)
- o = context.active_object
- size = o.dimensions
+def create_camera(context, loc, rot):
+ bpy.ops.object.camera_add(
+ view_align=True,
+ enter_editmode=False,
+ location=loc,
+ rotation=rot)
+ cam = context.active_object
+ context.scene.camera = cam
+ return cam
+
+
+def get_center(o):
x, y, z = o.bound_box[0]
min_x = x
min_y = y
@@ -73,93 +65,131 @@ def generateThumb(context, cls, preset):
max_x = x
max_y = y
max_z = z
- center = Vector((
+ return Vector((
min_x + 0.5 * (max_x - min_x),
min_y + 0.5 * (max_y - min_y),
min_z + 0.5 * (max_z - min_z)))
- # oposite / tan (0.5 * fov) where fov is 49.134 deg
+
+def apply_simple_material(o, name, color):
+ m = bpy.data.materials.new(name)
+ m.use_nodes = True
+ m.node_tree.nodes[1].inputs[0].default_value = color
+ o.data.materials.append(m)
+
+
+# /home/stephen/blender-28-git/build_linux_full/bin/blender --background --factory-startup -noaudio --python /home/stephen/blender-28-git/build_linux_full/bin/2.80/scripts/addons/archipack/archipack_thumbs.py -- addon:archipack matlib:/medias/stephen/DATA/lib/ cls:roof preset:/home/stephen/.config/blender/2.80/scripts/presets/archipack_roof/square.py
+
+
+def generateThumb(context, cls, preset, engine):
+ log("### RENDER THUMB ############################")
+
+ # Cleanup scene
+ for o in context.scene.objects:
+ o.select_set(state=True)
+
+ bpy.ops.object.delete()
+
+ log("Start generating: %s" % cls)
+
+ # setup render
+
+ context.scene.render.engine = engine
+
+ if engine == 'CYCLES':
+ cycles = context.scene.cycles
+ cycles.progressive = 'PATH'
+ cycles.samples = 24
+ try:
+ cycles.use_square_samples = True
+ except:
+ pass
+ cycles.preview_samples = 24
+ cycles.aa_samples = 24
+ cycles.transparent_max_bounces = 8
+ cycles.transparent_min_bounces = 8
+ cycles.transmission_bounces = 8
+ cycles.max_bounces = 8
+ cycles.min_bounces = 6
+ cycles.caustics_refractive = False
+ cycles.caustics_reflective = False
+ cycles.use_transparent_shadows = True
+ cycles.diffuse_bounces = 1
+ cycles.glossy_bounces = 4
+
+ elif engine == 'BLENDER_EEVEE':
+ eevee = context.scene.eevee
+ eevee.use_gtao = True
+ eevee.use_ssr = True
+ eevee.use_soft_shadows = True
+ eevee.taa_render_samples = 64
+ else:
+ raise RuntimeError("Unsupported render engine %s" % engine)
+
+ render = context.scene.render
+
+ # engine settings
+ render.resolution_x = 150
+ render.resolution_y = 100
+ render.filepath = preset[:-3] + ".png"
+
+ # create object, loading preset
+ getattr(bpy.ops.archipack, cls)('INVOKE_DEFAULT', filepath=preset, auto_manipulate=False)
+ o = context.active_object
+ size = o.dimensions
+ center = get_center(o)
+
+ # opposite / tan (0.5 * fov) where fov is 49.134 deg
dist = max(size) / 0.32
- loc = center + dist * Vector((0.5, -1, 0.5)).normalized()
+ loc = center + dist * Vector((-0.5, -1, 0.5)).normalized()
log("Prepare camera")
- bpy.ops.object.camera_add(view_align=True,
- enter_editmode=False,
- location=loc,
- rotation=(1.150952, 0.0, 0.462509))
- cam = context.active_object
+ cam = create_camera(context, loc, (1.150952, 0.0, -0.462509))
cam.data.lens = 50
- cam.select = True
- context.scene.camera = cam
- bpy.ops.object.select_all(action="DESELECT")
- o.select = True
+ for ob in context.scene.objects:
+ ob.select_set(state=False)
+
+ o.select_set(state=True)
+
bpy.ops.view3d.camera_to_view_selected()
+ cam.data.lens = 45
log("Prepare scene")
# add plane
bpy.ops.mesh.primitive_plane_add(
- radius=1000,
+ size=1000,
view_align=False,
enter_editmode=False,
location=(0, 0, 0)
- )
+ )
+
p = context.active_object
- m = bpy.data.materials.new("Plane")
- m.use_nodes = True
- m.node_tree.nodes[1].inputs[0].default_value = (1, 1, 1, 1)
- p.data.materials.append(m)
+ apply_simple_material(p, "Plane", (1, 1, 1, 1))
# add 3 lights
- bpy.ops.object.lamp_add(
- type='POINT',
- radius=1,
- view_align=False,
- location=(3.69736, -7, 6.0))
- l = context.active_object
- l.data.use_nodes = True
- tree = l.data.node_tree
- nodes = l.data.node_tree.nodes
+ tree, nodes, lamp = create_lamp(context, (3.69736, -7, 6.0))
+ lamp.energy = 50
emit = nodes["Emission"]
emit.inputs[1].default_value = 2000.0
- bpy.ops.object.lamp_add(
- type='POINT',
- radius=1,
- view_align=False,
- location=(9.414563179016113, 5.446230888366699, 5.903861999511719))
- l = context.active_object
- l.data.use_nodes = True
- tree = l.data.node_tree
- nodes = l.data.node_tree.nodes
+ tree, nodes, lamp = create_lamp(context, (9.414563179016113, 5.446230888366699, 5.903861999511719))
emit = nodes["Emission"]
falloff = nodes.new(type="ShaderNodeLightFalloff")
falloff.inputs[0].default_value = 5
tree.links.new(falloff.outputs[2], emit.inputs[1])
- bpy.ops.object.lamp_add(
- type='POINT',
- radius=1,
- view_align=False,
- location=(-7.847615718841553, 1.03135085105896, 5.903861999511719))
- l = context.active_object
- l.data.use_nodes = True
- tree = l.data.node_tree
- nodes = l.data.node_tree.nodes
+ tree, nodes, lamp = create_lamp(context, (-7.847615718841553, 1.03135085105896, 5.903861999511719))
emit = nodes["Emission"]
falloff = nodes.new(type="ShaderNodeLightFalloff")
falloff.inputs[0].default_value = 5
tree.links.new(falloff.outputs[2], emit.inputs[1])
# Set output filename.
- render = context.scene.render
- render.filepath = preset[:-3] + ".png"
render.use_file_extension = True
render.use_overwrite = True
render.use_compositing = False
render.use_sequencer = False
- render.resolution_x = 150
- render.resolution_y = 100
render.resolution_percentage = 100
# render.image_settings.file_format = 'PNG'
# render.image_settings.color_mode = 'RGBA'
@@ -175,8 +205,9 @@ def generateThumb(context, cls, preset):
if __name__ == "__main__":
- preset = ""
+ preset = ""
+ engine = 'BLENDER_EEVEE' #'CYCLES'
for arg in sys.argv:
if arg.startswith("cls:"):
cls = arg[4:]
@@ -186,9 +217,14 @@ if __name__ == "__main__":
matlib = arg[7:]
if arg.startswith("addon:"):
module = arg[6:]
+ if arg.startswith("engine:"):
+ engine = arg[7:]
try:
+ # log("### ENABLE %s ADDON ############################" % module)
bpy.ops.wm.addon_enable(module=module)
+ # log("### MATLIB PATH ############################")
bpy.context.user_preferences.addons[module].preferences.matlib_path = matlib
except:
raise RuntimeError("module name not found")
- generateThumb(bpy.context, cls, preset)
+ # log("### GENERATE ############################")
+ generateThumb(bpy.context, cls, preset, engine)
diff --git a/archipack/archipack_truss.py b/archipack/archipack_truss.py
index b8056daa..2931717a 100644
--- a/archipack/archipack_truss.py
+++ b/archipack/archipack_truss.py
@@ -43,7 +43,7 @@ def update(self, context):
class archipack_truss(ArchipackObject, Manipulable, PropertyGroup):
- truss_type = EnumProperty(
+ truss_type : EnumProperty(
name="Type",
items=(
('1', 'Prolyte E20', 'Prolyte E20', 0),
@@ -57,40 +57,40 @@ class archipack_truss(ArchipackObject, Manipulable, PropertyGroup):
default='2',
update=update
)
- z = FloatProperty(
+ z : FloatProperty(
name="Height",
default=2.0, min=0.01,
unit='LENGTH', subtype='DISTANCE',
update=update
)
- segs = IntProperty(
+ segs : IntProperty(
name="Segs",
default=6, min=3,
update=update
)
- master_segs = IntProperty(
+ master_segs : IntProperty(
name="Master Segs",
default=1, min=1,
update=update
)
- master_count = IntProperty(
+ master_count : IntProperty(
name="Masters",
default=3, min=2,
update=update
)
- entre_axe = FloatProperty(
+ entre_axe : FloatProperty(
name="Distance",
default=0.239, min=0.001,
unit='LENGTH', subtype='DISTANCE',
update=update
)
- master_radius = FloatProperty(
+ master_radius : FloatProperty(
name="Radius",
default=0.02415, min=0.0001,
unit='LENGTH', subtype='DISTANCE',
update=update
)
- slaves_radius = FloatProperty(
+ slaves_radius : FloatProperty(
name="Subs radius",
default=0.01, min=0.0001,
unit='LENGTH', subtype='DISTANCE',
@@ -101,7 +101,7 @@ class archipack_truss(ArchipackObject, Manipulable, PropertyGroup):
# .auto_update = False
# bulk changes
# .auto_update = True
- auto_update = BoolProperty(
+ auto_update : BoolProperty(
options={'SKIP_SAVE'},
default=True,
update=update
@@ -127,10 +127,10 @@ class archipack_truss(ArchipackObject, Manipulable, PropertyGroup):
if not add:
for seg in range(segs):
- verts.append(tM * tMb * tmpverts[seg])
+ verts.append(tM @ tMb @ tmpverts[seg])
for seg in range(segs):
- verts.append(tM * tMt * tmpverts[seg])
+ verts.append(tM @ tMt @ tmpverts[seg])
for seg in range(segs - 1):
f = cv + seg
@@ -280,7 +280,7 @@ class ARCHIPACK_PT_truss(Panel):
bl_label = "Truss"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
- bl_category = 'ArchiPack'
+ bl_category = 'Archipack'
@classmethod
def poll(cls, context):
@@ -292,7 +292,7 @@ class ARCHIPACK_PT_truss(Panel):
return
layout = self.layout
row = layout.row(align=True)
- row.operator('archipack.truss_manipulate', icon='HAND')
+ row.operator('archipack.truss_manipulate', icon='VIEW_PAN')
box = layout.box()
box.prop(prop, 'truss_type')
box.prop(prop, 'z')
@@ -318,9 +318,9 @@ class ARCHIPACK_OT_truss(ArchipackCreateTool, Operator):
d = m.archipack_truss.add()
# make manipulators selectable
# d.manipulable_selectable = True
- context.scene.objects.link(o)
- o.select = True
- context.scene.objects.active = o
+ self.link_object_to_scene(context, o)
+ o.select_set(state=True)
+ context.view_layer.objects.active = o
self.load_preset(d)
self.add_material(o)
m.auto_smooth_angle = 1.15
@@ -334,8 +334,8 @@ class ARCHIPACK_OT_truss(ArchipackCreateTool, Operator):
bpy.ops.object.select_all(action="DESELECT")
o = self.create(context)
o.location = bpy.context.scene.cursor_location
- o.select = True
- context.scene.objects.active = o
+ o.select_set(state=True)
+ context.view_layer.objects.active = o
self.manipulate()
return {'FINISHED'}
else:
diff --git a/archipack/archipack_wall2.py b/archipack/archipack_wall2.py
index 5f464a02..750cd313 100644
--- a/archipack/archipack_wall2.py
+++ b/archipack/archipack_wall2.py
@@ -45,11 +45,14 @@ from .archipack_manipulator import (
GlPolygon, GlPolyline,
GlLine, GlText, FeedbackPanel
)
-from .archipack_object import ArchipackObject, ArchipackCreateTool, ArchpackDrawTool
+from .archipack_object import ArchipackObject, ArchipackCreateTool, ArchipackDrawTool
from .archipack_2d import Line, Arc
from .archipack_snap import snap_point
from .archipack_keymaps import Keymaps
+import logging
+logger = logging.getLogger("archipack")
+
class Wall():
def __init__(self, wall_z, z, t, flip):
@@ -323,7 +326,7 @@ class WallGenerator():
for i in range(idx_from + 1, len(self.segs)):
seg = self.segs[i]
seg.rotate(a)
- dp = rM * (seg.p0 - p0)
+ dp = rM @ (seg.p0 - p0)
seg.translate(dp)
def translate(self, idx_from, dp):
@@ -338,7 +341,7 @@ class WallGenerator():
"""
move shape fromTM into toTM coordsys
"""
- dp = (toTM.inverted() * fromTM.translation).to_2d()
+ dp = (toTM.inverted() @ fromTM.translation).to_2d()
da = toTM.row[1].to_2d().angle_signed(fromTM.row[1].to_2d())
ca = cos(da)
sa = sin(da)
@@ -347,7 +350,7 @@ class WallGenerator():
[sa, ca]
])
for s in self.segs:
- tp = (rM * s.p0) - s.p0 + dp
+ tp = (rM @ s.p0) - s.p0 + dp
s.rotate(da)
s.translate(tp)
@@ -419,7 +422,7 @@ def update_t_part(self, context):
if o is not None:
# w is parent wall
- w = context.scene.objects.get(self.t_part)
+ w = context.scene.objects.get(self.t_part.strip())
wd = archipack_wall2.datablock(w)
if wd is not None:
@@ -446,27 +449,27 @@ def update_t_part(self, context):
if o.parent is None:
# create a reference point and make it active
x, y, z = w.bound_box[0]
- context.scene.cursor_location = w.matrix_world * Vector((x, y, z))
+ context.scene.cursor_location = w.matrix_world @ Vector((x, y, z))
# fix issue #9
- context.scene.objects.active = o
+ context.view_layer.objects.active = o
bpy.ops.archipack.reference_point()
- o.select = True
+ o.select_set(state=True)
else:
- context.scene.objects.active = o.parent
- w.select = True
+ context.view_layer.objects.active = o.parent
+ w.select_set(state=True)
else:
# w has parent
if o.parent is not w.parent:
link_to_parent = True
- context.scene.objects.active = w.parent
- o.select = True
+ context.view_layer.objects.active = w.parent
+ o.select_set(state=True)
if o.parent is not None:
# store o.parent to delete it
to_delete = o.parent
for c in o.parent.children:
if c is not o:
c.hide_select = False
- c.select = True
+ c.select_set(state=True)
parent = context.active_object
@@ -487,11 +490,11 @@ def update_t_part(self, context):
])
# dir in absolute world coordsys
- dir = orM * og.segs[0].straight(1, 0).v
+ dir = orM @ og.segs[0].straight(1, 0).v
# pt in w coordsys
pos = otM.translation
- pt = (wtM.inverted() * pos).to_2d()
+ pt = (wtM.inverted() @ pos).to_2d()
for wall_idx, wall in enumerate(wg.segs):
res, dist, t = wall.point_sur_segment(pt)
@@ -504,8 +507,8 @@ def update_t_part(self, context):
# rotation here is wrong when w has not parent while o has parent
if res and t > 0 and t < 1 and abs(dist) < dmax:
- x = wrM * wall.straight(1, t).v
- y = wrM * wall.normal(t).v.normalized()
+ x = wrM @ wall.straight(1, t).v
+ y = wrM @ wall.normal(t).v.normalized()
self.parts[0].a0 = dir.angle_signed(x)
o.matrix_world = Matrix([
[x.x, -y.x, 0, pos.x],
@@ -531,8 +534,8 @@ def update_t_part(self, context):
# delete unneeded reference point
if to_delete is not None:
bpy.ops.object.select_all(action="DESELECT")
- to_delete.select = True
- context.scene.objects.active = to_delete
+ to_delete.select_set(state=True)
+ context.view_layer.objects.active = to_delete
if bpy.ops.object.delete.poll():
bpy.ops.object.delete(use_global=False)
@@ -624,7 +627,7 @@ def update_type(self, context):
class archipack_wall2_part(PropertyGroup):
- type = EnumProperty(
+ type : EnumProperty(
items=(
('S_WALL', 'Straight', '', 0),
('C_WALL', 'Curved', '', 1)
@@ -632,21 +635,21 @@ class archipack_wall2_part(PropertyGroup):
default='S_WALL',
update=update_type
)
- length = FloatProperty(
+ length : FloatProperty(
name="Length",
min=0.01,
default=2.0,
unit='LENGTH', subtype='DISTANCE',
update=update
)
- radius = FloatProperty(
+ radius : FloatProperty(
name="Radius",
min=0.5,
default=0.7,
unit='LENGTH', subtype='DISTANCE',
update=update
)
- a0 = FloatProperty(
+ a0 : FloatProperty(
name="Start angle",
min=-pi,
max=pi,
@@ -654,7 +657,7 @@ class archipack_wall2_part(PropertyGroup):
subtype='ANGLE', unit='ROTATION',
update=update
)
- da = FloatProperty(
+ da : FloatProperty(
name="Angle",
min=-pi,
max=pi,
@@ -662,7 +665,7 @@ class archipack_wall2_part(PropertyGroup):
subtype='ANGLE', unit='ROTATION',
update=update
)
- z = FloatVectorProperty(
+ z : FloatVectorProperty(
name="Height",
default=[
0, 0, 0, 0, 0, 0, 0, 0,
@@ -673,7 +676,7 @@ class archipack_wall2_part(PropertyGroup):
size=31,
update=update
)
- t = FloatVectorProperty(
+ t : FloatVectorProperty(
name="Position",
min=0,
max=1,
@@ -686,24 +689,24 @@ class archipack_wall2_part(PropertyGroup):
size=31,
update=update
)
- splits = IntProperty(
+ splits : IntProperty(
name="Splits",
default=1,
min=1,
max=31,
get=get_splits, set=set_splits
)
- n_splits = IntProperty(
+ n_splits : IntProperty(
name="Splits",
default=1,
min=1,
max=31,
update=update
)
- auto_update = BoolProperty(default=True)
- manipulators = CollectionProperty(type=archipack_manipulator)
+ auto_update : BoolProperty(default=True)
+ manipulators : CollectionProperty(type=archipack_manipulator)
# ui related
- expand = BoolProperty(default=False)
+ expand : BoolProperty(default=False)
def _set_t(self, splits):
t = 1 / splits
@@ -715,7 +718,7 @@ class archipack_wall2_part(PropertyGroup):
find witch selected object this instance belongs to
provide support for "copy to selected"
"""
- selected = [o for o in context.selected_objects]
+ selected = context.selected_objects[:]
for o in selected:
props = archipack_wall2.datablock(o)
if props:
@@ -766,15 +769,15 @@ class archipack_wall2_part(PropertyGroup):
class archipack_wall2_child(PropertyGroup):
# Size Loc
# Delta Loc
- manipulators = CollectionProperty(type=archipack_manipulator)
- child_name = StringProperty()
- wall_idx = IntProperty()
- pos = FloatVectorProperty(subtype='XYZ')
- flip = BoolProperty(default=False)
+ manipulators : CollectionProperty(type=archipack_manipulator)
+ child_name : StringProperty()
+ wall_idx : IntProperty()
+ pos : FloatVectorProperty(subtype='XYZ')
+ flip : BoolProperty(default=False)
def get_child(self, context):
d = None
- child = context.scene.objects.get(self.child_name)
+ child = context.scene.objects.get(self.child_name.strip())
if child is not None and child.data is not None:
cd = child.data
if 'archipack_window' in cd:
@@ -785,14 +788,14 @@ class archipack_wall2_child(PropertyGroup):
class archipack_wall2(ArchipackObject, Manipulable, PropertyGroup):
- parts = CollectionProperty(type=archipack_wall2_part)
- n_parts = IntProperty(
+ parts : CollectionProperty(type=archipack_wall2_part)
+ n_parts : IntProperty(
name="Parts",
min=1,
max=1024,
default=1, update=update_manipulators
)
- step_angle = FloatProperty(
+ step_angle : FloatProperty(
description="Curved parts segmentation",
name="Step angle",
min=1 / 180 * pi,
@@ -801,34 +804,34 @@ class archipack_wall2(ArchipackObject, Manipulable, PropertyGroup):
subtype='ANGLE', unit='ROTATION',
update=update
)
- width = FloatProperty(
+ width : FloatProperty(
name="Width",
min=0.01,
default=0.2,
unit='LENGTH', subtype='DISTANCE',
update=update
)
- z = FloatProperty(
+ z : FloatProperty(
name='Height',
min=0.1,
default=2.7, precision=2,
unit='LENGTH', subtype='DISTANCE',
description='height', update=update,
)
- x_offset = FloatProperty(
+ x_offset : FloatProperty(
name="Offset",
min=-1, max=1,
default=-1, precision=2, step=1,
update=update
)
- radius = FloatProperty(
+ radius : FloatProperty(
name="Radius",
min=0.5,
default=0.7,
unit='LENGTH', subtype='DISTANCE',
update=update
)
- da = FloatProperty(
+ da : FloatProperty(
name="Angle",
min=-pi,
max=pi,
@@ -836,32 +839,32 @@ class archipack_wall2(ArchipackObject, Manipulable, PropertyGroup):
subtype='ANGLE', unit='ROTATION',
update=update
)
- flip = BoolProperty(
+ flip : BoolProperty(
name="Flip",
default=False,
update=update_childs
)
- closed = BoolProperty(
+ closed : BoolProperty(
default=False,
name="Close",
update=update_manipulators
)
- auto_update = BoolProperty(
+ auto_update : BoolProperty(
options={'SKIP_SAVE'},
default=True,
update=update_manipulators
)
- realtime = BoolProperty(
+ realtime : BoolProperty(
options={'SKIP_SAVE'},
default=True,
name="Real Time",
description="Relocate childs in realtime"
)
# dumb manipulators to show sizes between childs
- childs_manipulators = CollectionProperty(type=archipack_manipulator)
+ childs_manipulators : CollectionProperty(type=archipack_manipulator)
# store to manipulate windows and doors
- childs = CollectionProperty(type=archipack_wall2_child)
- t_part = StringProperty(
+ childs : CollectionProperty(type=archipack_wall2_child)
+ t_part : StringProperty(
name="Parent wall",
description="This part will follow parent when set",
default="",
@@ -1014,18 +1017,18 @@ class archipack_wall2(ArchipackObject, Manipulable, PropertyGroup):
def interpolate_bezier(self, pts, wM, p0, p1, resolution):
if resolution == 0:
- pts.append(wM * p0.co.to_3d())
+ pts.append(wM @ p0.co.to_3d())
else:
v = (p1.co - p0.co).normalized()
d1 = (p0.handle_right - p0.co).normalized()
d2 = (p1.co - p1.handle_left).normalized()
if d1 == v and d2 == v:
- pts.append(wM * p0.co.to_3d())
+ pts.append(wM @ p0.co.to_3d())
else:
- seg = interpolate_bezier(wM * p0.co,
- wM * p0.handle_right,
- wM * p1.handle_left,
- wM * p1.co,
+ seg = interpolate_bezier(wM @ p0.co,
+ wM @ p0.handle_right,
+ wM @ p1.handle_left,
+ wM @ p1.co,
resolution + 1)
for i in range(resolution):
pts.append(seg[i].to_3d())
@@ -1041,7 +1044,7 @@ class archipack_wall2(ArchipackObject, Manipulable, PropertyGroup):
def from_spline(self, wM, resolution, spline):
pts = []
if spline.type == 'POLY':
- pts = [wM * p.co.to_3d() for p in spline.points]
+ pts = [wM @ p.co.to_3d() for p in spline.points]
if spline.use_cyclic_u:
pts.append(pts[0])
elif spline.type == 'BEZIER':
@@ -1056,7 +1059,7 @@ class archipack_wall2(ArchipackObject, Manipulable, PropertyGroup):
self.interpolate_bezier(pts, wM, p0, p1, resolution)
pts.append(pts[0])
else:
- pts.append(wM * points[-1].co)
+ pts.append(wM @ points[-1].co)
if self.is_cw(pts):
pts = list(reversed(pts))
@@ -1134,7 +1137,7 @@ class archipack_wall2(ArchipackObject, Manipulable, PropertyGroup):
if not self.closed:
dp = pts[0] - pts[-1]
# pre-translate as dp is in local coordsys
- o.matrix_world = o.matrix_world * Matrix([
+ o.matrix_world = o.matrix_world @ Matrix([
[1, 0, 0, dp.x],
[0, 1, 0, dp.y],
[0, 0, 1, 0],
@@ -1326,7 +1329,7 @@ class archipack_wall2(ArchipackObject, Manipulable, PropertyGroup):
# pt in w coordsys
pos = ctM.translation
- pt = (witM * pos).to_2d()
+ pt = (witM @ pos).to_2d()
for wall_idx, wall in enumerate(g.segs):
# may be optimized with a bound check
@@ -1337,7 +1340,7 @@ class archipack_wall2(ArchipackObject, Manipulable, PropertyGroup):
# p0
if res and t > 0 and t < 1 and abs(dist) < dmax:
# dir in world coordsys
- dir = wrM * wall.sized_normal(t, 1).v
+ dir = wrM @ wall.sized_normal(t, 1).v
wall_with_childs[wall_idx] = 1
m = self.childs_manipulators.add()
m.type_key = 'DUMB_SIZE'
@@ -1345,14 +1348,14 @@ class archipack_wall2(ArchipackObject, Manipulable, PropertyGroup):
if "archipack_window" in cd:
flip = self.flip
else:
- dir_y = crM * Vector((0, -1))
+ dir_y = crM @ Vector((0, -1))
# let door orient where user want
flip = (dir_y - dir).length > 0.5
# store z in wall space
relocate.append((
child.name,
wall_idx,
- (t * wall.length, dist, (witM * pos).z),
+ (t * wall.length, dist, (witM @ pos).z),
flip,
t))
break
@@ -1392,19 +1395,19 @@ class archipack_wall2(ArchipackObject, Manipulable, PropertyGroup):
if d is not None:
# print("change flip:%s width:%s" % (d.flip != child.flip, d.y != self.width))
if d.y != self.width or d.flip != child.flip:
- c.select = True
+ c.select_set(state=True)
d.auto_update = False
d.flip = child.flip
d.y = self.width
d.auto_update = True
- c.select = False
+ c.select_set(state=False)
x, y = n.p - (0.5 * w * n.v)
else:
x, y = n.p - (child.pos.y * n.v)
- context.scene.objects.active = o
+ context.view_layer.objects.active = o
# preTranslate
- c.matrix_world = tM * Matrix([
+ c.matrix_world = tM @ Matrix([
[rx, -ry, 0, x],
[ry, rx, 0, y],
[0, 0, 1, child.pos.z],
@@ -1445,7 +1448,7 @@ class archipack_wall2(ArchipackObject, Manipulable, PropertyGroup):
# child is either a window or a door
wall_has_childs = True
dt = 0.5 * d.x / wall.length
- pt = (itM * c.matrix_world.translation).to_2d()
+ pt = (itM @ c.matrix_world.translation).to_2d()
res, y, t = wall.point_sur_segment(pt)
child.pos = (wall.length * t, y, child.pos.z)
p1 = wall.lerp(t - dt)
@@ -1503,7 +1506,7 @@ class archipack_wall2(ArchipackObject, Manipulable, PropertyGroup):
if o.parent is not None:
for c in o.parent.children:
if (archipack_wall2.datablock(c) == self):
- context.scene.objects.active = c
+ context.view_layer.objects.active = c
found = True
break
if found:
@@ -1522,12 +1525,12 @@ class archipack_wall2(ArchipackObject, Manipulable, PropertyGroup):
if o.parent is None:
return
g = self.get_generator()
- itM = o.matrix_world.inverted() * o.parent.matrix_world
+ itM = o.matrix_world.inverted() @ o.parent.matrix_world
for child in self.childs:
c, d = child.get_child(context)
if d is not None:
wall = g.segs[child.wall_idx]
- pt = (itM * c.location).to_2d()
+ pt = (itM @ c.location).to_2d()
res, d, t = wall.point_sur_segment(pt)
child.pos = (t * wall.length, d, child.pos.z)
# update childs manipulators
@@ -1631,14 +1634,14 @@ class archipack_wall2(ArchipackObject, Manipulable, PropertyGroup):
tM = o.matrix_world
up = Vector((0, 0, 1))
for seg in g.segs:
- p = tM * seg.p0.to_3d()
+ p = tM @ seg.p0.to_3d()
p.z = 0.01
# prevent self intersect
- o.hide = True
+ o.hide_viewport = True
res, pos, normal, face_index, r, matrix_world = context.scene.ray_cast(
p,
up)
- o.hide = False
+ o.hide_viewport = False
# print("res:%s" % res)
if res and r.data is not None and "archipack_roof" in r.data:
return r, r.data.archipack_roof[0]
@@ -1659,7 +1662,7 @@ class ARCHIPACK_OT_wall2_throttle_update(Operator):
bl_idname = "archipack.wall2_throttle_update"
bl_label = "Update childs with a delay"
- name = StringProperty()
+ name : StringProperty()
def modal(self, context, event):
global update_timer_updating
@@ -1667,12 +1670,12 @@ class ARCHIPACK_OT_wall2_throttle_update(Operator):
# cant rely on TIMER event as another timer may run
if time.time() - throttle_start > throttle_delay:
update_timer_updating = True
- o = context.scene.objects.get(self.name)
+ o = context.scene.objects.get(self.name.strip())
if o is not None:
m = o.modifiers.get("AutoBoolean")
if m is not None:
- o.hide = False
- # o.draw_type = 'TEXTURED'
+ o.hide_viewport = False
+ # o.display_type = 'TEXTURED'
# m.show_viewport = True
return self.cancel(context)
@@ -1709,7 +1712,7 @@ class ARCHIPACK_PT_wall2(Panel):
bl_label = "Wall"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
- bl_category = 'ArchiPack'
+ bl_category = 'Archipack'
def draw(self, context):
prop = archipack_wall2.datablock(context.object)
@@ -1717,7 +1720,7 @@ class ARCHIPACK_PT_wall2(Panel):
return
layout = self.layout
row = layout.row(align=True)
- row.operator("archipack.wall2_manipulate", icon='HAND')
+ row.operator("archipack.wall2_manipulate", icon='VIEW_PAN')
# row = layout.row(align=True)
# row.prop(prop, 'realtime')
box = layout.box()
@@ -1765,11 +1768,11 @@ class ARCHIPACK_OT_wall2(ArchipackCreateTool, Operator):
o = bpy.data.objects.new("Wall", m)
d = m.archipack_wall2.add()
d.manipulable_selectable = True
- context.scene.objects.link(o)
- o.select = True
+ self.link_object_to_scene(context, o)
+ o.select_set(state=True)
# around 12 degree
m.auto_smooth_angle = 0.20944
- context.scene.objects.active = o
+ context.view_layer.objects.active = o
self.load_preset(d)
self.add_material(o)
return o
@@ -1779,8 +1782,8 @@ class ARCHIPACK_OT_wall2(ArchipackCreateTool, Operator):
bpy.ops.object.select_all(action="DESELECT")
o = self.create(context)
o.location = bpy.context.scene.cursor_location
- o.select = True
- context.scene.objects.active = o
+ o.select_set(state=True)
+ context.view_layer.objects.active = o
self.manipulate()
return {'FINISHED'}
else:
@@ -1795,7 +1798,7 @@ class ARCHIPACK_OT_wall2_from_curve(Operator):
bl_category = 'Archipack'
bl_options = {'REGISTER', 'UNDO'}
- auto_manipulate = BoolProperty(default=True)
+ auto_manipulate : BoolProperty(default=True)
@classmethod
def poll(self, context):
@@ -1805,7 +1808,7 @@ class ARCHIPACK_OT_wall2_from_curve(Operator):
curve = context.active_object
for spline in curve.data.splines:
bpy.ops.archipack.wall2(auto_manipulate=self.auto_manipulate)
- o = context.scene.objects.active
+ o = context.view_layer.objects.active
d = archipack_wall2.datablock(o)
d.from_spline(curve.matrix_world, 12, spline)
if spline.type == 'POLY':
@@ -1815,7 +1818,7 @@ class ARCHIPACK_OT_wall2_from_curve(Operator):
else:
pt = Vector((0, 0, 0))
# pretranslate
- o.matrix_world = curve.matrix_world * Matrix([
+ o.matrix_world = curve.matrix_world @ Matrix([
[1, 0, 0, pt.x],
[0, 1, 0, pt.y],
[0, 0, 1, pt.z],
@@ -1831,8 +1834,8 @@ class ARCHIPACK_OT_wall2_from_curve(Operator):
bpy.ops.object.select_all(action="DESELECT")
o = self.create(context)
if o is not None:
- o.select = True
- context.scene.objects.active = o
+ o.select_set(state=True)
+ context.view_layer.objects.active = o
return {'FINISHED'}
else:
self.report({'WARNING'}, "Archipack: Option only valid in Object mode")
@@ -1846,7 +1849,7 @@ class ARCHIPACK_OT_wall2_from_slab(Operator):
bl_category = 'Archipack'
bl_options = {'REGISTER', 'UNDO'}
- auto_manipulate = BoolProperty(default=True)
+ auto_manipulate : BoolProperty(default=True)
@classmethod
def poll(self, context):
@@ -1857,7 +1860,7 @@ class ARCHIPACK_OT_wall2_from_slab(Operator):
slab = context.active_object
wd = slab.data.archipack_slab[0]
bpy.ops.archipack.wall2(auto_manipulate=self.auto_manipulate)
- o = context.scene.objects.active
+ o = context.view_layer.objects.active
d = archipack_wall2.datablock(o)
d.auto_update = False
d.parts.clear()
@@ -1873,8 +1876,8 @@ class ARCHIPACK_OT_wall2_from_slab(Operator):
p.radius = part.radius
p.da = part.da
p.a0 = part.a0
- o.select = True
- context.scene.objects.active = o
+ o.select_set(state=True)
+ context.view_layer.objects.active = o
d.auto_update = True
# pretranslate
o.matrix_world = slab.matrix_world.copy()
@@ -1883,17 +1886,17 @@ class ARCHIPACK_OT_wall2_from_slab(Operator):
# parenting childs to wall reference point
if o.parent is None:
x, y, z = o.bound_box[0]
- context.scene.cursor_location = o.matrix_world * Vector((x, y, z))
+ context.scene.cursor_location = o.matrix_world @ Vector((x, y, z))
# fix issue #9
- context.scene.objects.active = o
+ context.view_layer.objects.active = o
bpy.ops.archipack.reference_point()
else:
- o.parent.select = True
- context.scene.objects.active = o.parent
- o.select = True
- slab.select = True
+ o.parent.select_set(state=True)
+ context.view_layer.objects.active = o.parent
+ o.select_set(state=True)
+ slab.select_set(state=True)
bpy.ops.archipack.parent_to_reference()
- o.parent.select = False
+ o.parent.select_set(state=False)
return o
# -----------------------------------------------------
@@ -1903,8 +1906,8 @@ class ARCHIPACK_OT_wall2_from_slab(Operator):
if context.mode == "OBJECT":
bpy.ops.object.select_all(action="DESELECT")
o = self.create(context)
- o.select = True
- context.scene.objects.active = o
+ o.select_set(state=True)
+ context.view_layer.objects.active = o
return {'FINISHED'}
else:
self.report({'WARNING'}, "Archipack: Option only valid in Object mode")
@@ -1918,7 +1921,7 @@ class ARCHIPACK_OT_wall2_fit_roof(Operator):
bl_category = 'Archipack'
bl_options = {'REGISTER', 'UNDO'}
- inside = BoolProperty(default=False)
+ inside : BoolProperty(default=False)
@classmethod
def poll(self, context):
@@ -1939,12 +1942,11 @@ class ARCHIPACK_OT_wall2_fit_roof(Operator):
# ------------------------------------------------------------------
-class ARCHIPACK_OT_wall2_draw(ArchpackDrawTool, Operator):
+class ARCHIPACK_OT_wall2_draw(ArchipackDrawTool, Operator):
bl_idname = "archipack.wall2_draw"
bl_label = "Draw a Wall"
- bl_description = "Draw a Wall"
+ bl_description = "Create a wall by drawing its baseline in 3D view"
bl_category = 'Archipack'
- bl_options = {'REGISTER', 'UNDO'}
o = None
state = 'RUNNING'
@@ -1993,7 +1995,7 @@ class ARCHIPACK_OT_wall2_draw(ArchpackDrawTool, Operator):
self.line.draw(context)
def sp_callback(self, context, event, state, sp):
- # print("sp_callback event %s %s state:%s" % (event.type, event.value, state))
+ logger.debug("ARCHIPACK_OT_wall2_draw.sp_callback event %s %s state:%s", event.type, event.value, state)
if state == 'SUCCESS':
@@ -2004,19 +2006,21 @@ class ARCHIPACK_OT_wall2_draw(ArchpackDrawTool, Operator):
takeloc = sp.takeloc
delta = sp.delta
- old = context.active_object
+ old = context.object
if self.o is None:
bpy.ops.archipack.wall2(auto_manipulate=False)
- o = context.active_object
+ o = context.object
o.location = takeloc
self.o = o
d = archipack_wall2.datablock(o)
+
part = d.parts[0]
part.length = delta.length
else:
o = self.o
- o.select = True
- context.scene.objects.active = o
+ # select and make active
+ o.select_set(state=True)
+ context.view_layer.objects.active = o
d = archipack_wall2.datablock(o)
# Check for end close to start and close when applicable
dp = sp.placeloc - o.location
@@ -2031,7 +2035,7 @@ class ARCHIPACK_OT_wall2_draw(ArchpackDrawTool, Operator):
rM = o.matrix_world.inverted().to_3x3()
g = d.get_generator()
w = g.segs[-2]
- dp = rM * delta
+ dp = rM @ delta
da = atan2(dp.y, dp.x) - w.straight(1).angle
a0 = part.a0 + da
if a0 > pi:
@@ -2039,8 +2043,10 @@ class ARCHIPACK_OT_wall2_draw(ArchpackDrawTool, Operator):
if a0 < -pi:
a0 += 2 * pi
part.a0 = a0
+ d.update(context)
- context.scene.objects.active = old
+ old.select_set(state=True)
+ context.view_layer.objects.active = old
self.flag_next = True
context.area.tag_redraw()
# print("feedback.on:%s" % self.feedback.on)
@@ -2051,7 +2057,7 @@ class ARCHIPACK_OT_wall2_draw(ArchpackDrawTool, Operator):
# print("sp_init event %s %s %s" % (event.type, event.value, state))
if state == 'SUCCESS':
# point placed, check if a wall was under mouse
- res, tM, wall, y = self.mouse_hover_wall(context, event)
+ res, tM, wall, width, y, z_offset = self.mouse_hover_wall(context, event)
if res:
d = archipack_wall2.datablock(wall)
if event.ctrl:
@@ -2078,8 +2084,8 @@ class ARCHIPACK_OT_wall2_draw(ArchpackDrawTool, Operator):
so reverse order here when needed
"""
d = archipack_wall2.datablock(self.o)
- g = d.get_generator()
- pts = [seg.p0.to_3d() for seg in g.segs]
+ g = d.get_generator(axis=False)
+ pts = [seg.p0 for seg in g.segs]
if d.closed:
pts.append(pts[0])
@@ -2094,46 +2100,70 @@ class ARCHIPACK_OT_wall2_draw(ArchpackDrawTool, Operator):
def modal(self, context, event):
context.area.tag_redraw()
- # print("modal event %s %s" % (event.type, event.value))
- if event.type == 'NONE':
+ if event.type in {'NONE', 'TIMER', 'TIMER_REPORT', 'EVT_TWEAK_L', 'WINDOW_DEACTIVATE'}:
return {'PASS_THROUGH'}
- if self.state == 'STARTING':
- takeloc = self.mouse_to_plane(context, event)
- # wait for takeloc being visible when button is over horizon
- rv3d = context.region_data
- viewinv = rv3d.view_matrix.inverted()
+ if self.keymap.check(event, self.keymap.delete):
+ self.feedback.disable()
+ bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
+ self.o = None
+ return {'FINISHED', 'PASS_THROUGH'}
- if (takeloc * viewinv).z < 0 or not rv3d.is_perspective:
- # print("STARTING")
+ if self.state == 'STARTING' and event.type not in {'ESC', 'RIGHTMOUSE'}:
+ # wait for takeloc being visible when button is over horizon
+ takeloc = self.mouse_to_plane(context, event)
+ if takeloc is not None:
+ logger.debug("ARCHIPACK_OT_wall2_draw.modal(STARTING) location:%s", takeloc)
snap_point(takeloc=takeloc,
- callback=self.sp_init,
- constraint_axis=(True, True, False),
- release_confirm=True)
+ callback=self.sp_init,
+ constraint_axis=(True, True, False),
+ release_confirm=True)
return {'RUNNING_MODAL'}
elif self.state == 'RUNNING':
# print("RUNNING")
+ logger.debug("ARCHIPACK_OT_wall2_draw.modal(RUNNING) location:%s", self.takeloc)
self.state = 'CREATE'
snap_point(takeloc=self.takeloc,
- draw=self.sp_draw,
- takemat=self.takemat,
- transform_orientation=context.space_data.transform_orientation,
- callback=self.sp_callback,
- constraint_axis=(True, True, False),
- release_confirm=self.max_style_draw_tool)
+ draw=self.sp_draw,
+ takemat=self.takemat,
+ transform_orientation=context.scene.transform_orientation,
+ callback=self.sp_callback,
+ constraint_axis=(True, True, False),
+ release_confirm=self.max_style_draw_tool)
return {'RUNNING_MODAL'}
+ elif self.state != 'CANCEL' and event.type in {'C', 'c'}:
+
+ logger.debug("ARCHIPACK_OT_wall2_draw.modal(%s) C pressed", self.state)
+ self.feedback.disable()
+ bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
+
+ o = self.o
+ # select and make active
+ o.select_set(state=True)
+ context.view_layer.objects.active = o
+
+ d = archipack_wall2.datablock(o)
+ d.closed = True
+
+ if bpy.ops.archipack.manipulate.poll():
+ bpy.ops.archipack.manipulate('INVOKE_DEFAULT')
+
+ return {'FINISHED'}
+
elif self.state != 'CANCEL' and event.type in {'LEFTMOUSE', 'RET', 'NUMPAD_ENTER', 'SPACE'}:
# print('LEFTMOUSE %s' % (event.value))
self.feedback.instructions(context, "Draw a wall", "Click & Drag to add a segment", [
+ ('ENTER', 'Add part'),
+ ('BACK_SPACE', 'Remove part'),
('CTRL', 'Snap'),
+ ('C', 'Close wall and exit'),
('MMBTN', 'Constraint to axis'),
('X Y', 'Constraint to axis'),
- ('BACK_SPACE', 'Remove part'),
('RIGHTCLICK or ESC', 'exit')
- ])
+ ])
# press with max mode release with blender mode
if self.max_style_draw_tool:
@@ -2146,74 +2176,85 @@ class ARCHIPACK_OT_wall2_draw(ArchpackDrawTool, Operator):
if self.flag_next:
self.flag_next = False
o = self.o
- o.select = True
- context.scene.objects.active = o
+
+ # select and make active
+ o.select_set(state=True)
+ context.view_layer.objects.active = o
+
d = archipack_wall2.datablock(o)
g = d.get_generator()
p0 = g.segs[-2].p0
p1 = g.segs[-2].p1
dp = p1 - p0
- takemat = o.matrix_world * Matrix([
+ takemat = o.matrix_world @ Matrix([
[dp.x, dp.y, 0, p1.x],
[dp.y, -dp.x, 0, p1.y],
[0, 0, 1, 0],
[0, 0, 0, 1]
])
- takeloc = o.matrix_world * p1.to_3d()
- o.select = False
+ takeloc = o.matrix_world @ p1.to_3d()
+ o.select_set(state=False)
else:
- takeloc = self.mouse_to_plane(context, event)
takemat = None
+ takeloc = self.mouse_to_plane(context, event)
- snap_point(takeloc=takeloc,
- takemat=takemat,
- draw=self.sp_draw,
- callback=self.sp_callback,
- constraint_axis=(True, True, False),
- release_confirm=self.max_style_draw_tool)
+ if takeloc is not None:
+ logger.debug("ARCHIPACK_OT_wall2_draw.modal(CREATE) location:%s", takeloc)
+
+ snap_point(takeloc=takeloc,
+ takemat=takemat,
+ draw=self.sp_draw,
+ callback=self.sp_callback,
+ constraint_axis=(True, True, False),
+ release_confirm=self.max_style_draw_tool)
return {'RUNNING_MODAL'}
if self.keymap.check(event, self.keymap.undo) or (
event.type in {'BACK_SPACE'} and event.value == 'RELEASE'
- ):
+ ):
if self.o is not None:
o = self.o
- o.select = True
- context.scene.objects.active = o
+
+ # select and make active
+ o.select_set(state=True)
+ context.view_layer.objects.active = o
d = archipack_wall2.datablock(o)
if d.n_parts > 1:
d.n_parts -= 1
return {'RUNNING_MODAL'}
if self.state == 'CANCEL' or (event.type in {'ESC', 'RIGHTMOUSE'} and
- event.value == 'RELEASE'):
+ event.value == 'RELEASE'):
self.feedback.disable()
bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
-
+ logger.debug("ARCHIPACK_OT_wall2_draw.modal(CANCEL) %s", event.type)
if self.o is None:
- context.scene.objects.active = self.act
for o in self.sel:
- o.select = True
+ o.select_set(state=True)
+ # select and make active
+ self.act.select_set(state=True)
+ context.view_layer.objects.active = self.act
+
else:
- self.o.select = True
- context.scene.objects.active = self.o
+ o = self.o
+ o.select_set(state=True)
+ context.view_layer.objects.active = o
# remove last segment with blender mode
- d = archipack_wall2.datablock(self.o)
+ d = archipack_wall2.datablock(o)
if not self.max_style_draw_tool:
if not d.closed and d.n_parts > 1:
d.n_parts -= 1
-
- self.o.select = True
- context.scene.objects.active = self.o
+ o.select_set(state=True)
+ context.view_layer.objects.active = o
# make T child
if self.parent is not None:
d.t_part = self.parent
- if bpy.ops.archipack.wall2_manipulate.poll():
- bpy.ops.archipack.wall2_manipulate('INVOKE_DEFAULT')
+ if bpy.ops.archipack.manipulate.poll():
+ bpy.ops.archipack.manipulate('INVOKE_DEFAULT')
return {'FINISHED'}
@@ -2236,11 +2277,11 @@ class ARCHIPACK_OT_wall2_draw(ArchpackDrawTool, Operator):
('X Y', 'Constraint to axis'),
('SHIFT+CTRL+TAB', 'Switch snap mode'),
('RIGHTCLICK or ESC', 'exit without change')
- ])
+ ])
self.feedback.enable()
args = (self, context)
- self.sel = [o for o in context.selected_objects]
+ self.sel = context.selected_objects[:]
self.act = context.active_object
bpy.ops.object.select_all(action="DESELECT")
@@ -2265,7 +2306,7 @@ class ARCHIPACK_OT_wall2_insert(Operator):
bl_description = "Insert part"
bl_category = 'Archipack'
bl_options = {'REGISTER', 'UNDO'}
- index = IntProperty(default=0)
+ index : IntProperty(default=0)
def execute(self, context):
if context.mode == "OBJECT":
@@ -2286,7 +2327,7 @@ class ARCHIPACK_OT_wall2_remove(Operator):
bl_description = "Remove part"
bl_category = 'Archipack'
bl_options = {'REGISTER', 'UNDO'}
- index = IntProperty(default=0)
+ index : IntProperty(default=0)
def execute(self, context):
if context.mode == "OBJECT":
@@ -2352,8 +2393,8 @@ class ARCHIPACK_OT_wall2_manipulate(Operator):
d.setup_childs(o, g)
d.update_childs(context, o, g)
d.update(context)
- o.select = True
- context.scene.objects.active = o
+ o.select_set(state=True)
+ context.view_layer.objects.active = o
return {'FINISHED'}
diff --git a/archipack/archipack_window.py b/archipack/archipack_window.py
index 54d057e7..f79005e4 100644
--- a/archipack/archipack_window.py
+++ b/archipack/archipack_window.py
@@ -41,7 +41,7 @@ from .archipack_handle import create_handle, window_handle_vertical_01, window_h
from .archipack_manipulator import Manipulable
from .archipack_preset import ArchipackPreset, PresetMenuOperator
from .archipack_gl import FeedbackPanel
-from .archipack_object import ArchipackObject, ArchipackCreateTool, ArchpackDrawTool
+from .archipack_object import ArchipackObject, ArchipackCreateTool, ArchipackDrawTool, ArchipackCollectionManager
from .archipack_keymaps import Keymaps
@@ -71,7 +71,7 @@ def get_cols(self):
class archipack_window_panelrow(PropertyGroup):
- width = FloatVectorProperty(
+ width : FloatVectorProperty(
name="Width",
min=0.5,
max=100.0,
@@ -84,7 +84,7 @@ class archipack_window_panelrow(PropertyGroup):
size=31,
update=update
)
- fixed = BoolVectorProperty(
+ fixed : BoolVectorProperty(
name="Fixed",
default=[
False, False, False, False, False, False, False, False,
@@ -95,7 +95,7 @@ class archipack_window_panelrow(PropertyGroup):
size=32,
update=update
)
- cols = IntProperty(
+ cols : IntProperty(
name="Panels",
description="number of panels getter and setter, to avoid infinite recursion",
min=1,
@@ -103,7 +103,7 @@ class archipack_window_panelrow(PropertyGroup):
default=2,
get=get_cols, set=set_cols
)
- n_cols = IntProperty(
+ n_cols : IntProperty(
name="Panels",
description="store number of panels, internal use only to avoid infinite recursion",
min=1,
@@ -111,14 +111,14 @@ class archipack_window_panelrow(PropertyGroup):
default=2,
update=update
)
- height = FloatProperty(
+ height : FloatProperty(
name="Height",
min=0.1,
default=1.0, precision=2, step=1,
unit='LENGTH', subtype='DISTANCE',
update=update
)
- auto_update = BoolProperty(
+ auto_update : BoolProperty(
options={'SKIP_SAVE'},
name="auto_update",
description="disable auto update to avoid infinite recursion",
@@ -156,7 +156,7 @@ class archipack_window_panelrow(PropertyGroup):
find witch selected object this instance belongs to
provide support for "copy to selected"
"""
- selected = [o for o in context.selected_objects]
+ selected = context.selected_objects[:]
for o in selected:
props = archipack_window.datablock(o)
if props:
@@ -188,19 +188,19 @@ class archipack_window_panelrow(PropertyGroup):
class archipack_window_panel(ArchipackObject, PropertyGroup):
- center = FloatVectorProperty(
+ center : FloatVectorProperty(
subtype='XYZ'
)
- origin = FloatVectorProperty(
+ origin : FloatVectorProperty(
subtype='XYZ'
)
- size = FloatVectorProperty(
+ size : FloatVectorProperty(
subtype='XYZ'
)
- radius = FloatVectorProperty(
+ radius : FloatVectorProperty(
subtype='XYZ'
)
- angle_y = FloatProperty(
+ angle_y : FloatProperty(
name='angle',
unit='ROTATION',
subtype='ANGLE',
@@ -208,27 +208,27 @@ class archipack_window_panel(ArchipackObject, PropertyGroup):
default=0, precision=2,
description='angle'
)
- frame_y = FloatProperty(
+ frame_y : FloatProperty(
name='Depth',
min=0,
default=0.06, precision=2,
unit='LENGTH', subtype='DISTANCE',
description='frame depth'
)
- frame_x = FloatProperty(
+ frame_x : FloatProperty(
name='Width',
min=0,
default=0.06, precision=2,
unit='LENGTH', subtype='DISTANCE',
description='frame width'
)
- curve_steps = IntProperty(
+ curve_steps : IntProperty(
name="curve steps",
min=1,
max=128,
default=1
)
- shape = EnumProperty(
+ shape : EnumProperty(
name='Shape',
items=(
('RECTANGLE', 'Rectangle', '', 0),
@@ -239,19 +239,19 @@ class archipack_window_panel(ArchipackObject, PropertyGroup):
),
default='RECTANGLE'
)
- pivot = FloatProperty(
+ pivot : FloatProperty(
name='pivot',
min=-1, max=1,
default=-1, precision=2,
description='pivot'
)
- side_material = IntProperty(
+ side_material : IntProperty(
name="side material",
min=0,
max=2,
default=0
)
- handle = EnumProperty(
+ handle : EnumProperty(
name='Shape',
items=(
('NONE', 'No handle', '', 0),
@@ -260,24 +260,24 @@ class archipack_window_panel(ArchipackObject, PropertyGroup):
),
default='NONE'
)
- handle_model = IntProperty(
+ handle_model : IntProperty(
name="handle model",
default=1,
min=1,
max=2
)
- handle_altitude = FloatProperty(
+ handle_altitude : FloatProperty(
name='handle altitude',
min=0,
default=0.2, precision=2,
unit='LENGTH', subtype='DISTANCE',
description='handle altitude'
)
- fixed = BoolProperty(
+ fixed : BoolProperty(
name="Fixed",
default=False
)
- enable_glass = BoolProperty(
+ enable_glass : BoolProperty(
name="Enable glass",
default=True
)
@@ -404,7 +404,7 @@ class archipack_window_panel(ArchipackObject, PropertyGroup):
def remove_handle(self, context, o):
handle = self.find_handle(o)
if handle is not None:
- context.scene.objects.unlink(handle)
+ self.unlink_object_from_scene(handle)
bpy.data.objects.remove(handle, do_unlink=True)
def update(self, context):
@@ -425,28 +425,28 @@ class archipack_window_panel(ArchipackObject, PropertyGroup):
class archipack_window(ArchipackObject, Manipulable, PropertyGroup):
- x = FloatProperty(
+ x : FloatProperty(
name='Width',
min=0.25,
default=100.0, precision=2, step=1,
unit='LENGTH', subtype='DISTANCE',
description='Width', update=update
)
- y = FloatProperty(
+ y : FloatProperty(
name='Depth',
min=0.1,
default=0.20, precision=2, step=1,
unit='LENGTH', subtype='DISTANCE',
description='Depth', update=update,
)
- z = FloatProperty(
+ z : FloatProperty(
name='Height',
min=0.1,
default=1.2, precision=2, step=1,
unit='LENGTH', subtype='DISTANCE',
description='height', update=update,
)
- angle_y = FloatProperty(
+ angle_y : FloatProperty(
name='Angle',
unit='ROTATION',
subtype='ANGLE',
@@ -454,179 +454,179 @@ class archipack_window(ArchipackObject, Manipulable, PropertyGroup):
default=0, precision=2,
description='angle', update=update,
)
- radius = FloatProperty(
+ radius : FloatProperty(
name='Radius',
min=0.1,
default=2.5, precision=2, step=1,
unit='LENGTH', subtype='DISTANCE',
description='radius', update=update,
)
- elipsis_b = FloatProperty(
+ elipsis_b : FloatProperty(
name='Ellipsis',
min=0.1,
default=0.5, precision=2, step=1,
unit='LENGTH', subtype='DISTANCE',
description='ellipsis vertical size', update=update,
)
- altitude = FloatProperty(
+ altitude : FloatProperty(
name='Altitude',
default=1.0, precision=2, step=1,
unit='LENGTH', subtype='DISTANCE',
description='altitude', update=update,
)
- offset = FloatProperty(
+ offset : FloatProperty(
name='Offset',
default=0.1, precision=2, step=1,
unit='LENGTH', subtype='DISTANCE',
description='offset', update=update,
)
- frame_y = FloatProperty(
+ frame_y : FloatProperty(
name='Depth',
min=0,
default=0.06, precision=2, step=1,
unit='LENGTH', subtype='DISTANCE',
description='frame depth', update=update,
)
- frame_x = FloatProperty(
+ frame_x : FloatProperty(
name='Width',
min=0,
default=0.06, precision=2, step=1,
unit='LENGTH', subtype='DISTANCE',
description='frame width', update=update,
)
- out_frame = BoolProperty(
+ out_frame : BoolProperty(
name="Out frame",
default=False, update=update,
)
- out_frame_y = FloatProperty(
+ out_frame_y : FloatProperty(
name='Side depth',
min=0.001,
default=0.02, precision=2, step=1,
unit='LENGTH', subtype='DISTANCE',
description='frame side depth', update=update,
)
- out_frame_y2 = FloatProperty(
+ out_frame_y2 : FloatProperty(
name='Front depth',
min=0.001,
default=0.02, precision=2, step=1,
unit='LENGTH', subtype='DISTANCE',
description='frame front depth', update=update,
)
- out_frame_x = FloatProperty(
+ out_frame_x : FloatProperty(
name='Front Width',
min=0.0,
default=0.1, precision=2, step=1,
unit='LENGTH', subtype='DISTANCE',
description='frame width set to 0 disable front frame', update=update,
)
- out_frame_offset = FloatProperty(
+ out_frame_offset : FloatProperty(
name='Offset',
min=0.0,
default=0.0, precision=3, step=0.1,
unit='LENGTH', subtype='DISTANCE',
description='frame offset', update=update,
)
- out_tablet_enable = BoolProperty(
+ out_tablet_enable : BoolProperty(
name="Out tablet",
default=True, update=update,
)
- out_tablet_x = FloatProperty(
+ out_tablet_x : FloatProperty(
name='Width',
min=0.0,
default=0.04, precision=2, step=1,
unit='LENGTH', subtype='DISTANCE',
description='tablet width', update=update,
)
- out_tablet_y = FloatProperty(
+ out_tablet_y : FloatProperty(
name='Depth',
min=0.001,
default=0.04, precision=2, step=1,
unit='LENGTH', subtype='DISTANCE',
description='tablet depth', update=update,
)
- out_tablet_z = FloatProperty(
+ out_tablet_z : FloatProperty(
name='Height',
min=0.001,
default=0.03, precision=2, step=1,
unit='LENGTH', subtype='DISTANCE',
description='tablet height', update=update,
)
- in_tablet_enable = BoolProperty(
+ in_tablet_enable : BoolProperty(
name="In tablet",
default=True, update=update,
)
- in_tablet_x = FloatProperty(
+ in_tablet_x : FloatProperty(
name='Width',
min=0.0,
default=0.04, precision=2, step=1,
unit='LENGTH', subtype='DISTANCE',
description='tablet width', update=update,
)
- in_tablet_y = FloatProperty(
+ in_tablet_y : FloatProperty(
name='Depth',
min=0.001,
default=0.04, precision=2, step=1,
unit='LENGTH', subtype='DISTANCE',
description='tablet depth', update=update,
)
- in_tablet_z = FloatProperty(
+ in_tablet_z : FloatProperty(
name='Height',
min=0.001,
default=0.03, precision=2, step=1,
unit='LENGTH', subtype='DISTANCE',
description='tablet height', update=update,
)
- blind_enable = BoolProperty(
+ blind_enable : BoolProperty(
name="Blind",
default=False, update=update,
)
- blind_y = FloatProperty(
+ blind_y : FloatProperty(
name='Depth',
min=0.001,
default=0.002, precision=2, step=1,
unit='LENGTH', subtype='DISTANCE',
description='Store depth', update=update,
)
- blind_z = FloatProperty(
+ blind_z : FloatProperty(
name='Height',
min=0.001,
default=0.03, precision=2, step=1,
unit='LENGTH', subtype='DISTANCE',
description='Store height', update=update,
)
- blind_open = FloatProperty(
+ blind_open : FloatProperty(
name='Open',
min=0.0, max=100,
default=80, precision=1,
subtype='PERCENTAGE',
description='Store open', update=update,
)
- rows = CollectionProperty(type=archipack_window_panelrow)
- n_rows = IntProperty(
+ rows : CollectionProperty(type=archipack_window_panelrow)
+ n_rows : IntProperty(
name="Number of rows",
min=1,
max=32,
default=1, update=update,
)
- curve_steps = IntProperty(
+ curve_steps : IntProperty(
name="Steps",
min=6,
max=128,
default=16, update=update,
)
- hole_outside_mat = IntProperty(
+ hole_outside_mat : IntProperty(
name="Outside",
min=0,
max=128,
default=0, update=update,
)
- hole_inside_mat = IntProperty(
+ hole_inside_mat : IntProperty(
name="Inside",
min=0,
max=128,
default=1, update=update,
)
- window_shape = EnumProperty(
+ window_shape : EnumProperty(
name='Shape',
items=(
('RECTANGLE', 'Rectangle', '', 0),
@@ -637,7 +637,7 @@ class archipack_window(ArchipackObject, Manipulable, PropertyGroup):
),
default='RECTANGLE', update=update,
)
- window_type = EnumProperty(
+ window_type : EnumProperty(
name='Type',
items=(
('FLAT', 'Flat window', '', 0),
@@ -645,59 +645,59 @@ class archipack_window(ArchipackObject, Manipulable, PropertyGroup):
),
default='FLAT', update=update,
)
- enable_glass = BoolProperty(
+ enable_glass : BoolProperty(
name="Enable glass",
default=True,
update=update
)
- warning = BoolProperty(
+ warning : BoolProperty(
options={'SKIP_SAVE'},
name="Warning",
default=False
)
- handle_enable = BoolProperty(
+ handle_enable : BoolProperty(
name='Handle',
default=True, update=update_childs,
)
- handle_altitude = FloatProperty(
+ handle_altitude : FloatProperty(
name="Altitude",
min=0,
default=1.4, precision=2, step=1,
unit='LENGTH', subtype='DISTANCE',
description='handle altitude', update=update_childs,
)
- hole_margin = FloatProperty(
+ hole_margin : FloatProperty(
name='Hole margin',
min=0.0,
default=0.1, precision=2, step=1,
unit='LENGTH', subtype='DISTANCE',
description='how much hole surround wall'
)
- flip = BoolProperty(
+ flip : BoolProperty(
default=False,
update=update,
description='flip outside and outside material of hole'
)
# layout related
- display_detail = BoolProperty(
+ display_detail : BoolProperty(
options={'SKIP_SAVE'},
default=False
)
- display_panels = BoolProperty(
+ display_panels : BoolProperty(
options={'SKIP_SAVE'},
default=True
)
- display_materials = BoolProperty(
+ display_materials : BoolProperty(
options={'SKIP_SAVE'},
default=True
)
- auto_update = BoolProperty(
+ auto_update : BoolProperty(
options={'SKIP_SAVE'},
default=True,
update=update
)
- portal = BoolProperty(
+ portal : BoolProperty(
default=False,
name="Portal",
description="Generate a portal",
@@ -993,7 +993,7 @@ class archipack_window(ArchipackObject, Manipulable, PropertyGroup):
def find_portal(self, o):
for child in o.children:
- if child.type == 'LAMP':
+ if child.type == 'LIGHT':
return child
return None
@@ -1002,7 +1002,7 @@ class archipack_window(ArchipackObject, Manipulable, PropertyGroup):
lamp = self.find_portal(o)
if self.portal:
if lamp is None:
- bpy.ops.object.lamp_add(type='AREA')
+ bpy.ops.object.light_add(type='AREA')
lamp = context.active_object
lamp.name = "Portal"
lamp.parent = o
@@ -1019,15 +1019,15 @@ class archipack_window(ArchipackObject, Manipulable, PropertyGroup):
[0, 1, 0, 0.5 * self.z + self.altitude],
[0, 0, 0, 1]
])
- lamp.matrix_world = o.matrix_world * tM
+ lamp.matrix_world = o.matrix_world @ tM
elif lamp is not None:
d = lamp.data
- context.scene.objects.unlink(lamp)
+ self.unlink_object_from_scene(lamp)
bpy.data.objects.remove(lamp)
- bpy.data.lamps.remove(d)
+ bpy.data.lights.remove(d)
- context.scene.objects.active = o
+ context.view_layer.objects.active = o
def setup_manipulators(self):
if len(self.manipulators) == 4:
@@ -1054,13 +1054,13 @@ class archipack_window(ArchipackObject, Manipulable, PropertyGroup):
if archipack_window_panel.filter(child):
to_remove -= 1
self.remove_handle(context, child)
- context.scene.objects.unlink(child)
+ self.unlink_object_from_scene(child)
bpy.data.objects.remove(child, do_unlink=True)
def remove_handle(self, context, o):
handle = self.find_handle(o)
if handle is not None:
- context.scene.objects.unlink(handle)
+ self.unlink_object_from_scene(handle)
bpy.data.objects.remove(handle, do_unlink=True)
def update_rows(self, context, o):
@@ -1127,7 +1127,7 @@ class archipack_window(ArchipackObject, Manipulable, PropertyGroup):
id = c_names.index(c.data.name)
except:
self.remove_handle(context, c)
- context.scene.objects.unlink(c)
+ self.unlink_object_from_scene(c)
bpy.data.objects.remove(c, do_unlink=True)
# children ordering may not be the same, so get the right l_childs order
@@ -1145,7 +1145,7 @@ class archipack_window(ArchipackObject, Manipulable, PropertyGroup):
for i, child in enumerate(childs):
if order[i] < 0:
p = bpy.data.objects.new("Panel", child.data)
- context.scene.objects.link(p)
+ self.link_object_to_scene(context, p)
p.lock_location[0] = True
p.lock_location[1] = True
p.lock_location[2] = True
@@ -1169,20 +1169,20 @@ class archipack_window(ArchipackObject, Manipulable, PropertyGroup):
h = create_handle(context, p, handle.data)
h.location = handle.location.copy()
elif h is not None:
- context.scene.objects.unlink(h)
+ self.unlink_object_from_scene(h)
bpy.data.objects.remove(h, do_unlink=True)
p.location = child.location.copy()
# restore context
- context.scene.objects.active = o
+ context.view_layer.objects.active = o
def _synch_hole(self, context, linked, hole):
l_hole = self.find_hole(linked)
if l_hole is None:
l_hole = bpy.data.objects.new("hole", hole.data)
l_hole['archipack_hole'] = True
- context.scene.objects.link(l_hole)
+ self.link_object_to_scene(context, l_hole)
for mat in hole.data.materials:
l_hole.data.materials.append(mat)
l_hole.parent = linked
@@ -1196,8 +1196,8 @@ class archipack_window(ArchipackObject, Manipulable, PropertyGroup):
synch childs nodes of linked objects
"""
bpy.ops.object.select_all(action='DESELECT')
- o.select = True
- context.scene.objects.active = o
+ o.select_set(state=True)
+ context.view_layer.objects.active = o
childs = self.get_childs_panels(context, o)
hole = self.find_hole(o)
bpy.ops.object.select_linked(type='OBDATA')
@@ -1296,8 +1296,8 @@ class archipack_window(ArchipackObject, Manipulable, PropertyGroup):
else:
child = childs[child_n - 1]
- child.select = True
- context.scene.objects.active = child
+ child.select_set(state=True)
+ context.view_layer.objects.active = child
props = archipack_window_panel.datablock(child)
if props is not None:
props.origin = Vector((origin[panel].x, offset.y, 0))
@@ -1442,7 +1442,7 @@ class archipack_window(ArchipackObject, Manipulable, PropertyGroup):
if hole_obj is None:
m = bpy.data.meshes.new("hole")
hole_obj = bpy.data.objects.new("hole", m)
- context.scene.objects.link(hole_obj)
+ self.link_object_to_scene(context, hole_obj)
hole_obj['archipack_hole'] = True
hole_obj.parent = o
hole_obj.matrix_world = o.matrix_world.copy()
@@ -1496,11 +1496,11 @@ class archipack_window(ArchipackObject, Manipulable, PropertyGroup):
m = bpy.data.meshes.new("hole")
o = bpy.data.objects.new("hole", m)
o['archipack_robusthole'] = True
- context.scene.objects.link(o)
+ self.link_object_to_scene(context, o)
verts = hole.vertices(self.curve_steps, Vector((0, self.altitude, 0)), center, origin, size, radius,
self.angle_y, 0, shape_z=shape_z, path_type=self.shape)
- verts = [tM * Vector(v) for v in verts]
+ verts = [tM @ Vector(v) for v in verts]
faces = hole.faces(self.curve_steps, path_type=self.shape)
@@ -1511,8 +1511,8 @@ class archipack_window(ArchipackObject, Manipulable, PropertyGroup):
bmed.buildmesh(context, o, verts, faces, matids=matids, uvs=uvs)
# MaterialUtils.add_wall2_materials(o)
- o.select = True
- context.scene.objects.active = o
+ o.select_set(state=True)
+ context.view_layer.objects.active = o
return o
@@ -1522,13 +1522,13 @@ class ARCHIPACK_PT_window(Panel):
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
# bl_context = 'object'
- bl_category = 'ArchiPack'
+ bl_category = 'Archipack'
# layout related
- display_detail = BoolProperty(
+ display_detail : BoolProperty(
default=False
)
- display_panels = BoolProperty(
+ display_panels : BoolProperty(
default=True
)
@@ -1542,7 +1542,7 @@ class ARCHIPACK_PT_window(Panel):
if prop is None:
return
layout = self.layout
- layout.operator('archipack.window_manipulate', icon='HAND')
+ layout.operator('archipack.window_manipulate', icon='VIEW_PAN')
row = layout.row(align=True)
row.operator('archipack.window', text="Refresh", icon='FILE_REFRESH').mode = 'REFRESH'
if o.data.users > 1:
@@ -1552,8 +1552,8 @@ class ARCHIPACK_PT_window(Panel):
# box.label(text="Styles")
row = box.row(align=True)
row.operator("archipack.window_preset_menu", text=bpy.types.ARCHIPACK_OT_window_preset_menu.bl_label)
- row.operator("archipack.window_preset", text="", icon='ZOOMIN')
- row.operator("archipack.window_preset", text="", icon='ZOOMOUT').remove_active = True
+ row.operator("archipack.window_preset", text="", icon='ADD')
+ row.operator("archipack.window_preset", text="", icon='REMOVE').remove_active = True
box = layout.box()
box.prop(prop, 'window_type')
box.prop(prop, 'x')
@@ -1587,7 +1587,7 @@ class ARCHIPACK_PT_window(Panel):
box = layout.box()
box.prop(prop, 'enable_glass')
box = layout.box()
- box.label("Frame")
+ box.label(text="Frame")
box.prop(prop, 'frame_x')
box.prop(prop, 'frame_y')
if prop.window_shape != 'CIRCLE':
@@ -1654,11 +1654,11 @@ class ARCHIPACK_PT_window(Panel):
row.prop(prop, "display_materials", icon="TRIA_RIGHT", icon_only=True, text="Materials", emboss=False)
if prop.display_materials:
box = layout.box()
- box.label("Hole")
+ box.label(text="Hole")
box.prop(prop, 'hole_inside_mat')
box.prop(prop, 'hole_outside_mat')
- layout.prop(prop, 'portal', icon="LAMP_AREA")
+ layout.prop(prop, 'portal', icon="LIGHT_AREA")
class ARCHIPACK_PT_window_panel(Panel):
@@ -1666,7 +1666,7 @@ class ARCHIPACK_PT_window_panel(Panel):
bl_label = "Window panel"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
- bl_category = 'ArchiPack'
+ bl_category = 'Archipack'
@classmethod
def poll(cls, context):
@@ -1688,31 +1688,31 @@ class ARCHIPACK_OT_window(ArchipackCreateTool, Operator):
bl_description = "Window"
bl_category = 'Archipack'
bl_options = {'REGISTER', 'UNDO'}
- x = FloatProperty(
+ x : FloatProperty(
name='width',
min=0.1, max=10000,
default=2.0, precision=2,
description='Width'
)
- y = FloatProperty(
+ y : FloatProperty(
name='depth',
min=0.1, max=10000,
default=0.20, precision=2,
description='Depth'
)
- z = FloatProperty(
+ z : FloatProperty(
name='height',
min=0.1, max=10000,
default=1.2, precision=2,
description='height'
)
- altitude = FloatProperty(
+ altitude : FloatProperty(
name='altitude',
min=0.0, max=10000,
default=1.0, precision=2,
description='altitude'
)
- mode = EnumProperty(
+ mode : EnumProperty(
items=(
('CREATE', 'Create', '', 0),
('DELETE', 'Delete', '', 1),
@@ -1721,12 +1721,12 @@ class ARCHIPACK_OT_window(ArchipackCreateTool, Operator):
),
default='CREATE'
)
- # auto_manipulate = BoolProperty(default=True)
+ # auto_manipulate : BoolProperty(default=True)
def draw(self, context):
layout = self.layout
row = layout.row()
- row.label("Use Properties panel (N) to define parms", icon='INFO')
+ row.label(text="Use Properties panel (N) to define parms", icon='INFO')
def create(self, context):
m = bpy.data.meshes.new("Window")
@@ -1736,14 +1736,14 @@ class ARCHIPACK_OT_window(ArchipackCreateTool, Operator):
d.y = self.y
d.z = self.z
d.altitude = self.altitude
- context.scene.objects.link(o)
- o.select = True
- context.scene.objects.active = o
+ self.link_object_to_scene(context, o)
+ o.select_set(state=True)
+ context.view_layer.objects.active = o
self.add_material(o)
self.load_preset(d)
# select frame
- o.select = True
- context.scene.objects.active = o
+ o.select_set(state=True)
+ context.view_layer.objects.active = o
return o
def delete(self, context):
@@ -1751,22 +1751,22 @@ class ARCHIPACK_OT_window(ArchipackCreateTool, Operator):
if archipack_window.filter(o):
bpy.ops.archipack.disable_manipulate()
for child in o.children:
- if child.type == 'LAMP':
+ if child.type == 'LIGHT':
d = child.data
- context.scene.objects.unlink(child)
+ self.unlink_object_from_scene(child)
bpy.data.objects.remove(child)
- bpy.data.lamps.remove(d)
+ bpy.data.lights.remove(d)
elif 'archipack_hole' in child:
- context.scene.objects.unlink(child)
+ self.unlink_object_from_scene(child)
bpy.data.objects.remove(child, do_unlink=True)
elif child.data is not None and 'archipack_window_panel' in child.data:
for handle in child.children:
if 'archipack_handle' in handle:
- context.scene.objects.unlink(handle)
+ self.unlink_object_from_scene(handle)
bpy.data.objects.remove(handle, do_unlink=True)
- context.scene.objects.unlink(child)
+ self.unlink_object_from_scene(child)
bpy.data.objects.remove(child, do_unlink=True)
- context.scene.objects.unlink(o)
+ self.unlink_object_from_scene(o)
bpy.data.objects.remove(o, do_unlink=True)
def update(self, context):
@@ -1780,22 +1780,22 @@ class ARCHIPACK_OT_window(ArchipackCreateTool, Operator):
archipack_window.datablock(linked).update(context)
bpy.ops.object.select_all(action="DESELECT")
- o.select = True
- context.scene.objects.active = o
+ o.select_set(state=True)
+ context.view_layer.objects.active = o
def unique(self, context):
act = context.active_object
- sel = [o for o in context.selected_objects]
+ sel = context.selected_objects[:]
bpy.ops.object.select_all(action="DESELECT")
for o in sel:
if archipack_window.filter(o):
- o.select = True
+ o.select_set(state=True)
for child in o.children:
if 'archipack_hole' in child or (
child.data is not None and
'archipack_window_panel' in child.data):
child.hide_select = False
- child.select = True
+ child.select_set(state=True)
if len(context.selected_objects) > 0:
bpy.ops.object.make_single_user(type='SELECTED_OBJECTS', object=True,
obdata=True, material=False, texture=False, animation=False)
@@ -1803,9 +1803,9 @@ class ARCHIPACK_OT_window(ArchipackCreateTool, Operator):
if 'archipack_hole' in child:
child.hide_select = True
bpy.ops.object.select_all(action="DESELECT")
- context.scene.objects.active = act
+ context.view_layer.objects.active = act
for o in sel:
- o.select = True
+ o.select_set(state=True)
# -----------------------------------------------------
# Execute
@@ -1816,8 +1816,8 @@ class ARCHIPACK_OT_window(ArchipackCreateTool, Operator):
bpy.ops.object.select_all(action="DESELECT")
o = self.create(context)
o.location = bpy.context.scene.cursor_location
- o.select = True
- context.scene.objects.active = o
+ o.select_set(state=True)
+ context.view_layer.objects.active = o
self.manipulate()
elif self.mode == 'DELETE':
self.delete(context)
@@ -1831,14 +1831,14 @@ class ARCHIPACK_OT_window(ArchipackCreateTool, Operator):
return {'CANCELLED'}
-class ARCHIPACK_OT_window_draw(ArchpackDrawTool, Operator):
+class ARCHIPACK_OT_window_draw(ArchipackDrawTool, Operator):
bl_idname = "archipack.window_draw"
bl_label = "Draw Windows"
bl_description = "Draw Windows over walls"
bl_category = 'Archipack'
bl_options = {'REGISTER', 'UNDO'}
- filepath = StringProperty(default="")
+ filepath : StringProperty(default="")
feedback = None
stack = []
object_name = ""
@@ -1850,7 +1850,7 @@ class ARCHIPACK_OT_window_draw(ArchpackDrawTool, Operator):
def draw(self, context):
layout = self.layout
row = layout.row()
- row.label("Use Properties panel (N) to define parms", icon='INFO')
+ row.label(text="Use Properties panel (N) to define parms", icon='INFO')
def draw_callback(self, _self, context):
self.feedback.draw(context)
@@ -1862,8 +1862,8 @@ class ARCHIPACK_OT_window_draw(ArchpackDrawTool, Operator):
if archipack_window.filter(o):
- o.select = True
- context.scene.objects.active = o
+ o.select_set(state=True)
+ context.view_layer.objects.active = o
if event.shift:
bpy.ops.archipack.window(mode="UNIQUE")
@@ -1871,23 +1871,23 @@ class ARCHIPACK_OT_window_draw(ArchpackDrawTool, Operator):
# instance subs
new_w = o.copy()
new_w.data = o.data
- context.scene.objects.link(new_w)
+ self.link_object_to_scene(context, new_w)
for child in o.children:
if "archipack_hole" not in child:
new_c = child.copy()
new_c.data = child.data
new_c.parent = new_w
- context.scene.objects.link(new_c)
+ self.link_object_to_scene(context, new_c)
# dup handle if any
for c in child.children:
new_h = c.copy()
new_h.data = c.data
new_h.parent = new_c
- context.scene.objects.link(new_h)
+ self.link_object_to_scene(context, new_h)
o = new_w
- o.select = True
- context.scene.objects.active = o
+ o.select_set(state=True)
+ context.view_layer.objects.active = o
else:
bpy.ops.archipack.window(auto_manipulate=False, filepath=self.filepath)
@@ -1896,13 +1896,13 @@ class ARCHIPACK_OT_window_draw(ArchpackDrawTool, Operator):
self.object_name = o.name
bpy.ops.archipack.generate_hole('INVOKE_DEFAULT')
- o.select = True
- context.scene.objects.active = o
+ o.select_set(state=True)
+ context.view_layer.objects.active = o
def modal(self, context, event):
context.area.tag_redraw()
- o = context.scene.objects.get(self.object_name)
+ o = context.scene.objects.get(self.object_name.strip())
if o is None:
return {'FINISHED'}
@@ -1914,14 +1914,14 @@ class ARCHIPACK_OT_window_draw(ArchpackDrawTool, Operator):
# hide hole from raycast
if hole is not None:
- o.hide = True
- hole.hide = True
+ o.hide_viewport = True
+ hole.hide_viewport = True
- res, tM, wall, y = self.mouse_hover_wall(context, event)
+ res, tM, wall, width, y, z_offset = self.mouse_hover_wall(context, event)
if hole is not None:
- o.hide = False
- hole.hide = False
+ o.hide_viewport = False
+ hole.hide_viewport = False
if res and d is not None:
o.matrix_world = tM
@@ -1939,14 +1939,14 @@ class ARCHIPACK_OT_window_draw(ArchpackDrawTool, Operator):
if event.type in {'LEFTMOUSE', 'RET', 'NUMPAD_ENTER', 'SPACE'}:
if wall is not None:
- context.scene.objects.active = wall
- wall.select = True
+ context.view_layer.objects.active = wall
+ wall.select_set(state=True)
if bpy.ops.archipack.single_boolean.poll():
bpy.ops.archipack.single_boolean()
- wall.select = False
+ wall.select_set(state=False)
# o must be a window here
if d is not None:
- context.scene.objects.active = o
+ context.view_layer.objects.active = o
self.stack.append(o)
self.add_object(context, event)
context.active_object.matrix_world = tM
@@ -1960,9 +1960,9 @@ class ARCHIPACK_OT_window_draw(ArchpackDrawTool, Operator):
):
if len(self.stack) > 0:
last = self.stack.pop()
- context.scene.objects.active = last
+ context.view_layer.objects.active = last
bpy.ops.archipack.window(mode="DELETE")
- context.scene.objects.active = o
+ context.view_layer.objects.active = o
return {'RUNNING_MODAL'}
if event.value == 'RELEASE':
@@ -1986,11 +1986,11 @@ class ARCHIPACK_OT_window_draw(ArchpackDrawTool, Operator):
# invoke with shift pressed will use current object as basis for linked copy
if self.filepath == '' and archipack_window.filter(context.active_object):
o = context.active_object
- context.scene.objects.active = None
+ context.view_layer.objects.active = None
bpy.ops.object.select_all(action="DESELECT")
if o is not None:
- o.select = True
- context.scene.objects.active = o
+ o.select_set(state=True)
+ context.view_layer.objects.active = o
self.add_object(context, event)
self.feedback = FeedbackPanel()
self.feedback.instructions(context, "Draw a window", "Click & Drag over a wall", [
@@ -2035,25 +2035,25 @@ class ARCHIPACK_OT_window_portals(Operator):
# ------------------------------------------------------------------
-class ARCHIPACK_OT_window_panel(Operator):
+class ARCHIPACK_OT_window_panel(ArchipackCollectionManager, Operator):
bl_idname = "archipack.window_panel"
bl_label = "Window panel"
bl_description = "Window panel"
bl_category = 'Archipack'
bl_options = {'REGISTER', 'UNDO'}
- center = FloatVectorProperty(
+ center : FloatVectorProperty(
subtype='XYZ'
)
- origin = FloatVectorProperty(
+ origin : FloatVectorProperty(
subtype='XYZ'
)
- size = FloatVectorProperty(
+ size : FloatVectorProperty(
subtype='XYZ'
)
- radius = FloatVectorProperty(
+ radius : FloatVectorProperty(
subtype='XYZ'
)
- angle_y = FloatProperty(
+ angle_y : FloatProperty(
name='angle',
unit='ROTATION',
subtype='ANGLE',
@@ -2061,25 +2061,25 @@ class ARCHIPACK_OT_window_panel(Operator):
default=0, precision=2,
description='angle'
)
- frame_y = FloatProperty(
+ frame_y : FloatProperty(
name='Depth',
min=0, max=100,
default=0.06, precision=2,
description='frame depth'
)
- frame_x = FloatProperty(
+ frame_x : FloatProperty(
name='Width',
min=0, max=100,
default=0.06, precision=2,
description='frame width'
)
- curve_steps = IntProperty(
+ curve_steps : IntProperty(
name="curve steps",
min=1,
max=128,
default=16
)
- shape = EnumProperty(
+ shape : EnumProperty(
name='Shape',
items=(
('RECTANGLE', 'Rectangle', '', 0),
@@ -2090,19 +2090,19 @@ class ARCHIPACK_OT_window_panel(Operator):
),
default='RECTANGLE'
)
- pivot = FloatProperty(
+ pivot : FloatProperty(
name='pivot',
min=-1, max=1,
default=-1, precision=2,
description='pivot'
)
- side_material = IntProperty(
+ side_material : IntProperty(
name="side material",
min=0,
max=2,
default=0
)
- handle = EnumProperty(
+ handle : EnumProperty(
name='Handle',
items=(
('NONE', 'No handle', '', 0),
@@ -2111,27 +2111,27 @@ class ARCHIPACK_OT_window_panel(Operator):
),
default='NONE'
)
- handle_model = IntProperty(
+ handle_model : IntProperty(
name="handle model",
default=1,
min=1,
max=2
)
- handle_altitude = FloatProperty(
+ handle_altitude : FloatProperty(
name='handle altitude',
min=0, max=1000,
default=0.2, precision=2,
description='handle altitude'
)
- fixed = BoolProperty(
+ fixed : BoolProperty(
name="Fixed",
default=False
)
- material = StringProperty(
+ material : StringProperty(
name="material",
default=""
)
- enable_glass = BoolProperty(
+ enable_glass : BoolProperty(
name="Enable glass",
default=True
)
@@ -2139,7 +2139,7 @@ class ARCHIPACK_OT_window_panel(Operator):
def draw(self, context):
layout = self.layout
row = layout.row()
- row.label("Use Properties panel (N) to define parms", icon='INFO')
+ row.label(text="Use Properties panel (N) to define parms", icon='INFO')
def create(self, context):
m = bpy.data.meshes.new("Window Panel")
@@ -2161,9 +2161,9 @@ class ARCHIPACK_OT_window_panel(Operator):
d.handle_model = self.handle_model
d.handle_altitude = self.handle_altitude
d.enable_glass = self.enable_glass
- context.scene.objects.link(o)
- o.select = True
- context.scene.objects.active = o
+ self.link_object_to_scene(context, o)
+ o.select_set(state=True)
+ context.view_layer.objects.active = o
m = o.archipack_material.add()
m.category = "window"
m.material = self.material
@@ -2180,8 +2180,8 @@ class ARCHIPACK_OT_window_panel(Operator):
def execute(self, context):
if context.mode == "OBJECT":
o = self.create(context)
- o.select = True
- context.scene.objects.active = o
+ o.select_set(state=True)
+ context.view_layer.objects.active = o
return {'FINISHED'}
else:
self.report({'WARNING'}, "Archipack: Option only valid in Object mode")
diff --git a/archipack/bmesh_utils.py b/archipack/bmesh_utils.py
index 3f402d1d..54805f7b 100644
--- a/archipack/bmesh_utils.py
+++ b/archipack/bmesh_utils.py
@@ -34,8 +34,8 @@ class BmeshEdit():
"""
private, start bmesh editing of active object
"""
- o.select = True
- context.scene.objects.active = o
+ o.select_set(state=True)
+ context.view_layer.objects.active = o
bpy.ops.object.mode_set(mode='EDIT')
bm = bmesh.from_edit_mesh(o.data)
bm.verts.ensure_lookup_table()
@@ -212,7 +212,7 @@ class BmeshEdit():
@staticmethod
def bevel(context, o,
offset,
- offset_type=0,
+ offset_type='OFFSET',
segments=1,
profile=0.5,
vertex_only=False,
diff --git a/blender_id/CHANGELOG.md b/blender_id/CHANGELOG.md
index cf62a6e1..efda8c80 100644
--- a/blender_id/CHANGELOG.md
+++ b/blender_id/CHANGELOG.md
@@ -1,5 +1,18 @@
# Blender ID Add-on Changelog
+# Version 2.0 (in development)
+
+- Require Blender 2.80+.
+- API change: `blender_id.get_subclient_user_id()` now returns `''` instead of `None` when the user
+ is not logged in.
+- Log which Blender ID instance is communicated with.
+
+
+# Version 1.5 (released 2018-07-03)
+
+- Support Blender 2.80.
+
+
## Version 1.4.1 (released 2017-12-15)
- Improved error reporting when validating a token fails due to
diff --git a/blender_id/README.md b/blender_id/README.md
index 936e6e3e..8f73fbc7 100644
--- a/blender_id/README.md
+++ b/blender_id/README.md
@@ -13,6 +13,9 @@ Blender ID add-on version 1.2.0 removed some workarounds necessary for
Blender 2.77a. As such, versions 1.1.x are the last versions compatible with
Blender 2.77a, and 1.2.0 and newer require at least Blender 2.78.
+Blender ID add-on version 2.0 is the first to support and require Blender 2.80+.
+
+
Building & Bundling
-------------------
diff --git a/blender_id/__init__.py b/blender_id/__init__.py
index 73371945..e5d94715 100644
--- a/blender_id/__init__.py
+++ b/blender_id/__init__.py
@@ -14,15 +14,17 @@
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
+# Copyright (C) 2014-2018 Blender Foundation
+#
# ##### END GPL LICENSE BLOCK #####
# <pep8 compliant>
bl_info = {
'name': 'Blender ID authentication',
- 'author': 'Francesco Siddi, Inês Almeida and Sybren A. Stüvel',
- 'version': (1, 4, 1),
- 'blender': (2, 77, 0),
+ 'author': 'Sybren A. Stüvel, Francesco Siddi, and Inês Almeida',
+ 'version': (1, 9, 9),
+ 'blender': (2, 80, 0),
'location': 'Add-on preferences',
'description':
'Stores your Blender ID credentials for usage with other add-ons',
@@ -117,11 +119,11 @@ def get_subclient_user_id(subclient_id: str) -> str:
Requires that the user has been authenticated at the subclient using
a call to create_subclient_token(...)
- :returns: the subclient-local user ID, or None if not logged in.
+ :returns: the subclient-local user ID, or the empty string if not logged in.
"""
if not BlenderIdProfile.user_id:
- return None
+ return ''
return BlenderIdProfile.subclients[subclient_id]['subclient_user_id']
@@ -159,9 +161,9 @@ def token_expires() -> typing.Optional[datetime.datetime]:
# Try parsing as different formats. A new Blender ID is coming,
# which may change the format in which timestamps are sent.
formats = [
- '%Y-%m-%dT%H:%M:%SZ', # ISO 8601 with Z-suffix
- '%Y-%m-%dT%H:%M:%S.%fZ', # ISO 8601 with fractional seconds and Z-suffix
- '%a, %d %b %Y %H:%M:%S GMT', # RFC 1123, used by old Blender ID
+ '%Y-%m-%dT%H:%M:%SZ', # ISO 8601 with Z-suffix
+ '%Y-%m-%dT%H:%M:%S.%fZ', # ISO 8601 with fractional seconds and Z-suffix
+ '%a, %d %b %Y %H:%M:%S GMT', # RFC 1123, used by old Blender ID
]
for fmt in formats:
try:
@@ -177,22 +179,22 @@ def token_expires() -> typing.Optional[datetime.datetime]:
class BlenderIdPreferences(AddonPreferences):
bl_idname = __name__
- error_message = StringProperty(
+ error_message: StringProperty(
name='Error Message',
default='',
options={'HIDDEN', 'SKIP_SAVE'}
)
- ok_message = StringProperty(
+ ok_message: StringProperty(
name='Message',
default='',
options={'HIDDEN', 'SKIP_SAVE'}
)
- blender_id_username = StringProperty(
+ blender_id_username: StringProperty(
name='E-mail address',
default='',
options={'HIDDEN', 'SKIP_SAVE'}
)
- blender_id_password = StringProperty(
+ blender_id_password: StringProperty(
name='Password',
default='',
options={'HIDDEN', 'SKIP_SAVE'},
@@ -209,10 +211,10 @@ class BlenderIdPreferences(AddonPreferences):
if self.error_message:
sub = layout.row()
sub.alert = True # labels don't display in red :(
- sub.label(self.error_message, icon='ERROR')
+ sub.label(text=self.error_message, icon='ERROR')
if self.ok_message:
sub = layout.row()
- sub.label(self.ok_message, icon='FILE_TICK')
+ sub.label(text=self.ok_message, icon='FILE_TICK')
active_profile = get_active_profile()
if active_profile:
@@ -238,15 +240,17 @@ class BlenderIdPreferences(AddonPreferences):
exp_str = 'within seconds'
if time_left.days < 14:
- layout.label('You are logged in as %s.' % active_profile.username,
+ layout.label(text='You are logged in as %s.' % active_profile.username,
icon='WORLD_DATA')
layout.label(text='Your token will expire %s. Please log out and log in again '
- 'to refresh it.' % exp_str, icon='PREVIEW_RANGE')
+ 'to refresh it.' % exp_str, icon='PREVIEW_RANGE')
else:
- layout.label('You are logged in as %s. Your authentication token expires %s.'
- % (active_profile.username, exp_str), icon='WORLD_DATA')
+ layout.label(
+ text='You are logged in as %s. Your authentication token expires %s.'
+ % (active_profile.username, exp_str),
+ icon='WORLD_DATA')
- row = layout.row().split(0.8)
+ row = layout.row().split(factor=0.8)
row.operator('blender_id.logout')
row.operator('blender_id.validate')
else:
@@ -343,14 +347,20 @@ def register():
profiles.register()
BlenderIdProfile.read_json()
- bpy.utils.register_module(__name__)
+ bpy.utils.register_class(BlenderIdLogin)
+ bpy.utils.register_class(BlenderIdLogout)
+ bpy.utils.register_class(BlenderIdPreferences)
+ bpy.utils.register_class(BlenderIdValidate)
preferences = bpy.context.user_preferences.addons[__name__].preferences
preferences.reset_messages()
def unregister():
- bpy.utils.unregister_module(__name__)
+ bpy.utils.unregister_class(BlenderIdLogin)
+ bpy.utils.unregister_class(BlenderIdLogout)
+ bpy.utils.unregister_class(BlenderIdPreferences)
+ bpy.utils.unregister_class(BlenderIdValidate)
if __name__ == '__main__':
diff --git a/blender_id/communication.py b/blender_id/communication.py
index 5572b637..9960c338 100644
--- a/blender_id/communication.py
+++ b/blender_id/communication.py
@@ -55,7 +55,12 @@ def blender_id_endpoint(endpoint_path=None):
import os
import urllib.parse
- base_url = os.environ.get('BLENDER_ID_ENDPOINT', 'https://www.blender.org/id/')
+ base_url = os.environ.get('BLENDER_ID_ENDPOINT')
+ if base_url:
+ log.warning('Using overridden Blender ID url %s', base_url)
+ else:
+ base_url = 'https://www.blender.org/id/'
+ log.info('Using standard Blender ID url %s', base_url)
# urljoin() is None-safe for the 2nd parameter.
return urllib.parse.urljoin(base_url, endpoint_path)
diff --git a/bone_selection_sets.py b/bone_selection_sets.py
index 686eb808..e91d5557 100644
--- a/bone_selection_sets.py
+++ b/bone_selection_sets.py
@@ -20,7 +20,7 @@ bl_info = {
"name": "Bone Selection Sets",
"author": "Inês Almeida, Sybren A. Stüvel, Antony Riakiotakis, Dan Eicher",
"version": (2, 1, 1),
- "blender": (2, 75, 0),
+ "blender": (2, 80, 0),
"location": "Properties > Object Data (Armature) > Selection Sets",
"description": "List of Bone sets for easy selection while animating",
"warning": "",
@@ -41,6 +41,7 @@ from bpy.props import (
StringProperty,
IntProperty,
EnumProperty,
+ BoolProperty,
CollectionProperty,
)
@@ -48,14 +49,15 @@ from bpy.props import (
# Data Structure ##############################################################
# Note: bones are stored by name, this means that if the bone is renamed,
-# there can be problems. However, bone renaming is unlikely during animation
+# there can be problems. However, bone renaming is unlikely during animation.
class SelectionEntry(PropertyGroup):
- name = StringProperty(name="Bone Name")
+ name: StringProperty(name="Bone Name")
class SelectionSet(PropertyGroup):
- name = StringProperty(name="Set Name")
- bone_ids = CollectionProperty(type=SelectionEntry)
+ name: StringProperty(name="Set Name")
+ bone_ids: CollectionProperty(type=SelectionEntry)
+ is_selected: BoolProperty(name="Is Selected")
# UI Panel w/ UIList ##########################################################
@@ -103,8 +105,8 @@ class POSE_PT_selection_sets(Panel):
# add/remove/specials UI list Menu
col = row.column(align=True)
- col.operator("pose.selection_set_add", icon='ZOOMIN', text="")
- col.operator("pose.selection_set_remove", icon='ZOOMOUT', text="")
+ col.operator("pose.selection_set_add", icon='ADD', text="")
+ col.operator("pose.selection_set_remove", icon='REMOVE', text="")
col.menu("POSE_MT_selection_sets_specials", icon='DOWNARROW_HLT', text="")
# move up/down arrows
@@ -126,8 +128,11 @@ class POSE_PT_selection_sets(Panel):
class POSE_UL_selection_set(UIList):
- def draw_item(self, context, layout, data, set, icon, active_data, active_propname, index):
- layout.prop(set, "name", text="", icon='GROUP_BONE', emboss=False)
+ def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
+ sel_set = item
+ layout.prop(item, "name", text="", icon='GROUP_BONE', emboss=False)
+ if self.layout_type in ('DEFAULT', 'COMPACT'):
+ layout.prop(item, "is_selected", text="")
class POSE_MT_selection_set_create(Menu):
@@ -139,7 +144,7 @@ class POSE_MT_selection_set_create(Menu):
text="New Selection Set")
-class POSE_MT_selection_sets(Menu):
+class POSE_MT_selection_sets_select(Menu):
bl_label = 'Select Selection Set'
@classmethod
@@ -157,6 +162,7 @@ class POSE_MT_selection_sets(Menu):
# Operators ###################################################################
class PluginOperator(Operator):
+ """Operator only available for objects of type armature in pose mode."""
@classmethod
def poll(cls, context):
return (context.object and
@@ -165,6 +171,7 @@ class PluginOperator(Operator):
class NeedSelSetPluginOperator(PluginOperator):
+ """Operator only available if the armature has a selected selection set."""
@classmethod
def poll(cls, context):
if not super().poll(context):
@@ -187,8 +194,8 @@ class POSE_OT_selection_set_delete_all(PluginOperator):
class POSE_OT_selection_set_remove_bones(PluginOperator):
bl_idname = "pose.selection_set_remove_bones"
- bl_label = "Remove Bones from Sets"
- bl_description = "Removes the Active Bones from All Sets"
+ bl_label = "Remove Selected Bones from All Sets"
+ bl_description = "Removes the Selected Bones from All Sets"
bl_options = {'UNDO', 'REGISTER'}
def execute(self, context):
@@ -210,14 +217,15 @@ class POSE_OT_selection_set_move(NeedSelSetPluginOperator):
bl_description = "Move the active Selection Set up/down the list of sets"
bl_options = {'UNDO', 'REGISTER'}
- direction = EnumProperty(
+ direction: EnumProperty(
name="Move Direction",
description="Direction to move the active Selection Set: UP (default) or DOWN",
items=[
('UP', "Up", "", -1),
('DOWN', "Down", "", 1),
],
- default='UP'
+ default='UP',
+ options={'HIDDEN'},
)
@classmethod
@@ -334,10 +342,12 @@ class POSE_OT_selection_set_select(NeedSelSetPluginOperator):
bl_description = "Add Selection Set bones to current selection"
bl_options = {'UNDO', 'REGISTER'}
- selection_set_index = IntProperty(
+ selection_set_index: IntProperty(
name='Selection Set Index',
default=-1,
- description='Which Selection Set to select; -1 uses the active Selection Set')
+ description='Which Selection Set to select; -1 uses the active Selection Set',
+ options={'HIDDEN'},
+ )
def execute(self, context):
arm = context.object
@@ -386,20 +396,20 @@ class POSE_OT_selection_set_add_and_assign(PluginOperator):
class POSE_OT_selection_set_copy(NeedSelSetPluginOperator):
bl_idname = "pose.selection_set_copy"
- bl_label = "Copy Selection Set to Clipboard"
- bl_description = "Converts the Selection Set to JSON and places it on the clipboard"
+ bl_label = "Copy Selection Set(s)"
+ bl_description = "Copies the selected Selection Set(s) to the clipboard"
bl_options = {'UNDO', 'REGISTER'}
def execute(self, context):
context.window_manager.clipboard = to_json(context)
- self.report({'INFO'}, 'Copied Selection Set to Clipboard')
+ self.report({'INFO'}, 'Copied Selection Set(s) to Clipboard')
return {'FINISHED'}
class POSE_OT_selection_set_paste(PluginOperator):
bl_idname = "pose.selection_set_paste"
- bl_label = "Paste Selection Set from Clipboard"
- bl_description = "Adds a new Selection Set from copied JSON on the clipboard"
+ bl_label = "Paste Selection Set(s)"
+ bl_description = "Adds new Selection Set(s) from the Clipboard"
bl_options = {'UNDO', 'REGISTER'}
def execute(self, context):
@@ -416,67 +426,51 @@ class POSE_OT_selection_set_paste(PluginOperator):
return {'FINISHED'}
-# Registry ####################################################################
-
-classes = (
- POSE_MT_selection_set_create,
- POSE_MT_selection_sets_specials,
- POSE_MT_selection_sets,
- POSE_PT_selection_sets,
- POSE_UL_selection_set,
- SelectionEntry,
- SelectionSet,
- POSE_OT_selection_set_delete_all,
- POSE_OT_selection_set_remove_bones,
- POSE_OT_selection_set_move,
- POSE_OT_selection_set_add,
- POSE_OT_selection_set_remove,
- POSE_OT_selection_set_assign,
- POSE_OT_selection_set_unassign,
- POSE_OT_selection_set_select,
- POSE_OT_selection_set_deselect,
- POSE_OT_selection_set_add_and_assign,
- POSE_OT_selection_set_copy,
- POSE_OT_selection_set_paste,
-)
-
+# Helper Functions ############################################################
-def add_sss_button(self, context):
- self.layout.menu('POSE_MT_selection_sets')
+def menu_func_select_selection_set(self, context):
+ self.layout.menu('POSE_MT_selection_sets_select', text="Bone Selection Set")
def to_json(context) -> str:
- """Convert the active bone selection set of the current rig to JSON."""
+ """Convert the selected Selection Sets of the current rig to JSON.
+
+ Selected Sets are the active_selection_set determined by the UIList
+ plus any with the is_selected checkbox on."""
import json
arm = context.object
active_idx = arm.active_selection_set
- sel_set = arm.selection_sets[active_idx]
- return json.dumps({
- 'name': sel_set.name,
- 'bones': [bone_id.name for bone_id in sel_set.bone_ids]
- })
+ json_obj = {}
+ for idx, sel_set in enumerate(context.object.selection_sets):
+ if idx == active_idx or sel_set.is_selected:
+ bones = [bone_id.name for bone_id in sel_set.bone_ids]
+ json_obj[sel_set.name] = bones
+
+ return json.dumps(json_obj)
def from_json(context, as_json: str):
- """Add the single bone selection set from JSON to the current rig."""
+ """Add the selection sets (one or more) from JSON to the current rig."""
import json
- sel_set = json.loads(as_json)
-
- sel_sets = context.object.selection_sets
- new_sel_set = sel_sets.add()
- new_sel_set.name = uniqify(sel_set['name'], sel_sets.keys())
+ json_obj = json.loads(as_json)
+ arm_sel_sets = context.object.selection_sets
- for bone_name in sel_set['bones']:
- bone_id = new_sel_set.bone_ids.add()
- bone_id.name = bone_name
+ for name, bones in json_obj.items():
+ new_sel_set = arm_sel_sets.add()
+ new_sel_set.name = uniqify(name, arm_sel_sets.keys())
+ for bone_name in bones:
+ bone_id = new_sel_set.bone_ids.add()
+ bone_id.name = bone_name
def uniqify(name: str, other_names: list) -> str:
"""Return a unique name with .xxx suffix if necessary.
+ Example usage:
+
>>> uniqify('hey', ['there'])
'hey'
>>> uniqify('hey', ['hey.001', 'hey.005'])
@@ -514,7 +508,32 @@ def uniqify(name: str, other_names: list) -> str:
return "{}.{:03d}".format(name, min_index)
-# store keymaps here to access after registration
+# Registry ####################################################################
+
+classes = (
+ POSE_MT_selection_set_create,
+ POSE_MT_selection_sets_specials,
+ POSE_MT_selection_sets_select,
+ POSE_PT_selection_sets,
+ POSE_UL_selection_set,
+ SelectionEntry,
+ SelectionSet,
+ POSE_OT_selection_set_delete_all,
+ POSE_OT_selection_set_remove_bones,
+ POSE_OT_selection_set_move,
+ POSE_OT_selection_set_add,
+ POSE_OT_selection_set_remove,
+ POSE_OT_selection_set_assign,
+ POSE_OT_selection_set_unassign,
+ POSE_OT_selection_set_select,
+ POSE_OT_selection_set_deselect,
+ POSE_OT_selection_set_add_and_assign,
+ POSE_OT_selection_set_copy,
+ POSE_OT_selection_set_paste,
+)
+
+
+# Store keymaps here to access after registration.
addon_keymaps = []
@@ -522,6 +541,7 @@ def register():
for cls in classes:
bpy.utils.register_class(cls)
+ # Add properties.
bpy.types.Object.selection_sets = CollectionProperty(
type=SelectionSet,
name="Selection Sets",
@@ -533,28 +553,34 @@ def register():
default=0
)
+ # Add shortcuts to the keymap.
wm = bpy.context.window_manager
km = wm.keyconfigs.addon.keymaps.new(name='Pose')
-
kmi = km.keymap_items.new('wm.call_menu', 'W', 'PRESS', alt=True, shift=True)
- kmi.properties.name = 'POSE_MT_selection_sets'
+ kmi.properties.name = 'POSE_MT_selection_sets_select'
addon_keymaps.append((km, kmi))
- bpy.types.VIEW3D_MT_select_pose.append(add_sss_button)
+ # Add entries to menus.
+ bpy.types.VIEW3D_MT_select_pose.append(menu_func_select_selection_set)
def unregister():
for cls in classes:
bpy.utils.unregister_class(cls)
+ # Clear properties.
del bpy.types.Object.selection_sets
del bpy.types.Object.active_selection_set
- # handle the keymap
+ # Clear shortcuts from the keymap.
for km, kmi in addon_keymaps:
km.keymap_items.remove(kmi)
addon_keymaps.clear()
+ # Clear entries from menus.
+ bpy.types.VIEW3D_MT_select_pose.remove(menu_func_select_selection_set)
+
+
if __name__ == "__main__":
import doctest
diff --git a/btrace/bTrace.py b/btrace/bTrace.py
index 82d93f9d..8df06475 100644
--- a/btrace/bTrace.py
+++ b/btrace/bTrace.py
@@ -115,9 +115,9 @@ class OBJECT_OT_objectconnect(Operator):
if curve_handle == 'AUTOMATIC': # hackish because of naming conflict in api
curve_handle = 'AUTO'
# Check if Btrace group exists, if not create
- bgroup = bpy.data.groups.keys()
+ bgroup = bpy.data.collections.keys()
if 'Btrace' not in bgroup:
- bpy.ops.group.create(name="Btrace")
+ bpy.ops.collections.create(name="Btrace")
# check if noise
if Btrace.connect_noise:
bpy.ops.object.btfcnoise()
@@ -181,7 +181,7 @@ class OBJECT_OT_objectconnect(Operator):
if Btrace.animate: # Add Curve Grow it?
bpy.ops.curve.btgrow()
- bpy.ops.object.group_link(group="Btrace") # add to Btrace group
+ bpy.ops.object.collection_link(group="Btrace") # add to Btrace group
if Btrace.animate:
bpy.ops.curve.btgrow() # Add grow curve
@@ -245,9 +245,9 @@ class OBJECT_OT_particletrace(Operator):
curve_handle = 'FREE'
# Check if Btrace group exists, if not create
- bgroup = bpy.data.groups.keys()
+ bgroup = bpy.data.collections.keys()
if 'Btrace' not in bgroup:
- bpy.ops.group.create(name="Btrace")
+ bpy.ops.collection.create(name="Btrace")
if Btrace.curve_join:
tracer = curvetracer('Tracer', 'Splines')
@@ -271,7 +271,7 @@ class OBJECT_OT_particletrace(Operator):
for curveobject in curvelist:
curveobject.select = True
bpy.context.scene.objects.active = curveobject
- bpy.ops.object.group_link(group="Btrace")
+ bpy.ops.object.collection_link(group="Btrace")
# Materials
trace_mats = addtracemat(curveobject.data)
if not trace_mats and check_materials is True:
@@ -721,9 +721,9 @@ class OBJECT_OT_meshfollow(Operator):
# Run methods
# Check if Btrace group exists, if not create
- bgroup = bpy.data.groups.keys()
+ bgroup = bpy.data.collections.keys()
if 'Btrace' not in bgroup:
- bpy.ops.group.create(name="Btrace")
+ bpy.ops.collection.create(name="Btrace")
Btrace = bpy.context.window_manager.curve_tracer
sel = getsel_option() # Get selection
@@ -743,7 +743,7 @@ class OBJECT_OT_meshfollow(Operator):
if curveobject.type == 'CURVE':
curveobject.select = True
context.scene.objects.active = curveobject
- bpy.ops.object.group_link(group="Btrace")
+ bpy.ops.object.collection_link(group="Btrace")
# Materials
trace_mats = addtracemat(curveobject.data)
if not trace_mats and check_materials is True:
diff --git a/camera_dolly_crane_rigs.py b/camera_dolly_crane_rigs.py
index c55b19e8..5f482ac4 100644
--- a/camera_dolly_crane_rigs.py
+++ b/camera_dolly_crane_rigs.py
@@ -21,7 +21,7 @@ bl_info = {
"name": "Add Camera Rigs",
"author": "Wayne Dixon, Kris Wittig",
"version": (1, 1, 1),
- "blender": (2, 77, 0),
+ "blender": (2, 80, 0),
"location": "View3D > Add > Camera > Dolly or Crane Rig",
"description": "Adds a Camera Rig with UI",
"warning": "Enable Auto Run Python Scripts in User Preferences > File",
@@ -47,20 +47,21 @@ def create_widget(self, name):
obj_name = "WDGT_" + name
scene = bpy.context.scene
- # Check if it already exists
- if obj_name in scene.objects:
- return None
- else:
- mesh = bpy.data.meshes.new(obj_name)
- obj = bpy.data.objects.new(obj_name, mesh)
- scene.objects.link(obj)
+ mesh = bpy.data.meshes.new(obj_name)
+ obj = bpy.data.objects.new(obj_name, mesh)
- # this will put the Widget objects out of the way on layer 19
- WDGT_layers = (False, False, False, False, False, False, False, False, False, True,
- False, False, False, False, False, False, False, False, False, False)
- obj.layers = WDGT_layers
+ # create a new collection for the wigets
+ collection_name = "camera_widgets"
+ c = bpy.data.collections.get(collection_name)
+ if c is not None:
+ c.objects.link(obj)
+ else:
+ c = bpy.data.collections.new(collection_name)
+ # link the collection
+ scene.collection.children.link(c)
+ c.objects.link(obj)
- return obj
+ return obj
def create_root_widget(self, name):
@@ -320,6 +321,7 @@ class MakeCameraActive(Operator):
# Define function to add marker to timeline and bind camera
# =========================================================================
def markerBind():
+ view_layer = bpy.context.view_layer
ob = bpy.context.active_object # rig object
active_cam = ob.children[0] # camera object
@@ -329,11 +331,11 @@ def markerBind():
bpy.ops.marker.add()
bpy.ops.marker.rename(name="cam_" + str(bpy.context.scene.frame_current))
# select rig camera
- bpy.context.scene.objects.active = active_cam
+ view_layer.objects.active = active_cam
# bind marker to selected camera
bpy.ops.marker.camera_bind()
# switch selected object back to the rig
- bpy.context.scene.objects.active = ob
+ view_layer.objects.active = ob
# switch back to 3d view
bpy.context.area.type = 'VIEW_3D'
@@ -358,6 +360,7 @@ class AddMarkerBind(Operator):
# Define the function to add an Empty as DOF object
# =========================================================================
def add_DOF_Empty():
+ view_layer = bpy.context.view_layer
smode = bpy.context.mode
rig = bpy.context.active_object
bone = rig.data.bones['AIM_child']
@@ -383,7 +386,7 @@ def add_DOF_Empty():
# make this new empty the dof_object
cam.dof_object = obj
# reselect the rig
- bpy.context.scene.objects.active = rig
+ view_layer.objects.active = rig
obj.select = False
rig.select = True
@@ -409,6 +412,8 @@ class AddDofEmpty(Operator):
# Define the function to build the Dolly Rig
# =========================================================================
def build_dolly_rig(context):
+ view_layer = bpy.context.view_layer
+
# Define some useful variables:
boneLayer = (False, True, False, False, False, False, False, False,
False, False, False, False, False, False, False, False,
@@ -498,7 +503,7 @@ def build_dolly_rig(context):
prop["soft_max"] = prop["max"] = 1.0
# Add Driver to Lock/Unlock Camera from Aim Target
- rig = bpy.context.scene.objects.active
+ rig = view_layer.objects.active
pose_bone = bpy.data.objects[rig.name].pose.bones['CTRL']
constraint = pose_bone.constraints["Track To"]
@@ -533,7 +538,7 @@ def build_dolly_rig(context):
cam.data.name = "Dolly_Camera.000"
cam_data_name = bpy.context.object.data.name
- bpy.data.cameras[cam_data_name].draw_size = 1.0
+ bpy.data.cameras[cam_data_name].display_size = 1.0
cam.rotation_euler[0] = 1.5708 # rotate the camera 90 degrees in x
cam.location = (0.0, -2.0, 0.0) # move the camera to the correct position
cam.parent = rig
@@ -558,9 +563,9 @@ def build_dolly_rig(context):
bpy.context.object.hide_select = False
# make the rig the active object before finishing
- bpy.context.scene.objects.active = rig
- cam.select = False
- rig.select = True
+ view_layer.objects.active = rig
+ cam.select_set(False)
+ rig.select_set(True)
return rig
@@ -576,6 +581,7 @@ def build_crane_rig(context):
False, False, False, False, False, False, False, False)
# Add the new armature object:
+ view_layer = bpy.context.view_layer
bpy.ops.object.armature_add()
rig = context.active_object
@@ -631,9 +637,9 @@ def build_crane_rig(context):
ctrlAimChild.parent = ctrlAim
# change display to BBone: it just looks nicer
- bpy.context.object.data.draw_type = 'BBONE'
+ bpy.context.object.data.display_type = 'BBONE'
# change display to wire for object
- bpy.context.object.draw_type = 'WIRE'
+ bpy.context.object.display_type = 'WIRE'
# jump into pose mode and change bones to euler
bpy.ops.object.mode_set(mode='POSE')
@@ -683,7 +689,7 @@ def build_crane_rig(context):
prop["soft_max"] = prop["max"] = 1.0
# Add Driver to Lock/Unlock Camera from Aim Target
- rig = bpy.context.scene.objects.active
+ rig = view_layer.objects.active
pose_bone = bpy.data.objects[rig.name].pose.bones['CTRL']
constraint = pose_bone.constraints["Track To"]
@@ -718,7 +724,7 @@ def build_crane_rig(context):
cam.data.name = "Crane_Camera.000"
cam_data_name = bpy.context.object.data.name
- bpy.data.cameras[cam_data_name].draw_size = 1.0
+ bpy.data.cameras[cam_data_name].display_size = 1.0
cam.rotation_euler[0] = 1.5708 # rotate the camera 90 degrees in x
cam.location = (0.0, -2.0, 0.0) # move the camera to the correct position
cam.parent = rig
@@ -743,9 +749,9 @@ def build_crane_rig(context):
bpy.context.object.hide_select = False
# make the rig the active object before finishing
- bpy.context.scene.objects.active = rig
- cam.select = False
- rig.select = True
+ view_layer.objects.active = rig
+ cam.select_set(False)
+ rig.select_set(True)
return rig
@@ -801,7 +807,7 @@ class DollyCameraUI(Panel):
text="Make Active Camera", icon='CAMERA_DATA')
col.prop(context.active_object,
- 'show_x_ray', toggle=False, text='X Ray')
+ 'show_in_front', toggle=False, text='X Ray')
col.prop(cam, "show_limits")
col.prop(cam, "show_safe_areas")
col.prop(cam, "show_passepartout")
@@ -865,7 +871,7 @@ class CraneCameraUI(Panel):
col.operator(
"scene.make_camera_active", text="Make Active Camera", icon='CAMERA_DATA')
col.prop(
- context.active_object, 'show_x_ray', toggle=False, text='X Ray')
+ context.active_object, 'show_in_front', toggle=False, text='X Ray')
col.prop(cam, "show_limits")
col.prop(cam, "show_safe_areas")
col.prop(cam, "show_passepartout")
@@ -966,7 +972,7 @@ def register():
bpy.utils.register_class(MakeCameraActive)
bpy.utils.register_class(AddMarkerBind)
bpy.utils.register_class(AddDofEmpty)
- bpy.types.INFO_MT_camera_add.append(add_dolly_crane_buttons)
+ bpy.types.VIEW3D_MT_camera_add.append(add_dolly_crane_buttons)
def unregister():
@@ -977,7 +983,7 @@ def unregister():
bpy.utils.unregister_class(MakeCameraActive)
bpy.utils.unregister_class(AddMarkerBind)
bpy.utils.unregister_class(AddDofEmpty)
- bpy.types.INFO_MT_camera_add.remove(add_dolly_crane_buttons)
+ bpy.types.VIEW3D_MT_camera_add.remove(add_dolly_crane_buttons)
if __name__ == "__main__":
diff --git a/camera_turnaround.py b/camera_turnaround.py
index 6a6e710f..547c2834 100644
--- a/camera_turnaround.py
+++ b/camera_turnaround.py
@@ -96,7 +96,7 @@ class RunAction(Operator):
context.user_preferences.edit.keyframe_new_interpolation_type = 'LINEAR'
# create first frame
myempty.rotation_euler = (0, 0, 0)
- myempty.empty_draw_size = 0.1
+ myempty.empty_display_size = 0.1
context.scene.frame_set(scene.frame_start)
myempty.keyframe_insert(data_path='rotation_euler', frame=scene.frame_start)
diff --git a/curve_simplify.py b/curve_simplify.py
index b053a4b9..4a390feb 100644
--- a/curve_simplify.py
+++ b/curve_simplify.py
@@ -20,7 +20,7 @@ bl_info = {
"name": "Simplify Curves",
"author": "testscreenings",
"version": (1, 0, 3),
- "blender": (2, 75, 0),
+ "blender": (2, 80, 0),
"location": "View3D > Add > Curve > Simplify Curves",
"description": "Simplifies 3D Curve objects and animation F-Curves",
"warning": "",
@@ -35,16 +35,16 @@ This script simplifies Curve objects and animation F-Curves.
import bpy
from bpy.props import (
- BoolProperty,
- EnumProperty,
- FloatProperty,
- IntProperty,
- )
+ BoolProperty,
+ EnumProperty,
+ FloatProperty,
+ IntProperty,
+)
from mathutils import Vector
from math import (
- sin,
- pow,
- )
+ sin,
+ pow,
+)
from bpy.types import Operator
@@ -307,10 +307,11 @@ def main(context, obj, options, curve_dimension):
# create new object and put into scene
newCurve = bpy.data.objects.new("Simple_" + obj.name, curve)
- scene.objects.link(newCurve)
- newCurve.select = True
+ coll = context.view_layer.active_layer_collection.collection
+ coll.objects.link(newCurve)
+ newCurve.select_set(True)
- scene.objects.active = newCurve
+ context.view_layer.objects.active = newCurve
newCurve.matrix_world = obj.matrix_world
# set bezierhandles to auto
@@ -325,7 +326,7 @@ def getFcurveData(obj):
for fc in obj.animation_data.action.fcurves:
if fc.select:
fcVerts = [vcVert.co.to_3d()
- for vcVert in fc.keyframe_points.values()]
+ for vcVert in fc.keyframe_points.values()]
fcurves.append(fcVerts)
return fcurves
@@ -396,38 +397,38 @@ class GRAPH_OT_simplify(Operator):
opModes = [
('DISTANCE', 'Distance', 'Distance-based simplification (Poly)'),
('CURVATURE', 'Curvature', 'Curvature-based simplification (RDP)')]
- mode = EnumProperty(
+ mode: EnumProperty(
name="Mode",
description="Choose algorithm to use",
items=opModes
)
- k_thresh = FloatProperty(
+ k_thresh: FloatProperty(
name="k",
min=0, soft_min=0,
default=0, precision=3,
description="Threshold"
)
- pointsNr = IntProperty(
+ pointsNr: IntProperty(
name="n",
min=5, soft_min=5,
max=16, soft_max=9,
default=5,
description="Degree of curve to get averaged curvatures"
)
- error = FloatProperty(
+ error: FloatProperty(
name="Error",
description="Maximum allowed distance error",
min=0.0, soft_min=0.0,
default=0, precision=3
)
- degreeOut = IntProperty(
+ degreeOut: IntProperty(
name="Degree",
min=3, soft_min=3,
max=7, soft_max=7,
default=5,
description="Degree of new curve"
)
- dis_error = FloatProperty(
+ dis_error: FloatProperty(
name="Distance error",
description="Maximum allowed distance error in Blender Units",
min=0, soft_min=0,
@@ -490,7 +491,7 @@ class CURVE_OT_simplify(Operator):
('DISTANCE', 'Distance', 'Distance-based simplification (Poly)'),
('CURVATURE', 'Curvature', 'Curvature-based simplification (RDP)')
]
- mode = EnumProperty(
+ mode: EnumProperty(
name="Mode",
description="Choose algorithm to use",
items=opModes
@@ -501,42 +502,44 @@ class CURVE_OT_simplify(Operator):
('BEZIER', 'Bezier', 'BEZIER'),
('POLY', 'Poly', 'POLY')
]
- output = EnumProperty(
+ output: EnumProperty(
name="Output splines",
description="Type of splines to output",
items=SplineTypes
)
- k_thresh = FloatProperty(
+ k_thresh: FloatProperty(
name="k",
min=0, soft_min=0,
default=0, precision=3,
description="Threshold"
)
- pointsNr = IntProperty(name="n",
+ pointsNr: IntProperty(
+ name="n",
min=5, soft_min=5,
max=9, soft_max=9,
default=5,
description="Degree of curve to get averaged curvatures"
)
- error = FloatProperty(
+ error: FloatProperty(
name="Error",
description="Maximum allowed distance error in Blender Units",
min=0, soft_min=0,
default=0.0, precision=3
)
- degreeOut = IntProperty(name="Degree",
+ degreeOut: IntProperty(
+ name="Degree",
min=3, soft_min=3,
max=7, soft_max=7,
default=5,
description="Degree of new curve"
)
- dis_error = FloatProperty(
+ dis_error: FloatProperty(
name="Distance error",
description="Maximum allowed distance error in Blender Units",
min=0, soft_min=0,
default=0.0
)
- keepShort = BoolProperty(
+ keepShort: BoolProperty(
name="Keep short splines",
description="Keep short splines (less than 7 points)",
default=True
@@ -546,7 +549,7 @@ class CURVE_OT_simplify(Operator):
layout = self.layout
col = layout.column()
- col.label("Distance Error:")
+ col.label(text="Distance Error:")
col.prop(self, "error", expand=True)
col.prop(self, "output", text="Output", icon="OUTLINER_OB_CURVE")
if self.output == "NURBS":
@@ -591,21 +594,30 @@ class CURVE_OT_simplify(Operator):
# Register
+classes = [
+ GRAPH_OT_simplify,
+ CURVE_OT_simplify,
+]
+
def register():
- bpy.utils.register_module(__name__)
+ from bpy.utils import register_class
+ for cls in classes:
+ register_class(cls)
bpy.types.GRAPH_MT_channel.append(menu_func)
bpy.types.DOPESHEET_MT_channel.append(menu_func)
- bpy.types.INFO_MT_curve_add.append(menu)
+ bpy.types.VIEW3D_MT_curve_add.append(menu)
def unregister():
+ from bpy.utils import unregister_class
+ for cls in reversed(classes):
+ unregister_class(cls)
+
bpy.types.GRAPH_MT_channel.remove(menu_func)
bpy.types.DOPESHEET_MT_channel.remove(menu_func)
- bpy.types.INFO_MT_curve_add.remove(menu)
-
- bpy.utils.unregister_module(__name__)
+ bpy.types.VIEW3D_MT_curve_add.remove(menu)
if __name__ == "__main__":
diff --git a/depsgraph_debug.py b/depsgraph_debug.py
index 438d4885..76f25290 100644
--- a/depsgraph_debug.py
+++ b/depsgraph_debug.py
@@ -27,7 +27,7 @@ bl_info = {
"name": "Dependency Graph Debug",
"author": "Sergey Sharybin",
"version": (0, 1),
- "blender": (2, 79, 0),
+ "blender": (2, 80, 0),
"description": "Various dependency graph debugging tools",
"warning": "",
"wiki_url": "",
@@ -48,12 +48,12 @@ def _get_depsgraph(context):
# Save data from depsgraph to a specified file.
class SCENE_OT_depsgraph_save_common:
- filepath = StringProperty(
- name="File Path",
- description="Filepath used for saving the file",
- maxlen=1024,
- subtype='FILE_PATH',
- )
+ filepath: StringProperty(
+ name="File Path",
+ description="Filepath used for saving the file",
+ maxlen=1024,
+ subtype='FILE_PATH',
+ )
def _getExtension(self, context):
return ""
@@ -86,8 +86,10 @@ class SCENE_OT_depsgraph_save_common:
pass
-class SCENE_OT_depsgraph_relations_graphviz(Operator,
- SCENE_OT_depsgraph_save_common):
+class SCENE_OT_depsgraph_relations_graphviz(
+ Operator,
+ SCENE_OT_depsgraph_save_common,
+):
bl_idname = "scene.depsgraph_relations_graphviz"
bl_label = "Save Depsgraph"
bl_description = "Save current scene's dependency graph to a graphviz file"
@@ -96,13 +98,16 @@ class SCENE_OT_depsgraph_relations_graphviz(Operator,
return ".dot"
def performSave(self, context, depsgraph):
+ import os
basename, extension = os.path.splitext(self.filepath)
- depsgraph.debug_relations_graphviz(self.filepath, absename + ".png")
+ depsgraph.debug_relations_graphviz(os.path.join(self.filepath, basename + ".dot"))
return True
-class SCENE_OT_depsgraph_stats_gnuplot(Operator,
- SCENE_OT_depsgraph_save_common):
+class SCENE_OT_depsgraph_stats_gnuplot(
+ Operator,
+ SCENE_OT_depsgraph_save_common,
+):
bl_idname = "scene.depsgraph_stats_gnuplot"
bl_label = "Save Depsgraph Stats"
bl_description = "Save current scene's evaluaiton stats to gnuplot file"
diff --git a/development_api_navigator.py b/development_api_navigator.py
index 5e0276cb..deae8443 100644
--- a/development_api_navigator.py
+++ b/development_api_navigator.py
@@ -514,7 +514,7 @@ class OBJECT_PT_api_navigator(ApiNavigator, Panel):
elif iterable == 'b':
box = self.layout.box()
row = box.row()
- row.label(text="Item Values", icon="OOPS")
+ row.label(text="Item Values", icon='OUTLINER')
box = box.box()
col = box.column(align=True)
collection = list(current_module)
diff --git a/development_icon_get.py b/development_icon_get.py
index a7740c31..e32c58ef 100644
--- a/development_icon_get.py
+++ b/development_icon_get.py
@@ -23,9 +23,9 @@ bl_info = {
"name": "Icon Viewer",
"description": "Click an icon to copy its name to the clipboard",
"author": "roaoao",
- "version": (1, 3, 2),
- "blender": (2, 75, 0),
- "location": "Spacebar > Icon Viewer, Text Editor > Properties",
+ "version": (1, 4, 0),
+ "blender": (2, 80, 0),
+ "location": "Search Menu > Icon Viewer, Text Editor > Properties",
"wiki_url": "https://wiki.blender.org/index.php/Extensions:2.6"
"/Py/Scripts/Development/Display_All_Icons",
"category": "Development"
@@ -33,6 +33,10 @@ bl_info = {
import bpy
import math
+from bpy.props import (
+ BoolProperty,
+ StringProperty,
+)
DPI = 72
POPUP_PADDING = 10
@@ -88,6 +92,9 @@ class Icons:
not pr.show_brush_icons and "BRUSH_" in icon and \
icon != 'BRUSH_DATA' or \
not pr.show_matcap_icons and "MATCAP_" in icon or \
+ not pr.show_event_icons and (
+ "EVENT_" in icon or "MOUSE_" in icon
+ ) or \
not pr.show_colorset_icons and "COLORSET_" in icon:
continue
self._filtered_icons.append(icon)
@@ -109,8 +116,8 @@ class Icons:
else:
filtered_icons = self.filtered_icons
- column = layout.column(True)
- row = column.row(True)
+ column = layout.column(align=True)
+ row = column.row(align=True)
row.alignment = 'CENTER'
selected_icon = self.selected_icon if self.is_popup else \
@@ -118,7 +125,7 @@ class Icons:
col_idx = 0
for i, icon in enumerate(filtered_icons):
p = row.operator(
- IV_OT_icon_select.bl_idname, "",
+ IV_OT_icon_select.bl_idname, text="",
icon=icon, emboss=icon == selected_icon)
p.icon = icon
p.force_copy_on_select = not self.is_popup
@@ -129,16 +136,15 @@ class Icons:
break
col_idx = 0
if i < len(filtered_icons) - 1:
- row = column.row(True)
+ row = column.row(align=True)
row.alignment = 'CENTER'
if col_idx != 0 and not icons and i >= num_cols:
- sub = row.row(True)
- sub.scale_x = num_cols - col_idx
- sub.label("", icon='BLANK1')
+ for _ in range(num_cols - col_idx):
+ row.label(text="", icon='BLANK1')
if not filtered_icons:
- row.label("No icons were found")
+ row.label(text="No icons were found")
class IV_Preferences(bpy.types.AddonPreferences):
@@ -154,47 +160,51 @@ class IV_Preferences(bpy.types.AddonPreferences):
def set_panel_filter(self, value):
self.panel_icons.filter = value
- panel_filter = bpy.props.StringProperty(
+ panel_filter: StringProperty(
description="Filter",
default="",
get=lambda s: s.panel_icons.filter,
set=set_panel_filter,
options={'TEXTEDIT_UPDATE'})
- show_panel_icons = bpy.props.BoolProperty(
+ show_panel_icons: BoolProperty(
name="Show Icons",
description="Show icons", default=True)
- show_history = bpy.props.BoolProperty(
+ show_history: BoolProperty(
name="Show History",
description="Show history", default=True)
- show_brush_icons = bpy.props.BoolProperty(
+ show_brush_icons: BoolProperty(
name="Show Brush Icons",
description="Show brush icons", default=True,
update=update_icons)
- show_matcap_icons = bpy.props.BoolProperty(
+ show_matcap_icons: BoolProperty(
name="Show Matcap Icons",
description="Show matcap icons", default=True,
update=update_icons)
- show_colorset_icons = bpy.props.BoolProperty(
+ show_event_icons: BoolProperty(
+ name="Show Event Icons",
+ description="Show event icons", default=True,
+ update=update_icons)
+ show_colorset_icons: BoolProperty(
name="Show Colorset Icons",
description="Show colorset icons", default=True,
update=update_icons)
- copy_on_select = bpy.props.BoolProperty(
+ copy_on_select: BoolProperty(
name="Copy Icon On Click",
description="Copy icon on click", default=True)
- close_on_select = bpy.props.BoolProperty(
+ close_on_select: BoolProperty(
name="Close Popup On Click",
description=(
"Close the popup on click.\n"
"Not supported by some windows (User Preferences, Render)"
- ),
+ ),
default=False)
- auto_focus_filter = bpy.props.BoolProperty(
+ auto_focus_filter: BoolProperty(
name="Auto Focus Input Field",
description="Auto focus input field", default=True)
- show_panel = bpy.props.BoolProperty(
+ show_panel: BoolProperty(
name="Show Panel",
description="Show the panel in the Text Editor", default=True)
- show_header = bpy.props.BoolProperty(
+ show_header: BoolProperty(
name="Show Header",
description="Show the header in the Python Console",
default=True)
@@ -207,29 +217,30 @@ class IV_Preferences(bpy.types.AddonPreferences):
row = layout.row()
- col = row.column(True)
- col.label("Icons:")
+ col = row.column(align=True)
+ col.label(text="Icons:")
col.prop(self, "show_matcap_icons")
col.prop(self, "show_brush_icons")
col.prop(self, "show_colorset_icons")
+ col.prop(self, "show_event_icons")
col.separator()
col.prop(self, "show_history")
- col = row.column(True)
- col.label("Popup:")
+ col = row.column(align=True)
+ col.label(text="Popup:")
col.prop(self, "auto_focus_filter")
col.prop(self, "copy_on_select")
if self.copy_on_select:
col.prop(self, "close_on_select")
- col = row.column(True)
- col.label("Panel:")
+ col = row.column(align=True)
+ col.label(text="Panel:")
col.prop(self, "show_panel")
if self.show_panel:
col.prop(self, "show_panel_icons")
col.separator()
- col.label("Header:")
+ col.label(text="Header:")
col.prop(self, "show_header")
@@ -253,12 +264,13 @@ class IV_PT_icons(bpy.types.Panel):
def draw(self, context):
pr = prefs()
- row = self.layout.row(True)
+ row = self.layout.row(align=True)
if pr.show_panel_icons:
- row.prop(pr, "panel_filter", "", icon='VIEWZOOM')
+ row.prop(pr, "panel_filter", text="", icon='VIEWZOOM')
else:
row.operator(IV_OT_icons_show.bl_idname)
- row.operator(IV_OT_panel_menu_call.bl_idname, "", icon='COLLAPSEMENU')
+ row.operator(
+ IV_OT_panel_menu_call.bl_idname, text="", icon='COLLAPSEMENU')
_, y0 = context.region.view2d.region_to_view(0, 0)
_, y1 = context.region.view2d.region_to_view(0, 10)
@@ -271,11 +283,11 @@ class IV_PT_icons(bpy.types.Panel):
col = None
if HISTORY and pr.show_history:
- col = self.layout.column(True)
+ col = self.layout.column(align=True)
pr.panel_icons.draw(col.box(), num_cols, HISTORY)
if pr.show_panel_icons:
- col = col or self.layout.column(True)
+ col = col or self.layout.column(align=True)
pr.panel_icons.draw(col.box(), num_cols)
@classmethod
@@ -313,9 +325,10 @@ class IV_OT_panel_menu_call(bpy.types.Operator):
layout.prop(pr, "show_matcap_icons")
layout.prop(pr, "show_brush_icons")
layout.prop(pr, "show_colorset_icons")
+ layout.prop(pr, "show_event_icons")
def execute(self, context):
- context.window_manager.popup_menu(self.menu, "Icon Viewer")
+ context.window_manager.popup_menu(self.menu, title="Icon Viewer")
return {'FINISHED'}
@@ -325,8 +338,8 @@ class IV_OT_icon_select(bpy.types.Operator):
bl_description = "Select the icon"
bl_options = {'INTERNAL'}
- icon = bpy.props.StringProperty()
- force_copy_on_select = bpy.props.BoolProperty()
+ icon: StringProperty()
+ force_copy_on_select: BoolProperty()
def execute(self, context):
pr = prefs()
@@ -362,17 +375,17 @@ class IV_OT_icons_show(bpy.types.Operator):
if IV_OT_icons_show.instance:
IV_OT_icons_show.instance.auto_focusable = False
- filter_auto_focus = bpy.props.StringProperty(
+ filter_auto_focus: StringProperty(
description="Filter",
get=lambda s: prefs().popup_icons.filter,
set=set_filter,
options={'TEXTEDIT_UPDATE', 'SKIP_SAVE'})
- filter = bpy.props.StringProperty(
+ filter: StringProperty(
description="Filter",
get=lambda s: prefs().popup_icons.filter,
set=set_filter,
options={'TEXTEDIT_UPDATE'})
- selected_icon = bpy.props.StringProperty(
+ selected_icon: StringProperty(
description="Selected Icon",
get=lambda s: prefs().popup_icons.selected_icon,
set=set_selected_icon)
@@ -383,36 +396,38 @@ class IV_OT_icons_show(bpy.types.Operator):
def draw_header(self, layout):
pr = prefs()
header = layout.box()
- header = header.split(0.75) if self.selected_icon else header.row()
- row = header.row(True)
- row.prop(pr, "show_matcap_icons", "", icon='SMOOTH')
- row.prop(pr, "show_brush_icons", "", icon='BRUSH_DATA')
- row.prop(pr, "show_colorset_icons", "", icon='COLOR')
+ header = header.split(factor=0.75) if self.selected_icon else \
+ header.row()
+ row = header.row(align=True)
+ row.prop(pr, "show_matcap_icons", text="", icon='SHADING_RENDERED')
+ row.prop(pr, "show_brush_icons", text="", icon='BRUSH_DATA')
+ row.prop(pr, "show_colorset_icons", text="", icon='COLOR')
+ row.prop(pr, "show_event_icons", text="", icon='HAND')
row.separator()
row.prop(
- pr, "copy_on_select", "",
- icon='BORDER_RECT', toggle=True)
+ pr, "copy_on_select", text="",
+ icon='COPYDOWN', toggle=True)
if pr.copy_on_select:
- sub = row.row(True)
+ sub = row.row(align=True)
if bpy.context.window.screen.name == "temp":
sub.alert = True
sub.prop(
- pr, "close_on_select", "",
+ pr, "close_on_select", text="",
icon='RESTRICT_SELECT_OFF', toggle=True)
row.prop(
- pr, "auto_focus_filter", "",
+ pr, "auto_focus_filter", text="",
icon='OUTLINER_DATA_FONT', toggle=True)
row.separator()
if self.auto_focusable and pr.auto_focus_filter:
- row.prop(self, "filter_auto_focus", "", icon='VIEWZOOM')
+ row.prop(self, "filter_auto_focus", text="", icon='VIEWZOOM')
else:
- row.prop(self, "filter", "", icon='VIEWZOOM')
+ row.prop(self, "filter", text="", icon='VIEWZOOM')
if self.selected_icon:
row = header.row()
- row.prop(self, "selected_icon", "", icon=self.selected_icon)
+ row.prop(self, "selected_icon", text="", icon=self.selected_icon)
def draw(self, context):
pr = prefs()
@@ -425,7 +440,7 @@ class IV_OT_icons_show(bpy.types.Operator):
self.get_num_cols(len(pr.popup_icons.filtered_icons)),
history_num_cols)
- subcol = col.column(True)
+ subcol = col.column(align=True)
if HISTORY and pr.show_history:
pr.popup_icons.draw(subcol.box(), history_num_cols, HISTORY)
@@ -468,18 +483,31 @@ class IV_OT_icons_show(bpy.types.Operator):
ui_scale() * (num_cols * ICON_SIZE + POPUP_PADDING),
context.window.width - WIN_PADDING)
- return context.window_manager.invoke_props_dialog(self, self.width)
+ return context.window_manager.invoke_props_dialog(
+ self, width=self.width)
+
+
+classes = (
+ IV_PT_icons,
+ IV_HT_icons,
+ IV_OT_panel_menu_call,
+ IV_OT_icon_select,
+ IV_OT_icons_show,
+ IV_Preferences,
+)
def register():
if bpy.app.background:
return
- bpy.utils.register_module(__name__)
+ for cls in classes:
+ bpy.utils.register_class(cls)
def unregister():
if bpy.app.background:
return
- bpy.utils.unregister_module(__name__)
+ for cls in classes:
+ bpy.utils.unregister_class(cls)
diff --git a/development_iskeyfree.py b/development_iskeyfree.py
index 332e11d4..5d5600f4 100644
--- a/development_iskeyfree.py
+++ b/development_iskeyfree.py
@@ -146,9 +146,9 @@ class MyChecker():
allkeys = [
"LEFTMOUSE", "MIDDLEMOUSE", "RIGHTMOUSE", "BUTTON4MOUSE", "BUTTON5MOUSE", "BUTTON6MOUSE",
"BUTTON7MOUSE",
- "ACTIONMOUSE", "SELECTMOUSE", "MOUSEMOVE", "INBETWEEN_MOUSEMOVE", "TRACKPADPAN", "TRACKPADZOOM",
+ "MOUSEMOVE", "INBETWEEN_MOUSEMOVE", "TRACKPADPAN", "TRACKPADZOOM",
"MOUSEROTATE", "WHEELUPMOUSE", "WHEELDOWNMOUSE", "WHEELINMOUSE", "WHEELOUTMOUSE", "EVT_TWEAK_L",
- "EVT_TWEAK_M", "EVT_TWEAK_R", "EVT_TWEAK_A", "EVT_TWEAK_S", "A", "B", "C", "D", "E", "F", "G", "H",
+ "EVT_TWEAK_M", "EVT_TWEAK_R", "A", "B", "C", "D", "E", "F", "G", "H",
"I", "J",
"K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "ZERO", "ONE", "TWO",
"THREE", "FOUR", "FIVE", "SIX", "SEVEN", "EIGHT", "NINE", "LEFT_CTRL", "LEFT_ALT", "LEFT_SHIFT",
@@ -309,8 +309,6 @@ class IskeyFreeProperties(PropertyGroup):
("BUTTON5MOUSE", "BUTTON5MOUSE", ""),
("BUTTON6MOUSE", "BUTTON6MOUSE", ""),
("BUTTON7MOUSE", "BUTTON7MOUSE", ""),
- ("ACTIONMOUSE", "ACTIONMOUSE", ""),
- ("SELECTMOUSE", "SELECTMOUSE", ""),
("MOUSEMOVE", "MOUSEMOVE", ""),
("INBETWEEN_MOUSEMOVE", "INBETWEEN_MOUSEMOVE", ""),
("TRACKPADPAN", "TRACKPADPAN", ""),
@@ -323,8 +321,6 @@ class IskeyFreeProperties(PropertyGroup):
("EVT_TWEAK_L", "EVT_TWEAK_L", ""),
("EVT_TWEAK_M", "EVT_TWEAK_M", ""),
("EVT_TWEAK_R", "EVT_TWEAK_R", ""),
- ("EVT_TWEAK_A", "EVT_TWEAK_A", ""),
- ("EVT_TWEAK_S", "EVT_TWEAK_S", ""),
("A", "A", ""),
("B", "B", ""),
("C", "C", ""),
diff --git a/game_engine_publishing.py b/game_engine_publishing.py
deleted file mode 100644
index 495b0123..00000000
--- a/game_engine_publishing.py
+++ /dev/null
@@ -1,576 +0,0 @@
-# ##### BEGIN GPL LICENSE BLOCK #####
-#
-# This program is free software; you can redistribute it and/or
-# modify it under the terms of the GNU General Public License
-# as published by the Free Software Foundation; either version 2
-# of the License, or (at your option) any later version.
-#
-# This program 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 General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software Foundation,
-# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-#
-# ##### END GPL LICENSE BLOCK #####
-
-import bpy
-import os
-import tempfile
-import shutil
-import tarfile
-import time
-import stat
-
-
-bl_info = {
- "name": "Game Engine Publishing",
- "author": "Mitchell Stokes (Moguri), Oren Titane (Genome36)",
- "version": (0, 1, 0),
- "blender": (2, 75, 0),
- "location": "Render Properties > Publishing Info",
- "description": "Publish .blend file as game engine runtime, manage versions and platforms",
- "warning": "",
- "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/Scripts/Game_Engine/Publishing",
- "category": "Game Engine",
-}
-
-
-def WriteRuntime(player_path, output_path, asset_paths, copy_python, overwrite_lib, copy_dlls, make_archive, report=print):
- import struct
-
- player_path = bpy.path.abspath(player_path)
- ext = os.path.splitext(player_path)[-1].lower()
- output_path = bpy.path.abspath(output_path)
- output_dir = os.path.dirname(output_path)
- if not os.path.exists(output_dir):
- os.makedirs(output_dir)
-
- python_dir = os.path.join(os.path.dirname(player_path),
- bpy.app.version_string.split()[0],
- "python",
- "lib")
-
- # Check the paths
- if not os.path.isfile(player_path) and not(os.path.exists(player_path) and player_path.endswith('.app')):
- report({'ERROR'}, "The player could not be found! Runtime not saved")
- return
-
- # Check if we're bundling a .app
- if player_path.lower().endswith('.app'):
- # Python doesn't need to be copied for OS X since it's already inside blenderplayer.app
- copy_python = False
-
- output_path = bpy.path.ensure_ext(output_path, '.app')
-
- if os.path.exists(output_path):
- shutil.rmtree(output_path)
-
- shutil.copytree(player_path, output_path)
- bpy.ops.wm.save_as_mainfile(filepath=os.path.join(output_path, 'Contents', 'Resources', 'game.blend'),
- relative_remap=False,
- compress=False,
- copy=True,
- )
- else:
- # Enforce "exe" extension on Windows
- if player_path.lower().endswith('.exe'):
- output_path = bpy.path.ensure_ext(output_path, '.exe')
-
- # Get the player's binary and the offset for the blend
- with open(player_path, "rb") as file:
- player_d = file.read()
- offset = file.tell()
-
- # Create a tmp blend file (Blenderplayer doesn't like compressed blends)
- tempdir = tempfile.mkdtemp()
- blend_path = os.path.join(tempdir, bpy.path.clean_name(output_path))
- bpy.ops.wm.save_as_mainfile(filepath=blend_path,
- relative_remap=False,
- compress=False,
- copy=True,
- )
-
- # Get the blend data
- with open(blend_path, "rb") as blend_file:
- blend_d = blend_file.read()
-
- # Get rid of the tmp blend, we're done with it
- os.remove(blend_path)
- os.rmdir(tempdir)
-
- # Create a new file for the bundled runtime
- with open(output_path, "wb") as output:
- # Write the player and blend data to the new runtime
- print("Writing runtime...", end=" ", flush=True)
- output.write(player_d)
- output.write(blend_d)
-
- # Store the offset (an int is 4 bytes, so we split it up into 4 bytes and save it)
- output.write(struct.pack('BBBB', (offset >> 24) & 0xFF,
- (offset >> 16) & 0xFF,
- (offset >> 8) & 0xFF,
- (offset >> 0) & 0xFF))
-
- # Stuff for the runtime
- output.write(b'BRUNTIME')
-
- print("done", flush=True)
-
- # Make sure the runtime is executable
- os.chmod(output_path, 0o755)
-
- # Copy bundled Python
- blender_dir = os.path.dirname(player_path)
-
- if copy_python:
- print("Copying Python files...", end=" ", flush=True)
- py_folder = os.path.join(bpy.app.version_string.split()[0], "python", "lib")
- dst = os.path.join(output_dir, py_folder)
- src = python_dir
-
- if os.path.exists(dst) and overwrite_lib:
- shutil.rmtree(dst)
-
- if not os.path.exists(dst):
- shutil.copytree(src, dst, ignore=lambda dir, contents: [i for i in contents if i == '__pycache__'])
- print("done", flush=True)
- else:
- print("used existing Python folder", flush=True)
-
- # And DLLs if we're doing a Windows runtime)
- if copy_dlls and ext == ".exe":
- print("Copying DLLs...", end=" ", flush=True)
- for file in [i for i in os.listdir(blender_dir) if i.lower().endswith('.dll')]:
- src = os.path.join(blender_dir, file)
- dst = os.path.join(output_dir, file)
- shutil.copy2(src, dst)
-
- print("done", flush=True)
-
- # Copy assets
- for ap in asset_paths:
- src = bpy.path.abspath(ap.name)
- dst = os.path.join(output_dir, ap.name[2:] if ap.name.startswith('//') else ap.name)
-
- if os.path.exists(src):
- if os.path.isdir(src):
- if ap.overwrite and os.path.exists(dst):
- shutil.rmtree(dst)
- elif not os.path.exists(dst):
- shutil.copytree(src, dst)
- else:
- if ap.overwrite or not os.path.exists(dst):
- shutil.copy2(src, dst)
- else:
- report({'ERROR'}, "Could not find asset path: '%s'" % src)
-
- # Make archive
- if make_archive:
- print("Making archive...", end=" ", flush=True)
-
- arctype = ''
- if player_path.lower().endswith('.exe'):
- arctype = 'zip'
- elif player_path.lower().endswith('.app'):
- arctype = 'zip'
- else: # Linux
- arctype = 'gztar'
-
- basedir = os.path.normpath(os.path.join(os.path.dirname(output_path), '..'))
- afilename = os.path.join(basedir, os.path.basename(output_dir))
-
- if arctype == 'gztar':
- # Create the tarball ourselves instead of using shutil.make_archive
- # so we can handle permission bits.
-
- # The runtimename needs to use forward slashes as a path separator
- # since this is what tarinfo.name is using.
- runtimename = os.path.relpath(output_path, basedir).replace('\\', '/')
-
- def _set_ex_perm(tarinfo):
- if tarinfo.name == runtimename:
- tarinfo.mode = 0o755
- return tarinfo
-
- with tarfile.open(afilename + '.tar.gz', 'w:gz') as tf:
- tf.add(output_dir, os.path.relpath(output_dir, basedir), filter=_set_ex_perm)
- elif arctype == 'zip':
- shutil.make_archive(afilename, 'zip', output_dir)
- else:
- report({'ERROR'}, "Unknown archive type %s for runtime %s\n" % (arctype, player_path))
-
- print("done", flush=True)
-
-
-class PublishAllPlatforms(bpy.types.Operator):
- bl_idname = "wm.publish_platforms"
- bl_label = "Exports a runtime for each listed platform"
-
- def execute(self, context):
- ps = context.scene.ge_publish_settings
-
- if ps.publish_default_platform:
- print("Publishing default platform")
- blender_bin_path = bpy.app.binary_path
- blender_bin_dir = os.path.dirname(blender_bin_path)
- ext = os.path.splitext(blender_bin_path)[-1].lower()
- WriteRuntime(os.path.join(blender_bin_dir, 'blenderplayer' + ext),
- os.path.join(ps.output_path, 'default', ps.runtime_name),
- ps.asset_paths,
- True,
- True,
- True,
- ps.make_archive,
- self.report
- )
- else:
- print("Skipping default platform")
-
- for platform in ps.platforms:
- if platform.publish:
- print("Publishing", platform.name)
- WriteRuntime(platform.player_path,
- os.path.join(ps.output_path, platform.name, ps.runtime_name),
- ps.asset_paths,
- True,
- True,
- True,
- ps.make_archive,
- self.report
- )
- else:
- print("Skipping", platform.name)
-
- return {'FINISHED'}
-
-
-class RENDER_UL_assets(bpy.types.UIList):
- bl_label = "Asset Paths Listing"
-
- def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
- layout.prop(item, "name", text="", emboss=False)
-
-
-class RENDER_UL_platforms(bpy.types.UIList):
- bl_label = "Platforms Listing"
-
- def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
- row = layout.row()
- row.label(item.name)
- row.prop(item, "publish", text="")
-
-
-class RENDER_PT_publish(bpy.types.Panel):
- bl_label = "Publishing Info"
- bl_space_type = "PROPERTIES"
- bl_region_type = "WINDOW"
- bl_context = "render"
-
- @classmethod
- def poll(cls, context):
- scene = context.scene
- return scene and (scene.render.engine == "BLENDER_GAME")
-
- def draw(self, context):
- ps = context.scene.ge_publish_settings
- layout = self.layout
-
- # config
- layout.prop(ps, 'output_path')
- layout.prop(ps, 'runtime_name')
- layout.prop(ps, 'lib_path')
- layout.prop(ps, 'make_archive')
-
- layout.separator()
-
- # assets list
- layout.label("Asset Paths")
-
- # UI_UL_list
- row = layout.row()
- row.template_list("RENDER_UL_assets", "assets_list", ps, 'asset_paths', ps, 'asset_paths_active')
-
- # operators
- col = row.column(align=True)
- col.operator(PublishAddAssetPath.bl_idname, icon='ZOOMIN', text="")
- col.operator(PublishRemoveAssetPath.bl_idname, icon='ZOOMOUT', text="")
-
- # indexing
- if len(ps.asset_paths) > ps.asset_paths_active >= 0:
- ap = ps.asset_paths[ps.asset_paths_active]
- row = layout.row()
- row.prop(ap, 'overwrite')
-
- layout.separator()
-
- # publishing list
- row = layout.row(align=True)
- row.label("Platforms")
- row.prop(ps, 'publish_default_platform')
-
- # UI_UL_list
- row = layout.row()
- row.template_list("RENDER_UL_platforms", "platforms_list", ps, 'platforms', ps, 'platforms_active')
-
- # operators
- col = row.column(align=True)
- col.operator(PublishAddPlatform.bl_idname, icon='ZOOMIN', text="")
- col.operator(PublishRemovePlatform.bl_idname, icon='ZOOMOUT', text="")
- col.menu("PUBLISH_MT_platform_specials", icon='DOWNARROW_HLT', text="")
-
- # indexing
- if len(ps.platforms) > ps.platforms_active >= 0:
- platform = ps.platforms[ps.platforms_active]
- layout.prop(platform, 'name')
- layout.prop(platform, 'player_path')
-
- layout.operator(PublishAllPlatforms.bl_idname, 'Publish Platforms')
-
-
-class PublishAutoPlatforms(bpy.types.Operator):
- bl_idname = "scene.publish_auto_platforms"
- bl_label = "Auto Add Platforms"
-
- def execute(self, context):
- ps = context.scene.ge_publish_settings
-
- # verify lib folder
- lib_path = bpy.path.abspath(ps.lib_path)
- if not os.path.exists(lib_path):
- self.report({'ERROR'}, "Could not add platforms, lib folder (%s) does not exist" % lib_path)
- return {'CANCELLED'}
-
- for lib in [i for i in os.listdir(lib_path) if os.path.isdir(os.path.join(lib_path, i))]:
- print("Found folder:", lib)
- player_found = False
- for root, dirs, files in os.walk(os.path.join(lib_path, lib)):
- if "__MACOSX" in root:
- continue
-
- for f in dirs + files:
- if f.startswith("blenderplayer.app") or f.startswith("blenderplayer"):
- a = ps.platforms.add()
- if lib.startswith('blender-'):
- # Clean up names for packages from blender.org
- # example: blender-2.71-RC2-OSX_10.6-x86_64.zip => OSX_10.6-x86_64.zip
- # We're pretty consistent on naming, so this should hold up.
- a.name = '-'.join(lib.split('-')[3 if 'rc' in lib.lower() else 2:])
- else:
- a.name = lib
- a.player_path = bpy.path.relpath(os.path.join(root, f))
- player_found = True
- break
-
- if player_found:
- break
-
- return {'FINISHED'}
-
-# TODO This operator takes a long time to run, which is bad for UX. Could this instead be done as some sort of
-# modal dialog? This could also allow users to select which platforms to download and give a better progress
-# indicator.
-class PublishDownloadPlatforms(bpy.types.Operator):
- bl_idname = "scene.publish_download_platforms"
- bl_label = "Download Platforms"
-
- def execute(self, context):
- import html.parser
- import urllib.request
-
- remote_platforms = []
-
- ps = context.scene.ge_publish_settings
-
- # create lib folder if not already available
- lib_path = bpy.path.abspath(ps.lib_path)
- if not os.path.exists(lib_path):
- os.makedirs(lib_path)
-
- print("Retrieving list of platforms from blender.org...", end=" ", flush=True)
-
- class AnchorParser(html.parser.HTMLParser):
- def handle_starttag(self, tag, attrs):
- if tag == 'a':
- for key, value in attrs:
- if key == 'href' and value.startswith('blender'):
- remote_platforms.append(value)
-
- url = 'http://download.blender.org/release/Blender' + bpy.app.version_string.split()[0]
- parser = AnchorParser()
- data = urllib.request.urlopen(url).read()
- parser.feed(str(data))
-
- print("done", flush=True)
-
- print("Downloading files (this will take a while depending on your internet connection speed).", flush=True)
- for i in remote_platforms:
- src = '/'.join((url, i))
- dst = os.path.join(lib_path, i)
-
- dst_dir = '.'.join([i for i in dst.split('.') if i not in {'zip', 'tar', 'bz2'}])
- if not os.path.exists(dst) and not os.path.exists(dst.split('.')[0]):
- print("Downloading " + src + "...", end=" ", flush=True)
- urllib.request.urlretrieve(src, dst)
- print("done", flush=True)
- else:
- print("Reusing existing file: " + dst, flush=True)
-
- print("Unpacking " + dst + "...", end=" ", flush=True)
- if os.path.exists(dst_dir):
- shutil.rmtree(dst_dir)
- shutil.unpack_archive(dst, dst_dir)
- print("done", flush=True)
-
- print("Creating platform from libs...", flush=True)
- bpy.ops.scene.publish_auto_platforms()
- return {'FINISHED'}
-
-
-class PublishAddPlatform(bpy.types.Operator):
- bl_idname = "scene.publish_add_platform"
- bl_label = "Add Publish Platform"
-
- def execute(self, context):
- a = context.scene.ge_publish_settings.platforms.add()
- a.name = a.name
- return {'FINISHED'}
-
-
-class PublishRemovePlatform(bpy.types.Operator):
- bl_idname = "scene.publish_remove_platform"
- bl_label = "Remove Publish Platform"
-
- def execute(self, context):
- ps = context.scene.ge_publish_settings
- if ps.platforms_active < len(ps.platforms):
- ps.platforms.remove(ps.platforms_active)
- return {'FINISHED'}
- return {'CANCELLED'}
-
-
-# TODO maybe this should display a file browser?
-class PublishAddAssetPath(bpy.types.Operator):
- bl_idname = "scene.publish_add_assetpath"
- bl_label = "Add Asset Path"
-
- def execute(self, context):
- a = context.scene.ge_publish_settings.asset_paths.add()
- a.name = a.name
- return {'FINISHED'}
-
-
-class PublishRemoveAssetPath(bpy.types.Operator):
- bl_idname = "scene.publish_remove_assetpath"
- bl_label = "Remove Asset Path"
-
- def execute(self, context):
- ps = context.scene.ge_publish_settings
- if ps.asset_paths_active < len(ps.asset_paths):
- ps.asset_paths.remove(ps.asset_paths_active)
- return {'FINISHED'}
- return {'CANCELLED'}
-
-
-class PUBLISH_MT_platform_specials(bpy.types.Menu):
- bl_label = "Platform Specials"
-
- def draw(self, context):
- layout = self.layout
- layout.operator(PublishAutoPlatforms.bl_idname)
- layout.operator(PublishDownloadPlatforms.bl_idname)
-
-
-class PlatformSettings(bpy.types.PropertyGroup):
- name = bpy.props.StringProperty(
- name = "Platform Name",
- description = "The name of the platform",
- default = "Platform",
- )
-
- player_path = bpy.props.StringProperty(
- name = "Player Path",
- description = "The path to the Blenderplayer to use for this platform",
- default = "//lib/platform/blenderplayer",
- subtype = 'FILE_PATH',
- )
-
- publish = bpy.props.BoolProperty(
- name = "Publish",
- description = "Whether or not to publish to this platform",
- default = True,
- )
-
-
-class AssetPath(bpy.types.PropertyGroup):
- # TODO This needs a way to be a FILE_PATH or a DIR_PATH
- name = bpy.props.StringProperty(
- name = "Asset Path",
- description = "Path to the asset to be copied",
- default = "//src",
- subtype = 'FILE_PATH',
- )
-
- overwrite = bpy.props.BoolProperty(
- name = "Overwrite Asset",
- description = "Overwrite the asset if it already exists in the destination folder",
- default = True,
- )
-
-
-class PublishSettings(bpy.types.PropertyGroup):
- output_path = bpy.props.StringProperty(
- name = "Publish Output",
- description = "Where to publish the game",
- default = "//bin/",
- subtype = 'DIR_PATH',
- )
-
- runtime_name = bpy.props.StringProperty(
- name = "Runtime name",
- description = "The filename for the created runtime",
- default = "game",
- )
-
- lib_path = bpy.props.StringProperty(
- name = "Library Path",
- description = "Directory to search for platforms",
- default = "//lib/",
- subtype = 'DIR_PATH',
- )
-
- publish_default_platform = bpy.props.BoolProperty(
- name = "Publish Default Platform",
- description = "Whether or not to publish the default platform (the Blender install running this addon) when publishing platforms",
- default = True,
- )
-
-
- platforms = bpy.props.CollectionProperty(type=PlatformSettings, name="Platforms")
- platforms_active = bpy.props.IntProperty()
-
- asset_paths = bpy.props.CollectionProperty(type=AssetPath, name="Asset Paths")
- asset_paths_active = bpy.props.IntProperty()
-
- make_archive = bpy.props.BoolProperty(
- name = "Make Archive",
- description = "Create a zip archive of the published game",
- default = True,
- )
-
-
-def register():
- bpy.utils.register_module(__name__)
-
- bpy.types.Scene.ge_publish_settings = bpy.props.PointerProperty(type=PublishSettings)
-
-
-def unregister():
- bpy.utils.unregister_module(__name__)
- del bpy.types.Scene.ge_publish_settings
-
-
-if __name__ == "__main__":
- register()
diff --git a/game_engine_save_as_runtime.py b/game_engine_save_as_runtime.py
deleted file mode 100644
index 25e47d94..00000000
--- a/game_engine_save_as_runtime.py
+++ /dev/null
@@ -1,258 +0,0 @@
-# ##### BEGIN GPL LICENSE BLOCK #####
-#
-# This program is free software; you can redistribute it and/or
-# modify it under the terms of the GNU General Public License
-# as published by the Free Software Foundation; either version 2
-# of the License, or (at your option) any later version.
-#
-# This program 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 General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software Foundation,
-# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-#
-# ##### END GPL LICENSE BLOCK #####
-
-bl_info = {
- "name": "Save As Game Engine Runtime",
- "author": "Mitchell Stokes (Moguri)",
- "version": (0, 3, 1),
- "blender": (2, 61, 0),
- "location": "File > Export",
- "description": "Bundle a .blend file with the Blenderplayer",
- "warning": "",
- "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/"
- "Scripts/Game_Engine/Save_As_Runtime",
- "category": "Game Engine",
-}
-
-import bpy
-import os
-import sys
-import shutil
-import tempfile
-
-
-def CopyPythonLibs(dst, overwrite_lib, report=print):
- import platform
-
- # use python module to find pytohn's libpath
- src = os.path.dirname(platform.__file__)
-
- # dst points to lib/, but src points to current python's library path, eg:
- # '/usr/lib/python3.2' vs '/usr/lib'
- # append python's library dir name to destination, so only python's
- # libraries would be copied
- if os.name == 'posix':
- dst = os.path.join(dst, os.path.basename(src))
-
- if os.path.exists(src):
- write = False
- if os.path.exists(dst):
- if overwrite_lib:
- shutil.rmtree(dst)
- write = True
- else:
- write = True
- if write:
- shutil.copytree(src, dst, ignore=lambda dir, contents: [i for i in contents if i == '__pycache__'])
- else:
- report({'WARNING'}, "Python not found in %r, skipping pythn copy" % src)
-
-
-def WriteAppleRuntime(player_path, output_path, copy_python, overwrite_lib):
- # Enforce the extension
- if not output_path.endswith('.app'):
- output_path += '.app'
-
- # Use the system's cp command to preserve some meta-data
- os.system('cp -R "%s" "%s"' % (player_path, output_path))
-
- bpy.ops.wm.save_as_mainfile(filepath=os.path.join(output_path, "Contents/Resources/game.blend"),
- relative_remap=False,
- compress=False,
- copy=True,
- )
-
- # Python doesn't need to be copied for OS X since it's already inside blenderplayer.app
-
-
-def WriteRuntime(player_path, output_path, copy_python, overwrite_lib, copy_dlls, report=print):
- import struct
-
- # Check the paths
- if not os.path.isfile(player_path) and not(os.path.exists(player_path) and player_path.endswith('.app')):
- report({'ERROR'}, "The player could not be found! Runtime not saved")
- return
-
- # Check if we're bundling a .app
- if player_path.endswith('.app'):
- WriteAppleRuntime(player_path, output_path, copy_python, overwrite_lib)
- return
-
- # Enforce "exe" extension on Windows
- if player_path.endswith('.exe') and not output_path.endswith('.exe'):
- output_path += '.exe'
-
- # Get the player's binary and the offset for the blend
- file = open(player_path, 'rb')
- player_d = file.read()
- offset = file.tell()
- file.close()
-
- # Create a tmp blend file (Blenderplayer doesn't like compressed blends)
- tempdir = tempfile.mkdtemp()
- blend_path = os.path.join(tempdir, bpy.path.clean_name(output_path))
- bpy.ops.wm.save_as_mainfile(filepath=blend_path,
- relative_remap=False,
- compress=False,
- copy=True,
- )
-
- # Get the blend data
- blend_file = open(blend_path, 'rb')
- blend_d = blend_file.read()
- blend_file.close()
-
- # Get rid of the tmp blend, we're done with it
- os.remove(blend_path)
- os.rmdir(tempdir)
-
- # Create a new file for the bundled runtime
- output = open(output_path, 'wb')
-
- # Write the player and blend data to the new runtime
- print("Writing runtime...", end=" ")
- output.write(player_d)
- output.write(blend_d)
-
- # Store the offset (an int is 4 bytes, so we split it up into 4 bytes and save it)
- output.write(struct.pack('B', (offset>>24)&0xFF))
- output.write(struct.pack('B', (offset>>16)&0xFF))
- output.write(struct.pack('B', (offset>>8)&0xFF))
- output.write(struct.pack('B', (offset>>0)&0xFF))
-
- # Stuff for the runtime
- output.write(b'BRUNTIME')
- output.close()
-
- print("done")
-
- # Make the runtime executable on Linux
- if os.name == 'posix':
- os.chmod(output_path, 0o755)
-
- # Copy bundled Python
- blender_dir = os.path.dirname(bpy.app.binary_path)
- runtime_dir = os.path.dirname(output_path)
-
- if copy_python:
- print("Copying Python files...", end=" ")
- py_folder = os.path.join(bpy.app.version_string.split()[0], "python", "lib")
- dst = os.path.join(runtime_dir, py_folder)
- CopyPythonLibs(dst, overwrite_lib, report)
- print("done")
-
- # And DLLs
- if copy_dlls:
- print("Copying DLLs...", end=" ")
- for file in [i for i in os.listdir(blender_dir) if i.lower().endswith('.dll')]:
- src = os.path.join(blender_dir, file)
- dst = os.path.join(runtime_dir, file)
- shutil.copy2(src, dst)
-
- print("done")
-
-from bpy.props import *
-
-
-class SaveAsRuntime(bpy.types.Operator):
- bl_idname = "wm.save_as_runtime"
- bl_label = "Save As Game Engine Runtime"
- bl_options = {'REGISTER'}
-
- if sys.platform == 'darwin':
- # XXX, this line looks suspicious, could be done better?
- blender_bin_dir = '/' + os.path.join(*bpy.app.binary_path.split('/')[0:-4])
- ext = '.app'
- else:
- blender_bin_path = bpy.app.binary_path
- blender_bin_dir = os.path.dirname(blender_bin_path)
- ext = os.path.splitext(blender_bin_path)[-1].lower()
-
- default_player_path = os.path.join(blender_bin_dir, 'blenderplayer' + ext)
- player_path = StringProperty(
- name="Player Path",
- description="The path to the player to use",
- default=default_player_path,
- subtype='FILE_PATH',
- )
- filepath = StringProperty(
- subtype='FILE_PATH',
- )
- copy_python = BoolProperty(
- name="Copy Python",
- description="Copy bundle Python with the runtime",
- default=True,
- )
- overwrite_lib = BoolProperty(
- name="Overwrite 'lib' folder",
- description="Overwrites the lib folder (if one exists) with the bundled Python lib folder",
- default=False,
- )
-
- # Only Windows has dlls to copy
- if ext == '.exe':
- copy_dlls = BoolProperty(
- name="Copy DLLs",
- description="Copy all needed DLLs with the runtime",
- default=True,
- )
- else:
- copy_dlls = False
-
- def execute(self, context):
- import time
- start_time = time.clock()
- print("Saving runtime to %r" % self.filepath)
- WriteRuntime(self.player_path,
- self.filepath,
- self.copy_python,
- self.overwrite_lib,
- self.copy_dlls,
- self.report,
- )
- print("Finished in %.4fs" % (time.clock()-start_time))
- return {'FINISHED'}
-
- def invoke(self, context, event):
- if not self.filepath:
- ext = '.app' if sys.platform == 'darwin' else os.path.splitext(bpy.app.binary_path)[-1]
- self.filepath = bpy.path.ensure_ext(bpy.data.filepath, ext)
-
- wm = context.window_manager
- wm.fileselect_add(self)
- return {'RUNNING_MODAL'}
-
-
-def menu_func(self, context):
- self.layout.operator(SaveAsRuntime.bl_idname)
-
-
-def register():
- bpy.utils.register_module(__name__)
-
- bpy.types.INFO_MT_file_export.append(menu_func)
-
-
-def unregister():
- bpy.utils.unregister_module(__name__)
-
- bpy.types.INFO_MT_file_export.remove(menu_func)
-
-
-if __name__ == "__main__":
- register()
diff --git a/io_anim_acclaim/__init__.py b/io_anim_acclaim/__init__.py
index 620c1fc4..7cada817 100644
--- a/io_anim_acclaim/__init__.py
+++ b/io_anim_acclaim/__init__.py
@@ -189,7 +189,7 @@ class StructureBuilder(DataStructure):
self.armature = self.object.data
self.object.name = self.name
self.armature.name = self.name
- self.armature.draw_type = 'STICK'
+ self.armature.display_type = 'STICK'
self.object['source_file_path'] = self.file_path
self.object['source_scale'] = self.user_def_scale
self.object['MhxArmature'] = 'Daz'
@@ -530,16 +530,16 @@ def menu_func_me(self, context):
def register():
bpy.utils.register_module(__name__)
- bpy.types.INFO_MT_file_import.append(menu_func_s)
- bpy.types.INFO_MT_file_import.append(menu_func_mi)
- bpy.types.INFO_MT_file_export.append(menu_func_me)
+ bpy.types.TOPBAR_MT_file_import.append(menu_func_s)
+ bpy.types.TOPBAR_MT_file_import.append(menu_func_mi)
+ bpy.types.TOPBAR_MT_file_export.append(menu_func_me)
def unregister():
bpy.utils.unregister_module(__name__)
- bpy.types.INFO_MT_file_import.remove(menu_func_s)
- bpy.types.INFO_MT_file_import.remove(menu_func_mi)
- bpy.types.INFO_MT_file_export.remove(menu_func_me)
+ bpy.types.TOPBAR_MT_file_import.remove(menu_func_s)
+ bpy.types.TOPBAR_MT_file_import.remove(menu_func_mi)
+ bpy.types.TOPBAR_MT_file_export.remove(menu_func_me)
if __name__ == "__main__":
diff --git a/io_anim_bvh/__init__.py b/io_anim_bvh/__init__.py
index 4a4983ff..dd9c2037 100644
--- a/io_anim_bvh/__init__.py
+++ b/io_anim_bvh/__init__.py
@@ -22,7 +22,7 @@ bl_info = {
"name": "BioVision Motion Capture (BVH) format",
"author": "Campbell Barton",
"version": (1, 0, 0),
- "blender": (2, 74, 0),
+ "blender": (2, 80, 0),
"location": "File > Import-Export",
"description": "Import-Export BVH from armature objects",
"warning": "",
@@ -52,24 +52,22 @@ from bpy.props import (
from bpy_extras.io_utils import (
ImportHelper,
ExportHelper,
- orientation_helper_factory,
+ orientation_helper,
axis_conversion,
)
-ImportBVHOrientationHelper = orientation_helper_factory("ImportBVHOrientationHelper", axis_forward='-Z', axis_up='Y')
-
-
-class ImportBVH(bpy.types.Operator, ImportHelper, ImportBVHOrientationHelper):
+@orientation_helper(axis_forward='-Z', axis_up='Y')
+class ImportBVH(bpy.types.Operator, ImportHelper):
"""Load a BVH motion capture file"""
bl_idname = "import_anim.bvh"
bl_label = "Import BVH"
bl_options = {'REGISTER', 'UNDO'}
filename_ext = ".bvh"
- filter_glob = StringProperty(default="*.bvh", options={'HIDDEN'})
+ filter_glob: StringProperty(default="*.bvh", options={'HIDDEN'})
- target = EnumProperty(
+ target: EnumProperty(
items=(
('ARMATURE', "Armature", ""),
('OBJECT', "Object", ""),
@@ -78,20 +76,19 @@ class ImportBVH(bpy.types.Operator, ImportHelper, ImportBVHOrientationHelper):
description="Import target type",
default='ARMATURE',
)
-
- global_scale = FloatProperty(
+ global_scale: FloatProperty(
name="Scale",
description="Scale the BVH by this value",
min=0.0001, max=1000000.0,
soft_min=0.001, soft_max=100.0,
default=1.0,
)
- frame_start = IntProperty(
+ frame_start: IntProperty(
name="Start Frame",
description="Starting frame for the animation",
default=1,
)
- use_fps_scale = BoolProperty(
+ use_fps_scale: BoolProperty(
name="Scale FPS",
description=(
"Scale the framerate from the BVH to the current scenes, "
@@ -99,25 +96,25 @@ class ImportBVH(bpy.types.Operator, ImportHelper, ImportBVHOrientationHelper):
),
default=False,
)
- update_scene_fps = BoolProperty(
+ update_scene_fps: BoolProperty(
name="Update Scene FPS",
description=(
"Set the scene framerate to that of the BVH file (note that this "
"nullifies the 'Scale FPS' option, as the scale will be 1:1)"
),
- default=False
+ default=False,
)
- update_scene_duration = BoolProperty(
+ update_scene_duration: BoolProperty(
name="Update Scene Duration",
description="Extend the scene's duration to the BVH duration (never shortens the scene)",
default=False,
)
- use_cyclic = BoolProperty(
+ use_cyclic: BoolProperty(
name="Loop",
description="Loop the animation playback",
default=False,
)
- rotate_mode = EnumProperty(
+ rotate_mode: EnumProperty(
name="Rotation",
description="Rotation conversion",
items=(
@@ -143,7 +140,6 @@ class ImportBVH(bpy.types.Operator, ImportHelper, ImportBVHOrientationHelper):
"filter_glob",
)
)
-
global_matrix = axis_conversion(
from_forward=self.axis_forward,
from_up=self.axis_up,
@@ -161,29 +157,29 @@ class ExportBVH(bpy.types.Operator, ExportHelper):
bl_label = "Export BVH"
filename_ext = ".bvh"
- filter_glob = StringProperty(
+ filter_glob: StringProperty(
default="*.bvh",
options={'HIDDEN'},
)
- global_scale = FloatProperty(
+ global_scale: FloatProperty(
name="Scale",
description="Scale the BVH by this value",
min=0.0001, max=1000000.0,
soft_min=0.001, soft_max=100.0,
default=1.0,
)
- frame_start = IntProperty(
+ frame_start: IntProperty(
name="Start Frame",
description="Starting frame to export",
default=0,
)
- frame_end = IntProperty(
+ frame_end: IntProperty(
name="End Frame",
description="End frame to export",
default=0,
)
- rotate_mode = EnumProperty(
+ rotate_mode: EnumProperty(
name="Rotation",
description="Rotation conversion",
items=(
@@ -198,7 +194,7 @@ class ExportBVH(bpy.types.Operator, ExportHelper):
),
default='NATIVE',
)
- root_transform_only = BoolProperty(
+ root_transform_only: BoolProperty(
name="Root Translation Only",
description="Only write out translation channels for the root bone",
default=False,
@@ -220,7 +216,14 @@ class ExportBVH(bpy.types.Operator, ExportHelper):
self.frame_start = context.scene.frame_start
self.frame_end = context.scene.frame_end
- keywords = self.as_keywords(ignore=("check_existing", "filter_glob"))
+ keywords = self.as_keywords(
+ ignore=(
+ "axis_forward",
+ "axis_up",
+ "check_existing",
+ "filter_glob",
+ )
+ )
from . import export_bvh
return export_bvh.save(context, **keywords)
@@ -234,19 +237,25 @@ def menu_func_export(self, context):
self.layout.operator(ExportBVH.bl_idname, text="Motion Capture (.bvh)")
+classes = (
+ ImportBVH,
+ ExportBVH
+)
+
def register():
- bpy.utils.register_module(__name__)
+ for cls in classes:
+ bpy.utils.register_class(cls)
- bpy.types.INFO_MT_file_import.append(menu_func_import)
- bpy.types.INFO_MT_file_export.append(menu_func_export)
+ bpy.types.TOPBAR_MT_file_import.append(menu_func_import)
+ bpy.types.TOPBAR_MT_file_export.append(menu_func_export)
def unregister():
- bpy.utils.unregister_module(__name__)
-
- bpy.types.INFO_MT_file_import.remove(menu_func_import)
- bpy.types.INFO_MT_file_export.remove(menu_func_export)
+ for cls in classes:
+ bpy.utils.unregister_class(cls)
+ bpy.types.TOPBAR_MT_file_import.remove(menu_func_import)
+ bpy.types.TOPBAR_MT_file_export.remove(menu_func_export)
if __name__ == "__main__":
register()
diff --git a/io_anim_bvh/export_bvh.py b/io_anim_bvh/export_bvh.py
index 6a0c210f..a8b589df 100644
--- a/io_anim_bvh/export_bvh.py
+++ b/io_anim_bvh/export_bvh.py
@@ -255,12 +255,12 @@ def write_armature(
itrans = Matrix.Translation(-dbone.rest_bone.head_local)
if dbone.parent:
- mat_final = dbone.parent.rest_arm_mat * dbone.parent.pose_imat * dbone.pose_mat * dbone.rest_arm_imat
- mat_final = itrans * mat_final * trans
+ mat_final = dbone.parent.rest_arm_mat @ dbone.parent.pose_imat @ dbone.pose_mat @ dbone.rest_arm_imat
+ mat_final = itrans @ mat_final @ trans
loc = mat_final.to_translation() + (dbone.rest_bone.head_local - dbone.parent.rest_bone.head_local)
else:
- mat_final = dbone.pose_mat * dbone.rest_arm_imat
- mat_final = itrans * mat_final * trans
+ mat_final = dbone.pose_mat @ dbone.rest_arm_imat
+ mat_final = itrans @ mat_final @ trans
loc = mat_final.to_translation() + dbone.rest_bone.head
# keep eulers compatible, no jumping on interpolation.
diff --git a/io_anim_bvh/import_bvh.py b/io_anim_bvh/import_bvh.py
index 1f3c93fe..f609ca6c 100644
--- a/io_anim_bvh/import_bvh.py
+++ b/io_anim_bvh/import_bvh.py
@@ -348,19 +348,19 @@ def bvh_node_dict2objects(context, bvh_name, bvh_nodes, rotate_mode='NATIVE', fr
scene = context.scene
for obj in scene.objects:
- obj.select = False
+ obj.select_set(False)
objects = []
def add_ob(name):
obj = bpy.data.objects.new(name, None)
- scene.objects.link(obj)
+ context.collection.objects.link(obj)
objects.append(obj)
- obj.select = True
+ obj.select_set(True)
# nicer drawing.
- obj.empty_draw_type = 'CUBE'
- obj.empty_draw_size = 0.1
+ obj.empty_display_type = 'CUBE'
+ obj.empty_display_size = 0.1
return obj
@@ -422,15 +422,15 @@ def bvh_node_dict2armature(
# Add the new armature,
scene = context.scene
for obj in scene.objects:
- obj.select = False
+ obj.select_set(False)
arm_data = bpy.data.armatures.new(bvh_name)
arm_ob = bpy.data.objects.new(bvh_name, arm_data)
- scene.objects.link(arm_ob)
+ context.collection.objects.link(arm_ob)
- arm_ob.select = True
- scene.objects.active = arm_ob
+ arm_ob.select_set(True)
+ context.view_layer.objects.active = arm_ob
bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
bpy.ops.object.mode_set(mode='EDIT', toggle=False)
@@ -580,7 +580,7 @@ def bvh_node_dict2armature(
bone_translate_matrix = Matrix.Translation(
Vector(bvh_loc) - bvh_node.rest_head_local)
- location[frame_i] = (bone_rest_matrix_inv *
+ location[frame_i] = (bone_rest_matrix_inv @
bone_translate_matrix).to_translation()
# For each location x, y, z.
@@ -617,8 +617,8 @@ def bvh_node_dict2armature(
euler = Euler(bvh_rot, bvh_node.rot_order_str[::-1])
bone_rotation_matrix = euler.to_matrix().to_4x4()
bone_rotation_matrix = (
- bone_rest_matrix_inv *
- bone_rotation_matrix *
+ bone_rest_matrix_inv @
+ bone_rotation_matrix @
bone_rest_matrix
)
diff --git a/io_anim_c3d/__init__.py b/io_anim_c3d/__init__.py
index 2c9cd0d6..e14fcaec 100644
--- a/io_anim_c3d/__init__.py
+++ b/io_anim_c3d/__init__.py
@@ -272,7 +272,7 @@ class C3DImporter(bpy.types.Operator):
unames[name] = o.name
bpy.ops.transform.resize(value=empty_size)
o.show_name = self.properties.show_names
- o.show_x_ray = self.properties.x_ray
+ o.show_in_front = self.properties.x_ray
for name in unames.values():
bpy.context.scene.objects[name].select = True
return unames
@@ -291,7 +291,7 @@ class C3DImporter(bpy.types.Operator):
arm = bpy.context.active_object
arm.name = os.path.basename(self.properties.filepath)
arm.data.show_names = self.properties.show_names
- arm.show_x_ray = self.properties.x_ray
+ arm.show_in_front = self.properties.x_ray
for idx, ml in enumerate(ms.markerLabels):
name = self.properties.prefix + ml
bpy.ops.armature.select_all(action='DESELECT')
@@ -354,12 +354,12 @@ def menu_func(self, context):
def register():
bpy.utils.register_module(__name__)
- bpy.types.INFO_MT_file_import.append(menu_func)
+ bpy.types.TOPBAR_MT_file_import.append(menu_func)
def unregister():
bpy.utils.unregister_module(__name__)
- bpy.types.INFO_MT_file_import.remove(menu_func)
+ bpy.types.TOPBAR_MT_file_import.remove(menu_func)
if __name__ == "__main__":
diff --git a/io_anim_camera.py b/io_anim_camera.py
index 20cb5395..b382a32d 100644
--- a/io_anim_camera.py
+++ b/io_anim_camera.py
@@ -22,7 +22,7 @@ bl_info = {
"name": "Export Camera Animation",
"author": "Campbell Barton",
"version": (0, 1),
- "blender": (2, 57, 0),
+ "blender": (2, 80, 0),
"location": "File > Export > Cameras & Markers (.py)",
"description": "Export Cameras & Markers (.py)",
"warning": "",
@@ -45,7 +45,7 @@ def write_cameras(context, filepath, frame_start, frame_end, only_selected=False
'dof_distance',
'clip_start',
'clip_end',
- 'draw_size',
+ 'display_size',
)
obj_attrs = (
@@ -59,7 +59,7 @@ def write_cameras(context, filepath, frame_start, frame_end, only_selected=False
cameras = []
for obj in scene.objects:
- if only_selected and not obj.select:
+ if only_selected and not obj.select_get():
continue
if obj.type != 'CAMERA':
continue
@@ -84,7 +84,7 @@ def write_cameras(context, filepath, frame_start, frame_end, only_selected=False
for attr in obj_attrs:
fw("obj.%s = %s\n" % (attr, repr(getattr(obj, attr))))
- fw("scene.objects.link(obj)\n")
+ fw("bpy.context.collection.objects.link(obj)\n")
fw("cameras[%r] = obj\n" % obj.name)
fw("\n")
@@ -134,15 +134,15 @@ class CameraExporter(bpy.types.Operator, ExportHelper):
bl_label = "Export Camera & Markers"
filename_ext = ".py"
- filter_glob = StringProperty(default="*.py", options={'HIDDEN'})
+ filter_glob: StringProperty(default="*.py", options={'HIDDEN'})
- frame_start = IntProperty(name="Start Frame",
+ frame_start: IntProperty(name="Start Frame",
description="Start frame for export",
default=1, min=1, max=300000)
- frame_end = IntProperty(name="End Frame",
+ frame_end: IntProperty(name="End Frame",
description="End frame for export",
default=250, min=1, max=300000)
- only_selected = BoolProperty(name="Only Selected",
+ only_selected: BoolProperty(name="Only Selected",
default=True)
def execute(self, context):
@@ -165,15 +165,13 @@ def menu_export(self, context):
def register():
- bpy.utils.register_module(__name__)
-
- bpy.types.INFO_MT_file_export.append(menu_export)
+ bpy.utils.register_class(CameraExporter)
+ bpy.types.TOPBAR_MT_file_export.append(menu_export)
def unregister():
- bpy.utils.unregister_module(__name__)
-
- bpy.types.INFO_MT_file_export.remove(menu_export)
+ bpy.utils.unregister_class(CameraExporter)
+ bpy.types.TOPBAR_MT_file_export.remove(menu_export)
if __name__ == "__main__":
diff --git a/io_anim_nuke_chan/__init__.py b/io_anim_nuke_chan/__init__.py
index b3102329..9266c439 100644
--- a/io_anim_nuke_chan/__init__.py
+++ b/io_anim_nuke_chan/__init__.py
@@ -143,15 +143,15 @@ def menu_func_export(self, context):
def register():
bpy.utils.register_class(ImportChan)
bpy.utils.register_class(ExportChan)
- bpy.types.INFO_MT_file_import.append(menu_func_import)
- bpy.types.INFO_MT_file_export.append(menu_func_export)
+ bpy.types.TOPBAR_MT_file_import.append(menu_func_import)
+ bpy.types.TOPBAR_MT_file_export.append(menu_func_export)
def unregister():
bpy.utils.unregister_class(ImportChan)
bpy.utils.unregister_class(ExportChan)
- bpy.types.INFO_MT_file_import.remove(menu_func_import)
- bpy.types.INFO_MT_file_export.remove(menu_func_export)
+ bpy.types.TOPBAR_MT_file_import.remove(menu_func_import)
+ bpy.types.TOPBAR_MT_file_export.remove(menu_func_export)
if __name__ == "__main__":
diff --git a/io_blend_utils/__init__.py b/io_blend_utils/__init__.py
index d44d4754..9d102102 100644
--- a/io_blend_utils/__init__.py
+++ b/io_blend_utils/__init__.py
@@ -135,14 +135,14 @@ def register():
for cls in classes:
bpy.utils.register_class(cls)
- bpy.types.INFO_MT_file_external_data.append(menu_func)
+ bpy.types.TOPBAR_MT_file_external_data.append(menu_func)
def unregister():
for cls in classes:
bpy.utils.unregister_class(cls)
- bpy.types.INFO_MT_file_external_data.remove(menu_func)
+ bpy.types.TOPBAR_MT_file_external_data.remove(menu_func)
if __name__ == "__main__":
diff --git a/io_blend_utils/blend/blendfile_path_walker.py b/io_blend_utils/blend/blendfile_path_walker.py
index df07235e..ef499604 100644
--- a/io_blend_utils/blend/blendfile_path_walker.py
+++ b/io_blend_utils/blend/blendfile_path_walker.py
@@ -726,7 +726,7 @@ class ExpandID:
del dup_group
yield block.get_pointer(b'proxy')
- yield block.get_pointer(b'proxy_group')
+ yield block.get_pointer(b'proxy_collection')
if USE_ALEMBIC_BRANCH:
if has_dup_group:
diff --git a/io_blend_utils/blender_bam-unpacked.whl/bam/blend/blendfile_path_walker.py b/io_blend_utils/blender_bam-unpacked.whl/bam/blend/blendfile_path_walker.py
index df07235e..ef499604 100644
--- a/io_blend_utils/blender_bam-unpacked.whl/bam/blend/blendfile_path_walker.py
+++ b/io_blend_utils/blender_bam-unpacked.whl/bam/blend/blendfile_path_walker.py
@@ -726,7 +726,7 @@ class ExpandID:
del dup_group
yield block.get_pointer(b'proxy')
- yield block.get_pointer(b'proxy_group')
+ yield block.get_pointer(b'proxy_collection')
if USE_ALEMBIC_BRANCH:
if has_dup_group:
diff --git a/io_coat3D/__init__.py b/io_coat3D/__init__.py
index b5539ede..7c16e22a 100644
--- a/io_coat3D/__init__.py
+++ b/io_coat3D/__init__.py
@@ -19,8 +19,8 @@
bl_info = {
"name": "3D-Coat Applink",
"author": "Kalle-Samuli Riihikoski (haikalle)",
- "version": (3, 5, 22),
- "blender": (2, 59, 0),
+ "version": (5, 0, 00),
+ "blender": (2, 80, 0),
"location": "Scene > 3D-Coat Applink",
"description": "Transfer data between 3D-Coat/Blender",
"warning": "",
@@ -35,10 +35,16 @@ if "bpy" in locals():
importlib.reload(coat)
importlib.reload(tex)
else:
- from . import coat
from . import tex
+from io_coat3D import tex
+import os
+import ntpath
+import re
+
+import time
import bpy
+import subprocess
from bpy.types import PropertyGroup
from bpy.props import (
BoolProperty,
@@ -49,244 +55,1257 @@ from bpy.props import (
)
+bpy.coat3D = dict()
+bpy.coat3D['active_coat'] = ''
+bpy.coat3D['status'] = 0
+
+def update_exe_path():
+ if (bpy.context.scene.coat3D.coat3D_exe != ''):
+ importfile = bpy.context.scene.coat3D.exchangedir
+ importfile += ('%scoat3D_exe.txt' % (os.sep))
+ file = open(importfile, "w")
+ file.write("%s" % (bpy.context.scene.coat3D.coat3D_exe))
+ file.close()
+
+def folder_size(path):
+
+ tosi = True
+ while tosi:
+ list_of_files = []
+ for file in os.listdir(path):
+ list_of_files.append(path + os.sep + file)
+
+ if len(list_of_files) >= 400:
+ oldest_file = min(list_of_files, key=os.path.getctime)
+ os.remove(os.path.abspath(oldest_file))
+ else:
+ tosi = False
+
+def set_exchange_folder():
+ platform = os.sys.platform
+ coat3D = bpy.context.scene.coat3D
+ Blender_export = ""
+
+ if(platform == 'win32'):
+ exchange = os.path.expanduser("~") + os.sep + 'Documents' + os.sep + '3D-CoatV48' + os.sep +'Exchange'
+ if not(os.path.isdir(exchange)):
+ exchange = os.path.expanduser("~") + os.sep + 'Documents' + os.sep + '3D-CoatV4' + os.sep +'Exchange'
+ if not (os.path.isdir(exchange)):
+ exchange = os.path.expanduser("~") + os.sep + 'Documents' + os.sep + '3D-CoatV3' + os.sep + 'Exchange'
+
+ else:
+ exchange = os.path.expanduser("~") + os.sep + '3D-CoatV4' + os.sep + 'Exchange'
+ if not(os.path.isdir(exchange)):
+ exchange = os.path.expanduser("~") + os.sep + '3D-CoatV3' + os.sep + 'Exchange'
+ if(not(os.path.isdir(exchange))):
+ exchange = coat3D.exchangedir
+
+ if(os.path.isdir(exchange)):
+ bpy.coat3D['status'] = 1
+ if(platform == 'win32'):
+ exchange_path = os.path.expanduser("~") + os.sep + 'Documents' + os.sep + '3DC2Blender' + os.sep + 'Exchange_folder.txt'
+ applink_folder = os.path.expanduser("~") + os.sep + 'Documents' + os.sep + '3DC2Blender'
+ if(not(os.path.isdir(applink_folder))):
+ os.makedirs(applink_folder)
+ else:
+ exchange_path = os.path.expanduser("~") + os.sep + 'Documents' + os.sep + '3DC2Blender' + os.sep + 'Exchange_folder.txt'
+ applink_folder = os.path.expanduser("~") + os.sep + 'Documents' + os.sep + '3DC2Blender'
+ if(not(os.path.isdir(applink_folder))):
+ os.makedirs(applink_folder)
+ file = open(exchange_path, "w")
+ file.write("%s"%(coat3D.exchangedir))
+ file.close()
+
+ else:
+ if(platform == 'win32'):
+ exchange_path = os.path.expanduser("~") + os.sep + 'Documents' + os.sep + '3DC2Blender' + os.sep + 'Exchange_folder.txt'
+ else:
+ exchange_path = os.path.expanduser("~") + os.sep + '3DC2Blender' + os.sep + 'Exchange_folder.txt'
+ if(os.path.isfile(exchange_path)):
+ ex_path =''
+
+ ex_pathh = open(exchange_path)
+ for line in ex_pathh:
+ ex_path = line
+ break
+ ex_pathh.close()
+
+ if(os.path.isdir(ex_path) and ex_path.rfind('Exchange') >= 0):
+ exchange = ex_path
+ bpy.coat3D['status'] = 1
+ else:
+ bpy.coat3D['status'] = 0
+ else:
+ bpy.coat3D['status'] = 0
+ if(bpy.coat3D['status'] == 1):
+ Blender_folder = ("%s%sBlender"%(exchange,os.sep))
+ Blender_export = Blender_folder
+ path3b_now = exchange
+ path3b_now += ('last_saved_3b_file.txt')
+ Blender_export += ('%sexport.txt'%(os.sep))
+
+ if(not(os.path.isdir(Blender_folder))):
+ os.makedirs(Blender_folder)
+ Blender_folder = os.path.join(Blender_folder,"run.txt")
+ file = open(Blender_folder, "w")
+ file.close()
+ return exchange
+
+def set_working_folders():
+ platform = os.sys.platform
+ coat3D = bpy.context.scene.coat3D
+ if(platform == 'win32'):
+ folder_objects = os.path.expanduser("~") + os.sep + 'Documents' + os.sep + '3DC2Blender' + os.sep + 'ApplinkObjects'
+ if(not(os.path.isdir(folder_objects))):
+ os.makedirs(folder_objects)
+ else:
+ folder_objects = os.path.expanduser("~") + os.sep + '3DC2Blender' + os.sep + 'ApplinkObjects'
+ if(not(os.path.isdir(folder_objects))):
+ os.makedirs(folder_objects)
+
+ return folder_objects
+
+def make_texture_list(texturefolder):
+ texturefolder += ('%stextures.txt'%(os.sep))
+ texturelist = []
+
+ if (os.path.isfile(texturefolder)):
+ texturefile = open(texturefolder)
+ index = 0
+ for line in texturefile:
+ if line != '' and index == 0:
+ line = line.rstrip('\n')
+ objekti = line
+ index += 1
+ elif index == 1:
+ line = line.rstrip('\n')
+ material = line
+ index += 1
+ elif index == 2:
+ line = line.rstrip('\n')
+ type = line
+ index += 1
+ elif index == 3:
+ line = line.rstrip('\n')
+ address = line
+ texturelist.append([objekti,material,type,address])
+ index = 0
+ texturefile.close()
+ return texturelist
+
+
+'''
+#Updating objects MESH part ( Mesh, Vertex Groups, Vertex Colors )
+'''
+
+def updatemesh(objekti, proxy):
+
+
+ #TO DO VERTEX GROUPS, gives an error with this code.
+
+ if(objekti.vertex_groups.keys() != []):
+ bpy.ops.object.select_all(action='DESELECT')
+ proxy.select_set(True)
+ objekti.select_set(True)
+ bpy.ops.object.vertex_group_copy_to_selected()
+ bpy.ops.object.select_all(action='DESELECT')
+
+ # UV Set Copy
+
+ proxy.select_set(True)
+ objekti.select_set(True)
+
+ if len(objekti.data.uv_layers) > 1:
+ obj_uv_index = objekti.data.uv_layers.active_index
+ index = 0
+ for uv_layer in objekti.data.uv_layers:
+ if (uv_layer != objekti.data.uv_layers[0]):
+ proxy.data.uv_layers.new(name=uv_layer.name)
+ proxy.data.uv_layers.active_index = index
+ objekti.data.uv_layers.active_index = index
+ bpy.ops.object.join_uvs()
+ index += 1
+ proxy.data.uv_layers.active_index = obj_uv_index
+
+ bpy.ops.object.select_all(action='DESELECT')
+
+ #Mesh Copy
+
+ proxy.select_set(True)
+ obj_data = objekti.data.id_data
+ objekti.data = proxy.data.id_data
+ objekti.data.id_data.name = obj_data.name
+ if (bpy.data.meshes[obj_data.name].users == 0):
+ bpy.data.meshes.remove(obj_data)
+
+def running():
+ n=0# number of instances of the program running
+ prog=[line.split() for line in subprocess.check_output("tasklist").splitlines()]
+ [prog.pop(e) for e in [0,1,2]] #useless
+ for task in prog:
+ if str(task[0]) == "b'3D-CoatDX64C.exe'" or str(task[0]) == "b'3D-CoatGL64C.exe'":
+ n+=1
+ break
+ if n>0:
+ return True
+ else:
+ return False
+
+class SCENE_OT_folder(bpy.types.Operator):
+ bl_idname = "update_exchange_folder.pilgway_3d_coat"
+ bl_label = "Export your custom property"
+ bl_description = "Export your custom property"
+ bl_options = {'UNDO'}
+
+ def invoke(self, context, event):
+ coat3D = bpy.context.scene.coat3D
+
+ if(os.path.isdir(coat3D.exchangedir)):
+ coat3D.exchange_found = True
+ else:
+ coat3D.exchange_found = False
+
+ return {'FINISHED'}
+
+class SCENE_OT_opencoat(bpy.types.Operator):
+ bl_idname = "open_3dcoat.pilgway_3d_coat"
+ bl_label = "Export your custom property"
+ bl_description = "Export your custom property"
+ bl_options = {'UNDO'}
+
+ def invoke(self, context, event):
+
+ update_exe_path()
+
+ exefile = bpy.context.scene.coat3D.exchangedir
+ exefile += ('%scoat3D_exe.txt' % (os.sep))
+ exe_path = ''
+ if (os.path.isfile(exefile)):
+
+ ex_pathh = open(exefile)
+ for line in ex_pathh:
+ exe_path = line
+ break
+ ex_pathh.close()
+
+ coat3D = bpy.context.selected_objects[0].coat3D.applink_3b_path
+ platform = os.sys.platform
+ prog_path = os.environ['PROGRAMFILES']
+ if (platform == 'win32'):
+
+ active_3dcoat = exe_path
+
+ if running() == False:
+ print('tulele tanne')
+ os.popen('"' + active_3dcoat + '" ' + coat3D)
+ else:
+ importfile = bpy.context.scene.coat3D.exchangedir
+ importfile += ('%simport.txt' % (os.sep))
+ file = open(importfile, "w")
+ file.write("%s" % (coat3D))
+ file.write("\n%s" % (coat3D))
+ file.write("\n[3B]")
+ file.close()
+
+ '''
+ If not Windows Os it will only write import.txt. Auto run 3d-coat.exe is disabled.
+ '''
+
+ else:
+ importfile = bpy.context.scene.coat3D.exchangedir
+ importfile += ('%simport.txt' % (os.sep))
+ file = open(importfile, "w")
+ file.write("%s" % (coat3D))
+ file.write("\n%s" % (coat3D))
+ file.write("\n[3B]")
+ file.close()
+
+
+
+ return {'FINISHED'}
+
+class SCENE_OT_export(bpy.types.Operator):
+ bl_idname = "export_applink.pilgway_3d_coat"
+ bl_label = "Export your custom property"
+ bl_description = "Export your custom property"
+ bl_options = {'UNDO'}
+
+ def invoke(self, context, event):
+
+ update_exe_path()
+
+ for mesh in bpy.data.meshes:
+ if (mesh.users == 0 and mesh.coat3D.name == '3DC'):
+ bpy.data.meshes.remove(mesh)
+
+ for material in bpy.data.materials:
+ if (material.users == 1 and material.coat3D.name == '3DC'):
+ bpy.data.materials.remove(material)
+
+ export_ok = False
+ coat3D = bpy.context.scene.coat3D
+
+ if (bpy.context.selected_objects == []):
+ return {'FINISHED'}
+ else:
+ for objec in bpy.context.selected_objects:
+ if objec.type == 'MESH':
+ export_ok = True
+ if (export_ok == False):
+ return {'FINISHED'}
+
+ activeobj = bpy.context.active_object.name
+ checkname = ''
+ coa = bpy.context.active_object.coat3D
+ coat3D.exchangedir = set_exchange_folder()
+
+ if (not os.path.isdir(coat3D.exchangedir)):
+ coat3D.exchange_found = False
+ return {'FINISHED'}
+
+
+ folder_objects = set_working_folders()
+ folder_size(folder_objects)
+
+ importfile = coat3D.exchangedir
+ texturefile = coat3D.exchangedir
+ importfile += ('%simport.txt'%(os.sep))
+ texturefile += ('%stextures.txt'%(os.sep))
+
+ looking = True
+ object_index = 0
+
+ while(looking == True):
+ checkname = folder_objects + os.sep + "3DC"
+ checkname = ("%s%.3d.fbx"%(checkname,object_index))
+ if(os.path.isfile(checkname)):
+ object_index += 1
+ else:
+ looking = False
+ coa.applink_name = ("%s%.2d"%(activeobj,object_index))
+ coa.applink_address = checkname
+
+ matindex = 0
+
+ for objekti in bpy.context.selected_objects:
+ if(objekti.material_slots.keys() == []):
+ newmat = bpy.data.materials.new('Material')
+ newmat.use_nodes = True
+ objekti.data.materials.append(newmat)
+ matindex += 1
+ new_name = objekti.data.name
+ name_boxs = new_name.split('.')
+ if(len(name_boxs)>1):
+ objekti.name = name_boxs[0] + name_boxs[1]
+ nimi = name_boxs[0] + name_boxs[1]
+ nimiNum = int(name_boxs[1])
+ looking = False
+ lyytyi = False
+ while(looking == False):
+ for all_ob in bpy.data.meshes:
+ numero = ("%.3d"%(nimiNum))
+ nimi2 = name_boxs[0] + numero
+ if(all_ob.name == nimi2):
+ lyytyi = True
+ break
+ else:
+ lyytyi = False
+
+ if(lyytyi == True):
+ nimiNum += 1
+ else:
+ looking = True
+ objekti.data.name = nimi2
+ objekti.name = nimi2
+
+
+
+ else:
+ objekti.name = name_boxs[0]
+ objekti.data.name = name_boxs[0]
+ objekti.coat3D.applink_name = objekti.data.name
+
+ for objekti in bpy.context.selected_objects:
+ objekti.coat3D.applink_scale = objekti.scale
+
+ bpy.ops.object.origin_set(type='ORIGIN_GEOMETRY')
+ if(len(bpy.context.selected_objects) > 1 and coat3D.type != 'vox'):
+ bpy.ops.object.transforms_to_deltas(mode='ROT')
+
+
+ bpy.ops.export_scene.fbx(filepath=coa.applink_address, use_selection=True, use_mesh_modifiers=coat3D.exportmod, axis_forward='X', axis_up='Y')
+
+ file = open(importfile, "w")
+ file.write("%s"%(checkname))
+ file.write("\n%s"%(checkname))
+ file.write("\n[%s]"%(coat3D.type))
+ file.close()
+ group_index = -1.0
+ for idx, objekti in enumerate(bpy.context.selected_objects):
+
+ if(len(bpy.context.selected_objects) == 1):
+ objekti.coat3D.applink_onlyone = True
+ objekti.coat3D.type = coat3D.type
+ objekti.coat3D.applink_mesh = True
+ objekti.coat3D.applink_address = coa.applink_address
+ objekti.coat3D.obj_mat = ''
+ objekti.coat3D.applink_firsttime = True
+ objekti.coat3D.objecttime = str(os.path.getmtime(objekti.coat3D.applink_address))
+ objekti.data.coat3D.name = '3DC'
+
+ if(coat3D.type != 'vox'):
+ if(objekti.material_slots.keys() != []):
+ for material in objekti.material_slots:
+ if material.material.use_nodes == True:
+ for node in material.material.node_tree.nodes:
+ if(node.name.startswith('3DC_') == True):
+ material.material.node_tree.nodes.remove(node)
+
+ return {'FINISHED'}
+
+class SCENE_OT_import(bpy.types.Operator):
+ bl_idname = "import_applink.pilgway_3d_coat"
+ bl_label = "import your custom property"
+ bl_description = "import your custom property"
+ bl_options = {'UNDO'}
+
+ def invoke(self, context, event):
+
+ update_exe_path()
+
+ for node_group in bpy.data.node_groups:
+ if(node_group.users == 0):
+ bpy.data.node_groups.remove(node_group)
+
+ for mesh in bpy.data.meshes:
+ if(mesh.users == 0 and mesh.coat3D.name == '3DC'):
+ bpy.data.meshes.remove(mesh)
+
+ for material in bpy.data.materials:
+ img_list = []
+ if (material.users == 1 and material.coat3D.name == '3DC'):
+ if material.use_nodes == True:
+ for node in material.node_tree.nodes:
+ if node.type == 'TEX_IMAGE' and node.name.startswith('3DC'):
+ img_list.append(node.image)
+ if img_list != []:
+ for del_img in img_list:
+ bpy.data.images.remove(del_img)
+
+ bpy.data.materials.remove(material)
+
+ coat3D = bpy.context.scene.coat3D
+ coat = bpy.coat3D
+ coat3D.exchangedir = set_exchange_folder()
+
+
+ texturelist = make_texture_list(coat3D.exchangedir)
+ for texturepath in texturelist:
+ for image in bpy.data.images:
+ if(image.filepath == texturepath[3] and image.users == 0):
+ bpy.data.images.remove(image)
+
+
+ kokeilu = coat3D.exchangedir[:-10]
+ Blender_folder = ("%s%sExchange%sBlender"%(kokeilu,os.sep,os.sep))
+ Blender_export = Blender_folder
+ path3b_now = coat3D.exchangedir
+ path3b_now += ('last_saved_3b_file.txt')
+ Blender_export += ('%sexport.txt'%(os.sep))
+ new_applink_address = 'False'
+ new_object = False
+
+ if(os.path.isfile(Blender_export)):
+ obj_pathh = open(Blender_export)
+ new_object = True
+ for line in obj_pathh:
+ new_applink_address = line
+ break
+ obj_pathh.close()
+
+ for scene_objects in bpy.context.collection.all_objects:
+ if(scene_objects.type == 'MESH'):
+ if(scene_objects.coat3D.applink_address == new_applink_address):
+ new_object = False
+
+ exportfile = coat3D.exchangedir
+ exportfile += ('%sBlender' % (os.sep))
+ exportfile += ('%sexport.txt' % (os.sep))
+ if (os.path.isfile(exportfile)):
+ os.remove(exportfile)
+ if(new_object == False):
+
+ '''
+ #Blender -> 3DC -> Blender workflow
+ #First check if objects needs to be imported, if imported it will then delete extra mat and objs.
+ '''
+
+ old_materials = bpy.data.materials.keys()
+ old_objects = bpy.data.objects.keys()
+ cache_base = bpy.data.objects.keys()
+
+ object_list = []
+ import_list = []
+ import_type = []
+
+ for objekti in bpy.data.objects:
+ if objekti.type == 'MESH':
+ obj_coat = objekti.coat3D
+ if(obj_coat.applink_mesh == True):
+ object_list.append(objekti.name)
+
+ if(os.path.isfile(obj_coat.applink_address)):
+ if (obj_coat.objecttime != str(os.path.getmtime(obj_coat.applink_address))):
+ obj_coat.dime = objekti.dimensions
+ obj_coat.import_mesh = True
+ obj_coat.objecttime = str(os.path.getmtime(obj_coat.applink_address))
+ if(obj_coat.applink_address not in import_list):
+ import_list.append(obj_coat.applink_address)
+ import_type.append(coat3D.type)
+
+ if(import_list or coat3D.importmesh):
+ print('import_list:', import_list)
+ for idx, list in enumerate(import_list):
+ bpy.ops.import_scene.fbx(filepath=list, global_scale = 1,axis_forward='X',use_custom_normals=False)
+ cache_objects = bpy.data.objects.keys()
+ cache_objects = [i for i in cache_objects if i not in cache_base]
+ for cache_object in cache_objects:
+ bpy.data.objects[cache_object].coat3D.type = import_type[idx]
+ bpy.data.objects[cache_object].coat3D.applink_address = list
+ cache_base.append(cache_object)
+
+
+ bpy.ops.object.select_all(action='DESELECT')
+
+ new_materials = bpy.data.materials.keys()
+ new_objects = bpy.data.objects.keys()
+ new_images = bpy.data.images.keys()
+
+
+ diff_mat = [i for i in new_materials if i not in old_materials]
+ diff_objects = [i for i in new_objects if i not in old_objects]
+
+ for mark_mesh in diff_objects:
+ bpy.data.objects[mark_mesh].data.coat3D.name = '3DC'
+ for c_index in diff_mat:
+ bpy.data.materials.remove(bpy.data.materials[c_index])
+ '''The main Applink Object Loop'''
+
+ remove_path = True
+ for oname in object_list:
+ objekti = bpy.data.objects[oname]
+ if(objekti.coat3D.applink_mesh == True):
+ exportfile = coat3D.exchangedir
+ path3b_n = coat3D.exchangedir
+ path3b_n += ('%slast_saved_3b_file.txt' % (os.sep))
+ if(objekti.coat3D.import_mesh and coat3D.importmesh == True):
+ objekti.coat3D.import_mesh = False
+ objekti.select_set(True)
+
+ use_smooth = objekti.data.polygons[0].use_smooth
+
+ new_name = objekti.data.name
+ name_boxs = new_name.split('.')
+ found_obj = False
+
+ '''Changes objects mesh into proxy mesh'''
+ print('ONAME:',oname)
+ if(objekti.coat3D.type):
+ for proxy_objects in diff_objects:
+ print('tryis to found: ',proxy_objects)
+ if (proxy_objects.startswith(objekti.coat3D.applink_name + '.')):
+ obj_proxy = bpy.data.objects[proxy_objects]
+ obj_proxy.coat3D.delete_proxy_mesh = True
+ found_obj = True
+
+ mat_list = []
+ if (objekti.material_slots):
+ act_mat = objekti.active_material
+ for obj_mat in objekti.material_slots:
+ mat_list.append(obj_mat.material)
+
+ if(found_obj == True):
+ exportfile = coat3D.exchangedir
+ path3b_n = coat3D.exchangedir
+ path3b_n += ('%slast_saved_3b_file.txt' % (os.sep))
+ exportfile += ('%sBlender' % (os.sep))
+ exportfile += ('%sexport.txt'%(os.sep))
+ if(os.path.isfile(exportfile)):
+ export_file = open(exportfile)
+ for line in export_file:
+ if line.rfind('.3b'):
+ coat['active_coat'] = line
+ export_file.close()
+ os.remove(exportfile)
+ if(os.path.isfile(path3b_n)):
+ export_file = open(path3b_n)
+ for line in export_file:
+ objekti.coat3D.applink_3b_path = line
+ export_file.close()
+ coat3D.remove_path = True
+
+
+
+ bpy.ops.object.select_all(action='DESELECT')
+ obj_proxy.select_set(True)
+
+ bpy.ops.object.select_all(action='TOGGLE')
+
+ if objekti.coat3D.applink_firsttime == True and objekti.coat3D.type == 'vox':
+ objekti.select_set(True)
+
+ objekti.rotation_euler[0] = 1.5708
+ objekti.rotation_euler[2] = 1.5708
+ bpy.ops.object.transforms_to_deltas(mode='ROT')
+ objekti.scale = (0.01, 0.01, 0.01)
+ bpy.ops.object.transforms_to_deltas(mode='SCALE')
+ objekti.coat3D.applink_firsttime = False
+ objekti.select_set(False)
+
+ elif objekti.coat3D.applink_firsttime == True:
+ objekti.scale = (objekti.scale[0]/objekti.coat3D.applink_scale[0],objekti.scale[1]/objekti.coat3D.applink_scale[1],objekti.scale[2]/objekti.coat3D.applink_scale[2])
+ bpy.ops.object.transforms_to_deltas(mode='SCALE')
+ if(objekti.coat3D.applink_onlyone == False):
+ objekti.rotation_euler = (0,0,0)
+ objekti.scale = (0.01,0.01,0.01)
+ objekti.coat3D.applink_firsttime = False
+
+ if(coat3D.importlevel):
+ obj_proxy.select = True
+ obj_proxy.modifiers.new(name='temp',type='MULTIRES')
+ objekti.select = True
+ bpy.ops.object.multires_reshape(modifier=multires_name)
+ bpy.ops.object.select_all(action='TOGGLE')
+ multires_on = False
+ else:
+ updatemesh(objekti,obj_proxy)
+
+ #tärkee että saadaan oikein käännettyä objekt
+
+ objekti.select_set(True)
+ bpy.ops.object.origin_set(type='GEOMETRY_ORIGIN')
+
+ objekti.data.materials.pop()
+ for mat in mat_list:
+ objekti.data.materials.append(mat)
+
+ if (use_smooth):
+ for data_mesh in objekti.data.polygons:
+ data_mesh.use_smooth = True
+ else:
+ for data_mesh in objekti.data.polygons:
+ data_mesh.use_smooth = False
+
+ bpy.ops.object.select_all(action='DESELECT')
+
+ if(coat3D.importmesh and not(os.path.isfile(objekti.coat3D.applink_address))):
+ coat3D.importmesh = False
+
+ objekti.select_set(True)
+ if(coat3D.importtextures):
+ is_new = False
+ tex.matlab(objekti,mat_list,texturelist,is_new)
+ objekti.select_set(False)
+ else:
+ print('JAAKO TAHAN KOHTAAN 2')
+ mat_list = []
+ if (objekti.material_slots):
+ for obj_mat in objekti.material_slots:
+ mat_list.append(obj_mat.material)
+
+ if (coat3D.importtextures):
+ is_new = False
+ tex.matlab(objekti,mat_list,texturelist, is_new)
+ objekti.select_set(False)
+
+ if(coat3D.remove_path == True):
+ os.remove(path3b_n)
+ coat3D.remove_path = False
+
+ bpy.ops.object.select_all(action='DESELECT')
+ if(import_list):
+ for del_obj in diff_objects:
+ print('diff_objects', diff_objects)
+
+ if(bpy.context.collection.all_objects[del_obj].coat3D.type == 'vox' and bpy.context.collection.all_objects[del_obj].coat3D.delete_proxy_mesh == False):
+ bpy.context.collection.all_objects[del_obj].select_set(True)
+ objekti = bpy.context.collection.all_objects[del_obj]
+ objekti.rotation_euler[2] = 1.5708
+ bpy.ops.object.transforms_to_deltas(mode='ROT')
+ # objekti.rotation_euler = (0, 0, 0)
+ objekti.scale = (0.02, 0.02, 0.02)
+ bpy.ops.object.origin_set(type='ORIGIN_GEOMETRY')
+
+ objekti.data.coat3D.name = '3DC'
+
+ objekti.coat3D.objecttime = str(os.path.getmtime(objekti.coat3D.applink_address))
+ objekti.coat3D.applink_name = objekti.name
+ objekti.coat3D.applink_mesh = True
+ objekti.coat3D.import_mesh = False
+ bpy.ops.object.transforms_to_deltas(mode='SCALE')
+ objekti.coat3D.applink_firsttime = False
+ bpy.context.collection.all_objects[del_obj].select_set(False)
+
+ else:
+ bpy.context.collection.all_objects[del_obj].select_set(True)
+ bpy.ops.object.delete()
+
+ else:
+
+ '''
+ 3DC -> Blender workflow
+ '''
+
+ for old_obj in bpy.context.collection.objects:
+ old_obj.coat3D.applink_old = True
+
+ coat3D = bpy.context.scene.coat3D
+ scene = context.scene
+ Blender_folder = ("%s%sBlender"%(coat3D.exchangedir,os.sep))
+ Blender_export = Blender_folder
+ path3b_now = coat3D.exchangedir + os.sep
+ path3b_now += ('last_saved_3b_file.txt')
+ Blender_export += ('%sexport.txt'%(os.sep))
+ mat_list = []
+ osoite_3b = ''
+ if (os.path.isfile(path3b_now)):
+ path3b_fil = open(path3b_now)
+ for lin in path3b_fil:
+ osoite_3b = lin
+ path3b_fil.close()
+ head, tail = os.path.split(osoite_3b)
+ just_3b_name = tail
+ os.remove(path3b_now)
+
+ old_materials = bpy.data.materials.keys()
+ old_objects = bpy.data.objects.keys()
+
+ bpy.ops.import_scene.fbx(filepath=new_applink_address, global_scale = 0.001, use_manual_orientation=True, axis_forward='X', axis_up='Y')
+
+ new_materials = bpy.data.materials.keys()
+ new_objects = bpy.data.objects.keys()
+
+ diff_mat = [i for i in new_materials if i not in old_materials]
+ diff_objects = [i for i in new_objects if i not in old_objects]
+
+ for mark_mesh in diff_mat:
+ bpy.data.materials[mark_mesh].coat3D.name = '3DC'
+ bpy.data.materials[mark_mesh].use_fake_user = True
+ laskuri = 0
+ index = 0
+ for c_index in diff_objects:
+ bpy.data.objects[c_index].data.coat3D.name = '3DC'
+ bpy.data.objects[c_index].material_slots[0].material = bpy.data.materials[diff_mat[laskuri]]
+ laskuri += 1
+
+ bpy.ops.object.select_all(action='DESELECT')
+ for new_obj in bpy.context.collection.objects:
+
+ if(new_obj.coat3D.applink_old == False):
+ new_obj.select_set(True)
+ #bpy.ops.object.origin_set(type='GEOMETRY_ORIGIN')
+ #new_obj.rotation_euler = (0, 0, 0)
+ new_obj.scale = (0.03, 0.03, 0.03)
+ new_obj.coat3D.applink_firsttime = False
+ new_obj.select_set(False)
+ new_obj.coat3D.applink_address = new_applink_address
+ new_obj.coat3D.applink_mesh = True
+ new_obj.coat3D.objecttime = str(os.path.getmtime(new_obj.coat3D.applink_address))
+
+ new_obj.coat3D.applink_name = new_obj.material_slots[0].material.name
+ index = index + 1
+
+ new_obj.coat3D.applink_export = True
+ if(osoite_3b != ''):
+ new_obj.coat3D.applink_3b_path = osoite_3b
+ new_obj.coat3D.applink_3b_just_name = just_3b_name
+
+ mat_list.append(new_obj.material_slots[0].material)
+ is_new = True
+ tex.matlab(new_obj, mat_list, texturelist, is_new)
+ mat_list.pop()
+
+ for new_obj in bpy.context.collection.objects:
+ if(new_obj.coat3D.applink_old == False):
+ new_obj.coat3D.applink_old = True
+
+ bpy.ops.object.select_all(action='SELECT')
+ bpy.ops.object.origin_set(type='ORIGIN_GEOMETRY')
+ bpy.ops.object.select_all(action='DESELECT')
+
+ kokeilu = coat3D.exchangedir[:-10]
+ Blender_folder2 = ("%s%sExchange%sBlender" % (kokeilu, os.sep, os.sep))
+ Blender_folder2 += ('%sexport.txt' % (os.sep))
+
+ if (os.path.isfile(Blender_export)):
+ os.remove(Blender_export)
+ if (os.path.isfile(Blender_folder2)):
+ os.remove(Blender_folder2)
+ for material in bpy.data.materials:
+ if material.use_nodes == True:
+ for node in material.node_tree.nodes:
+ if (node.name).startswith('3DC'):
+ node.location = node.location
+
+ return {'FINISHED'}
+
+from bpy import *
+from mathutils import Vector, Matrix
+
+class SCENE_PT_Main(bpy.types.Panel):
+ bl_label = "3D-Coat Applink"
+ bl_space_type = "VIEW_3D"
+ bl_region_type = "UI"
+ bl_category = 'View'
+
+ @classmethod
+ def poll(cls, context):
+ if bpy.context.mode == 'OBJECT':
+ return True
+ else:
+ return False
+
+ def draw(self, context):
+ layout = self.layout
+ coat = bpy.coat3D
+ coat3D = bpy.context.scene.coat3D
+ if(bpy.context.active_object):
+ coa = bpy.context.active_object.coat3D
+ if(coat['status'] == 0):
+ row = layout.row()
+ row.label(text="Applink didn't find your 3d-Coat/Excahnge folder.")
+ row = layout.row()
+ row.label("Please select it before using Applink.")
+ row = layout.row()
+ row.prop(coat3D,"exchangedir",text="")
+ row = layout.row()
+ row.operator("update_exchange_folder.pilgway_3d_coat", text="Apply folder")
+
+ else:
+
+ #Here you add your GUI
+ row = layout.row()
+ row.prop(coat3D,"type",text = "")
+ flow = layout.grid_flow(row_major=True, columns=0, even_columns=False, even_rows=False, align=True)
+
+ col = flow.column()
+
+ col.operator("export_applink.pilgway_3d_coat", text="Transfer")
+ col = flow.column()
+ col.operator("import_applink.pilgway_3d_coat", text="Update")
+
+
+
+
+class ObjectButtonsPanel():
+ bl_space_type = 'PROPERTIES'
+ bl_region_type = 'WINDOW'
+ bl_context = "object"
+
+class SCENE_PT_Settings(ObjectButtonsPanel,bpy.types.Panel):
+ bl_label = "3D-Coat Applink Settings"
+ bl_space_type = "PROPERTIES"
+ bl_region_type = "WINDOW"
+ bl_context = "scene"
+
+ def draw(self, context):
+ pass
+
+class SCENE_PT_Settings_Update(ObjectButtonsPanel, bpy.types.Panel):
+ bl_label = "Update"
+ bl_parent_id = "SCENE_PT_Settings"
+ COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_OPENGL'}
+
+ def draw(self, context):
+ layout = self.layout
+ layout.use_property_split = False
+ coat3D = bpy.context.scene.coat3D
+
+ rd = context.scene.render
+
+ layout.active = True
+
+ flow = layout.grid_flow(row_major=True, columns=0, even_columns=False, even_rows=False, align=True)
+
+ col = flow.column()
+ col.prop(coat3D, "importmesh", text="Update Mesh/UV")
+ col = flow.column()
+ col.prop(coat3D, "createnodes", text="Create Extra Nodes")
+ col = flow.column()
+ col.prop(coat3D, "importtextures", text="Update Textures")
+ col = flow.column()
+ col.prop(coat3D, "creategroup", text="Group Nodes")
+ col = flow.column()
+ col.prop(coat3D, "exportmod", text="Export with modifiers")
+
+class SCENE_PT_Settings_Folders(ObjectButtonsPanel, bpy.types.Panel):
+ bl_label = "Folders"
+ bl_parent_id = "SCENE_PT_Settings"
+ COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_OPENGL'}
+
+ def draw(self, context):
+ layout = self.layout
+ layout.use_property_split = False
+ coat3D = bpy.context.scene.coat3D
+
+ rd = context.scene.render
+
+ layout.active = True
+
+ flow = layout.grid_flow(row_major=True, columns=0, even_columns=False, even_rows=False, align=True)
+
+ col = flow.column()
+ col.prop(coat3D, "exchangedir", text="Exchange folder")
+
+ col = flow.column()
+ col.prop(coat3D, "coat3D_exe", text="3D-Coat.exe")
+
+# 3D-Coat Dynamic Menu
+class VIEW3D_MT_Coat_Dynamic_Menu(bpy.types.Menu):
+ bl_label = "3D-Coat Applink Menu"
+
+ def draw(self, context):
+ layout = self.layout
+
+ layout.operator_context = 'INVOKE_REGION_WIN'
+
+ ob = context
+ if ob.mode == 'OBJECT':
+ if(len(context.selected_objects) > 0):
+ layout.operator("import_applink.pilgway_3d_coat", text="Update Scene")
+ layout.separator()
+
+ layout.operator("export_applink.pilgway_3d_coat", text="Copy selected object(s) into 3D-Coat")
+ layout.separator()
+ if(context.selected_objects[0].coat3D.applink_3b_path != ''):
+ layout.operator("open_3dcoat.pilgway_3d_coat", text="Open .3b file" +context.selected_objects[0].coat3D.applink_3b_just_name)
+ layout.separator()
+
+ else:
+ layout.operator("import_applink.pilgway_3d_coat", text="Update Scene")
+ layout.separator()
+
+
+
+class ObjectCoat3D(PropertyGroup):
+
+ obj_mat: StringProperty(
+ name="Object_Path",
+ default=''
+ )
+ applink_address: StringProperty(
+ name="Object_Applink_address"
+ )
+ applink_3b_path: StringProperty(
+ name="Object_3B_Path"
+ )
+ applink_name: StringProperty(
+ name="Applink object name"
+ )
+ applink_3b_just_name: StringProperty(
+ name="Applink object name"
+ )
+ applink_firsttime: BoolProperty(
+ name="FirstTime",
+ description="FirstTime",
+ default=True
+ )
+ delete_proxy_mesh: BoolProperty(
+ name="FirstTime",
+ description="FirstTime",
+ default=False
+ )
+ applink_onlyone: BoolProperty(
+ name="FirstTime",
+ description="FirstTime",
+ default=False
+ )
+ type: StringProperty(
+ name="type",
+ description="shows type",
+ default=''
+ )
+ import_mesh: BoolProperty(
+ name="ImportMesh",
+ description="ImportMesh",
+ default=False
+ )
+ applink_mesh: BoolProperty(
+ name="ImportMesh",
+ description="ImportMesh",
+ default=False
+ )
+ applink_old: BoolProperty(
+ name="OldObject",
+ description="Old Object",
+ default=False
+ )
+ applink_export: BoolProperty(
+ name="FirstTime",
+ description="Object is from 3d-ocat",
+ default=False
+ )
+ objecttime: StringProperty(
+ name="ObjectTime",
+ subtype="FILE_PATH"
+ )
+ path3b: StringProperty(
+ name="3B Path",
+ subtype="FILE_PATH"
+ )
+ dime: FloatVectorProperty(
+ name="dime",
+ description="Dimension"
+ )
+ applink_scale: FloatVectorProperty(
+ name="Scale",
+ description="Scale"
+ )
+class MaterialCoat3D(PropertyGroup):
+ Nodegroup: StringProperty(
+ name="NodeGroup",
+ )
+
+class SceneCoat3D(PropertyGroup):
+ defaultfolder: StringProperty(
+ name="FilePath",
+ subtype="DIR_PATH",
+ )
+ coat3D_exe: StringProperty(
+ name="FilePath",
+ subtype="FILE_PATH",
+ )
+ cursor_loc: FloatVectorProperty(
+ name="Cursor_loc",
+ description="location"
+ )
+ exchangedir: StringProperty(
+ name="FilePath",
+ subtype="DIR_PATH"
+ )
+ exchangefolder: StringProperty(
+ name="FilePath",
+ subtype="DIR_PATH"
+ )
+ wasactive: StringProperty(
+ name="Pass active object",
+ )
+ import_box: BoolProperty(
+ name="Import window",
+ description="Allows to skip import dialog",
+ default=True
+ )
+ remove_path: BoolProperty(
+ name="Import window",
+ description="Allows to skip import dialog",
+ default=False
+ )
+ exchange_found: BoolProperty(
+ name="Exchange Found",
+ description="Alert if Exchange folder is not found",
+ default=True
+ )
+ export_box: BoolProperty(
+ name="Export window",
+ description="Allows to skip export dialog",
+ default=True
+ )
+ export_color: BoolProperty(
+ name="Export color",
+ description="Export color texture",
+ default=True
+ )
+ export_spec: BoolProperty(
+ name="Export specular",
+ description="Export specular texture",
+ default=True
+ )
+ export_normal: BoolProperty(
+ name="Export Normal",
+ description="Export normal texture",
+ default=True
+ )
+ export_disp: BoolProperty(
+ name="Export Displacement",
+ description="Export displacement texture",
+ default=True
+ )
+ export_position: BoolProperty(
+ name="Export Source Position",
+ description="Export source position",
+ default=True
+ )
+ export_zero_layer: BoolProperty(
+ name="Export from Layer 0",
+ description="Export mesh from Layer 0",
+ default=True
+ )
+ export_coarse: BoolProperty(
+ name="Export Coarse",
+ description="Export Coarse",
+ default=True
+ )
+ exportfile: BoolProperty(
+ name="No Import File",
+ description="Add Modifiers and export",
+ default=False
+ )
+ importmod: BoolProperty(
+ name="Remove Modifiers",
+ description="Import and add modifiers",
+ default=False
+ )
+ exportmod: BoolProperty(
+ name="Modifiers",
+ description="Export modifiers",
+ default=False
+ )
+ export_pos: BoolProperty(
+ name="Remember Position",
+ description="Remember position",
+ default=True
+ )
+ importtextures: BoolProperty(
+ name="Bring Textures",
+ description="Import Textures",
+ default=True
+ )
+ createnodes: BoolProperty(
+ name="Bring Textures",
+ description="Import Textures",
+ default=True
+ )
+ creategroup: BoolProperty(
+ name="Bring Textures",
+ description="Import Textures",
+ default=True
+ )
+ importlevel: BoolProperty(
+ name="Multires. Level",
+ description="Bring Specific Multires Level",
+ default=False
+ )
+ exportover: BoolProperty(
+ name="Export Obj",
+ description="Import Textures",
+ default=False
+ )
+ importmesh: BoolProperty(
+ name="Mesh",
+ description="Import Mesh",
+ default=False
+ )
+
+ # copy location
+
+ loca: FloatVectorProperty(
+ name="location",
+ description="Location",
+ subtype="XYZ",
+ default=(0.0, 0.0, 0.0)
+ )
+ rota: FloatVectorProperty(
+ name="location",
+ description="Location",
+ subtype="EULER",
+ default=(0.0, 0.0, 0.0)
+ )
+ scal: FloatVectorProperty(
+ name="location",
+ description="Location",
+ subtype="XYZ",
+ default=(0.0, 0.0, 0.0)
+ )
+ dime: FloatVectorProperty(
+ name="dimension",
+ description="Dimension",
+ subtype="XYZ",
+ default=(0.0, 0.0, 0.0)
+ )
+ type: EnumProperty(
+ name="Export Type",
+ description="Different Export Types",
+ items=(("ppp", "Per-Pixel Painting", ""),
+ ("mv", "Microvertex Painting", ""),
+ ("ptex", "Ptex Painting", ""),
+ ("uv", "UV-Mapping", ""),
+ ("ref", "Reference Mesh", ""),
+ ("retopo", "Retopo mesh as new layer", ""),
+ ("vox", "Mesh As Voxel Object", ""),
+ ("alpha", "Mesh As New Pen Alpha", ""),
+ ("prim", "Mesh As Voxel Primitive", ""),
+ ("curv", "Mesh As a Curve Profile", ""),
+ ("autopo", "Mesh For Auto-retopology", ""),
+ ),
+ default="ppp"
+ )
+class MeshCoat3D(PropertyGroup):
+ applink_address: StringProperty(
+ name="ApplinkAddress",
+ subtype="APPLINK_ADDRESS",
+ )
+class MaterialCoat3D(PropertyGroup):
+ name: StringProperty(
+ name="ApplinkAddress",
+ subtype="APPLINK_ADDRESS",
+ )
+
+classes = (
+ #ObjectButtonsPanel,
+ SCENE_PT_Main,
+ SCENE_PT_Settings,
+ SCENE_PT_Settings_Update,
+ SCENE_PT_Settings_Folders,
+ SCENE_OT_folder,
+ SCENE_OT_opencoat,
+ SCENE_OT_export,
+ SCENE_OT_import,
+ VIEW3D_MT_Coat_Dynamic_Menu,
+ ObjectCoat3D,
+ SceneCoat3D,
+ MeshCoat3D,
+ MaterialCoat3D,
+ )
+
def register():
bpy.coat3D = dict()
bpy.coat3D['active_coat'] = ''
- bpy.coat3D['status'] = 0
+ bpy.coat3D['status'] = 1
bpy.coat3D['kuva'] = 1
- class ObjectCoat3D(PropertyGroup):
- objpath = StringProperty(
- name="Object_Path"
- )
- applink_name = StringProperty(
- name="Object_Applink_name"
- )
- coatpath = StringProperty(
- name="Coat_Path"
- )
- objectdir = StringProperty(
- name="ObjectPath",
- subtype="FILE_PATH"
- )
- objecttime = StringProperty(
- name="ObjectTime",
- subtype="FILE_PATH"
- )
- texturefolder = StringProperty(
- name="Texture folder:",
- subtype="DIR_PATH"
- )
- path3b = StringProperty(
- name="3B Path",
- subtype="FILE_PATH"
- )
- export_on = BoolProperty(
- name="Export_On",
- description="Add Modifiers and export",
- default=False
- )
- dime = FloatVectorProperty(
- name="dime",
- description="Dimension"
- )
- loc = FloatVectorProperty(
- name="Location",
- description="Location"
- )
- rot = FloatVectorProperty(
- name="Rotation",
- description="Rotation",
- subtype='EULER'
- )
- sca = FloatVectorProperty(
- name="Scale",
- description="Scale"
- )
-
- class SceneCoat3D(PropertyGroup):
- defaultfolder = StringProperty(
- name="FilePath",
- subtype="DIR_PATH",
- )
- cursor_loc = FloatVectorProperty(
- name="Cursor_loc",
- description="location"
- )
- exchangedir = StringProperty(
- name="FilePath",
- subtype="DIR_PATH"
- )
- exchangefolder = StringProperty(
- name="FilePath",
- subtype="DIR_PATH"
- )
- wasactive = StringProperty(
- name="Pass active object",
- )
- import_box = BoolProperty(
- name="Import window",
- description="Allows to skip import dialog",
- default=True
- )
- exchange_found = BoolProperty(
- name="Exchange Found",
- description="Alert if Exchange folder is not found",
- default=True
- )
- export_box = BoolProperty(
- name="Export window",
- description="Allows to skip export dialog",
- default=True
- )
- export_color = BoolProperty(
- name="Export color",
- description="Export color texture",
- default=True
- )
- export_spec = BoolProperty(
- name="Export specular",
- description="Export specular texture",
- default=True
- )
- export_normal = BoolProperty(
- name="Export Normal",
- description="Export normal texture",
- default=True
- )
- export_disp = BoolProperty(
- name="Export Displacement",
- description="Export displacement texture",
- default=True
- )
- export_position = BoolProperty(
- name="Export Source Position",
- description="Export source position",
- default=True
- )
- export_zero_layer = BoolProperty(
- name="Export from Layer 0",
- description="Export mesh from Layer 0",
- default=True
- )
- export_coarse = BoolProperty(
- name="Export Coarse",
- description="Export Coarse",
- default=True
- )
- exportfile = BoolProperty(
- name="No Import File",
- description="Add Modifiers and export",
- default=False
- )
- importmod = BoolProperty(
- name="Remove Modifiers",
- description="Import and add modifiers",
- default=False
- )
- exportmod = BoolProperty(
- name="Modifiers",
- description="Export modifiers",
- default=False
- )
- export_pos = BoolProperty(
- name="Remember Position",
- description="Remember position",
- default=True
- )
- importtextures = BoolProperty(
- name="Bring Textures",
- description="Import Textures",
- default=True
- )
- importlevel = BoolProperty(
- name="Multires. Level",
- description="Bring Specific Multires Level",
- default=False
- )
- exportover = BoolProperty(
- name="Export Obj",
- description="Import Textures",
- default=False
- )
- importmesh = BoolProperty(
- name="Mesh",
- description="Import Mesh",
- default=True
- )
-
- # copy location
- cursor = FloatVectorProperty(
- name="Cursor",
- description="Location",
- subtype="XYZ",
- default=(0.0, 0.0, 0.0)
- )
- loca = FloatVectorProperty(
- name="location",
- description="Location",
- subtype="XYZ",
- default=(0.0, 0.0, 0.0)
- )
- rota = FloatVectorProperty(
- name="location",
- description="Location",
- subtype="EULER",
- default=(0.0, 0.0, 0.0)
- )
- scal = FloatVectorProperty(
- name="location",
- description="Location",
- subtype="XYZ",
- default=(0.0, 0.0, 0.0)
- )
- dime = FloatVectorProperty(
- name="dimension",
- description="Dimension",
- subtype="XYZ",
- default=(0.0, 0.0, 0.0)
- )
- type = EnumProperty(
- name="Export Type",
- description="Different Export Types",
- items=(("ppp", "Per-Pixel Painting", ""),
- ("mv", "Microvertex Painting", ""),
- ("ptex", "Ptex Painting", ""),
- ("uv", "UV-Mapping", ""),
- ("ref", "Reference Mesh", ""),
- ("retopo", "Retopo mesh as new layer", ""),
- ("vox", "Mesh As Voxel Object", ""),
- ("alpha", "Mesh As New Pen Alpha", ""),
- ("prim", "Mesh As Voxel Primitive", ""),
- ("curv", "Mesh As a Curve Profile", ""),
- ("autopo", "Mesh For Auto-retopology", ""),
- ),
- default="ppp"
- )
-
- bpy.utils.register_module(__name__)
-
- bpy.types.Object.coat3D = PointerProperty(
- name="Applink Variables",
- type=ObjectCoat3D,
- description="Applink variables"
- )
- bpy.types.Scene.coat3D = PointerProperty(
- name="Applink Variables",
- type=SceneCoat3D,
- description="Applink variables"
- )
+ from bpy.utils import register_class
+ for cls in classes:
+ register_class(cls)
+
+ bpy.types.Object.coat3D = PointerProperty(type=ObjectCoat3D)
+ bpy.types.Scene.coat3D = PointerProperty(type=SceneCoat3D)
+ bpy.types.Mesh.coat3D = PointerProperty(type=MeshCoat3D)
+ bpy.types.Material.coat3D = PointerProperty(type=MaterialCoat3D)
+ kc = bpy.context.window_manager.keyconfigs.addon
+
+ if kc:
+ km = kc.keymaps.new(name="Object Mode")
+ kmi = km.keymap_items.new('wm.call_menu', 'Q', 'PRESS', shift=True)
+ kmi.properties.name = "VIEW3D_MT_Coat_Dynamic_Menu"
def unregister():
+
import bpy
+ from bpy.utils import unregister_class
del bpy.types.Object.coat3D
del bpy.types.Scene.coat3D
+ del bpy.types.Mesh.coat3D
del bpy.coat3D
- bpy.utils.unregister_module(__name__)
-
+ kc = bpy.context.window_manager.keyconfigs.addon
+ if kc:
+ km = kc.keymaps.get('Object Mode')
+ for kmi in km.keymap_items:
+ if kmi.idname == 'wm.call_menu':
+ if kmi.properties.name == "VIEW3D_MT_Coat_Dynamic_Menu":
+ km.keymap_items.remove(kmi)
-if __name__ == "__main__":
- register()
+ for cls in reversed(classes):
+ unregister_class(cls)
diff --git a/io_coat3D/coat.py b/io_coat3D/coat.py
deleted file mode 100644
index fc3cb747..00000000
--- a/io_coat3D/coat.py
+++ /dev/null
@@ -1,626 +0,0 @@
-# ***** BEGIN GPL LICENSE BLOCK *****
-#
-#
-# This program is free software; you can redistribute it and/or
-# modify it under the terms of the GNU General Public License
-# as published by the Free Software Foundation; either version 2
-# of the License, or (at your option) any later version.
-#
-# This program 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 General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software Foundation,
-# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-#
-# ***** END GPL LICENCE BLOCK *****
-
-import bpy
-from bpy.props import *
-from io_coat3D import tex
-import os
-
-
-bpy.coat3D = dict()
-bpy.coat3D['active_coat'] = ''
-bpy.coat3D['status'] = 0
-def set_exchange_folder():
- platform = os.sys.platform
- coat3D = bpy.context.scene.coat3D
- Blender_export = ""
-
- if(platform == 'win32'):
- exchange = os.path.expanduser("~") + os.sep + 'Documents' + os.sep + '3D-CoatV4' + os.sep +'Exchange'
- if not(os.path.isdir(exchange)):
- exchange = os.path.expanduser("~") + os.sep + 'Documents' + os.sep + '3D-CoatV3' + os.sep +'Exchange'
- else:
- exchange = os.path.expanduser("~") + os.sep + '3D-CoatV4' + os.sep + 'Exchange'
- if not(os.path.isdir(exchange)):
- exchange = os.path.expanduser("~") + os.sep + '3D-CoatV3' + os.sep + 'Exchange'
- if(not(os.path.isdir(exchange))):
- exchange = coat3D.exchangedir
-
- if(os.path.isdir(exchange)):
- bpy.coat3D['status'] = 1
- if(platform == 'win32'):
- exchange_path = os.path.expanduser("~") + os.sep + 'Documents' + os.sep + '3DC2Blender' + os.sep + 'Exchange_folder.txt'
- applink_folder = os.path.expanduser("~") + os.sep + 'Documents' + os.sep + '3DC2Blender'
- if(not(os.path.isdir(applink_folder))):
- os.makedirs(applink_folder)
- else:
- exchange_path = os.path.expanduser("~") + os.sep + 'Documents' + os.sep + '3DC2Blender' + os.sep + 'Exchange_folder.txt'
- applink_folder = os.path.expanduser("~") + os.sep + 'Documents' + os.sep + '3DC2Blender'
- if(not(os.path.isdir(applink_folder))):
- os.makedirs(applink_folder)
- file = open(exchange_path, "w")
- file.write("%s"%(coat3D.exchangedir))
- file.close()
-
- else:
- if(platform == 'win32'):
- exchange_path = os.path.expanduser("~") + os.sep + 'Documents' + os.sep + '3DC2Blender' + os.sep + 'Exchange_folder.txt'
- else:
- exchange_path = os.path.expanduser("~") + os.sep + '3DC2Blender' + os.sep + 'Exchange_folder.txt'
- if(os.path.isfile(exchange_path)):
- ex_path =''
-
- ex_pathh = open(exchange_path)
- for line in ex_pathh:
- ex_path = line
- break
- ex_pathh.close()
-
- if(os.path.isdir(ex_path) and ex_path.rfind('Exchange') >= 0):
- exchange = ex_path
- bpy.coat3D['status'] = 1
- else:
- bpy.coat3D['status'] = 0
- else:
- bpy.coat3D['status'] = 0
- if(bpy.coat3D['status'] == 1):
- Blender_folder = ("%s%sBlender"%(exchange,os.sep))
- Blender_export = Blender_folder
- path3b_now = exchange
- path3b_now += ('last_saved_3b_file.txt')
- Blender_export += ('%sexport.txt'%(os.sep))
-
- if(not(os.path.isdir(Blender_folder))):
- os.makedirs(Blender_folder)
- Blender_folder = os.path.join(Blender_folder,"run.txt")
- file = open(Blender_folder, "w")
- file.close()
- return exchange
-
-def set_working_folders():
- platform = os.sys.platform
- coat3D = bpy.context.scene.coat3D
- if(platform == 'win32'):
- folder_objects = os.path.expanduser("~") + os.sep + 'Documents' + os.sep + '3DC2Blender' + os.sep + 'Objects'
- folder_textures = os.path.expanduser("~") + os.sep + 'Documents' + os.sep + '3DC2Blender' + os.sep + 'Textures' + os.sep
- if(not(os.path.isdir(folder_objects))):
- os.makedirs(folder_objects)
- if(not(os.path.isdir(folder_textures))):
- os.makedirs(folder_textures)
- else:
- folder_objects = os.path.expanduser("~") + os.sep + '3DC2Blender' + os.sep + 'Objects'
- folder_textures = os.path.expanduser("~") + os.sep + '3DC2Blender' + os.sep + 'Textures' + os.sep
- if(not(os.path.isdir(folder_objects))):
- os.makedirs(folder_objects)
- if(not(os.path.isdir(folder_textures))):
- os.makedirs(folder_textures)
-
-
- return folder_objects,folder_textures
-
-class ObjectButtonsPanel():
- bl_space_type = 'PROPERTIES'
- bl_region_type = 'WINDOW'
- bl_context = "object"
-
-class SCENE_PT_Main(ObjectButtonsPanel,bpy.types.Panel):
- bl_label = "3D-Coat Applink"
- bl_space_type = "PROPERTIES"
- bl_region_type = "WINDOW"
- bl_context = "scene"
-
- def draw(self, context):
- layout = self.layout
- scene = context.scene
- me = context.scene.objects
- mat_list = []
- import_no = 0
- coat = bpy.coat3D
- coat3D = bpy.context.scene.coat3D
- if(bpy.context.scene.objects.active):
- coa = bpy.context.scene.objects.active.coat3D
-
-
- if(bpy.coat3D['status'] == 0 and not(os.path.isdir(coat3D.exchangedir))):
- bpy.coat3D['active_coat'] = set_exchange_folder()
- row = layout.row()
- row.label(text="Applink didn't find your 3d-Coat/Excahnge folder.")
- row = layout.row()
- row.label("Please select it before using Applink.")
- row = layout.row()
- row.prop(coat3D,"exchangedir",text="")
-
- else:
-
-
- #Here you add your GUI
- row = layout.row()
- row.prop(coat3D,"type",text = "")
- row = layout.row()
- colL = row.column()
- colR = row.column()
-
- colR.operator("export_applink.pilgway_3d_coat", text="Transfer")
-
- colL.operator("import_applink.pilgway_3d_coat", text="Update")
-
-
-
-
-
-
-
-
-
-
-class SCENE_OT_export(bpy.types.Operator):
- bl_idname = "export_applink.pilgway_3d_coat"
- bl_label = "Export your custom property"
- bl_description = "Export your custom property"
- bl_options = {'UNDO'}
-
- def invoke(self, context, event):
- checkname = ''
- coat3D = bpy.context.scene.coat3D
- scene = context.scene
- activeobj = bpy.context.active_object.name
- obj = scene.objects[activeobj]
- coa = bpy.context.scene.objects.active.coat3D
- coat3D.exchangedir = set_exchange_folder()
- export_ok = False
-
- folder_objects,folder_textures = set_working_folders()
-
- if(coat3D.exchange_found == False):
- return {'FINISHED'}
-
- if(bpy.context.selected_objects == []):
- return {'FINISHED'}
- else:
- for objec in bpy.context.selected_objects:
- if objec.type == 'MESH':
- export_ok = True
- if(export_ok == False):
- return {'FINISHED'}
-
- importfile = coat3D.exchangedir
- texturefile = coat3D.exchangedir
- importfile += ('%simport.txt'%(os.sep))
- texturefile += ('%stextures.txt'%(os.sep))
-
- looking = True
- object_index = 0
- if(coa.applink_name and os.path.isfile(coa.applink_name)):
- checkname = coa.applink_name
-
- else:
- while(looking == True):
- checkname = folder_objects + os.sep + activeobj
- checkname = ("%s%.2d.obj"%(checkname,object_index))
- if(os.path.isfile(checkname)):
- object_index += 1
- else:
- looking = False
- coa.applink_name = checkname
-
-
- coat3D.cursor_loc = obj.location
- coat3D.cursor_orginal = bpy.context.scene.cursor_location
-
-
-
- coa.loc = obj.location
- coa.rot = obj.rotation_euler
- coa.sca = obj.scale
- coa.dime = obj.dimensions
-
- obj.location = (0,0,0)
- obj.rotation_euler = (0,0,0)
- bpy.ops.object.transform_apply(scale=True)
-
- bpy.ops.export_scene.obj(filepath=coa.applink_name,use_selection=True,
- use_mesh_modifiers=False,use_blen_objects=True, use_materials = True,
- keep_vertex_order = True,axis_forward='-Z',axis_up='Y')
-
- obj.location = coa.loc
- obj.rotation_euler = coa.rot
-
-
- bpy.context.scene.cursor_location = coat3D.cursor_loc
- bpy.context.scene.cursor_location = coat3D.cursor_orginal
-
- file = open(importfile, "w")
- file.write("%s"%(checkname))
- file.write("\n%s"%(checkname))
- file.write("\n[%s]"%(coat3D.type))
- file.write("\n[TexOutput:%s]"%(folder_textures))
- file.close()
-
- coa.objecttime = str(os.path.getmtime(coa.applink_name))
-
-
-
- return {'FINISHED'}
-
-class SCENE_OT_import(bpy.types.Operator):
- bl_idname = "import_applink.pilgway_3d_coat"
- bl_label = "import your custom property"
- bl_description = "import your custom property"
- bl_options = {'UNDO'}
-
- def invoke(self, context, event):
- scene = context.scene
- coat3D = bpy.context.scene.coat3D
- coat = bpy.coat3D
- test = bpy.context.selected_objects
- act_first = bpy.context.scene.objects.active
- bpy.context.scene.game_settings.material_mode = 'GLSL'
- coat3D.exchangedir = set_exchange_folder()
-
- folder_objects,folder_textures = set_working_folders()
-
- Blender_folder = ("%s%sBlender"%(coat3D.exchangedir,os.sep))
- Blender_export = Blender_folder
- path3b_now = coat3D.exchangedir
- path3b_now += ('last_saved_3b_file.txt')
- Blender_export += ('%sexport.txt'%(os.sep))
- new_applink_name = 'False'
- new_object = False
- if(os.path.isfile(Blender_export)):
- obj_pathh = open(Blender_export)
- new_object = True
- for line in obj_pathh:
- new_applink_name = line
- break
- obj_pathh.close()
-
- for scene_objects in bpy.context.scene.objects:
- if(scene_objects.type == 'MESH'):
- if(scene_objects.coat3D.applink_name == new_applink_name):
- new_object = False
-
- for act_name in test:
- coa = act_name.coat3D
- path_object = coa.applink_name
- if act_name.type == 'MESH' and os.path.isfile(path_object):
- multires_on = False
- activeobj = act_name.name
- mat_list = []
- scene.objects[activeobj].select = True
- objekti = scene.objects[activeobj]
- coat3D.loca = objekti.location
- coat3D.rota = objekti.rotation_euler
- coat3D.dime = objekti.scale
-
-
-
- #See if there is multres modifier.
- for modifiers in objekti.modifiers:
- if modifiers.type == 'MULTIRES' and (modifiers.total_levels > 0):
- if(not(coat3D.importlevel)):
- bpy.ops.object.multires_external_pack()
- multires = coat3D.exchangedir
- multires += ('%stemp.btx'%(os.sep))
- bpy.ops.object.multires_external_save(filepath=multires)
- #bpy.ops.object.multires_external_pack()
- multires_on = True
- multires_name = modifiers.name
- break
-
- exportfile = coat3D.exchangedir
- path3b_n = coat3D.exchangedir
- path3b_n += ('last_saved_3b_file.txt')
- exportfile += ('%sexport.txt'%(os.sep))
- if(os.path.isfile(exportfile)):
- export_file = open(exportfile)
- for line in export_file:
- if line.rfind('.3b'):
- objekti.coat3D.coatpath = line
- coat['active_coat'] = line
- export_file.close()
- os.remove(exportfile)
-
- if(len(objekti.material_slots) == 0):
- delete_material = False
- else:
- delete_material = True
-
-
- if(not(objekti.active_material) and objekti.material_slots):
- act_mat_index = objekti.active_material_index
- materials_old = bpy.data.materials.keys()
- bpy.ops.material.new()
- materials_new = bpy.data.materials.keys()
- new_ma = list(set(materials_new).difference(set(materials_old)))
- new_mat = new_ma[0]
- ki = bpy.data.materials[new_mat]
- objekti.material_slots[act_mat_index].material = ki
-
-
-
- if(os.path.isfile(path_object) and (coa.objecttime != str(os.path.getmtime(path_object)))):
-
- if(objekti.material_slots):
- act_mat_index = objekti.active_material_index
- for obj_mat in objekti.material_slots:
- mat_list.append(obj_mat.material)
-
- coa.dime = objekti.dimensions
- coa.objecttime = str(os.path.getmtime(path_object))
- mtl = coa.applink_name
- mtl = mtl.replace('.obj','.mtl')
- if(os.path.isfile(mtl)):
- os.remove(mtl)
-
- bpy.ops.import_scene.obj(filepath=path_object,axis_forward='-Z',axis_up='Y',use_image_search=False)
- obj_proxy = scene.objects[0]
- bpy.ops.object.select_all(action='TOGGLE')
- obj_proxy.select = True
-
-
- bpy.ops.object.transform_apply(rotation=True)
- proxy_mat = obj_proxy.material_slots[0].material
- if(delete_material):
- while(list(obj_proxy.data.materials) != []):
- proxy_mat = obj_proxy.material_slots[0].material
- obj_proxy.data.materials.pop(0,1)
- proxy_mat.user_clear()
- bpy.data.materials.remove(proxy_mat)
- bpy.ops.object.select_all(action='TOGGLE')
-
- if(coat3D.importlevel):
- obj_proxy.select = True
- obj_proxy.modifiers.new(name='temp',type='MULTIRES')
- objekti.select = True
- bpy.ops.object.multires_reshape(modifier=multires_name)
- bpy.ops.object.select_all(action='TOGGLE')
- multires_on = False
- else:
-
- scene.objects.active = obj_proxy
-
- obj_data = objekti.data.id_data
- objekti.data = obj_proxy.data.id_data
- if(bpy.data.meshes[obj_data.name].users == 0):
- objekti.data.id_data.name = obj_data.name
- bpy.data.meshes.remove(obj_data)
-
-
- obj_proxy.select = True
- bpy.ops.object.delete()
- objekti.select = True
- objekti.scale = coat3D.dime
-
- bpy.context.scene.objects.active = objekti
-
- if(os.path.isfile(path3b_n)):
- path3b_fil = open(path3b_n)
- for lin in path3b_fil:
- objekti.coat3D.path3b = lin
- path3b_fil.close()
- os.remove(path3b_n)
-
- if(coat3D.importmesh and not(os.path.isfile(path_object))):
- coat3D.importmesh = False
-
- if(mat_list and coat3D.importmesh):
- for mat_one in mat_list:
- objekti.data.materials.append(mat_one)
- objekti.active_material_index = act_mat_index
-
- if(mat_list):
- for obj_mate in objekti.material_slots:
- if(hasattr(obj_mate.material,'texture_slots')):
- for tex_slot in obj_mate.material.texture_slots:
- if(hasattr(tex_slot,'texture')):
- if(tex_slot.texture.type == 'IMAGE'):
- if tex_slot.texture.image is not None:
- tex_slot.texture.image.reload()
-
-
- if(coat3D.importtextures):
- export = ''
- tex.gettex(mat_list,objekti,scene,export)
-
- if(multires_on):
- temp_file = coat3D.exchangedir
- temp_file += ('%stemp2.btx'%(os.sep))
- if(objekti.modifiers[multires_name].levels == 0):
- objekti.modifiers[multires_name].levels = 1
- bpy.ops.object.multires_external_save(filepath=temp_file)
- objekti.modifiers[multires_name].filepath = multires
- objekti.modifiers[multires_name].levels = 0
-
- else:
- bpy.ops.object.multires_external_save(filepath=temp_file)
- objekti.modifiers[multires_name].filepath = multires
- #bpy.ops.object.multires_external_pack()
- bpy.ops.object.shade_smooth()
-
-
- for act_name in test:
- act_name.select = True
- bpy.context.scene.objects.active = act_first
-
- if(new_object == True):
- coat3D = bpy.context.scene.coat3D
- scene = context.scene
-
- Blender_folder = ("%s%sBlender"%(coat3D.exchangedir,os.sep))
- Blender_export = Blender_folder
- path3b_now = coat3D.exchangedir
- path3b_now += ('last_saved_3b_file.txt')
- Blender_export += ('%sexport.txt'%(os.sep))
-
-
- mat_list = []
- obj_path =''
-
-
- export = new_applink_name
- mod_time = os.path.getmtime(new_applink_name)
- mtl_list = new_applink_name.replace('.obj','.mtl')
- if(os.path.isfile(mtl_list)):
- os.remove(mtl_list)
-
- bpy.ops.import_scene.obj(filepath=new_applink_name,axis_forward='-Z',axis_up='Y')
- bpy.ops.object.transform_apply(rotation=True)
- new_obj = scene.objects[0]
- new_obj.coat3D.applink_name = obj_path
- scene.objects[0].coat3D.applink_name = export #objectdir muutettava
-
- os.remove(Blender_export)
-
- bpy.context.scene.objects.active = new_obj
-
- bpy.ops.object.shade_smooth()
-
- Blender_tex = ("%s%stextures.txt"%(coat3D.exchangedir,os.sep))
- mat_list.append(new_obj.material_slots[0].material)
- tex.gettex(mat_list, new_obj, scene,export)
-
- return {'FINISHED'}
-
-
-
-from bpy import *
-from mathutils import Vector, Matrix
-
-# 3D-Coat Dynamic Menu
-class VIEW3D_MT_Coat_Dynamic_Menu(bpy.types.Menu):
- bl_label = "3D-Coat Applink Menu"
-
- def draw(self, context):
- layout = self.layout
- settings = context.tool_settings
- layout.operator_context = 'INVOKE_REGION_WIN'
- coat3D = bpy.context.scene.coat3D
- Blender_folder = ("%s%sBlender"%(coat3D.exchangedir,os.sep))
- Blender_export = Blender_folder
- Blender_export += ('%sexport.txt'%(os.sep))
-
- ob = context
- if ob.mode == 'OBJECT':
- if(bpy.context.selected_objects):
- for ind_obj in bpy.context.selected_objects:
- if(ind_obj.type == 'MESH'):
- layout.active = True
- break
- layout.active = False
-
- if(layout.active == True):
-
- layout.operator("import_applink.pilgway_3d_coat", text="Import")
- layout.separator()
-
- layout.operator("export_applink.pilgway_3d_coat", text="Export")
- layout.separator()
-
- layout.menu("VIEW3D_MT_ImportMenu")
- layout.separator()
-
- layout.menu("VIEW3D_MT_ExportMenu")
- layout.separator()
-
- layout.menu("VIEW3D_MT_ExtraMenu")
- layout.separator()
-
- if(len(bpy.context.selected_objects) == 1):
- if(os.path.isfile(bpy.context.selected_objects[0].coat3D.path3b)):
- layout.operator("import_applink.pilgway_3d_coat_3b", text="Load 3b")
- layout.separator()
-
- if(os.path.isfile(Blender_export)):
-
- layout.operator("import3b_applink.pilgway_3d_coat", text="Bring from 3D-Coat")
- layout.separator()
- else:
- if(os.path.isfile(Blender_export)):
- layout.active = True
-
- layout.operator("import3b_applink.pilgway_3d_coat", text="Bring from 3D-Coat")
- layout.separator()
- else:
- if(os.path.isfile(Blender_export)):
-
-
- layout.operator("import3b_applink.pilgway_3d_coat", text="Bring from 3D-Coat")
- layout.separator()
-
-class VIEW3D_MT_ImportMenu(bpy.types.Menu):
- bl_label = "Import Settings"
-
- def draw(self, context):
- layout = self.layout
- coat3D = bpy.context.scene.coat3D
- settings = context.tool_settings
- layout.operator_context = 'INVOKE_REGION_WIN'
- layout.prop(coat3D,"importmesh")
- layout.prop(coat3D,"importmod")
- layout.prop(coat3D,"smooth_on")
- layout.prop(coat3D,"importtextures")
-
-class VIEW3D_MT_ExportMenu(bpy.types.Menu):
- bl_label = "Export Settings"
-
- def draw(self, context):
- layout = self.layout
- coat3D = bpy.context.scene.coat3D
- settings = context.tool_settings
- layout.operator_context = 'INVOKE_REGION_WIN'
- layout.prop(coat3D,"exportover")
- if(coat3D.exportover):
- layout.prop(coat3D,"exportmod")
-
-class VIEW3D_MT_ExtraMenu(bpy.types.Menu):
- bl_label = "Extra"
-
- def draw(self, context):
- layout = self.layout
- coat3D = bpy.context.scene.coat3D
- settings = context.tool_settings
- layout.operator_context = 'INVOKE_REGION_WIN'
-
- layout.operator("import_applink.pilgway_3d_deltex",text="Delete all Textures")
- layout.separator()
-
-def register():
- bpy.utils.register_module(__name__)
-
- kc = bpy.context.window_manager.keyconfigs.addon
- if kc:
- km = kc.keymaps.new(name='3D View', space_type='VIEW_3D')
- kmi = km.keymap_items.new('wm.call_menu2', 'Q', 'PRESS')
- kmi.properties.name = "VIEW3D_MT_Coat_Dynamic_Menu"
-
-def unregister():
- bpy.utils.unregister_module(__name__)
-
- kc = bpy.context.window_manager.keyconfigs.addon
- if kc:
- km = kc.keymapskeymaps['3D View']
- for kmi in km.keymap_items:
- if kmi.idname == '':
- if kmi.properties.name == "VIEW3D_MT_Coat_Dynamic_Menu":
- km.keymap_items.remove(kmi)
- break
-
-
-if __name__ == "__main__":
- register()
diff --git a/io_coat3D/tex.py b/io_coat3D/tex.py
index f532177c..7af4d68e 100644
--- a/io_coat3D/tex.py
+++ b/io_coat3D/tex.py
@@ -19,8 +19,9 @@
import bpy
import os
-
+import re
def find_index(objekti):
+
luku = 0
for tex in objekti.active_material.texture_slots:
if(not(hasattr(tex,'texture'))):
@@ -28,308 +29,371 @@ def find_index(objekti):
luku = luku +1
return luku
-def gettex(mat_list, objekti, scene,export):
+def RemoveFbxNodes(objekti):
+ Node_Tree = objekti.active_material.node_tree
+ for node in Node_Tree.nodes:
+ if node.type != 'OUTPUT_MATERIAL':
+ Node_Tree.nodes.remove(node)
+ else:
+ output = node
+ output.location = 340,400
+ Prin_mat = Node_Tree.nodes.new(type="ShaderNodeBsdfPrincipled")
+ Prin_mat.location = 13, 375
+
+ Node_Tree.links.new(Prin_mat.outputs[0], output.inputs[0])
+
+def readtexturefolder(objekti, mat_list, texturelist, is_new): #read textures from texture file
+
+ create_nodes = False
+ for index_mat in objekti.material_slots:
+
+ texcoat = {}
+ texcoat['color'] = []
+ texcoat['ao'] = []
+ texcoat['rough'] = []
+ texcoat['metalness'] = []
+ texcoat['nmap'] = []
+ texcoat['disp'] = []
+ texcoat['emissive'] = []
+ texcoat['emissive_power'] = []
+ texcoat['displacement'] = []
+
+
+ for texture_info in texturelist:
+ if texture_info[0] == index_mat.name:
+ if texture_info[2] == 'color' or texture_info[2] == 'diffuse':
+ texcoat['color'].append(texture_info[3])
+ create_nodes = True
+ if texture_info[2] == 'metalness' or texture_info[2] == 'specular' or texture_info[2] == 'reflection':
+ texcoat['metalness'].append(texture_info[3])
+ create_nodes = True
+ if texture_info[2] == 'rough' or texture_info[2] == 'roughness':
+ texcoat['rough'].append(texture_info[3])
+ create_nodes = True
+ if texture_info[2] == 'nmap' or texture_info[2] == 'normalmap' or texture_info[2] == 'normal_map':
+ texcoat['nmap'].append(texture_info[3])
+ create_nodes = True
+ if texture_info[2] == 'emissive':
+ texcoat['emissive'].append(texture_info[3])
+ create_nodes = True
+ if texture_info[2] == 'emissive_power':
+ texcoat['emissive_power'].append(texture_info[3])
+ create_nodes = True
+ if texture_info[2] == 'ao':
+ texcoat['ao'].append(texture_info[3])
+ create_nodes = True
+ if texture_info[2].startswith('displacement'):
+ texcoat['displacement'].append(texture_info[3])
+ create_nodes = True
+ if(create_nodes):
+ coat3D = bpy.context.scene.coat3D
+ path3b_n = coat3D.exchangedir
+ path3b_n += ('%slast_saved_3b_file.txt' % (os.sep))
+
+ if (os.path.isfile(path3b_n)):
+ export_file = open(path3b_n)
+ for line in export_file:
+ objekti.coat3D.applink_3b_path = line
+ export_file.close()
+ coat3D.remove_path = True
+ createnodes(index_mat, texcoat)
+
+def checkmaterial(mat_list, objekti): #check how many materials object has
+ mat_list = []
+
+ for obj_mate in objekti.material_slots:
+ if(obj_mate.material.use_nodes == False):
+ obj_mate.material.use_nodes = True
+
+def createnodes(active_mat,texcoat): # Cretes new nodes and link textures into them
+ bring_color = True # Meaning of these is to check if we can only update textures or do we need to create new nodes
+ bring_metalness = True
+ bring_roughness = True
+ bring_normal = True
+ bring_displacement = True
+ bring_AO = True
coat3D = bpy.context.scene.coat3D
- coa = objekti.coat3D
+ coatMat = active_mat.material
+
+ if(coatMat.use_nodes == False):
+ coatMat.use_nodes = True
+ act_material = coatMat.node_tree
+ main_material = coatMat.node_tree
+ applink_group_node = False
+ #ensimmaiseksi kaydaan kaikki image nodet lapi ja tarkistetaan onko nimi 3DC alkunen jos on niin reload
+
+ for node in coatMat.node_tree.nodes:
+ if (node.type == 'OUTPUT_MATERIAL'):
+ out_mat = node
+ break
- if(bpy.context.scene.render.engine == 'VRAY_RENDER' or bpy.context.scene.render.engine == 'VRAY_RENDER_PREVIEW'):
- vray = True
- else:
- vray = False
-
- take_color = 0
- take_spec = 0
- take_normal = 0
- take_disp = 0
-
- bring_color = 1
- bring_spec = 1
- bring_normal = 1
- bring_disp = 1
-
- texcoat = {}
- texcoat['color'] = []
- texcoat['specular'] = []
- texcoat['nmap'] = []
- texcoat['disp'] = []
- texu = []
-
- if(export):
- objekti.coat3D.objpath = export
- nimi = os.path.split(export)[1]
- osoite = os.path.dirname(export) + os.sep #pitaa ehka muuttaa
- for mate in objekti.material_slots:
- for tex_slot in mate.material.texture_slots:
- if(hasattr(tex_slot,'texture')):
- if(tex_slot.texture.type == 'IMAGE'):
- if tex_slot.texture.image is not None:
- tex_slot.texture.image.reload()
+ for node in act_material.nodes:
+ if(node.name == '3DC_Applink' and node.type == 'GROUP'):
+ applink_group_node = True
+ act_material = node.node_tree
+ group_tree = node.node_tree
+ applink_tree = node
+ break
+
+ print('TeXture UPDATE happens')
+ for node in act_material.nodes:
+ if(node.type == 'TEX_IMAGE'):
+ if(node.name == '3DC_color'):
+ bring_color = False
+ node.image.reload()
+ elif(node.name == '3DC_metalness'):
+ bring_metalness = False
+ node.image.reload()
+ elif(node.name == '3DC_roughness'):
+ bring_roughness = False
+ node.image.reload()
+ elif(node.name == '3DC_normal'):
+ bring_normal = False
+ node.image.reload()
+ elif(node.name == '3DC_displacement'):
+ bring_displacement = False
+ node.image.reload()
+ elif (node.name == '3DC_AO'):
+ bring_AO = False
+ node.image.reload()
+
+ #seuraavaksi lahdemme rakentamaan node tree. Lahdetaan Material Outputista rakentaa
+
+ if(applink_group_node == False and coat3D.creategroup):
+ group_tree = bpy.data.node_groups.new( type="ShaderNodeTree", name="3DC_Applink")
+ group_tree.outputs.new("NodeSocketColor", "Color")
+ group_tree.outputs.new("NodeSocketColor", "Metallic")
+ group_tree.outputs.new("NodeSocketColor", "Roughness")
+ group_tree.outputs.new("NodeSocketVector", "Normal map")
+ group_tree.outputs.new("NodeSocketColor", "Displacement")
+ group_tree.outputs.new("NodeSocketColor", "Emissive")
+ group_tree.outputs.new("NodeSocketColor", "Emissive Power")
+ group_tree.outputs.new("NodeSocketColor", "AO")
+ applink_tree = act_material.nodes.new('ShaderNodeGroup')
+ applink_tree.name = '3DC_Applink'
+ applink_tree.node_tree = group_tree
+ applink_tree.location = -400, 300
+ act_material = group_tree
+ notegroup = act_material.nodes.new('NodeGroupOutput')
+ notegroup.location = 220, -260
else:
- if(os.sys.platform == 'win32'):
- osoite = os.path.expanduser("~") + os.sep + 'Documents' + os.sep + '3DC2Blender' + os.sep + 'Textures' + os.sep
+ index = 0
+ for node in coatMat.node_tree.nodes:
+ if (node.type == 'GROUP' and node.name =='3DC_Applink'):
+ for in_node in node.node_tree.nodes:
+ if(in_node.type == 'GROUP_OUTPUT'):
+ notegroup = in_node
+ index = 1
+ break
+ if(index == 1):
+ break
+
+ if(out_mat.inputs['Surface'].is_linked == True):
+ main_mat = out_mat.inputs['Surface'].links[0].from_node
+ if(main_mat.inputs.find('Base Color') == -1):
+ input_color = main_mat.inputs.find('Color')
else:
- osoite = os.path.expanduser("~") + os.sep + '3DC2Blender' + os.sep + 'Textures' + os.sep
- ki = os.path.split(coa.applink_name)[1]
- ko = os.path.splitext(ki)[0]
- just_nimi = ko + '_'
- just_nimi_len = len(just_nimi)
- print('terve:' + coa.applink_name)
-
- if(len(objekti.material_slots) != 0):
- for obj_tex in objekti.active_material.texture_slots:
- if(hasattr(obj_tex,'texture')):
- if(obj_tex.texture.type == 'IMAGE'):
- if(obj_tex.use_map_color_diffuse):
- bring_color = 0;
- if(obj_tex.use_map_specular):
- bring_spec = 0;
- if(obj_tex.use_map_normal):
- bring_normal = 0;
- if(obj_tex.use_map_displacement):
- bring_disp = 0;
-
- files = os.listdir(osoite)
- for i in files:
- tui = i[:just_nimi_len]
- if(tui == just_nimi):
- texu.append(i)
-
- for yy in texu:
- minimi = (yy.rfind('_'))+1
- maksimi = (yy.rfind('.'))
- tex_name = yy[minimi:maksimi]
- koko = ''
- koko += osoite
- koko += yy
- texcoat[tex_name].append(koko)
-
- if((texcoat['color'] or texcoat['nmap'] or texcoat['disp'] or texcoat['specular']) and (len(objekti.material_slots)) == 0):
- materials_old = bpy.data.materials.keys()
- bpy.ops.material.new()
- materials_new = bpy.data.materials.keys()
- new_ma = list(set(materials_new).difference(set(materials_old)))
- new_mat = new_ma[0]
- ki = bpy.data.materials[new_mat]
- objekti.data.materials.append(ki)
-
- if(bring_color == 1 and texcoat['color']):
- index = find_index(objekti)
- tex = bpy.ops.Texture
- objekti.active_material.texture_slots.create(index)
- total_mat = len(objekti.active_material.texture_slots.items())
- useold = ''
-
- for seekco in bpy.data.textures:
- if((seekco.name[:5] == 'Color') and (seekco.users_material == ())):
- useold = seekco
-
-
- if(useold == ''):
-
- textures_old = bpy.data.textures.keys()
- bpy.data.textures.new('Color',type='IMAGE')
- textures_new = bpy.data.textures.keys()
- name_te = list(set(textures_new).difference(set(textures_old)))
- name_tex = name_te[0]
-
- bpy.ops.image.new(name=name_tex)
- bpy.data.images[name_tex].filepath = texcoat['color'][0]
- bpy.data.images[name_tex].source = 'FILE'
-
- objekti.active_material.texture_slots[index].texture = bpy.data.textures[name_tex]
- objekti.active_material.texture_slots[index].texture.image = bpy.data.images[name_tex]
-
- if(objekti.data.uv_textures.active):
- objekti.active_material.texture_slots[index].texture_coords = 'UV'
- objekti.active_material.texture_slots[index].uv_layer = objekti.data.uv_textures.active.name
-
- objekti.active_material.texture_slots[index].texture.image.reload()
-
-
- elif(useold != ''):
-
- objekti.active_material.texture_slots[index].texture = useold
- objekti.active_material.texture_slots[index].texture.image = bpy.data.images[useold.name]
- objekti.active_material.texture_slots[index].texture.image.filepath = texcoat['color'][0]
- if(objekti.data.uv_textures.active):
- objekti.active_material.texture_slots[index].texture_coords = 'UV'
- objekti.active_material.texture_slots[index].uv_layer = objekti.data.uv_textures.active.name
-
-
- if(bring_normal == 1 and texcoat['nmap']):
- index = find_index(objekti)
- tex = bpy.ops.Texture
- objekti.active_material.texture_slots.create(index)
- total_mat = len(objekti.active_material.texture_slots.items())
- useold = ''
-
- for seekco in bpy.data.textures:
- if((seekco.name[:6] == 'Normal') and (seekco.users_material == ())):
- useold = seekco
-
- if(useold == ''):
-
- textures_old = bpy.data.textures.keys()
- bpy.data.textures.new('Normal',type='IMAGE')
- textures_new = bpy.data.textures.keys()
- name_te = list(set(textures_new).difference(set(textures_old)))
- name_tex = name_te[0]
-
- bpy.ops.image.new(name=name_tex)
- bpy.data.images[name_tex].filepath = texcoat['nmap'][0]
- bpy.data.images[name_tex].source = 'FILE'
-
- objekti.active_material.texture_slots[index].texture = bpy.data.textures[name_tex]
- objekti.active_material.texture_slots[index].texture.image = bpy.data.images[name_tex]
-
- if(objekti.data.uv_textures.active):
- objekti.active_material.texture_slots[index].texture_coords = 'UV'
- objekti.active_material.texture_slots[index].uv_layer = objekti.data.uv_textures.active.name
-
- objekti.active_material.texture_slots[index].use_map_color_diffuse = False
- objekti.active_material.texture_slots[index].use_map_normal = True
-
- objekti.active_material.texture_slots[index].texture.image.reload()
- if(vray):
- bpy.data.textures[name_tex].vray_slot.BRDFBump.map_type = 'TANGENT'
-
+ input_color = main_mat.inputs.find('Base Color')
+
+ ''' COLOR '''
+
+ if(bring_color == True and texcoat['color'] != []):
+ print('Color:', texcoat['color'][0])
+ node = act_material.nodes.new('ShaderNodeTexImage')
+ node.name = '3DC_color'
+ node.label = 'Color'
+ if (texcoat['color']):
+ node.image = bpy.data.images.load(texcoat['color'][0])
+
+ if(coat3D.createnodes):
+ curvenode = act_material.nodes.new('ShaderNodeRGBCurve')
+ curvenode.name = '3DC_RGBCurve'
+ huenode = act_material.nodes.new('ShaderNodeHueSaturation')
+ huenode.name = '3DC_HueSaturation'
+
+ act_material.links.new(curvenode.outputs[0], huenode.inputs[4])
+ act_material.links.new(node.outputs[0], curvenode.inputs[1])
+ if(coat3D.creategroup):
+ act_material.links.new(huenode.outputs[0], notegroup.inputs[0])
+ if(main_mat.type != 'MIX_SHADER'):
+ main_material.links.new(applink_tree.outputs[0],main_mat.inputs[input_color])
+ else:
+ location = main_mat.location
+ applink_tree.location = main_mat.location[0], main_mat.location[1] + 200
+ else:
+ act_material.links.new(huenode.outputs[0], main_mat.inputs[input_color])
+ node.location = -990, 530
+ curvenode.location = -660, 480
+ huenode.location = -337, 335
else:
- bpy.data.textures[name_tex].use_normal_map = True
- objekti.active_material.texture_slots[index].normal_map_space = 'TANGENT'
- objekti.active_material.texture_slots[index].normal_factor = 1
-
-
-
- elif(useold != ''):
-
- objekti.active_material.texture_slots[index].texture = useold
- objekti.active_material.texture_slots[index].texture.image = bpy.data.images[useold.name]
- objekti.active_material.texture_slots[index].texture.image.filepath = texcoat['nmap'][0]
- if(objekti.data.uv_textures.active):
- objekti.active_material.texture_slots[index].texture_coords = 'UV'
- objekti.active_material.texture_slots[index].uv_layer = objekti.data.uv_textures.active.name
- objekti.active_material.texture_slots[index].use_map_color_diffuse = False
- objekti.active_material.texture_slots[index].use_map_normal = True
- objekti.active_material.texture_slots[index].normal_factor = 1
-
-
- if(bring_spec == 1 and texcoat['specular']):
-
- index = find_index(objekti)
-
- objekti.active_material.texture_slots.create(index)
- useold = ''
-
- for seekco in bpy.data.textures:
- if((seekco.name[:8] == 'Specular') and (seekco.users_material == ())):
- useold = seekco
-
- if(useold == ''):
-
- textures_old = bpy.data.textures.keys()
- bpy.data.textures.new('Specular',type='IMAGE')
- textures_new = bpy.data.textures.keys()
- name_te = list(set(textures_new).difference(set(textures_old)))
- name_tex = name_te[0]
-
- bpy.ops.image.new(name=name_tex)
- bpy.data.images[name_tex].filepath = texcoat['specular'][0]
- bpy.data.images[name_tex].source = 'FILE'
+ if (coat3D.creategroup):
+ node.location = -400, 400
+ act_material.links.new(node.outputs[0], notegroup.inputs[len(notegroup.inputs)-1])
+ if (input_color != -1):
+ main_material.links.new(applink_tree.outputs[len(applink_tree.outputs)-1], main_mat.inputs[input_color])
+
+ else:
+ node.location = -400,400
+ if (input_color != -1):
+ act_material.links.new(node.outputs[0], main_mat.inputs[input_color])
+
+ ''' METALNESS '''
+
+ if(bring_metalness == True and texcoat['metalness'] != []):
+ node = act_material.nodes.new('ShaderNodeTexImage')
+ node.name='3DC_metalness'
+ node.label = 'Metalness'
+ input_color = main_mat.inputs.find('Metallic')
+ if(texcoat['metalness']):
+ node.image = bpy.data.images.load(texcoat['metalness'][0])
+ node.color_space = 'NONE'
+ if (coat3D.createnodes):
+ curvenode = act_material.nodes.new('ShaderNodeRGBCurve')
+ curvenode.name = '3DC_RGBCurve'
+ huenode = act_material.nodes.new('ShaderNodeHueSaturation')
+ huenode.name = '3DC_HueSaturation'
+
+ act_material.links.new(curvenode.outputs[0], huenode.inputs[4])
+ act_material.links.new(node.outputs[0], curvenode.inputs[1])
+
+ if (coat3D.creategroup):
+ act_material.links.new(huenode.outputs[0], notegroup.inputs[1])
+ if (main_mat.type == 'BSDF_PRINCIPLED'):
+ main_material.links.new(applink_tree.outputs[1], main_mat.inputs[input_color])
+ else:
+ act_material.links.new(huenode.outputs[0], main_mat.inputs[input_color])
+
+ node.location = -994, 119
+ curvenode.location = -668, 113
+ huenode.location = -345, 118
- objekti.active_material.texture_slots[index].texture = bpy.data.textures[name_tex]
- objekti.active_material.texture_slots[index].texture.image = bpy.data.images[name_tex]
-
- if(objekti.data.uv_textures.active):
- objekti.active_material.texture_slots[index].texture_coords = 'UV'
- objekti.active_material.texture_slots[index].uv_layer = objekti.data.uv_textures.active.name
-
- objekti.active_material.texture_slots[index].use_map_color_diffuse = False
- objekti.active_material.texture_slots[index].use_map_specular = True
-
- objekti.active_material.texture_slots[index].texture.image.reload()
-
-
- elif(useold != ''):
-
- objekti.active_material.texture_slots[index].texture = useold
- objekti.active_material.texture_slots[index].texture.image = bpy.data.images[useold.name]
- objekti.active_material.texture_slots[index].texture.image.filepath = texcoat['specular'][0]
- if(objekti.data.uv_textures.active):
- objekti.active_material.texture_slots[index].texture_coords = 'UV'
- objekti.active_material.texture_slots[index].uv_layer = objekti.data.uv_textures.active.name
- objekti.active_material.texture_slots[index].use_map_color_diffuse = False
- objekti.active_material.texture_slots[index].use_map_specular = True
-
- if(bring_disp == 1 and texcoat['disp']):
-
- index = find_index(objekti)
-
-
- objekti.active_material.texture_slots.create(index)
- useold = ''
-
- for seekco in bpy.data.textures:
- if((seekco.name[:12] == 'Displacement') and (seekco.users_material == ())):
- useold = seekco
-
- if useold == "":
-
- textures_old = bpy.data.textures.keys()
- bpy.data.textures.new('Displacement',type='IMAGE')
- textures_new = bpy.data.textures.keys()
- name_te = list(set(textures_new).difference(set(textures_old)))
- name_tex = name_te[0]
-
- bpy.ops.image.new(name=name_tex)
- bpy.data.images[name_tex].filepath = texcoat['disp'][0]
- bpy.data.images[name_tex].source = 'FILE'
-
- objekti.active_material.texture_slots[index].texture = bpy.data.textures[name_tex]
- objekti.active_material.texture_slots[index].texture.image = bpy.data.images[name_tex]
+ else:
+ if (coat3D.creategroup):
+ node.location = -830, 160
+ act_material.links.new(node.outputs[0], notegroup.inputs[len(notegroup.inputs)-1])
+ if (input_color != -1):
+ main_material.links.new(applink_tree.outputs[len(applink_tree.outputs)-1], main_mat.inputs[input_color])
+ else:
+ node.location = -830, 160
+ if (input_color != -1):
+ act_material.links.new(node.outputs[0], main_mat.inputs[input_color])
+
+ ''' ROUGHNESS '''
+
+ if(bring_roughness == True and texcoat['rough'] != []):
+ node = act_material.nodes.new('ShaderNodeTexImage')
+ node.name='3DC_roughness'
+ node.label = 'Roughness'
+ input_color = main_mat.inputs.find('Roughness')
+ if(texcoat['rough']):
+ node.image = bpy.data.images.load(texcoat['rough'][0])
+ node.color_space = 'NONE'
+
+ if (coat3D.createnodes):
+ curvenode = act_material.nodes.new('ShaderNodeRGBCurve')
+ curvenode.name = '3DC_RGBCurve'
+ huenode = act_material.nodes.new('ShaderNodeHueSaturation')
+ huenode.name = '3DC_HueSaturation'
+
+ act_material.links.new(curvenode.outputs[0], huenode.inputs[4])
+ act_material.links.new(node.outputs[0], curvenode.inputs[1])
+
+ if (coat3D.creategroup):
+ act_material.links.new(huenode.outputs[0], notegroup.inputs[2])
+ if(main_mat.type == 'BSDF_PRINCIPLED'):
+ main_material.links.new(applink_tree.outputs[2], main_mat.inputs[input_color])
+ else:
+ act_material.links.new(huenode.outputs[0], main_mat.inputs[input_color])
+
+ node.location = -1000, -276
+ curvenode.location = -670, -245
+ huenode.location = -340, -100
- if(objekti.data.uv_textures.active):
- objekti.active_material.texture_slots[index].texture_coords = 'UV'
- objekti.active_material.texture_slots[index].uv_layer = objekti.data.uv_textures.active.name
+ else:
+ if (coat3D.creategroup):
+ node.location = -550, 0
+ act_material.links.new(node.outputs[0],notegroup.inputs[len(notegroup.inputs)-1])
+ if (input_color != -1):
+ main_material.links.new(applink_tree.outputs[len(applink_tree.outputs)-1], main_mat.inputs[input_color])
+
+ else:
+ node.location = -550, 0
+ if (input_color != -1):
+ act_material.links.new(node.outputs[0], main_mat.inputs[input_color])
+
+ ''' NORMAL MAP'''
+
+ if(bring_normal == True and texcoat['nmap'] != []):
+ node = act_material.nodes.new('ShaderNodeTexImage')
+ normal_node = act_material.nodes.new('ShaderNodeNormalMap')
+
+ node.location = -600,-670
+ normal_node.location = -300,-300
+
+ node.name='3DC_normal'
+ node.label = 'Normal Map'
+ normal_node.name='3DC_normalnode'
+ if(texcoat['nmap']):
+ node.image = bpy.data.images.load(texcoat['nmap'][0])
+ node.color_space = 'NONE'
+ input_color = main_mat.inputs.find('Normal')
+ act_material.links.new(node.outputs[0], normal_node.inputs[1])
+ act_material.links.new(normal_node.outputs[0], main_mat.inputs[input_color])
+ if (coat3D.creategroup):
+ act_material.links.new(normal_node.outputs[0], notegroup.inputs[3])
+ if(main_mat.inputs[input_color].name == 'Normal'):
+ main_material.links.new(applink_tree.outputs[3], main_mat.inputs[input_color])
+
+ ''' DISPLACEMENT '''
+
+ if (bring_displacement == True and texcoat['displacement'] != []):
+ node = act_material.nodes.new('ShaderNodeTexImage')
+ node.name = '3DC_displacement'
+ node.label = 'Displacement'
+ # input_color = main_mat.inputs.find('Roughness') Blender 2.8 Does not support Displacement yet.
+ if (texcoat['displacement']):
+ node.image = bpy.data.images.load(texcoat['displacement'][0])
+ node.color_space = 'NONE'
+
+ if (coat3D.createnodes):
+ '''
+ curvenode = act_material.nodes.new('ShaderNodeRGBCurve')
+ curvenode.name = '3DC_RGBCurve'
+ huenode = act_material.nodes.new('ShaderNodeHueSaturation')
+ huenode.name = '3DC_HueSaturation'
+
+ act_material.links.new(curvenode.outputs[0], huenode.inputs[4])
+ act_material.links.new(node.outputs[0], curvenode.inputs[1])
+ '''
+
+ if (coat3D.creategroup):
+ act_material.links.new(node.outputs[0], notegroup.inputs[4])
+
+ #if (main_mat.type == 'BSDF_PRINCIPLED'):
+ #main_material.links.new(applink_tree.outputs[2], main_mat.inputs[input_color])
+ #else:
+ #act_material.links.new(huenode.outputs[0], main_mat.inputs[input_color])
+
+ node.location = -276, -579
- objekti.active_material.texture_slots[index].use_map_color_diffuse = False
- objekti.active_material.texture_slots[index].use_map_displacement = True
+ else:
+ if (coat3D.creategroup):
+ node.location = -550, 0
+ act_material.links.new(node.outputs[0], notegroup.inputs[len(notegroup.inputs) - 1])
- objekti.active_material.texture_slots[index].texture.image.reload()
- elif(useold != ''):
- objekti.active_material.texture_slots[index].texture = useold
- objekti.active_material.texture_slots[index].texture.image = bpy.data.images[useold.name]
- objekti.active_material.texture_slots[index].texture.image.filepath = texcoat['disp'][0]
- if(objekti.data.uv_textures.active):
- objekti.active_material.texture_slots[index].texture_coords = 'UV'
- objekti.active_material.texture_slots[index].uv_layer = objekti.data.uv_textures.active.name
- objekti.active_material.texture_slots[index].use_map_color_diffuse = False
- objekti.active_material.texture_slots[index].use_map_displacement = True
- if(vray):
- objekti.active_material.texture_slots[index].texture.use_interpolation = False
- objekti.active_material.texture_slots[index].displacement_factor = 0.05
+def matlab(objekti,mat_list,texturelist,is_new):
+ ''' FBX Materials: remove all nodes and create princibles node'''
+ if(is_new):
+ RemoveFbxNodes(objekti)
- else:
- disp_modi = ''
- for seek_modi in objekti.modifiers:
- if(seek_modi.type == 'DISPLACE'):
- disp_modi = seek_modi
- break
- if(disp_modi):
- disp_modi.texture = objekti.active_material.texture_slots[index].texture
- if(objekti.data.uv_textures.active):
- disp_modi.texture_coords = 'UV'
- disp_modi.uv_layer = objekti.data.uv_textures.active.name
- else:
- objekti.modifiers.new('Displace',type='DISPLACE')
- objekti.modifiers['Displace'].texture = objekti.active_material.texture_slots[index].texture
- if(objekti.data.uv_textures.active):
- objekti.modifiers['Displace'].texture_coords = 'UV'
- objekti.modifiers['Displace'].uv_layer = objekti.data.uv_textures.active.name
+ '''Main Loop for Texture Update'''
+ #checkmaterial(mat_list, objekti)
+ readtexturefolder(objekti,mat_list,texturelist,is_new)
return('FINISHED')
diff --git a/io_convert_image_to_mesh_img/__init__.py b/io_convert_image_to_mesh_img/__init__.py
index 1e5bdddf..a884fb90 100644
--- a/io_convert_image_to_mesh_img/__init__.py
+++ b/io_convert_image_to_mesh_img/__init__.py
@@ -51,12 +51,12 @@ def menu_import(self, context):
def register():
bpy.utils.register_module(__name__)
- bpy.types.INFO_MT_file_import.append(menu_import)
+ bpy.types.TOPBAR_MT_file_import.append(menu_import)
def unregister():
bpy.utils.unregister_module(__name__)
- bpy.types.INFO_MT_file_import.remove(menu_import)
+ bpy.types.TOPBAR_MT_file_import.remove(menu_import)
if __name__ == '__main__':
diff --git a/io_curve_svg/__init__.py b/io_curve_svg/__init__.py
index 188e9189..c8a9988d 100644
--- a/io_curve_svg/__init__.py
+++ b/io_curve_svg/__init__.py
@@ -21,8 +21,7 @@
bl_info = {
"name": "Scalable Vector Graphics (SVG) 1.1 format",
"author": "JM Soler, Sergey Sharybin",
- "version": (1, 0, 0),
- "blender": (2, 57, 0),
+ "blender": (2, 80, 0),
"location": "File > Import > Scalable Vector Graphics (.svg)",
"description": "Import SVG as curves",
"warning": "",
@@ -53,13 +52,12 @@ class ImportSVG(bpy.types.Operator, ImportHelper):
bl_options = {'UNDO'}
filename_ext = ".svg"
- filter_glob = StringProperty(default="*.svg", options={'HIDDEN'})
+ filter_glob: StringProperty(default="*.svg", options={'HIDDEN'})
def execute(self, context):
from . import import_svg
- return import_svg.load(self, context,
- **self.as_keywords(ignore=("filter_glob",)))
+ return import_svg.load(self, context, filepath=self.filepath)
def menu_func_import(self, context):
@@ -68,15 +66,15 @@ def menu_func_import(self, context):
def register():
- bpy.utils.register_module(__name__)
+ bpy.utils.register_class(ImportSVG)
- bpy.types.INFO_MT_file_import.append(menu_func_import)
+ bpy.types.TOPBAR_MT_file_import.append(menu_func_import)
def unregister():
- bpy.utils.unregister_module(__name__)
+ bpy.utils.unregister_class(ImportSVG)
- bpy.types.INFO_MT_file_import.remove(menu_func_import)
+ bpy.types.TOPBAR_MT_file_import.remove(menu_func_import)
# NOTES
# - blender version is hardcoded
diff --git a/io_curve_svg/import_svg.py b/io_curve_svg/import_svg.py
index 6b45d91d..c7302711 100644
--- a/io_curve_svg/import_svg.py
+++ b/io_curve_svg/import_svg.py
@@ -122,14 +122,15 @@ def SVGParseFloat(s, i=0):
return token, i
-def SVGCreateCurve():
+def SVGCreateCurve(context):
"""
Create new curve object to hold splines in
"""
cu = bpy.data.curves.new("Curve", 'CURVE')
obj = bpy.data.objects.new("Curve", cu)
- bpy.context.scene.objects.link(obj)
+
+ context['collection'].objects.link(obj)
return obj
@@ -217,8 +218,8 @@ def SVGMatrixFromNode(node, context):
m = Matrix.Translation(Vector((x, y, 0.0)))
if has_user_coordinate:
if rect[0] != 0 and rect[1] != 0:
- m = m * Matrix.Scale(w / rect[0], 4, Vector((1.0, 0.0, 0.0)))
- m = m * Matrix.Scale(h / rect[1], 4, Vector((0.0, 1.0, 0.0)))
+ m = m @ Matrix.Scale(w / rect[0], 4, Vector((1.0, 0.0, 0.0)))
+ m = m @ Matrix.Scale(h / rect[1], 4, Vector((0.0, 1.0, 0.0)))
if node.getAttribute('viewBox'):
viewBox = node.getAttribute('viewBox').replace(',', ' ').split()
@@ -241,11 +242,11 @@ def SVGMatrixFromNode(node, context):
tx = (w - vw * scale) / 2
ty = (h - vh * scale) / 2
- m = m * Matrix.Translation(Vector((tx, ty, 0.0)))
+ m = m @ Matrix.Translation(Vector((tx, ty, 0.0)))
- m = m * Matrix.Translation(Vector((-vx, -vy, 0.0)))
- m = m * Matrix.Scale(scale, 4, Vector((1.0, 0.0, 0.0)))
- m = m * Matrix.Scale(scale, 4, Vector((0.0, 1.0, 0.0)))
+ m = m @ Matrix.Translation(Vector((-vx, -vy, 0.0)))
+ m = m @ Matrix.Scale(scale, 4, Vector((1.0, 0.0, 0.0)))
+ m = m @ Matrix.Scale(scale, 4, Vector((0.0, 1.0, 0.0)))
return m
@@ -267,7 +268,7 @@ def SVGParseTransform(transform):
if proc is None:
raise Exception('Unknown trasnform function: ' + func)
- m = m * proc(params)
+ m = m @ proc(params)
return m
@@ -308,7 +309,6 @@ def SVGGetMaterial(color, context):
mat = bpy.data.materials.new(name='SVGMat')
mat.diffuse_color = diffuse_color
- mat.diffuse_intensity = 1.0
materials[color] = mat
@@ -354,8 +354,8 @@ def SVGTransformScale(params):
m = Matrix()
- m = m * Matrix.Scale(sx, 4, Vector((1.0, 0.0, 0.0)))
- m = m * Matrix.Scale(sy, 4, Vector((0.0, 1.0, 0.0)))
+ m = m @ Matrix.Scale(sx, 4, Vector((1.0, 0.0, 0.0)))
+ m = m @ Matrix.Scale(sy, 4, Vector((0.0, 1.0, 0.0)))
return m
@@ -399,7 +399,7 @@ def SVGTransformRotate(params):
tm = Matrix.Translation(Vector((cx, cy, 0.0)))
rm = Matrix.Rotation(ang, 4, Vector((0.0, 0.0, 1.0)))
- return tm * rm * tm.inverted()
+ return tm @ rm @ tm.inverted()
SVGTransforms = {'translate': SVGTransformTranslate,
'scale': SVGTransformScale,
@@ -1038,7 +1038,7 @@ class SVGGeometry:
"""
self._context['transform'].append(matrix)
- self._context['matrix'] = self._context['matrix'] * matrix
+ self._context['matrix'] = self._context['matrix'] @ matrix
def _popMatrix(self):
"""
@@ -1046,7 +1046,7 @@ class SVGGeometry:
"""
matrix = self._context['transform'].pop()
- self._context['matrix'] = self._context['matrix'] * matrix.inverted()
+ self._context['matrix'] = self._context['matrix'] @ matrix.inverted()
def _pushStyle(self, style):
"""
@@ -1071,7 +1071,7 @@ class SVGGeometry:
v = Vector((point[0], point[1], 0.0))
- return self._context['matrix'] * v
+ return self._context['matrix'] @ v
def getNodeMatrix(self):
"""
@@ -1219,7 +1219,7 @@ class SVGGeometryPATH(SVGGeometry):
Create real geometries
"""
- ob = SVGCreateCurve()
+ ob = SVGCreateCurve(self._context)
cu = ob.data
if self._node.getAttribute('id'):
@@ -1255,7 +1255,7 @@ class SVGGeometryPATH(SVGGeometry):
act_spline = cu.splines[-1]
act_spline.use_cyclic_u = spline['closed']
else:
- act_spline.bezier_points.add()
+ act_spline.bezier_points.add(1)
bezt = act_spline.bezier_points[-1]
bezt.co = co
@@ -1399,7 +1399,7 @@ class SVGGeometryRECT(SVGGeometry):
co = self._transformCoord(coord)
if not firstTime:
- spline.bezier_points.add()
+ spline.bezier_points.add(1)
bezt = spline.bezier_points[-1]
bezt.co = co
@@ -1450,7 +1450,7 @@ class SVGGeometryRECT(SVGGeometry):
radius = (rx, ry)
# Geometry creation
- ob = SVGCreateCurve()
+ ob = SVGCreateCurve(self._context)
cu = ob.data
if self._styles['useFill']:
@@ -1560,7 +1560,7 @@ class SVGGeometryELLIPSE(SVGGeometry):
return
# Create circle
- ob = SVGCreateCurve()
+ ob = SVGCreateCurve(self._context)
cu = ob.data
if self._node.getAttribute('id'):
@@ -1599,7 +1599,7 @@ class SVGGeometryELLIPSE(SVGGeometry):
spline = cu.splines[-1]
spline.use_cyclic_u = True
else:
- spline.bezier_points.add()
+ spline.bezier_points.add(1)
bezt = spline.bezier_points[-1]
bezt.co = co
@@ -1677,7 +1677,7 @@ class SVGGeometryLINE(SVGGeometry):
y2 = SVGParseCoord(self._y2, crect[1])
# Create cline
- ob = SVGCreateCurve()
+ ob = SVGCreateCurve(self._context)
cu = ob.data
coords = [(x1, y1), (x2, y2)]
@@ -1691,7 +1691,7 @@ class SVGGeometryLINE(SVGGeometry):
spline = cu.splines[-1]
spline.use_cyclic_u = True
else:
- spline.bezier_points.add()
+ spline.bezier_points.add(1)
bezt = spline.bezier_points[-1]
bezt.co = co
@@ -1748,7 +1748,7 @@ class SVGGeometryPOLY(SVGGeometry):
Create real geometries
"""
- ob = SVGCreateCurve()
+ ob = SVGCreateCurve(self._context)
cu = ob.data
if self._closed and self._styles['useFill']:
@@ -1767,7 +1767,7 @@ class SVGGeometryPOLY(SVGGeometry):
spline = cu.splines[-1]
spline.use_cyclic_u = self._closed
else:
- spline.bezier_points.add()
+ spline.bezier_points.add(1)
bezt = spline.bezier_points[-1]
bezt.co = co
@@ -1819,7 +1819,7 @@ class SVGGeometrySVG(SVGGeometryContainer):
if self._node.getAttribute('inkscape:version'):
raw_height = self._node.getAttribute('height')
document_height = SVGParseCoord(raw_height, 1.0)
- matrix = matrix * Matrix.Translation([0.0, -document_height , 0.0])
+ matrix = matrix @ matrix.Translation([0.0, -document_height , 0.0])
self._pushMatrix(matrix)
self._pushRect(rect)
@@ -1845,16 +1845,22 @@ class SVGLoader(SVGGeometryContainer):
return None
- def __init__(self, filepath, do_colormanage):
+ def __init__(self, context, filepath, do_colormanage):
"""
Initialize SVG loader
"""
+ import os
+
+ svg_name = os.path.basename(filepath)
+ scene = context.scene
+ collection = bpy.data.collections.new(name=svg_name)
+ scene.collection.children.link(collection)
node = xml.dom.minidom.parse(filepath)
m = Matrix()
- m = m * Matrix.Scale(1.0 / 90.0 * 0.3048 / 12.0, 4, Vector((1.0, 0.0, 0.0)))
- m = m * Matrix.Scale(-1.0 / 90.0 * 0.3048 / 12.0, 4, Vector((0.0, 1.0, 0.0)))
+ m = m @ Matrix.Scale(1.0 / 90.0 * 0.3048 / 12.0, 4, Vector((1.0, 0.0, 0.0)))
+ m = m @ Matrix.Scale(-1.0 / 90.0 * 0.3048 / 12.0, 4, Vector((0.0, 1.0, 0.0)))
rect = (0, 0)
@@ -1866,7 +1872,8 @@ class SVGLoader(SVGGeometryContainer):
'materials': {},
'styles': [None],
'style': None,
- 'do_colormanage': do_colormanage}
+ 'do_colormanage': do_colormanage,
+ 'collection': collection}
super().__init__(node, self._context)
@@ -1903,7 +1910,7 @@ def parseAbstractNode(node, context):
return None
-def load_svg(filepath, do_colormanage):
+def load_svg(context, filepath, do_colormanage):
"""
Load specified SVG file
"""
@@ -1911,7 +1918,7 @@ def load_svg(filepath, do_colormanage):
if bpy.ops.object.mode_set.poll():
bpy.ops.object.mode_set(mode='OBJECT')
- loader = SVGLoader(filepath, do_colormanage)
+ loader = SVGLoader(context, filepath, do_colormanage)
loader.parse()
loader.createGeom(False)
@@ -1922,7 +1929,7 @@ def load(operator, context, filepath=""):
# non SVG files can give useful messages.
do_colormanage = context.scene.display_settings.display_device != 'NONE'
try:
- load_svg(filepath, do_colormanage)
+ load_svg(context, filepath, do_colormanage)
except (xml.parsers.expat.ExpatError, UnicodeEncodeError) as e:
import traceback
traceback.print_exc()
diff --git a/io_export_after_effects.py b/io_export_after_effects.py
index bb7ec73c..d682497d 100644
--- a/io_export_after_effects.py
+++ b/io_export_after_effects.py
@@ -110,7 +110,7 @@ def get_selected(context):
# not ready yet. is_plane(object) returns False in all cases. This is temporary
solids.append([ob, convert_name(ob.name)])
- elif ob.type == 'LAMP':
+ elif ob.type == 'LIGHT':
lights.append([ob, ob.data.type + convert_name(ob.name)]) # Type of lamp added to name
else:
@@ -768,12 +768,12 @@ def menu_func(self, context):
def register():
bpy.utils.register_class(ExportJsx)
- bpy.types.INFO_MT_file_export.append(menu_func)
+ bpy.types.TOPBAR_MT_file_export.append(menu_func)
def unregister():
bpy.utils.unregister_class(ExportJsx)
- bpy.types.INFO_MT_file_export.remove(menu_func)
+ bpy.types.TOPBAR_MT_file_export.remove(menu_func)
if __name__ == "__main__":
register()
diff --git a/io_export_dxf/__init__.py b/io_export_dxf/__init__.py
index 254ffdae..41fd79e6 100644
--- a/io_export_dxf/__init__.py
+++ b/io_export_dxf/__init__.py
@@ -45,7 +45,7 @@ classes = (
)
def register():
- bpy.types.INFO_MT_file_export.append(menu_func)
+ bpy.types.TOPBAR_MT_file_export.append(menu_func)
from bpy.utils import register_class
for cls in classes:
@@ -53,7 +53,7 @@ def register():
def unregister():
- bpy.types.INFO_MT_file_export.remove(menu_func)
+ bpy.types.TOPBAR_MT_file_export.remove(menu_func)
from bpy.utils import unregister_class
for cls in reversed(classes):
diff --git a/io_export_dxf/export_dxf.py b/io_export_dxf/export_dxf.py
index df0dcddc..934ae90e 100644
--- a/io_export_dxf/export_dxf.py
+++ b/io_export_dxf/export_dxf.py
@@ -27,7 +27,7 @@ if DEBUG:
from .model.migiusModel import MigiusDXFLibDrawing
-SUPPORTED_TYPES = ('MESH')#,'CURVE','EMPTY','TEXT','CAMERA','LAMP')
+SUPPORTED_TYPES = ('MESH')#,'CURVE','EMPTY','TEXT','CAMERA','LIGHT')
def exportDXF(context, filePath, settings):
"""
@@ -277,8 +277,8 @@ def _exportItem(ctx, o, mw, drawing, settings):
elif (o.type == 'CAMERA') and settings['camera_as']:
from .primitive_exporters.camera_exporter import CameraDXFExporter
e = CameraDXFExporter(settings)
- elif (o.type == 'LAMP') and settings['lamp_as']:
- from .primitive_exporters.lamp_exporter import LampDXFExporter
+ elif (o.type == 'LIGHT') and settings['light_as']:
+ from .primitive_exporters.light_exporter import LampDXFExporter
e = LampDXFExporter(settings)
return e.export(ctx, drawing, o, mx, mx_n, color=ecolor, layer=elayer, lineType=eltype)
diff --git a/io_export_dxf/operator.py b/io_export_dxf/operator.py
index e419608f..7e2dd7a7 100644
--- a/io_export_dxf/operator.py
+++ b/io_export_dxf/operator.py
@@ -160,10 +160,10 @@ class DXFExporter(bpy.types.Operator):
# ('VIEW', 'VIEW', ''),
# ('POINT', 'POINT', '')
# )
-# lamp_asItems = (
+# light_asItems = (
# ('NO', 'Do not export', ''),
# ('..BLOCK', '..BLOCK', ''),
-# ('..A_LAMP', '..A_LAMP', ''),
+# ('..A_LIGHT', '..A_LIGHT', ''),
# ('POINT', 'POINT', '')
# )
# --------- CONTROL PROPERTIES --------------------------------------------
@@ -207,9 +207,9 @@ class DXFExporter(bpy.types.Operator):
# camera_as = EnumProperty( name="Export camera As:", default='NO',
# description="Select representation of a camera",
# items=camera_asItems)
-# lamp_as = EnumProperty( name="Export lamp As:", default='NO',
+# light_as = EnumProperty( name="Export lamp As:", default='NO',
# description="Select representation of a lamp",
-# items=lamp_asItems)
+# items=light_asItems)
# ----------------------------------------------------------
entitylayer_from = EnumProperty(name="Entity Layer", default="obj.data.name",
description="Entity LAYER assigned to?",
@@ -265,7 +265,7 @@ class DXFExporter(bpy.types.Operator):
# 'group_as' : self._checkNO(self.group_as),
# 'proxy_as' : self._checkNO(self.proxy_as),
# 'camera_as' : self._checkNO(self.camera_as),
-# 'lamp_as' : self._checkNO(self.lamp_as),
+# 'light_as' : self._checkNO(self.light_as),
'entitylayer_from' : self.entitylayer_from,
'entitycolor_from' : self.entitycolor_from,
diff --git a/io_export_dxf/primitive_exporters/lamp_exporter.py b/io_export_dxf/primitive_exporters/lamp_exporter.py
index c67eb032..849f0984 100644
--- a/io_export_dxf/primitive_exporters/lamp_exporter.py
+++ b/io_export_dxf/primitive_exporters/lamp_exporter.py
@@ -13,7 +13,7 @@ def exportLamp(ob, mx, mw, **common):
[p] = toGlobalOrigin([p])
entities = []
- c = lamp_as_list[GUI_A['lamp_as'].val]
+ c = light_as_list[GUI_A['light_as'].val]
if c=="POINT": # export as POINT
dxfPOINT = DXF.Point(points=[p],**common)
entities.append(dxfPOINT)
diff --git a/io_export_paper_model.py b/io_export_paper_model.py
index 38444b37..0906eb33 100644
--- a/io_export_paper_model.py
+++ b/io_export_paper_model.py
@@ -2238,8 +2238,8 @@ class ExportPaperModel(bpy.types.Operator):
row = layout.row(align=True)
row.menu("VIEW3D_MT_paper_model_presets", text=bpy.types.VIEW3D_MT_paper_model_presets.bl_label)
- row.operator("export_mesh.paper_model_preset_add", text="", icon='ZOOMIN')
- row.operator("export_mesh.paper_model_preset_add", text="", icon='ZOOMOUT').remove_active = True
+ row.operator("export_mesh.paper_model_preset_add", text="", icon='ADD')
+ row.operator("export_mesh.paper_model_preset_add", text="", icon='REMOVE').remove_active = True
# a little hack: this prints out something like "Scale: 1: 72"
layout.prop(self.properties, "scale", text="Scale: 1")
@@ -2582,12 +2582,12 @@ def register():
bpy.types.Mesh.paper_island_index = bpy.props.IntProperty(
name="Island List Index",
default=-1, min=-1, max=100, options={'SKIP_SAVE'})
- bpy.types.INFO_MT_file_export.append(menu_func)
+ bpy.types.TOPBAR_MT_file_export.append(menu_func)
def unregister():
bpy.utils.unregister_module(__name__)
- bpy.types.INFO_MT_file_export.remove(menu_func)
+ bpy.types.TOPBAR_MT_file_export.remove(menu_func)
if display_islands.handle:
bpy.types.SpaceView3D.draw_handler_remove(display_islands.handle, 'WINDOW')
display_islands.handle = None
diff --git a/io_export_pc2.py b/io_export_pc2.py
index 752df5c4..7384e21e 100644
--- a/io_export_pc2.py
+++ b/io_export_pc2.py
@@ -19,8 +19,8 @@
bl_info = {
"name": "Export Pointcache Format(.pc2)",
"author": "Florian Meyer (tstscr)",
- "version": (1, 1, 1),
- "blender": (2, 71, 0),
+ "version": (1, 1, 2),
+ "blender": (2, 80, 0),
"location": "File > Export > Pointcache (.pc2)",
"description": "Export mesh Pointcache data (.pc2)",
"warning": "",
@@ -50,9 +50,11 @@ import time
import math
import struct
+
def get_sampled_frames(start, end, sampling):
return [math.modf(start + x * sampling) for x in range(int((end - start) / sampling) + 1)]
+
def do_export(context, props, filepath):
mat_x90 = mathutils.Matrix.Rotation(-math.pi/2, 4, 'X')
ob = context.active_object
@@ -61,13 +63,13 @@ def do_export(context, props, filepath):
end = props.range_end
sampling = float(props.sampling)
apply_modifiers = props.apply_modifiers
- me = ob.to_mesh(sc, apply_modifiers, 'PREVIEW')
+ me = ob.to_mesh(context.depsgraph, apply_modifiers)
vertCount = len(me.vertices)
sampletimes = get_sampled_frames(start, end, sampling)
sampleCount = len(sampletimes)
# Create the header
- headerFormat='<12siiffi'
+ headerFormat = '<12siiffi'
headerStr = struct.pack(headerFormat, b'POINTCACHE2\0',
1, vertCount, start, sampling, sampleCount)
@@ -75,8 +77,9 @@ def do_export(context, props, filepath):
file.write(headerStr)
for frame in sampletimes:
- sc.frame_set(int(frame[1]), frame[0]) # stupid modf() gives decimal part first!
- me = ob.to_mesh(sc, apply_modifiers, 'PREVIEW')
+ # stupid modf() gives decimal part first!
+ sc.frame_set(int(frame[1]), subframe=frame[0])
+ me = ob.to_mesh(context.depsgraph, apply_modifiers)
if len(me.vertices) != vertCount:
bpy.data.meshes.remove(me, do_unlink=True)
@@ -97,19 +100,18 @@ def do_export(context, props, filepath):
for v in me.vertices:
thisVertex = struct.pack('<fff', float(v.co[0]),
- float(v.co[1]),
- float(v.co[2]))
+ float(v.co[1]),
+ float(v.co[2]))
file.write(thisVertex)
bpy.data.meshes.remove(me, do_unlink=True)
-
file.flush()
file.close()
return True
-###### EXPORT OPERATOR #######
+# EXPORT OPERATOR
class Export_pc2(bpy.types.Operator, ExportHelper):
"""Export the active Object as a .pc2 Pointcache file"""
bl_idname = "export_shape.pc2"
@@ -117,50 +119,51 @@ class Export_pc2(bpy.types.Operator, ExportHelper):
filename_ext = ".pc2"
- rot_x90 = BoolProperty(name="Convert to Y-up",
- description="Rotate 90 degrees around X to convert to y-up",
- default=True,
- )
- world_space = BoolProperty(name="Export into Worldspace",
- description="Transform the Vertexcoordinates into Worldspace",
- default=False,
- )
- apply_modifiers = BoolProperty(name="Apply Modifiers",
- description="Applies the Modifiers",
- default=True,
- )
- range_start = IntProperty(name='Start Frame',
- description='First frame to use for Export',
- default=1,
- )
- range_end = IntProperty(name='End Frame',
- description='Last frame to use for Export',
- default=250,
- )
- sampling = EnumProperty(name='Sampling',
- description='Sampling --> frames per sample (0.1 yields 10 samples per frame)',
- items=(('0.01', '0.01', ''),
- ('0.05', '0.05', ''),
- ('0.1', '0.1', ''),
- ('0.2', '0.2', ''),
- ('0.25', '0.25', ''),
- ('0.5', '0.5', ''),
- ('1', '1', ''),
- ('2', '2', ''),
- ('3', '3', ''),
- ('4', '4', ''),
- ('5', '5', ''),
- ('10', '10', ''),
- ),
- default='1',
- )
+ rot_x90: BoolProperty(
+ name="Convert to Y-up",
+ description="Rotate 90 degrees around X to convert to y-up",
+ default=True,)
+ world_space: BoolProperty(
+ name="Export into Worldspace",
+ description="Transform the Vertexcoordinates into Worldspace",
+ default=False,)
+ apply_modifiers: BoolProperty(
+ name="Apply Modifiers",
+ description="Applies the Modifiers",
+ default=True,)
+ range_start: IntProperty(
+ name='Start Frame',
+ description='First frame to use for Export',
+ default=1,)
+ range_end: IntProperty(
+ name='End Frame',
+ description='Last frame to use for Export',
+ default=250,)
+ sampling: EnumProperty(
+ name='Sampling',
+ description='Sampling --> frames per sample (0.1 yields 10 samples per frame)',
+ items=(('0.01', '0.01', ''),
+ ('0.05', '0.05', ''),
+ ('0.1', '0.1', ''),
+ ('0.2', '0.2', ''),
+ ('0.25', '0.25', ''),
+ ('0.5', '0.5', ''),
+ ('1', '1', ''),
+ ('2', '2', ''),
+ ('3', '3', ''),
+ ('4', '4', ''),
+ ('5', '5', ''),
+ ('10', '10', ''),
+ ),
+ default='1',
+ )
@classmethod
def poll(cls, context):
obj = context.active_object
return (
- obj is not None and
- obj.type in {'MESH', 'CURVE', 'SURFACE', 'FONT'}
+ obj is not None
+ and obj.type in {'MESH', 'CURVE', 'SURFACE', 'FONT'}
)
def execute(self, context):
@@ -173,7 +176,8 @@ class Export_pc2(bpy.types.Operator, ExportHelper):
exported = do_export(context, props, filepath)
if exported:
- print('finished export in %s seconds' %((time.time() - start_time)))
+ print('finished export in %s seconds' %
+ ((time.time() - start_time)))
print(filepath)
return {'FINISHED'}
@@ -183,7 +187,7 @@ class Export_pc2(bpy.types.Operator, ExportHelper):
if True:
# File selector
- wm.fileselect_add(self) # will run self.execute()
+ wm.fileselect_add(self) # will run self.execute()
return {'RUNNING_MODAL'}
elif True:
# search the enum
@@ -196,23 +200,29 @@ class Export_pc2(bpy.types.Operator, ExportHelper):
return self.execute(context)
-### REGISTER ###
-
-def menu_func(self, context):
+def menu_func_export_button(self, context):
self.layout.operator(Export_pc2.bl_idname, text="Pointcache (.pc2)")
+classes = [
+ Export_pc2,
+]
+
+
def register():
- bpy.utils.register_module(__name__)
+ for cls in classes:
+ bpy.utils.register_class(cls)
+
+ bpy.types.TOPBAR_MT_file_export.append(menu_func_export_button)
+ #bpy.types.VIEW3D_PT_tools_objectmode.prepend(menu_func_export_button)
- bpy.types.INFO_MT_file_export.append(menu_func)
- #bpy.types.VIEW3D_PT_tools_objectmode.prepend(menu_func)
def unregister():
- bpy.utils.unregister_module(__name__)
+ bpy.types.TOPBAR_MT_file_export.remove(menu_func_export_button)
+ #bpy.types.VIEW3D_PT_tools_objectmode.remove(menu_func_export_button)
+ for cls in classes:
+ bpy.utils.unregister_class(cls)
- bpy.types.INFO_MT_file_export.remove(menu_func)
- #bpy.types.VIEW3D_PT_tools_objectmode.remove(menu_func)
if __name__ == "__main__":
register()
diff --git a/io_export_unreal_psk_psa.py b/io_export_unreal_psk_psa.py
index 6179470c..2f0a9fe2 100644
--- a/io_export_unreal_psk_psa.py
+++ b/io_export_unreal_psk_psa.py
@@ -1201,8 +1201,8 @@ def parse_mesh(mesh, psk):
# does with the mesh Y coordinates. this is otherwise known as MAGIC-2
uv[1] = 1.0 - uv[1]
- # clamp UV coords if udk_option_clamp_uv is True
- if bpy.context.scene.udk_option_clamp_uv:
+ # clamp UV coords if udk_option_clight_uv is True
+ if bpy.context.scene.udk_option_clight_uv:
if (uv[0] > 1):
uv[0] = 1
if (uv[0] < 0):
@@ -2170,7 +2170,7 @@ def rebuildmesh(obj):
# vertices weight groups
for vgroup in vertGroups:
- group = obmesh.vertex_groups.new(vgroup)
+ group = obmesh.vertex_groups.new(name=vgroup)
for v in vertGroups[vgroup]:
group.add([v[0]], v[1], 'ADD') # group.add(array[vertex id],weight,add)
bpy.context.scene.objects.link(obmesh)
@@ -2422,7 +2422,7 @@ class Panel_UDKExport(Panel):
object_name = context.active_object.name
row10 = layout.row()
row10.prop(context.scene, "udk_option_smoothing_groups")
- row10.prop(context.scene, "udk_option_clamp_uv")
+ row10.prop(context.scene, "udk_option_clight_uv")
row10.prop(context.scene, "udk_option_verbose")
row = layout.row()
@@ -2792,7 +2792,7 @@ class ExportUDKAnimData(Operator):
scene = context.scene
layout.prop(scene, "udk_option_smoothing_groups")
- layout.prop(scene, "udk_option_clamp_uv")
+ layout.prop(scene, "udk_option_clight_uv")
layout.prop(scene, "udk_option_verbose")
layout.prop(scene, "udk_option_filename_src")
layout.prop(scene, "udk_option_export")
@@ -2865,7 +2865,7 @@ class PskAddonPreferences(AddonPreferences):
def register():
bpy.utils.register_module(__name__)
- bpy.types.INFO_MT_file_export.append(menu_func)
+ bpy.types.TOPBAR_MT_file_export.append(menu_func)
update_panel(None, bpy.context)
# Added by [MGVS]
@@ -2888,7 +2888,7 @@ def register():
description="Boolean for exporting psa format (Animation Data)",
default=True
)
- bpy.types.Scene.udk_option_clamp_uv = BoolProperty(
+ bpy.types.Scene.udk_option_clight_uv = BoolProperty(
name="Clamp UV",
description="True is to limit Clamp UV co-ordinates to [0-1]. False is unrestricted (x,y)",
default=False
@@ -2972,12 +2972,12 @@ def register():
def unregister():
bpy.utils.unregister_module(__name__)
- bpy.types.INFO_MT_file_export.remove(menu_func)
+ bpy.types.TOPBAR_MT_file_export.remove(menu_func)
del bpy.types.Scene.udk_option_filename_src
del bpy.types.Scene.udk_option_export_psk
del bpy.types.Scene.udk_option_export_psa
- del bpy.types.Scene.udk_option_clamp_uv
+ del bpy.types.Scene.udk_option_clight_uv
del bpy.types.Scene.udk_copy_merge
del bpy.types.Scene.udk_option_export
del bpy.types.Scene.udk_option_verbose
diff --git a/io_import_dxf/__init__.py b/io_import_dxf/__init__.py
index d768d624..46e84fd5 100644
--- a/io_import_dxf/__init__.py
+++ b/io_import_dxf/__init__.py
@@ -559,12 +559,12 @@ def menu_func(self, context):
def register():
bpy.utils.register_module(__name__)
- bpy.types.INFO_MT_file_import.append(menu_func)
+ bpy.types.TOPBAR_MT_file_import.append(menu_func)
def unregister():
bpy.utils.unregister_module(__name__)
- bpy.types.INFO_MT_file_import.remove(menu_func)
+ bpy.types.TOPBAR_MT_file_import.remove(menu_func)
if __name__ == "__main__":
diff --git a/io_import_dxf/dxfimport/do.py b/io_import_dxf/dxfimport/do.py
index 7ac4c392..8db6b854 100644
--- a/io_import_dxf/dxfimport/do.py
+++ b/io_import_dxf/dxfimport/do.py
@@ -738,7 +738,7 @@ class Do:
if self.import_light:
type_map = ["NONE", "SUN", "POINT", "SPOT"]
layer = self.dwg.layers[en.layer]
- lamp = bpy.data.lamps.new(en.name, type_map[en.light_type])
+ lamp = bpy.data.lights.new(en.name, type_map[en.light_type])
if en.color != 256:
aci = en.color
else:
@@ -989,13 +989,13 @@ class Do:
bbox = self._object_bbox(objects + inserts, block_scene, name, True)
for i in inserts:
- sub_group = i.dupli_group
+ sub_group = i.instance_collection
block_scene.objects.unlink(i)
block_group.objects.unlink(i)
i_empty = bpy.data.objects.new(i.name, None)
i_empty.matrix_basis = i.matrix_basis
- i_empty.dupli_type = "GROUP"
- i_empty.dupli_group = sub_group
+ i_empty.instance_type = "COLLECTION"
+ i_empty.instance_collection = sub_group
block_group.objects.link(i_empty)
block_scene.objects.link(i_empty)
@@ -1005,9 +1005,9 @@ class Do:
bpy.context.screen.scene = scene
o = bbox.copy()
- # o.empty_draw_size = 0.3
- o.dupli_type = "GROUP"
- o.dupli_group = block_group
+ # o.empty_display_size = 0.3
+ o.instance_type = "COLLECTION"
+ o.instance_collection = block_group
group.objects.link(o)
if invisible is not None:
o.hide = invisible
@@ -1021,7 +1021,7 @@ class Do:
"""
entity: DXF entity
name: String; not used but required to be consistent with the methods being called from _call_type()
- group: Blender group of type (bpy_types.group) being set if called from block()
+ group: Blender group of type (bpy_types.Collection) being set if called from block()
invisible: boolean to control visibility; being set if called from block()
"""
aunits = self.dwg.header.get('$AUNITS', 0)
@@ -1066,7 +1066,7 @@ class Do:
entity.col_count, entity.row_count)
o = bpy.data.objects.new(entity.name, dm)
instance.parent = o
- o.dupli_type = "VERTS"
+ o.instance_type = "VERTS"
# insert transformations
rot = radians(entity.rotation) if aunits == 0 else entity.rotation
@@ -1342,11 +1342,11 @@ class Do:
name: name of group (String)
Finds group by name or creates it if it does not exist.
"""
- groups = bpy.data.groups
+ groups = bpy.data.collections
if name in groups.keys():
group = groups[name]
else:
- group = bpy.data.groups.new(name)
+ group = bpy.data.collections.new(name)
return group
def _call_object_types(self, TYPE, entities, group, name, scene, separated=False):
@@ -1466,9 +1466,9 @@ class Do:
scene.objects.link(o)
self._nest_block(o, blockname, blgroup, scene)
- o.dupli_type = "FACES"
- o.use_dupli_faces_scale = True
- o.dupli_faces_scale = f
+ o.instance_type = "FACES"
+ o.use_instance_faces_scale = True
+ o.instance_faces_scale = f
def _nest_block(self, parent, name, blgroup, scene):
b = self.dwg.blocks[name]
diff --git a/io_import_gimp_image_to_scene.py b/io_import_gimp_image_to_scene.py
index 6ff1d66f..94d496e3 100644
--- a/io_import_gimp_image_to_scene.py
+++ b/io_import_gimp_image_to_scene.py
@@ -619,7 +619,7 @@ class GIMPImageToScene(bpy.types.Operator):
if self.OpacityMode == 'COMPO' and self.SetupCompo == False:
box.label('Tip: Enable Node Compositing', icon='INFO')
box.prop(self, 'AlphaMode', icon='IMAGE_RGB_ALPHA')
- box.prop(self, 'ShadelessMats', icon='SOLID')
+ box.prop(self, 'ShadelessMats', icon='SHADING_SOLID')
box.prop(self, 'LayerOffset')
box.prop(self, 'LayerScale')
@@ -680,13 +680,13 @@ def menu_func(self, context):
def register():
bpy.utils.register_module(__name__)
- bpy.types.INFO_MT_file_import.append(menu_func)
+ bpy.types.TOPBAR_MT_file_import.append(menu_func)
def unregister():
bpy.utils.unregister_module(__name__)
- bpy.types.INFO_MT_file_import.remove(menu_func)
+ bpy.types.TOPBAR_MT_file_import.remove(menu_func)
if __name__ == "__main__":
diff --git a/io_import_images_as_planes.py b/io_import_images_as_planes.py
index a4aa98f2..88cee32a 100644
--- a/io_import_images_as_planes.py
+++ b/io_import_images_as_planes.py
@@ -21,14 +21,15 @@
bl_info = {
"name": "Import Images as Planes",
"author": "Florian Meyer (tstscr), mont29, matali, Ted Schundler (SpkyElctrc)",
- "version": (3, 1, 1),
- "blender": (2, 78, 0),
+ "version": (3, 2, 1),
+ "blender": (2, 80, 0),
"location": "File > Import > Images as Planes or Add > Mesh > Images as Planes",
"description": "Imports images and creates planes with the appropriate aspect ratio. "
"The images are mapped to the planes.",
"warning": "",
"wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/"
"Scripts/Add_Mesh/Planes_from_Images",
+ "support": 'OFFICIAL',
"category": "Import-Export",
}
@@ -179,7 +180,6 @@ def load_images(filenames, directory, force_reload=False, frame_start=1, find_se
frames = image.frame_duration
elif frames > 1: # Not movie, but multiple frames -> image sequence
- image.use_animation = True
image.source = 'SEQUENCE'
yield ImageSpec(image, size, frame_start, offset - 1, frames)
@@ -200,11 +200,10 @@ def offset_planes(planes, gap, axis):
prior = planes[0]
offset = Vector()
for current in planes[1:]:
-
- local_offset = abs((prior.dimensions + current.dimensions) * axis) / 2.0 + gap
+ local_offset = abs((prior.dimensions + current.dimensions).dot(axis)) / 2.0 + gap
offset += local_offset * axis
- current.location = current.matrix_world * offset
+ current.location = current.matrix_world @ offset
prior = current
@@ -242,7 +241,7 @@ def compute_camera_size(context, center, fill_mode, aspect):
def center_in_camera(scene, camera, obj, axis=(1, 1)):
- """Center object along specified axiis of the camera"""
+ """Center object along specified axis of the camera"""
camera_matrix_col = camera.matrix_world.col
location = obj.location
@@ -250,11 +249,11 @@ def center_in_camera(scene, camera, obj, axis=(1, 1)):
delta = camera_matrix_col[3].xyz - location
# How far off center we are along the camera's local X
- camera_x_mag = delta * camera_matrix_col[0].xyz * axis[0]
+ camera_x_mag = delta.dot(camera_matrix_col[0].xyz) * axis[0]
# How far off center we are along the camera's local Y
- camera_y_mag = delta * camera_matrix_col[1].xyz * axis[1]
+ camera_y_mag = delta.dot(camera_matrix_col[1].xyz) * axis[1]
- # Now offet only along camera local axiis
+ # Now offset only along camera local axis
offset = camera_matrix_col[0].xyz * camera_x_mag + \
camera_matrix_col[1].xyz * camera_y_mag
@@ -262,7 +261,7 @@ def center_in_camera(scene, camera, obj, axis=(1, 1)):
# -----------------------------------------------------------------------------
-# Cycles utils
+# Cycles/Eevee utils
def get_input_nodes(node, links):
"""Get nodes that are a inputs to the given node"""
@@ -327,7 +326,7 @@ def clean_node_tree(node_tree):
def get_shadeless_node(dest_node_tree):
- """Return a "shadless" cycles node, creating a node group if nonexistent"""
+ """Return a "shadless" cycles/eevee node, creating a node group if nonexistent"""
try:
node_tree = bpy.data.node_groups['IAP_SHADELESS']
@@ -474,7 +473,7 @@ def find_plane_corner(object_name, x, y, axis, camera=None, *args, **kwargs):
v = plane.dimensions.copy()
v.x *= x / scale.x
v.y *= y / scale.y
- v = plane.matrix_world * v
+ v = plane.matrix_world @ v
camera_vertex = world_to_camera_view(
bpy.context.scene, camera, v)
@@ -614,22 +613,22 @@ class IMPORT_IMAGE_OT_to_plane(Operator, AddObjectHelper):
# ----------------------
# File dialog properties
- files = CollectionProperty(type=bpy.types.OperatorFileListElement, options={'HIDDEN', 'SKIP_SAVE'})
+ files: CollectionProperty(type=bpy.types.OperatorFileListElement, options={'HIDDEN', 'SKIP_SAVE'})
- directory = StringProperty(maxlen=1024, subtype='FILE_PATH', options={'HIDDEN', 'SKIP_SAVE'})
+ directory: StringProperty(maxlen=1024, subtype='FILE_PATH', options={'HIDDEN', 'SKIP_SAVE'})
- filter_image = BoolProperty(default=True, options={'HIDDEN', 'SKIP_SAVE'})
- filter_movie = BoolProperty(default=True, options={'HIDDEN', 'SKIP_SAVE'})
- filter_folder = BoolProperty(default=True, options={'HIDDEN', 'SKIP_SAVE'})
+ filter_image: BoolProperty(default=True, options={'HIDDEN', 'SKIP_SAVE'})
+ filter_movie: BoolProperty(default=True, options={'HIDDEN', 'SKIP_SAVE'})
+ filter_folder: BoolProperty(default=True, options={'HIDDEN', 'SKIP_SAVE'})
# ----------------------
# Properties - Importing
- force_reload = BoolProperty(
+ force_reload: BoolProperty(
name="Force Reload", default=False,
description="Force reloading of the image if already opened elsewhere in Blender"
)
- image_sequence = BoolProperty(
+ image_sequence: BoolProperty(
name="Animate Image Sequences", default=False,
description="Import sequentially numbered images as an animated "
"image sequence instead of separate planes"
@@ -646,7 +645,7 @@ class IMPORT_IMAGE_OT_to_plane(Operator, AddObjectHelper):
'Z-': Vector(( 0, 0, -1)),
}
- offset = BoolProperty(name="Offset Planes", default=True, description="Offset Planes From Each Other")
+ offset: BoolProperty(name="Offset Planes", default=True, description="Offset Planes From Each Other")
OFFSET_MODES = (
('X+', "X+", "Side by Side to the Left"),
@@ -656,12 +655,12 @@ class IMPORT_IMAGE_OT_to_plane(Operator, AddObjectHelper):
('Y-', "Y-", "Side by Side, Upward"),
('Z-', "Z-", "Stacked Below"),
)
- offset_axis = EnumProperty(
+ offset_axis: EnumProperty(
name="Orientation", default='X+', items=OFFSET_MODES,
description="How planes are oriented relative to each others' local axis"
)
- offset_amount = FloatProperty(
+ offset_amount: FloatProperty(
name="Offset", soft_min=0, default=0.1, description="Space between planes",
subtype='DISTANCE', unit='LENGTH'
)
@@ -676,14 +675,14 @@ class IMPORT_IMAGE_OT_to_plane(Operator, AddObjectHelper):
('CAM', "Face Camera", "Facing Camera"),
('CAM_AX', "Main Axis", "Facing the Camera's dominant axis"),
)
- align_axis = EnumProperty(
+ align_axis: EnumProperty(
name="Align", default='CAM_AX', items=AXIS_MODES,
description="How to align the planes"
)
# prev_align_axis is used only by update_size_model
- prev_align_axis = EnumProperty(
+ prev_align_axis: EnumProperty(
items=AXIS_MODES + (('NONE', '', ''),), default='NONE', options={'HIDDEN', 'SKIP_SAVE'})
- align_track = BoolProperty(
+ align_track: BoolProperty(
name="Track Camera", default=False, description="Always face the camera"
)
@@ -707,7 +706,7 @@ class IMPORT_IMAGE_OT_to_plane(Operator, AddObjectHelper):
('DPI', "Dpi", "Use definition of the image as dots per inch"),
('DPBU', "Dots/BU", "Use definition of the image as dots per Blender Unit"),
)
- size_mode = EnumProperty(
+ size_mode: EnumProperty(
name="Size Mode", default='ABSOLUTE', items=SIZE_MODES,
update=update_size_mode,
description="How the size of the plane is computed")
@@ -716,13 +715,13 @@ class IMPORT_IMAGE_OT_to_plane(Operator, AddObjectHelper):
('FILL', "Fill", "Fill camera frame, spilling outside the frame"),
('FIT', "Fit", "Fit entire image within the camera frame"),
)
- fill_mode = EnumProperty(name="Scale", default='FILL', items=FILL_MODES,
+ fill_mode: EnumProperty(name="Scale", default='FILL', items=FILL_MODES,
description="How large in the camera frame is the plane")
- height = FloatProperty(name="Height", description="Height of the created plane",
+ height: FloatProperty(name="Height", description="Height of the created plane",
default=1.0, min=0.001, soft_min=0.001, subtype='DISTANCE', unit='LENGTH')
- factor = FloatProperty(name="Definition", min=1.0, default=600.0,
+ factor: FloatProperty(name="Definition", min=1.0, default=600.0,
description="Number of pixels per inch or Blender Unit")
# ------------------------------
@@ -732,40 +731,37 @@ class IMPORT_IMAGE_OT_to_plane(Operator, AddObjectHelper):
('SHADELESS', "Shadeless", "Only visible to camera and reflections."),
('EMISSION', "Emit", "Emission Shader"),
)
- shader = EnumProperty(name="Shader", items=SHADERS, default='DIFFUSE', description="Node shader to use")
+ shader: EnumProperty(name="Shader", items=SHADERS, default='DIFFUSE', description="Node shader to use")
- emit_strength = FloatProperty(
+ emit_strength: FloatProperty(
name="Strength", min=0.0, default=1.0, soft_max=10.0,
step=100, description="Brightness of Emission Texture")
- overwrite_material = BoolProperty(
+ overwrite_material: BoolProperty(
name="Overwrite Material", default=True,
description="Overwrite existing Material (based on material name)")
- compositing_nodes = BoolProperty(
+ compositing_nodes: BoolProperty(
name="Setup Corner Pin", default=False,
description="Build Compositor Nodes to reference this image "
"without re-rendering")
# ------------------
# Properties - Image
- use_transparency = BoolProperty(
+ use_transparency: BoolProperty(
name="Use Alpha", default=True,
description="Use alphachannel for transparency")
t = bpy.types.Image.bl_rna.properties["alpha_mode"]
alpha_mode_items = tuple((e.identifier, e.name, e.description) for e in t.enum_items)
- alpha_mode = EnumProperty(
+ alpha_mode: EnumProperty(
name=t.name, items=alpha_mode_items, default=t.default,
description=t.description)
- t = bpy.types.Image.bl_rna.properties["use_fields"]
- use_fields = BoolProperty(name=t.name, default=False, description=t.description)
-
t = bpy.types.ImageUser.bl_rna.properties["use_auto_refresh"]
- use_auto_refresh = BoolProperty(name=t.name, default=True, description=t.description)
+ use_auto_refresh: BoolProperty(name=t.name, default=True, description=t.description)
- relative = BoolProperty(name="Relative Paths", default=True, description="Use relative file paths")
+ relative: BoolProperty(name="Relative Paths", default=True, description="Use relative file paths")
# -------
# Draw UI
@@ -798,7 +794,7 @@ class IMPORT_IMAGE_OT_to_plane(Operator, AddObjectHelper):
box.prop(self, "emit_strength")
engine = context.scene.render.engine
- if engine not in ('CYCLES', 'BLENDER_RENDER'):
+ if engine not in ('CYCLES', 'BLENDER_EEVEE', 'BLENDER_OPENGL'):
box.label(text="%s is not supported" % engine, icon='ERROR')
box.prop(self, "overwrite_material")
@@ -809,7 +805,6 @@ class IMPORT_IMAGE_OT_to_plane(Operator, AddObjectHelper):
sub = row.row()
sub.active = self.use_transparency
sub.prop(self, "alpha_mode", text="")
- box.prop(self, "use_fields")
box.prop(self, "use_auto_refresh")
def draw_spatial_config(self, context):
@@ -837,7 +832,7 @@ class IMPORT_IMAGE_OT_to_plane(Operator, AddObjectHelper):
else:
box.prop(self, "factor")
- box.label(text="Orientation:", icon='MANIPUL')
+ box.label(text="Orientation:")
row = box.row()
row.enabled = 'CAM' not in self.size_mode
row.prop(self, "align_axis")
@@ -857,9 +852,13 @@ class IMPORT_IMAGE_OT_to_plane(Operator, AddObjectHelper):
# Core functionality
def invoke(self, context, event):
engine = context.scene.render.engine
- if engine not in ('CYCLES', 'BLENDER_RENDER', 'BLENDER_GAME'):
- # Use default blender texture, but acknowledge things may not work
- self.report({'WARNING'}, "Cannot generate materials for unknown %s render engine" % engine)
+ if engine not in {'CYCLES', 'BLENDER_EEVEE'}:
+ if engine not in {'BLENDER_OPENGL'}:
+ self.report({'ERROR'}, "Cannot generate materials for unknown %s render engine" % engine)
+ return {'CANCELLED'}
+ else:
+ self.report({'WARNING'},
+ "Generating Cycles/EEVEE compatible material, but won't be visible with %s engine" % engine)
# Open file browser
context.window_manager.fileselect_add(self)
@@ -910,7 +909,7 @@ class IMPORT_IMAGE_OT_to_plane(Operator, AddObjectHelper):
# setup new selection
for plane in planes:
- plane.select = True
+ plane.select_set(True)
# all done!
self.report({'INFO'}, "Added {} Image Plane(s)".format(len(planes)))
@@ -923,22 +922,14 @@ class IMPORT_IMAGE_OT_to_plane(Operator, AddObjectHelper):
# Configure material
engine = context.scene.render.engine
- if engine == 'CYCLES':
+ if engine in {'CYCLES', 'BLENDER_EEVEE', 'BLENDER_OPENGL'}:
material = self.create_cycles_material(context, img_spec)
- else:
- tex = self.create_image_textures(context, img_spec)
- material = self.create_material_for_texture(tex)
-
- # Game Engine Material Settings
- material.game_settings.use_backface_culling = False
- material.game_settings.alpha_blend = 'ALPHA'
# Create and position plane object
plane = self.create_image_plane(context, material.name, img_spec)
# Assign Material
plane.data.materials.append(material)
- plane.data.uv_textures[0].data[0].image = img_spec.image
# If applicable, setup Corner Pin node
if self.compositing_nodes:
@@ -949,7 +940,6 @@ class IMPORT_IMAGE_OT_to_plane(Operator, AddObjectHelper):
def apply_image_options(self, image):
image.use_alpha = self.use_transparency
image.alpha_mode = self.alpha_mode
- image.use_fields = self.use_fields
if self.relative:
try: # can't always find the relative path (between drive letters on windows)
@@ -971,48 +961,6 @@ class IMPORT_IMAGE_OT_to_plane(Operator, AddObjectHelper):
texture.extension = 'CLIP' # Default of "Repeat" can cause artifacts
- # -------------------------------------------------------------------------
- # Blender Internal Material
- def create_image_textures(self, context, img_spec):
- image = img_spec.image
- fn_full = os.path.normpath(bpy.path.abspath(image.filepath))
-
- # look for texture referencing this file
- for texture in bpy.data.textures:
- if texture.type == 'IMAGE':
- tex_img = texture.image
- if (tex_img is not None) and (tex_img.library is None):
- fn_tex_full = os.path.normpath(bpy.path.abspath(tex_img.filepath))
- if fn_full == fn_tex_full:
- if self.overwrite_material:
- self.apply_texture_options(texture, img_spec)
- return texture
-
- # if no texture is found: create one
- name_compat = bpy.path.display_name_from_filepath(image.filepath)
- texture = bpy.data.textures.new(name=name_compat, type='IMAGE')
- texture.image = image
- self.apply_texture_options(texture, img_spec)
- return texture
-
- def create_material_for_texture(self, texture):
- # look for material with the needed texture
- for material in bpy.data.materials:
- slot = material.texture_slots[0]
- if slot and slot.texture == texture:
- if self.overwrite_material:
- self.apply_material_options(material, slot)
- return material
-
- # if no material found: create one
- name_compat = bpy.path.display_name_from_filepath(texture.image.filepath)
- material = bpy.data.materials.new(name=name_compat)
- slot = material.texture_slots.add()
- slot.texture = texture
- slot.texture_coords = 'UV'
- self.apply_material_options(material, slot)
- return material
-
def apply_material_options(self, material, slot):
shader = self.shader
@@ -1034,7 +982,7 @@ class IMPORT_IMAGE_OT_to_plane(Operator, AddObjectHelper):
material.emit = self.emit_strength if shader == 'EMISSION' else 0.0
# -------------------------------------------------------------------------
- # Cycles
+ # Cycles/Eevee
def create_cycles_texnode(self, context, node_tree, img_spec):
tex_image = node_tree.nodes.new('ShaderNodeTexImage')
tex_image.image = img_spec.image
@@ -1092,14 +1040,13 @@ class IMPORT_IMAGE_OT_to_plane(Operator, AddObjectHelper):
# Create new mesh
bpy.ops.mesh.primitive_plane_add('INVOKE_REGION_WIN')
- plane = context.scene.objects.active
+ plane = context.active_object
# Why does mesh.primitive_plane_add leave the object in edit mode???
if plane.mode != 'OBJECT':
bpy.ops.object.mode_set(mode='OBJECT')
plane.dimensions = width, height, 0.0
plane.data.name = plane.name = name
bpy.ops.object.transform_apply(scale=True)
- plane.data.uv_textures.new()
# If sizing for camera, also insert into the camera's field of view
if self.size_mode == 'CAMERA':
@@ -1148,7 +1095,7 @@ class IMPORT_IMAGE_OT_to_plane(Operator, AddObjectHelper):
camera = context.scene.camera
if (camera):
# Find the axis that best corresponds to the camera's view direction
- axis = camera.matrix_world * \
+ axis = camera.matrix_world @ \
Vector((0, 0, 1)) - camera.matrix_world.col[3].xyz
# pick the axis with the greatest magnitude
mag = max(map(abs, axis))
@@ -1213,16 +1160,16 @@ def register():
for cls in classes:
bpy.utils.register_class(cls)
- bpy.types.INFO_MT_file_import.append(import_images_button)
- bpy.types.INFO_MT_mesh_add.append(import_images_button)
+ bpy.types.TOPBAR_MT_file_import.append(import_images_button)
+ bpy.types.VIEW3D_MT_image_add.append(import_images_button)
bpy.app.handlers.load_post.append(register_driver)
register_driver()
def unregister():
- bpy.types.INFO_MT_file_import.remove(import_images_button)
- bpy.types.INFO_MT_mesh_add.remove(import_images_button)
+ bpy.types.TOPBAR_MT_file_import.remove(import_images_button)
+ bpy.types.VIEW3D_MT_image_add.remove(import_images_button)
# This will only exist if drivers are active
if check_drivers in bpy.app.handlers.scene_update_post:
diff --git a/io_import_scene_lwo.py b/io_import_scene_lwo.py
index 3cfe207c..be5f55aa 100644
--- a/io_import_scene_lwo.py
+++ b/io_import_scene_lwo.py
@@ -1251,13 +1251,13 @@ def menu_func(self, context):
def register():
bpy.utils.register_module(__name__)
- bpy.types.INFO_MT_file_import.append(menu_func)
+ bpy.types.TOPBAR_MT_file_import.append(menu_func)
def unregister():
bpy.utils.unregister_module(__name__)
- bpy.types.INFO_MT_file_import.remove(menu_func)
+ bpy.types.TOPBAR_MT_file_import.remove(menu_func)
if __name__ == "__main__":
register()
diff --git a/io_import_scene_unreal_psa_psk.py b/io_import_scene_unreal_psa_psk.py
index 5fb0c8f6..d6896f6d 100644
--- a/io_import_scene_unreal_psa_psk.py
+++ b/io_import_scene_unreal_psa_psk.py
@@ -673,7 +673,7 @@ def pskimport(infile,importmesh,importbone,bDebugLogPSK,importmultiuvtextures):
for bone in ob_new.data.bones:
#print("names:", bone.name, ":", dir(bone))
#print("names:", bone.name)
- group = obmesh.vertex_groups.new(bone.name)
+ group = obmesh.vertex_groups.new(name=bone.name)
for vgroup in obmesh.vertex_groups:
#print(vgroup.name, ":", vgroup.index)
@@ -1275,11 +1275,11 @@ def menu_func(self, context):
def register():
bpy.utils.register_module(__name__)
- bpy.types.INFO_MT_file_import.append(menu_func)
+ bpy.types.TOPBAR_MT_file_import.append(menu_func)
def unregister():
bpy.utils.unregister_module(__name__)
- bpy.types.INFO_MT_file_import.remove(menu_func)
+ bpy.types.TOPBAR_MT_file_import.remove(menu_func)
if __name__ == "__main__":
register()
diff --git a/io_mesh_pdb/__init__.py b/io_mesh_pdb/__init__.py
index 1355d73b..4e731cf5 100644
--- a/io_mesh_pdb/__init__.py
+++ b/io_mesh_pdb/__init__.py
@@ -82,7 +82,7 @@ class ImportPDB(Operator, ImportHelper):
use_camera = BoolProperty(
name="Camera", default=False,
description="Do you need a camera?")
- use_lamp = BoolProperty(
+ use_light = BoolProperty(
name="Lamp", default=False,
description = "Do you need a lamp?")
ball = EnumProperty(
@@ -162,7 +162,7 @@ class ImportPDB(Operator, ImportHelper):
layout = self.layout
row = layout.row()
row.prop(self, "use_camera")
- row.prop(self, "use_lamp")
+ row.prop(self, "use_light")
row = layout.row()
row.prop(self, "use_center")
# Balls
@@ -256,7 +256,7 @@ class ImportPDB(Operator, ImportHelper):
self.sticks_radius,
self.use_center,
self.use_camera,
- self.use_lamp,
+ self.use_light,
filepath_pdb)
return {'FINISHED'}
@@ -301,13 +301,13 @@ def menu_func_export(self, context):
def register():
bpy.utils.register_module(__name__)
- bpy.types.INFO_MT_file_import.append(menu_func_import)
- bpy.types.INFO_MT_file_export.append(menu_func_export)
+ bpy.types.TOPBAR_MT_file_import.append(menu_func_import)
+ bpy.types.TOPBAR_MT_file_export.append(menu_func_export)
def unregister():
bpy.utils.unregister_module(__name__)
- bpy.types.INFO_MT_file_import.remove(menu_func_import)
- bpy.types.INFO_MT_file_export.remove(menu_func_export)
+ bpy.types.TOPBAR_MT_file_import.remove(menu_func_import)
+ bpy.types.TOPBAR_MT_file_export.remove(menu_func_export)
if __name__ == "__main__":
diff --git a/io_mesh_pdb/import_pdb.py b/io_mesh_pdb/import_pdb.py
index a7067692..bf243899 100644
--- a/io_mesh_pdb/import_pdb.py
+++ b/io_mesh_pdb/import_pdb.py
@@ -536,7 +536,7 @@ def build_stick(radius, length, sectors):
# Function, which puts a camera and light source into the 3D scene
def camera_light_source(use_camera,
- use_lamp,
+ use_light,
object_center_vec,
object_size):
@@ -592,27 +592,27 @@ def camera_light_source(use_camera,
release_confirm=False)
# Here a lamp is put into the scene, if chosen.
- if use_lamp == True:
+ if use_light == True:
# This is the distance from the object measured in terms of %
# of the camera distance. It is set onto 50% (1/2) distance.
- lamp_dl = sqrt(object_size) * 15 * 0.5
+ light_dl = sqrt(object_size) * 15 * 0.5
# This is a factor to which extend the lamp shall go to the right
# (from the camera point of view).
- lamp_dy_right = lamp_dl * (3.0/4.0)
+ light_dy_right = light_dl * (3.0/4.0)
# Create x, y and z for the lamp.
- object_lamp_vec = Vector((lamp_dl,lamp_dy_right,lamp_dl))
- lamp_xyz_vec = object_center_vec + object_lamp_vec
+ object_light_vec = Vector((light_dl,light_dy_right,light_dl))
+ light_xyz_vec = object_center_vec + object_light_vec
# Create the lamp
current_layers=bpy.context.scene.layers
- lamp_data = bpy.data.lamps.new(name="A_lamp", type="POINT")
- lamp_data.distance = 500.0
- lamp_data.energy = 3.0
- lamp_data.shadow_method = 'RAY_SHADOW'
- lamp = bpy.data.objects.new("A_lamp", lamp_data)
- lamp.location = lamp_xyz_vec
+ light_data = bpy.data.lights.new(name="A_light", type="POINT")
+ light_data.distance = 500.0
+ light_data.energy = 3.0
+ light_data.shadow_method = 'RAY_SHADOW'
+ lamp = bpy.data.objects.new("A_light", light_data)
+ lamp.location = light_xyz_vec
lamp.layers = current_layers
bpy.context.scene.objects.link(lamp)
@@ -685,7 +685,7 @@ def draw_atoms_one_type(draw_all_atoms_type,
ball.name = "Ball_"+atom[0]
ball.active_material = atom[1]
ball.parent = new_atom_mesh
- new_atom_mesh.dupli_type = 'VERTS'
+ new_atom_mesh.instance_type = 'VERTS'
# The object is back translated to 'object_center_vec'.
new_atom_mesh.location = object_center_vec
@@ -858,7 +858,7 @@ def draw_sticks_dupliverts(all_atoms,
# Parenting the mesh to the cylinder.
stick_cylinder.parent = new_mesh
stick_cups.parent = new_mesh
- new_mesh.dupli_type = 'FACES'
+ new_mesh.instance_type = 'FACES'
new_mesh.location = center
atom_object_list.append(new_mesh)
@@ -1145,7 +1145,7 @@ def import_pdb(Ball_type,
Stick_diameter,
put_to_center,
use_camera,
- use_lamp,
+ use_light,
filepath_pdb):
@@ -1376,7 +1376,7 @@ def import_pdb(Ball_type,
# CAMERA and LIGHT SOURCES
camera_light_source(use_camera,
- use_lamp,
+ use_light,
object_center_vec,
object_size)
diff --git a/io_mesh_ply/__init__.py b/io_mesh_ply/__init__.py
index 6764dcb4..30051796 100644
--- a/io_mesh_ply/__init__.py
+++ b/io_mesh_ply/__init__.py
@@ -22,7 +22,7 @@ bl_info = {
"name": "Stanford PLY format",
"author": "Bruce Merry, Campbell Barton",
"version": (1, 0, 0),
- "blender": (2, 74, 0),
+ "blender": (2, 80, 0),
"location": "File > Import-Export",
"description": "Import-Export PLY mesh data with UV's and vertex colors",
"warning": "",
@@ -56,29 +56,26 @@ from bpy.props import (
from bpy_extras.io_utils import (
ImportHelper,
ExportHelper,
- orientation_helper_factory,
axis_conversion,
+ orientation_helper
)
-IOPLYOrientationHelper = orientation_helper_factory("IOPLYOrientationHelper", axis_forward='Y', axis_up='Z')
-
-
class ImportPLY(bpy.types.Operator, ImportHelper):
"""Load a PLY geometry file"""
bl_idname = "import_mesh.ply"
bl_label = "Import PLY"
bl_options = {'UNDO'}
- files = CollectionProperty(name="File Path",
+ files: CollectionProperty(name="File Path",
description="File path used for importing "
"the PLY file",
type=bpy.types.OperatorFileListElement)
- directory = StringProperty()
+ directory: StringProperty()
filename_ext = ".ply"
- filter_glob = StringProperty(default="*.ply", options={'HIDDEN'})
+ filter_glob: StringProperty(default="*.ply", options={'HIDDEN'})
def execute(self, context):
paths = [os.path.join(self.directory, name.name)
@@ -94,21 +91,22 @@ class ImportPLY(bpy.types.Operator, ImportHelper):
return {'FINISHED'}
-class ExportPLY(bpy.types.Operator, ExportHelper, IOPLYOrientationHelper):
+@orientation_helper(axis_forward='Y', axis_up='Z')
+class ExportPLY(bpy.types.Operator, ExportHelper):
"""Export a single object as a Stanford PLY with normals, """ \
"""colors and texture coordinates"""
bl_idname = "export_mesh.ply"
bl_label = "Export PLY"
filename_ext = ".ply"
- filter_glob = StringProperty(default="*.ply", options={'HIDDEN'})
+ filter_glob: StringProperty(default="*.ply", options={'HIDDEN'})
- use_mesh_modifiers = BoolProperty(
+ use_mesh_modifiers: BoolProperty(
name="Apply Modifiers",
description="Apply Modifiers to the exported mesh",
default=True,
)
- use_normals = BoolProperty(
+ use_normals: BoolProperty(
name="Normals",
description="Export Normals for smooth and "
"hard shaded faces "
@@ -116,18 +114,18 @@ class ExportPLY(bpy.types.Operator, ExportHelper, IOPLYOrientationHelper):
"as individual faces)",
default=True,
)
- use_uv_coords = BoolProperty(
+ use_uv_coords: BoolProperty(
name="UVs",
description="Export the active UV layer",
default=True,
)
- use_colors = BoolProperty(
+ use_colors: BoolProperty(
name="Vertex Colors",
description="Export the active vertex color layer",
default=True,
)
- global_scale = FloatProperty(
+ global_scale: FloatProperty(
name="Scale",
min=0.01, max=1000.0,
default=1.0,
@@ -150,7 +148,7 @@ class ExportPLY(bpy.types.Operator, ExportHelper, IOPLYOrientationHelper):
))
global_matrix = axis_conversion(to_forward=self.axis_forward,
to_up=self.axis_up,
- ).to_4x4() * Matrix.Scale(self.global_scale, 4)
+ ).to_4x4() @ Matrix.Scale(self.global_scale, 4)
keywords["global_matrix"] = global_matrix
filepath = self.filepath
@@ -191,16 +189,16 @@ def register():
for cls in classes:
bpy.utils.register_class(cls)
- bpy.types.INFO_MT_file_import.append(menu_func_import)
- bpy.types.INFO_MT_file_export.append(menu_func_export)
+ bpy.types.TOPBAR_MT_file_import.append(menu_func_import)
+ bpy.types.TOPBAR_MT_file_export.append(menu_func_export)
def unregister():
for cls in classes:
bpy.utils.unregister_class(cls)
- bpy.types.INFO_MT_file_import.remove(menu_func_import)
- bpy.types.INFO_MT_file_export.remove(menu_func_export)
+ bpy.types.TOPBAR_MT_file_import.remove(menu_func_import)
+ bpy.types.TOPBAR_MT_file_export.remove(menu_func_export)
if __name__ == "__main__":
register()
diff --git a/io_mesh_ply/export_ply.py b/io_mesh_ply/export_ply.py
index 8de5d674..b50b6544 100644
--- a/io_mesh_ply/export_ply.py
+++ b/io_mesh_ply/export_ply.py
@@ -44,12 +44,12 @@ def save_mesh(filepath,
file = open(filepath, "w", encoding="utf8", newline="\n")
fw = file.write
- # Be sure tessface & co are available!
- if not mesh.tessfaces and mesh.polygons:
- mesh.calc_tessface()
+ # Be sure tessellated loop trianlges are available!
+ if not mesh.loop_triangles and mesh.polygons:
+ mesh.calc_loop_triangles()
- has_uv = bool(mesh.tessface_uv_textures)
- has_vcol = bool(mesh.tessface_vertex_colors)
+ has_uv = bool(mesh.uv_layers)
+ has_vcol = bool(mesh.vertex_colors)
if not has_uv:
use_uv_coords = False
@@ -62,7 +62,7 @@ def save_mesh(filepath,
has_vcol = False
if has_uv:
- active_uv_layer = mesh.tessface_uv_textures.active
+ active_uv_layer = mesh.uv_layers.active
if not active_uv_layer:
use_uv_coords = False
has_uv = False
@@ -70,7 +70,7 @@ def save_mesh(filepath,
active_uv_layer = active_uv_layer.data
if has_vcol:
- active_col_layer = mesh.tessface_vertex_colors.active
+ active_col_layer = mesh.vertex_colors.active
if not active_col_layer:
use_colors = False
has_vcol = False
@@ -84,9 +84,9 @@ def save_mesh(filepath,
ply_verts = [] # list of dictionaries
# vdict = {} # (index, normal, uv) -> new index
vdict = [{} for i in range(len(mesh_verts))]
- ply_faces = [[] for f in range(len(mesh.tessfaces))]
+ ply_faces = [[] for f in range(len(mesh.loop_triangles))]
vert_count = 0
- for i, f in enumerate(mesh.tessfaces):
+ for i, f in enumerate(mesh.loop_triangles):
smooth = not use_normals or f.use_smooth
if not smooth:
@@ -94,16 +94,12 @@ def save_mesh(filepath,
normal_key = rvec3d(normal)
if has_uv:
- uv = active_uv_layer[i]
- uv = uv.uv1, uv.uv2, uv.uv3, uv.uv4
+ uv = [active_uv_layer[l].uv[:] for l in f.loops]
if has_vcol:
- col = active_col_layer[i]
- col = col.color1[:], col.color2[:], col.color3[:], col.color4[:]
-
- f_verts = f.vertices
+ col = [active_col_layer[l].color[:] for l in f.loops]
pf = ply_faces[i]
- for j, vidx in enumerate(f_verts):
+ for j, vidx in enumerate(f.vertices):
v = mesh_verts[vidx]
if smooth:
@@ -119,6 +115,7 @@ def save_mesh(filepath,
color = (int(color[0] * 255.0),
int(color[1] * 255.0),
int(color[2] * 255.0),
+ 255
)
key = normal_key, uvcoord_key, color
@@ -157,7 +154,7 @@ def save_mesh(filepath,
"property uchar blue\n"
"property uchar alpha\n")
- fw("element face %d\n" % len(mesh.tessfaces))
+ fw("element face %d\n" % len(mesh.loop_triangles))
fw("property list uchar uint vertex_indices\n")
fw("end_header\n")
@@ -193,7 +190,6 @@ def save(operator,
global_matrix=None
):
- scene = context.scene
obj = context.active_object
if global_matrix is None:
@@ -204,14 +200,15 @@ def save(operator,
bpy.ops.object.mode_set(mode='OBJECT')
if use_mesh_modifiers and obj.modifiers:
- mesh = obj.to_mesh(scene, True, 'PREVIEW')
+ mesh = obj.to_mesh(context.depsgraph, True)
+
else:
mesh = obj.data.copy()
if not mesh:
raise Exception("Error, could not get mesh data from active object")
- mesh.transform(global_matrix * obj.matrix_world)
+ mesh.transform(global_matrix @ obj.matrix_world)
if use_normals:
mesh.calc_normals()
@@ -221,7 +218,6 @@ def save(operator,
use_colors=use_colors,
)
- if use_mesh_modifiers:
- bpy.data.meshes.remove(mesh)
+ bpy.data.meshes.remove(mesh)
return ret
diff --git a/io_mesh_ply/import_ply.py b/io_mesh_ply/import_ply.py
index 86fc2e1b..5ec24f8a 100644
--- a/io_mesh_ply/import_ply.py
+++ b/io_mesh_ply/import_ply.py
@@ -176,6 +176,7 @@ def read(filepath):
else:
texture = tokens[2]
continue
+
elif tokens[0] == b'obj_info':
continue
elif tokens[0] == b'format':
@@ -222,9 +223,9 @@ import bpy
def load_ply_mesh(filepath, ply_name):
from bpy_extras.io_utils import unpack_face_list
- # from bpy_extras.image_utils import load_image # UNUSED
obj_spec, obj, texture = read(filepath)
+ # XXX28: use texture
if obj is None:
print('Invalid file')
return
@@ -262,9 +263,9 @@ def load_ply_mesh(filepath, ply_name):
def add_face(vertices, indices, uvindices, colindices):
mesh_faces.append(indices)
if uvindices:
- mesh_uvs.append([(vertices[index][uvindices[0]], vertices[index][uvindices[1]]) for index in indices])
+ mesh_uvs.extend([(vertices[index][uvindices[0]], vertices[index][uvindices[1]]) for index in indices])
if colindices:
- mesh_colors.append([(vertices[index][colindices[0]] * colmultiply[0],
+ mesh_colors.extend([(vertices[index][colindices[0]] * colmultiply[0],
vertices[index][colindices[1]] * colmultiply[1],
vertices[index][colindices[2]] * colmultiply[2],
vertices[index][colindices[3]] * colmultiply[3],
@@ -316,41 +317,45 @@ def load_ply_mesh(filepath, ply_name):
mesh.edges.foreach_set("vertices", [a for e in obj[b'edge'] for a in (e[eindex1], e[eindex2])])
if mesh_faces:
- mesh.tessfaces.add(len(mesh_faces))
- mesh.tessfaces.foreach_set("vertices_raw", unpack_face_list(mesh_faces))
-
- if uvindices or colindices:
- if uvindices:
- uvlay = mesh.tessface_uv_textures.new()
- if colindices:
- vcol_lay = mesh.tessface_vertex_colors.new()
-
- if uvindices:
- for i, f in enumerate(uvlay.data):
- ply_uv = mesh_uvs[i]
- for j, uv in enumerate(f.uv):
- uv[0], uv[1] = ply_uv[j]
-
- if colindices:
- for i, f in enumerate(vcol_lay.data):
- # XXX, colors dont come in right, needs further investigation.
- ply_col = mesh_colors[i]
- if len(ply_col) == 4:
- f_col = f.color1, f.color2, f.color3, f.color4
- else:
- f_col = f.color1, f.color2, f.color3
+ loops_vert_idx = []
+ faces_loop_start = []
+ faces_loop_total = []
+ lidx = 0
+ for f in mesh_faces:
+ nbr_vidx = len(f)
+ loops_vert_idx.extend(f)
+ faces_loop_start.append(lidx)
+ faces_loop_total.append(nbr_vidx)
+ lidx += nbr_vidx
+
+ mesh.loops.add(len(loops_vert_idx))
+ mesh.polygons.add(len(mesh_faces))
+
+ mesh.loops.foreach_set("vertex_index", loops_vert_idx)
+ mesh.polygons.foreach_set("loop_start", faces_loop_start)
+ mesh.polygons.foreach_set("loop_total", faces_loop_total)
+
+ if uvindices:
+ uv_layer = mesh.uv_layers.new()
+ for i, uv in enumerate(uv_layer.data):
+ uv.uv = mesh_uvs[i]
- for j, col in enumerate(f_col):
- col[0] = ply_col[j][0]
- col[1] = ply_col[j][1]
- col[2] = ply_col[j][2]
- col[3] = ply_col[j][3]
+ if colindices:
+ vcol_lay = mesh.vertex_colors.new()
+
+ for i, col in enumerate(vcol_lay.data):
+ col.color[0] = mesh_colors[i][0]
+ col.color[1] = mesh_colors[i][1]
+ col.color[2] = mesh_colors[i][2]
+ col.color[3] = mesh_colors[i][3]
- mesh.validate()
mesh.update()
+ mesh.validate()
if texture and uvindices:
-
+ pass
+ # XXX28: add support for using texture.
+ '''
import os
import sys
from bpy_extras.image_utils import load_image
@@ -375,6 +380,7 @@ def load_ply_mesh(filepath, ply_name):
mesh.materials.append(material)
for face in mesh.uv_textures[0].data:
face.image = image
+ '''
return mesh
@@ -389,12 +395,10 @@ def load_ply(filepath):
if not mesh:
return {'CANCELLED'}
- scn = bpy.context.scene
-
obj = bpy.data.objects.new(ply_name, mesh)
- scn.objects.link(obj)
- scn.objects.active = obj
- obj.select = True
+ bpy.context.collection.objects.link(obj)
+ bpy.context.view_layer.objects.active = obj
+ obj.select_set(True)
print('\nSuccessfully imported %r in %.3f sec' % (filepath, time.time() - t))
return {'FINISHED'}
diff --git a/io_mesh_raw/__init__.py b/io_mesh_raw/__init__.py
index 69640932..5ad4c6f5 100644
--- a/io_mesh_raw/__init__.py
+++ b/io_mesh_raw/__init__.py
@@ -107,15 +107,15 @@ def menu_export(self, context):
def register():
bpy.utils.register_module(__name__)
- bpy.types.INFO_MT_file_import.append(menu_import)
- bpy.types.INFO_MT_file_export.append(menu_export)
+ bpy.types.TOPBAR_MT_file_import.append(menu_import)
+ bpy.types.TOPBAR_MT_file_export.append(menu_export)
def unregister():
bpy.utils.unregister_module(__name__)
- bpy.types.INFO_MT_file_import.remove(menu_import)
- bpy.types.INFO_MT_file_export.remove(menu_export)
+ bpy.types.TOPBAR_MT_file_import.remove(menu_import)
+ bpy.types.TOPBAR_MT_file_export.remove(menu_export)
if __name__ == "__main__":
register()
diff --git a/io_mesh_stl/__init__.py b/io_mesh_stl/__init__.py
index fc8bced2..b1807d2a 100644
--- a/io_mesh_stl/__init__.py
+++ b/io_mesh_stl/__init__.py
@@ -21,8 +21,8 @@
bl_info = {
"name": "STL format",
"author": "Guillaume Bouchard (Guillaum)",
- "version": (1, 1, 2),
- "blender": (2, 74, 0),
+ "version": (1, 1, 3),
+ "blender": (2, 80, 0),
"location": "File > Import-Export > Stl",
"description": "Import-Export STL files",
"warning": "",
@@ -67,7 +67,7 @@ from bpy.props import (
from bpy_extras.io_utils import (
ImportHelper,
ExportHelper,
- orientation_helper_factory,
+ orientation_helper,
axis_conversion,
)
from bpy.types import (
@@ -76,10 +76,8 @@ from bpy.types import (
)
-IOSTLOrientationHelper = orientation_helper_factory("IOSTLOrientationHelper", axis_forward='Y', axis_up='Z')
-
-
-class ImportSTL(Operator, ImportHelper, IOSTLOrientationHelper):
+@orientation_helper(axis_forward='Y', axis_up='Z')
+class ImportSTL(Operator, ImportHelper):
"""Load STL triangle mesh data"""
bl_idname = "import_mesh.stl"
bl_label = "Import STL"
@@ -87,32 +85,32 @@ class ImportSTL(Operator, ImportHelper, IOSTLOrientationHelper):
filename_ext = ".stl"
- filter_glob = StringProperty(
+ filter_glob: StringProperty(
default="*.stl",
options={'HIDDEN'},
)
- files = CollectionProperty(
+ files: CollectionProperty(
name="File Path",
type=OperatorFileListElement,
)
- directory = StringProperty(
+ directory: StringProperty(
subtype='DIR_PATH',
)
- global_scale = FloatProperty(
+ global_scale: FloatProperty(
name="Scale",
soft_min=0.001, soft_max=1000.0,
min=1e-6, max=1e6,
default=1.0,
)
- use_scene_unit = BoolProperty(
+ use_scene_unit: BoolProperty(
name="Scene Unit",
description="Apply current scene's unit (as defined by unit scale) to imported data",
default=False,
)
- use_facet_normal = BoolProperty(
+ use_facet_normal: BoolProperty(
name="Facet Normals",
description="Use (import) facet normals (note that this will still give flat shading)",
default=False,
@@ -135,7 +133,7 @@ class ImportSTL(Operator, ImportHelper, IOSTLOrientationHelper):
global_matrix = axis_conversion(from_forward=self.axis_forward,
from_up=self.axis_up,
- ).to_4x4() * Matrix.Scale(global_scale, 4)
+ ).to_4x4() @ Matrix.Scale(global_scale, 4)
if not paths:
paths.append(self.filepath)
@@ -155,41 +153,42 @@ class ImportSTL(Operator, ImportHelper, IOSTLOrientationHelper):
return {'FINISHED'}
-class ExportSTL(Operator, ExportHelper, IOSTLOrientationHelper):
+@orientation_helper(axis_forward='Y', axis_up='Z')
+class ExportSTL(Operator, ExportHelper):
"""Save STL triangle mesh data from the active object"""
bl_idname = "export_mesh.stl"
bl_label = "Export STL"
filename_ext = ".stl"
- filter_glob = StringProperty(default="*.stl", options={'HIDDEN'})
+ filter_glob: StringProperty(default="*.stl", options={'HIDDEN'})
- use_selection = BoolProperty(
+ use_selection: BoolProperty(
name="Selection Only",
description="Export selected objects only",
default=False,
)
- global_scale = FloatProperty(
+ global_scale: FloatProperty(
name="Scale",
min=0.01, max=1000.0,
default=1.0,
)
- use_scene_unit = BoolProperty(
+ use_scene_unit: BoolProperty(
name="Scene Unit",
description="Apply current scene's unit (as defined by unit scale) to exported data",
default=False,
)
- ascii = BoolProperty(
+ ascii: BoolProperty(
name="Ascii",
description="Save the file in ASCII file format",
default=False,
)
- use_mesh_modifiers = BoolProperty(
+ use_mesh_modifiers: BoolProperty(
name="Apply Modifiers",
description="Apply the modifiers before saving",
default=True,
)
- batch_mode = EnumProperty(
+ batch_mode: EnumProperty(
name="Batch Mode",
items=(('OFF', "Off", "All data in one file"),
('OBJECT', "Object", "Each object as a file"),
@@ -228,7 +227,7 @@ class ExportSTL(Operator, ExportHelper, IOSTLOrientationHelper):
global_matrix = axis_conversion(to_forward=self.axis_forward,
to_up=self.axis_up,
- ).to_4x4() * Matrix.Scale(global_scale, 4)
+ ).to_4x4() @ Matrix.Scale(global_scale, 4)
if self.batch_mode == 'OFF':
faces = itertools.chain.from_iterable(
@@ -252,22 +251,28 @@ def menu_import(self, context):
def menu_export(self, context):
- default_path = os.path.splitext(bpy.data.filepath)[0] + ".stl"
self.layout.operator(ExportSTL.bl_idname, text="Stl (.stl)")
+classes = (
+ ImportSTL,
+ ExportSTL
+)
+
def register():
- bpy.utils.register_module(__name__)
+ for cls in classes:
+ bpy.utils.register_class(cls)
- bpy.types.INFO_MT_file_import.append(menu_import)
- bpy.types.INFO_MT_file_export.append(menu_export)
+ bpy.types.TOPBAR_MT_file_import.append(menu_import)
+ bpy.types.TOPBAR_MT_file_export.append(menu_export)
def unregister():
- bpy.utils.unregister_module(__name__)
+ for cls in classes:
+ bpy.utils.unregister_class(cls)
- bpy.types.INFO_MT_file_import.remove(menu_import)
- bpy.types.INFO_MT_file_export.remove(menu_export)
+ bpy.types.TOPBAR_MT_file_import.remove(menu_import)
+ bpy.types.TOPBAR_MT_file_export.remove(menu_export)
if __name__ == "__main__":
diff --git a/io_mesh_stl/blender_utils.py b/io_mesh_stl/blender_utils.py
index 864335ab..fcc4889a 100644
--- a/io_mesh_stl/blender_utils.py
+++ b/io_mesh_stl/blender_utils.py
@@ -57,15 +57,13 @@ def create_and_link_mesh(name, faces, face_nors, points, global_matrix):
mesh.update()
- scene = bpy.context.scene
-
obj = bpy.data.objects.new(name, mesh)
- scene.objects.link(obj)
- scene.objects.active = obj
- obj.select = True
+ bpy.context.collection.objects.link(obj)
+ bpy.context.view_layer.objects.active = obj
+ obj.select_set(True)
-def faces_from_mesh(ob, global_matrix, use_mesh_modifiers=False, triangulate=True):
+def faces_from_mesh(ob, global_matrix, use_mesh_modifiers=False):
"""
From an object, return a generator over a list of faces.
@@ -84,34 +82,19 @@ def faces_from_mesh(ob, global_matrix, use_mesh_modifiers=False, triangulate=Tru
# get the modifiers
try:
- mesh = ob.to_mesh(bpy.context.scene, use_mesh_modifiers, "PREVIEW")
+ mesh = ob.to_mesh(bpy.context.depsgraph, use_mesh_modifiers)
except RuntimeError:
- raise StopIteration
+ return
- mat = global_matrix * ob.matrix_world
+ mat = global_matrix @ ob.matrix_world
mesh.transform(mat)
if mat.is_negative:
mesh.flip_normals()
- mesh.calc_tessface()
-
- if triangulate:
- # From a list of faces, return the face triangulated if needed.
- def iter_face_index():
- for face in mesh.tessfaces:
- vertices = face.vertices[:]
- if len(vertices) == 4:
- yield vertices[0], vertices[1], vertices[2]
- yield vertices[2], vertices[3], vertices[0]
- else:
- yield vertices
- else:
- def iter_face_index():
- for face in mesh.tessfaces:
- yield face.vertices[:]
+ mesh.calc_loop_triangles()
vertices = mesh.vertices
- for indexes in iter_face_index():
- yield [vertices[index].co.copy() for index in indexes]
+ for tri in mesh.loop_triangles:
+ yield [vertices[index].co.copy() for index in tri.vertices]
bpy.data.meshes.remove(mesh)
diff --git a/io_mesh_uv_layout/__init__.py b/io_mesh_uv_layout/__init__.py
index 30dff949..ad20a7a1 100644
--- a/io_mesh_uv_layout/__init__.py
+++ b/io_mesh_uv_layout/__init__.py
@@ -22,7 +22,7 @@ bl_info = {
"name": "UV Layout",
"author": "Campbell Barton, Matt Ebb",
"version": (1, 1, 1),
- "blender": (2, 75, 0),
+ "blender": (2, 80, 0),
"location": "Image-Window > UVs > Export UV Layout",
"description": "Export the UV layout as a 2D graphic",
"warning": "",
@@ -43,15 +43,16 @@ if "bpy" in locals():
if "export_uv_svg" in locals():
importlib.reload(export_uv_svg)
+import os
import bpy
from bpy.props import (
- StringProperty,
- BoolProperty,
- EnumProperty,
- IntVectorProperty,
- FloatProperty,
- )
+ StringProperty,
+ BoolProperty,
+ EnumProperty,
+ IntVectorProperty,
+ FloatProperty,
+)
class ExportUVLayout(bpy.types.Operator):
@@ -61,172 +62,171 @@ class ExportUVLayout(bpy.types.Operator):
bl_label = "Export UV Layout"
bl_options = {'REGISTER', 'UNDO'}
- filepath = StringProperty(
- subtype='FILE_PATH',
- )
- check_existing = BoolProperty(
- name="Check Existing",
- description="Check and warn on overwriting existing files",
- default=True,
- options={'HIDDEN'},
- )
- export_all = BoolProperty(
- name="All UVs",
- description="Export all UVs in this mesh (not just visible ones)",
- default=False,
- )
- modified = BoolProperty(
- name="Modified",
- description="Exports UVs from the modified mesh",
- default=False,
- )
- mode = EnumProperty(
- items=(('SVG', "Scalable Vector Graphic (.svg)",
- "Export the UV layout to a vector SVG file"),
- ('EPS', "Encapsulate PostScript (.eps)",
- "Export the UV layout to a vector EPS file"),
- ('PNG', "PNG Image (.png)",
- "Export the UV layout to a bitmap image"),
- ),
- name="Format",
- description="File format to export the UV layout to",
- default='PNG',
- )
- size = IntVectorProperty(
- size=2,
- default=(1024, 1024),
- min=8, max=32768,
- description="Dimensions of the exported file",
- )
- opacity = FloatProperty(
- name="Fill Opacity",
- min=0.0, max=1.0,
- default=0.25,
- description="Set amount of opacity for exported UV layout"
- )
- tessellated = BoolProperty(
- name="Tessellated UVs",
- description="Export tessellated UVs instead of polygons ones",
- default=False,
- options={'HIDDEN'}, # As not working currently :/
- )
+ filepath: StringProperty(
+ subtype='FILE_PATH',
+ )
+ export_all: BoolProperty(
+ name="All UVs",
+ description="Export all UVs in this mesh (not just visible ones)",
+ default=False,
+ )
+ modified: BoolProperty(
+ name="Modified",
+ description="Exports UVs from the modified mesh",
+ default=False,
+ )
+ mode: EnumProperty(
+ items=(
+ ('SVG', "Scalable Vector Graphic (.svg)",
+ "Export the UV layout to a vector SVG file"),
+ ('EPS', "Encapsulate PostScript (.eps)",
+ "Export the UV layout to a vector EPS file"),
+ ('PNG', "PNG Image (.png)",
+ "Export the UV layout to a bitmap image"),
+ ),
+ name="Format",
+ description="File format to export the UV layout to",
+ default='PNG',
+ )
+ size: IntVectorProperty(
+ size=2,
+ default=(1024, 1024),
+ min=8, max=32768,
+ description="Dimensions of the exported file",
+ )
+ opacity: FloatProperty(
+ name="Fill Opacity",
+ min=0.0, max=1.0,
+ default=0.25,
+ description="Set amount of opacity for exported UV layout",
+ )
@classmethod
def poll(cls, context):
obj = context.active_object
- return (obj and obj.type == 'MESH' and obj.data.uv_textures)
+ return obj is not None and obj.type == 'MESH' and obj.data.uv_layers
- def _space_image(self, context):
- space_data = context.space_data
- if isinstance(space_data, bpy.types.SpaceImageEditor):
- return space_data
- else:
- return None
-
- def _image_size(self, context, default_width=1024, default_height=1024):
- # fallback if not in image context.
- image_width, image_height = default_width, default_height
+ def invoke(self, context, event):
+ self.size = self.get_image_size(context)
+ self.filepath = self.get_default_file_name(context) + "." + self.mode.lower()
+ context.window_manager.fileselect_add(self)
+ return {'RUNNING_MODAL'}
- space_data = self._space_image(context)
- if space_data:
- image = space_data.image
- if image:
- width, height = tuple(context.space_data.image.size)
- # in case no data is found.
- if width and height:
- image_width, image_height = width, height
+ def get_default_file_name(self, context):
+ AMOUNT = 3
+ objects = list(self.iter_objects_to_export(context))
+ name = " ".join(sorted([obj.name for obj in objects[:AMOUNT]]))
+ if len(objects) > AMOUNT:
+ name += " and more"
+ return name
- return image_width, image_height
+ def check(self, context):
+ if any(self.filepath.endswith(ext) for ext in (".png", ".eps", ".svg")):
+ self.filepath = self.filepath[:-4]
- def _face_uv_iter(self, context, mesh, tessellated):
- uv_layer = mesh.uv_layers.active.data
- polys = mesh.polygons
-
- if not self.export_all:
- uv_tex = mesh.uv_textures.active.data
- local_image = Ellipsis
-
- if context.tool_settings.show_uv_local_view:
- space_data = self._space_image(context)
- if space_data:
- local_image = space_data.image
-
- for i, p in enumerate(polys):
- # context checks
- if polys[i].select and local_image in {Ellipsis,
- uv_tex[i].image}:
- start = p.loop_start
- end = start + p.loop_total
- uvs = tuple((uv.uv[0], uv.uv[1])
- for uv in uv_layer[start:end])
-
- # just write what we see.
- yield (i, uvs)
- else:
- # all, simple
- for i, p in enumerate(polys):
- start = p.loop_start
- end = start + p.loop_total
- uvs = tuple((uv.uv[0], uv.uv[1]) for uv in uv_layer[start:end])
- yield (i, uvs)
+ ext = "." + self.mode.lower()
+ self.filepath = bpy.path.ensure_ext(self.filepath, ext)
+ return True
def execute(self, context):
-
obj = context.active_object
is_editmode = (obj.mode == 'EDIT')
if is_editmode:
bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
- mode = self.mode
-
filepath = self.filepath
- filepath = bpy.path.ensure_ext(filepath, "." + mode.lower())
- file = open(filepath, "w")
- fw = file.write
-
- if mode == 'EPS':
- from . import export_uv_eps
- func = export_uv_eps.write
- elif mode == 'PNG':
- from . import export_uv_png
- func = export_uv_png.write
- elif mode == 'SVG':
- from . import export_uv_svg
- func = export_uv_svg.write
+ filepath = bpy.path.ensure_ext(filepath, "." + self.mode.lower())
+ meshes = list(self.iter_meshes_to_export(context))
+ polygon_data = list(self.iter_polygon_data_to_draw(context, meshes))
+ different_colors = set(color for _, color in polygon_data)
if self.modified:
- mesh = obj.to_mesh(context.scene, True, 'PREVIEW')
- else:
- mesh = obj.data
+ self.free_meshes(meshes)
- func(fw, mesh, self.size[0], self.size[1], self.opacity,
- lambda: self._face_uv_iter(context, mesh, self.tessellated))
-
- if self.modified:
- bpy.data.meshes.remove(mesh)
+ export = self.get_exporter()
+ export(filepath, polygon_data, different_colors, self.size[0], self.size[1], self.opacity)
if is_editmode:
bpy.ops.object.mode_set(mode='EDIT', toggle=False)
- file.close()
-
return {'FINISHED'}
- def check(self, context):
- filepath = bpy.path.ensure_ext(self.filepath, "." + self.mode.lower())
- if filepath != self.filepath:
- self.filepath = filepath
- return True
- else:
- return False
+ def iter_meshes_to_export(self, context):
+ for obj in self.iter_objects_to_export(context):
+ if self.modified:
+ yield obj.to_mesh(context.depsgraph, apply_modifiers=True)
+ else:
+ yield obj.data
+
+ @staticmethod
+ def iter_objects_to_export(context):
+ for obj in {*context.selected_objects, context.active_object}:
+ if obj.type != 'MESH':
+ continue
+ mesh = obj.data
+ if mesh.uv_layers.active is None:
+ continue
+ yield obj
- def invoke(self, context, event):
- import os
- self.size = self._image_size(context)
- self.filepath = os.path.splitext(bpy.data.filepath)[0]
- wm = context.window_manager
- wm.fileselect_add(self)
- return {'RUNNING_MODAL'}
+ @staticmethod
+ def free_meshes(meshes):
+ for mesh in meshes:
+ bpy.data.meshes.remove(mesh)
+
+ @staticmethod
+ def currently_image_image_editor(context):
+ return isinstance(context.space_data, bpy.types.SpaceImageEditor)
+
+ def get_currently_opened_image(self, context):
+ if not self.currently_image_image_editor(context):
+ return None
+ return context.space_data.image
+
+ def get_image_size(self, context):
+ # fallback if not in image context
+ image_width = self.size[0]
+ image_height = self.size[1]
+
+ # get size of "active" image if some exist
+ image = self.get_currently_opened_image(context)
+ if image is not None:
+ width, height = image.size
+ if width and height:
+ image_width = width
+ image_height = height
+
+ return image_width, image_height
+
+ def iter_polygon_data_to_draw(self, context, meshes):
+ for mesh in meshes:
+ uv_layer = mesh.uv_layers.active.data
+ for polygon in mesh.polygons:
+ if self.export_all or polygon.select:
+ start = polygon.loop_start
+ end = start + polygon.loop_total
+ uvs = tuple(tuple(uv.uv) for uv in uv_layer[start:end])
+ yield (uvs, self.get_polygon_color(mesh, polygon))
+
+ @staticmethod
+ def get_polygon_color(mesh, polygon, default=(0.8, 0.8, 0.8)):
+ if polygon.material_index < len(mesh.materials):
+ material = mesh.materials[polygon.material_index]
+ if material is not None:
+ return tuple(material.diffuse_color)
+ return default
+
+ def get_exporter(self):
+ if self.mode == 'PNG':
+ from . import export_uv_png
+ return export_uv_png.export
+ elif self.mode == 'EPS':
+ from . import export_uv_eps
+ return export_uv_eps.export
+ elif self.mode == 'SVG':
+ from . import export_uv_svg
+ return export_uv_svg.export
+ else:
+ assert False
def menu_func(self, context):
@@ -234,13 +234,14 @@ def menu_func(self, context):
def register():
- bpy.utils.register_module(__name__)
+ bpy.utils.register_class(ExportUVLayout)
bpy.types.IMAGE_MT_uvs.append(menu_func)
def unregister():
- bpy.utils.unregister_module(__name__)
+ bpy.utils.unregister_class(ExportUVLayout)
bpy.types.IMAGE_MT_uvs.remove(menu_func)
+
if __name__ == "__main__":
register()
diff --git a/io_mesh_uv_layout/export_uv_eps.py b/io_mesh_uv_layout/export_uv_eps.py
index a15dc266..3280cefa 100644
--- a/io_mesh_uv_layout/export_uv_eps.py
+++ b/io_mesh_uv_layout/export_uv_eps.py
@@ -21,66 +21,72 @@
import bpy
-def write(fw, mesh, image_width, image_height, opacity, face_iter_func):
- fw("%!PS-Adobe-3.0 EPSF-3.0\n")
- fw("%%%%Creator: Blender %s\n" % bpy.app.version_string)
- fw("%%Pages: 1\n")
- fw("%%Orientation: Portrait\n")
- fw("%%%%BoundingBox: 0 0 %d %d\n" % (image_width, image_height))
- fw("%%%%HiResBoundingBox: 0.0 0.0 %.4f %.4f\n" %
- (image_width, image_height))
- fw("%%EndComments\n")
- fw("%%Page: 1 1\n")
- fw("0 0 translate\n")
- fw("1.0 1.0 scale\n")
- fw("0 0 0 setrgbcolor\n")
- fw("[] 0 setdash\n")
- fw("1 setlinewidth\n")
- fw("1 setlinejoin\n")
- fw("1 setlinecap\n")
-
- polys = mesh.polygons
+def export(filepath, face_data, colors, width, height, opacity):
+ with open(filepath, "w") as file:
+ for text in get_file_parts(face_data, colors, width, height, opacity):
+ file.write(text)
+def get_file_parts(face_data, colors, width, height, opacity):
+ yield from header(width, height)
if opacity > 0.0:
- for i, mat in enumerate(mesh.materials if mesh.materials else [None]):
- fw("/DRAW_%d {" % i)
- fw("gsave\n")
- if mat:
- color = tuple((1.0 - ((1.0 - c) * opacity))
- for c in mat.diffuse_color)
- else:
- color = 1.0, 1.0, 1.0
- fw("%.3g %.3g %.3g setrgbcolor\n" % color)
- fw("fill\n")
- fw("grestore\n")
- fw("0 setgray\n")
- fw("} def\n")
+ name_by_color = {}
+ yield from prepare_colors(colors, name_by_color)
+ yield from draw_colored_polygons(face_data, name_by_color, width, height)
+ yield from draw_lines(face_data, width, height)
+ yield from footer()
+
+
+def header(width, height):
+ yield "%!PS-Adobe-3.0 EPSF-3.0\n"
+ yield f"%%Creator: Blender {bpy.app.version_string}\n"
+ yield "%%Pages: 1\n"
+ yield "%%Orientation: Portrait\n"
+ yield f"%%BoundingBox: 0 0 {width} {height}\n"
+ yield f"%%HiResBoundingBox: 0.0 0.0 {width:.4f} {height:.4f}\n"
+ yield "%%EndComments\n"
+ yield "%%Page: 1 1\n"
+ yield "0 0 translate\n"
+ yield "1.0 1.0 scale\n"
+ yield "0 0 0 setrgbcolor\n"
+ yield "[] 0 setdash\n"
+ yield "1 setlinewidth\n"
+ yield "1 setlinejoin\n"
+ yield "1 setlinecap\n"
+
+def prepare_colors(colors, out_name_by_color):
+ for i, color in enumerate(colors):
+ name = f"COLOR_{i}"
+ yield "/%s {" % name
+ out_name_by_color[color] = name
- # fill
- for i, uvs in face_iter_func():
- fw("newpath\n")
- for j, uv in enumerate(uvs):
- uv_scale = (uv[0] * image_width, uv[1] * image_height)
- if j == 0:
- fw("%.5f %.5f moveto\n" % uv_scale)
- else:
- fw("%.5f %.5f lineto\n" % uv_scale)
+ yield "gsave\n"
+ yield "%.3g %.3g %.3g setrgbcolor\n" % color
+ yield "fill\n"
+ yield "grestore\n"
+ yield "0 setgray\n"
+ yield "} def\n"
- fw("closepath\n")
- fw("DRAW_%d\n" % polys[i].material_index)
+def draw_colored_polygons(face_data, name_by_color, width, height):
+ for uvs, color in face_data:
+ yield from draw_polygon_path(uvs, width, height)
+ yield "closepath\n"
+ yield "%s\n" % name_by_color[color]
- # stroke only
- for i, uvs in face_iter_func():
- fw("newpath\n")
- for j, uv in enumerate(uvs):
- uv_scale = (uv[0] * image_width, uv[1] * image_height)
- if j == 0:
- fw("%.5f %.5f moveto\n" % uv_scale)
- else:
- fw("%.5f %.5f lineto\n" % uv_scale)
+def draw_lines(face_data, width, height):
+ for uvs, _ in face_data:
+ yield from draw_polygon_path(uvs, width, height)
+ yield "closepath\n"
+ yield "stroke\n"
- fw("closepath\n")
- fw("stroke\n")
+def draw_polygon_path(uvs, width, height):
+ yield "newpath\n"
+ for j, uv in enumerate(uvs):
+ uv_scale = (uv[0] * width, uv[1] * height)
+ if j == 0:
+ yield "%.5f %.5f moveto\n" % uv_scale
+ else:
+ yield "%.5f %.5f lineto\n" % uv_scale
- fw("showpage\n")
- fw("%%EOF\n")
+def footer():
+ yield "showpage\n"
+ yield "%%EOF\n" \ No newline at end of file
diff --git a/io_mesh_uv_layout/export_uv_png.py b/io_mesh_uv_layout/export_uv_png.py
index b556c982..8aa61151 100644
--- a/io_mesh_uv_layout/export_uv_png.py
+++ b/io_mesh_uv_layout/export_uv_png.py
@@ -19,133 +19,96 @@
# <pep8-80 compliant>
import bpy
-
-
-def write(fw, mesh_source, image_width, image_height, opacity, face_iter_func):
- filepath = fw.__self__.name
- fw.__self__.close()
-
- material_solids = [bpy.data.materials.new("uv_temp_solid")
- for i in range(max(1, len(mesh_source.materials)))]
-
- material_wire = bpy.data.materials.new("uv_temp_wire")
-
- scene = bpy.data.scenes.new("uv_temp")
- mesh = bpy.data.meshes.new("uv_temp")
- for mat_solid in material_solids:
- mesh.materials.append(mat_solid)
-
- polys_source = mesh_source.polygons
-
- # get unique UV's in case there are many overlapping
- # which slow down filling.
- face_hash = {(uvs, polys_source[i].material_index)
- for i, uvs in face_iter_func()}
-
- # now set the faces coords and locations
- # build mesh data
- mesh_new_vertices = []
- mesh_new_materials = []
- mesh_new_polys_startloop = []
- mesh_new_polys_totloop = []
- mesh_new_loops_vertices = []
-
- current_vert = 0
-
- for uvs, mat_idx in face_hash:
- num_verts = len(uvs)
- dummy = (0.0,) * num_verts
- for uv in uvs:
- mesh_new_vertices += (uv[0], uv[1], 0.0)
- mesh_new_polys_startloop.append(current_vert)
- mesh_new_polys_totloop.append(num_verts)
- mesh_new_loops_vertices += range(current_vert,
- current_vert + num_verts)
- mesh_new_materials.append(mat_idx)
- current_vert += num_verts
-
- mesh.vertices.add(current_vert)
- mesh.loops.add(current_vert)
- mesh.polygons.add(len(mesh_new_polys_startloop))
-
- mesh.vertices.foreach_set("co", mesh_new_vertices)
- mesh.loops.foreach_set("vertex_index", mesh_new_loops_vertices)
- mesh.polygons.foreach_set("loop_start", mesh_new_polys_startloop)
- mesh.polygons.foreach_set("loop_total", mesh_new_polys_totloop)
- mesh.polygons.foreach_set("material_index", mesh_new_materials)
-
- mesh.update(calc_edges=True)
-
- obj_solid = bpy.data.objects.new("uv_temp_solid", mesh)
- obj_wire = bpy.data.objects.new("uv_temp_wire", mesh)
- base_solid = scene.objects.link(obj_solid)
- base_wire = scene.objects.link(obj_wire)
- base_solid.layers[0] = True
- base_wire.layers[0] = True
-
- # place behind the wire
- obj_solid.location = 0, 0, -1
-
- obj_wire.material_slots[0].link = 'OBJECT'
- obj_wire.material_slots[0].material = material_wire
-
- # setup the camera
- cam = bpy.data.cameras.new("uv_temp")
- cam.type = 'ORTHO'
- cam.ortho_scale = 1.0
- obj_cam = bpy.data.objects.new("uv_temp_cam", cam)
- obj_cam.location = 0.5, 0.5, 1.0
- scene.objects.link(obj_cam)
- scene.camera = obj_cam
-
- # setup materials
- for i, mat_solid in enumerate(material_solids):
- if mesh_source.materials and mesh_source.materials[i]:
- mat_solid.diffuse_color = mesh_source.materials[i].diffuse_color
-
- mat_solid.use_shadeless = True
- mat_solid.use_transparency = True
- mat_solid.alpha = opacity
-
- material_wire.type = 'WIRE'
- material_wire.use_shadeless = True
- material_wire.diffuse_color = 0, 0, 0
- material_wire.use_transparency = True
-
- # scene render settings
- scene.render.use_raytrace = False
- scene.render.alpha_mode = 'TRANSPARENT'
- scene.render.image_settings.color_mode = 'RGBA'
-
- scene.render.resolution_x = image_width
- scene.render.resolution_y = image_height
- scene.render.resolution_percentage = 100
-
- if image_width > image_height:
- scene.render.pixel_aspect_y = image_width / image_height
- elif image_width < image_height:
- scene.render.pixel_aspect_x = image_height / image_width
-
- scene.frame_start = 1
- scene.frame_end = 1
-
- scene.render.image_settings.file_format = 'PNG'
- scene.render.filepath = filepath
-
- scene.update()
-
- data_context = {"blend_data": bpy.context.blend_data, "scene": scene}
- bpy.ops.render.render(data_context, write_still=True)
-
- # cleanup
- bpy.data.scenes.remove(scene, do_unlink=True)
- bpy.data.objects.remove(obj_cam, do_unlink=True)
- bpy.data.objects.remove(obj_solid, do_unlink=True)
- bpy.data.objects.remove(obj_wire, do_unlink=True)
-
- bpy.data.cameras.remove(cam, do_unlink=True)
- bpy.data.meshes.remove(mesh, do_unlink=True)
-
- bpy.data.materials.remove(material_wire, do_unlink=True)
- for mat_solid in material_solids:
- bpy.data.materials.remove(mat_solid, do_unlink=True)
+import gpu
+import bgl
+from mathutils import Vector, Matrix
+from mathutils.geometry import tessellate_polygon
+from gpu_extras.batch import batch_for_shader
+
+def export(filepath, face_data, colors, width, height, opacity):
+ offscreen = gpu.types.GPUOffScreen(width, height)
+ offscreen.bind()
+
+ try:
+ bgl.glClear(bgl.GL_COLOR_BUFFER_BIT)
+ draw_image(face_data, opacity)
+
+ pixel_data = get_pixel_data_from_current_back_buffer(width, height)
+ save_pixels(filepath, pixel_data, width, height)
+ finally:
+ offscreen.unbind()
+ offscreen.free()
+
+def draw_image(face_data, opacity):
+ bgl.glLineWidth(1)
+ bgl.glEnable(bgl.GL_BLEND)
+ bgl.glEnable(bgl.GL_LINE_SMOOTH)
+ bgl.glHint(bgl.GL_LINE_SMOOTH_HINT, bgl.GL_NICEST)
+
+ with gpu.matrix.push_pop():
+ gpu.matrix.load_matrix(get_normalize_uvs_matrix())
+ gpu.matrix.load_projection_matrix(Matrix.Identity(4))
+
+ draw_background_colors(face_data, opacity)
+ draw_lines(face_data)
+
+ bgl.glDisable(bgl.GL_BLEND)
+ bgl.glDisable(bgl.GL_LINE_SMOOTH)
+
+def get_normalize_uvs_matrix():
+ '''matrix maps x and y coordinates from [0, 1] to [-1, 1]'''
+ matrix = Matrix.Identity(4)
+ matrix.col[3][0] = -1
+ matrix.col[3][1] = -1
+ matrix[0][0] = 2
+ matrix[1][1] = 2
+ return matrix
+
+def draw_background_colors(face_data, opacity):
+ coords = [uv for uvs, _ in face_data for uv in uvs]
+ colors = [(*color, opacity) for uvs, color in face_data for _ in range(len(uvs))]
+
+ indices = []
+ offset = 0
+ for uvs, _ in face_data:
+ triangles = tessellate_uvs(uvs)
+ indices.extend([index + offset for index in triangle] for triangle in triangles)
+ offset += len(uvs)
+
+ shader = gpu.shader.from_builtin('2D_FLAT_COLOR')
+ batch = batch_for_shader(shader, 'TRIS',
+ {"pos" : coords,
+ "color" : colors},
+ indices=indices)
+ batch.draw(shader)
+
+def tessellate_uvs(uvs):
+ return tessellate_polygon([[Vector(uv) for uv in uvs]])
+
+def draw_lines(face_data):
+ coords = []
+ for uvs, _ in face_data:
+ for i in range(len(uvs)):
+ start = uvs[i]
+ end = uvs[(i+1) % len(uvs)]
+ coords.append((start[0], start[1]))
+ coords.append((end[0], end[1]))
+
+ shader = gpu.shader.from_builtin('2D_UNIFORM_COLOR')
+ batch = batch_for_shader(shader, 'LINES', {"pos" : coords})
+ shader.bind()
+ shader.uniform_float("color", (0, 0, 0, 1))
+ batch.draw(shader)
+
+def get_pixel_data_from_current_back_buffer(width, height):
+ buffer = bgl.Buffer(bgl.GL_BYTE, width * height * 4)
+ bgl.glReadBuffer(bgl.GL_BACK)
+ bgl.glReadPixels(0, 0, width, height, bgl.GL_RGBA, bgl.GL_UNSIGNED_BYTE, buffer)
+ return buffer
+
+def save_pixels(filepath, pixel_data, width, height):
+ image = bpy.data.images.new("temp", width, height, alpha=True)
+ image.filepath = filepath
+ image.pixels = [v / 255 for v in pixel_data]
+ image.save()
+ bpy.data.images.remove(image)
diff --git a/io_mesh_uv_layout/export_uv_svg.py b/io_mesh_uv_layout/export_uv_svg.py
index 764f0d34..d00f9402 100644
--- a/io_mesh_uv_layout/export_uv_svg.py
+++ b/io_mesh_uv_layout/export_uv_svg.py
@@ -19,50 +19,46 @@
# <pep8-80 compliant>
import bpy
+from os.path import basename
+from xml.sax.saxutils import escape
+def export(filepath, face_data, colors, width, height, opacity):
+ with open(filepath, "w") as file:
+ for text in get_file_parts(face_data, colors, width, height, opacity):
+ file.write(text)
-def write(fw, mesh, image_width, image_height, opacity, face_iter_func):
- # for making an XML compatible string
- from xml.sax.saxutils import escape
- from os.path import basename
+def get_file_parts(face_data, colors, width, height, opacity):
+ yield from header(width, height)
+ yield from draw_polygons(face_data, width, height, opacity)
+ yield from footer()
- fw('<?xml version="1.0" standalone="no"?>\n')
- fw('<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" \n')
- fw(' "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n')
- fw('<svg width="%d" height="%d" viewBox="0 0 %d %d"\n' %
- (image_width, image_height, image_width, image_height))
- fw(' xmlns="http://www.w3.org/2000/svg" version="1.1">\n')
- desc = ("%r, %s, (Blender %s)" %
- (basename(bpy.data.filepath), mesh.name, bpy.app.version_string))
- fw('<desc>%s</desc>\n' % escape(desc))
+def header(width, height):
+ yield '<?xml version="1.0" standalone="no"?>\n'
+ yield '<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" \n'
+ yield ' "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n'
+ yield f'<svg width="{width}" height="{height}" viewBox="0 0 {width} {height}"\n'
+ yield ' xmlns="http://www.w3.org/2000/svg" version="1.1">\n'
+ desc = f"{basename(bpy.data.filepath)}, (Blender {bpy.app.version_string})"
+ yield f'<desc>{escape(desc)}</desc>\n'
- # svg colors
- fill_settings = []
- fill_default = 'fill="grey"'
- for mat in mesh.materials if mesh.materials else [None]:
- if mat:
- fill_settings.append('fill="rgb(%d, %d, %d)"' %
- tuple(int(c * 255)
- for c in mat.diffuse_color))
- else:
- fill_settings.append(fill_default)
+def draw_polygons(face_data, width, height, opacity):
+ for uvs, color in face_data:
+ fill = f'fill="{get_color_string(color)}"'
- polys = mesh.polygons
- for i, uvs in face_iter_func():
- try: # rare cases material index is invalid.
- fill = fill_settings[polys[i].material_index]
- except IndexError:
- fill = fill_default
+ yield '<polygon stroke="black" stroke-width="1"'
+ yield f' {fill} fill-opacity="{opacity:.2g}"'
- fw('<polygon stroke="black" stroke-width="1"')
- if opacity > 0.0:
- fw(' %s fill-opacity="%.2g"' % (fill, opacity))
+ yield ' points="'
- fw(' points="')
-
- for j, uv in enumerate(uvs):
+ for uv in uvs:
x, y = uv[0], 1.0 - uv[1]
- fw('%.3f,%.3f ' % (x * image_width, y * image_height))
- fw('" />\n')
- fw('\n')
- fw('</svg>\n')
+ yield f'{x*width:.3f},{y*height:.3f} '
+ yield '" />\n'
+
+def get_color_string(color):
+ r, g, b = color
+ return f"rgb({round(r*255)}, {round(g*255)}, {round(b*255)})"
+
+def footer():
+ yield '\n'
+ yield '</svg>\n' \ No newline at end of file
diff --git a/io_online_sketchfab/__init__.py b/io_online_sketchfab/__init__.py
index ba73bd19..2205f497 100644
--- a/io_online_sketchfab/__init__.py
+++ b/io_online_sketchfab/__init__.py
@@ -247,7 +247,7 @@ class ExportSketchfab(Operator):
with open(SKETCHFAB_EXPORT_DATA_FILE, 'w') as s:
json.dump({
"models": props.models,
- "lamps": props.lamps,
+ "lights": props.lights,
}, s)
subprocess.check_call([
@@ -311,7 +311,7 @@ class VIEW3D_PT_sketchfab(Panel):
layout.label("Export:")
col = layout.box().column(align=True)
col.prop(props, "models")
- col.prop(props, "lamps")
+ col.prop(props, "lights")
layout.label("Model info:")
col = layout.box().column(align=True)
diff --git a/io_online_sketchfab/pack_for_export.py b/io_online_sketchfab/pack_for_export.py
index 587c94c2..972735c8 100644
--- a/io_online_sketchfab/pack_for_export.py
+++ b/io_online_sketchfab/pack_for_export.py
@@ -87,12 +87,12 @@ def prepare_assets(export_settings):
images.add(image)
if ((export_settings['models'] == 'SELECTION' and ob.type == 'MESH') or
- (export_settings['lamps'] == 'SELECTION' and ob.type == 'LAMP')):
+ (export_settings['lamps'] == 'SELECTION' and ob.type == 'LIGHT')):
if not ob.select and not ob.hide:
ob.hide = True
hidden.add(ob)
- elif export_settings['lamps'] == 'NONE' and ob.type == 'LAMP':
+ elif export_settings['lamps'] == 'NONE' and ob.type == 'LIGHT':
if not ob.hide:
ob.hide = True
hidden.add(ob)
diff --git a/io_scene_3ds/__init__.py b/io_scene_3ds/__init__.py
index 9236c07e..4daa4c29 100644
--- a/io_scene_3ds/__init__.py
+++ b/io_scene_3ds/__init__.py
@@ -50,15 +50,13 @@ from bpy.props import (
from bpy_extras.io_utils import (
ImportHelper,
ExportHelper,
- orientation_helper_factory,
+ orientation_helper,
axis_conversion,
)
-IO3DSOrientationHelper = orientation_helper_factory("IO3DSOrientationHelper", axis_forward='Y', axis_up='Z')
-
-
-class Import3DS(bpy.types.Operator, ImportHelper, IO3DSOrientationHelper):
+@orientation_helper(axis_forward='Y', axis_up='Z')
+class Import3DS(bpy.types.Operator, ImportHelper):
"""Import from 3DS file format (.3ds)"""
bl_idname = "import_scene.autodesk_3ds"
bl_label = 'Import 3DS'
@@ -104,7 +102,8 @@ class Import3DS(bpy.types.Operator, ImportHelper, IO3DSOrientationHelper):
return import_3ds.load(self, context, **keywords)
-class Export3DS(bpy.types.Operator, ExportHelper, IO3DSOrientationHelper):
+@orientation_helper(axis_forward='Y', axis_up='Z')
+class Export3DS(bpy.types.Operator, ExportHelper):
"""Export to 3DS file format (.3ds)"""
bl_idname = "export_scene.autodesk_3ds"
bl_label = 'Export 3DS'
@@ -149,15 +148,15 @@ def menu_func_import(self, context):
def register():
bpy.utils.register_module(__name__)
- bpy.types.INFO_MT_file_import.append(menu_func_import)
- bpy.types.INFO_MT_file_export.append(menu_func_export)
+ bpy.types.TOPBAR_MT_file_import.append(menu_func_import)
+ bpy.types.TOPBAR_MT_file_export.append(menu_func_export)
def unregister():
bpy.utils.unregister_module(__name__)
- bpy.types.INFO_MT_file_import.remove(menu_func_import)
- bpy.types.INFO_MT_file_export.remove(menu_func_export)
+ bpy.types.TOPBAR_MT_file_import.remove(menu_func_import)
+ bpy.types.TOPBAR_MT_file_export.remove(menu_func_export)
# NOTES:
# why add 1 extra vertex? and remove it when done? -
diff --git a/io_scene_3ds/import_3ds.py b/io_scene_3ds/import_3ds.py
index b9de42d5..28d7b25f 100644
--- a/io_scene_3ds/import_3ds.py
+++ b/io_scene_3ds/import_3ds.py
@@ -83,27 +83,27 @@ MAT_24BIT_COLOR = 0x0011 # color defined as 3 bytes
#>------ sub defines of OBJECT
OBJECT_MESH = 0x4100 # This lets us know that we are reading a new object
-OBJECT_LAMP = 0x4600 # This lets un know we are reading a light object
-OBJECT_LAMP_SPOT = 0x4610 # The light is a spotloght.
-OBJECT_LAMP_OFF = 0x4620 # The light off.
-OBJECT_LAMP_ATTENUATE = 0x4625
-OBJECT_LAMP_RAYSHADE = 0x4627
-OBJECT_LAMP_SHADOWED = 0x4630
-OBJECT_LAMP_LOCAL_SHADOW = 0x4640
-OBJECT_LAMP_LOCAL_SHADOW2 = 0x4641
-OBJECT_LAMP_SEE_CONE = 0x4650
-OBJECT_LAMP_SPOT_RECTANGULAR = 0x4651
-OBJECT_LAMP_SPOT_OVERSHOOT = 0x4652
-OBJECT_LAMP_SPOT_PROJECTOR = 0x4653
-OBJECT_LAMP_EXCLUDE = 0x4654
-OBJECT_LAMP_RANGE = 0x4655
-OBJECT_LAMP_ROLL = 0x4656
-OBJECT_LAMP_SPOT_ASPECT = 0x4657
-OBJECT_LAMP_RAY_BIAS = 0x4658
-OBJECT_LAMP_INNER_RANGE = 0x4659
-OBJECT_LAMP_OUTER_RANGE = 0x465A
-OBJECT_LAMP_MULTIPLIER = 0x465B
-OBJECT_LAMP_AMBIENT_LIGHT = 0x4680
+OBJECT_LIGHT = 0x4600 # This lets un know we are reading a light object
+OBJECT_LIGHT_SPOT = 0x4610 # The light is a spotloght.
+OBJECT_LIGHT_OFF = 0x4620 # The light off.
+OBJECT_LIGHT_ATTENUATE = 0x4625
+OBJECT_LIGHT_RAYSHADE = 0x4627
+OBJECT_LIGHT_SHADOWED = 0x4630
+OBJECT_LIGHT_LOCAL_SHADOW = 0x4640
+OBJECT_LIGHT_LOCAL_SHADOW2 = 0x4641
+OBJECT_LIGHT_SEE_CONE = 0x4650
+OBJECT_LIGHT_SPOT_RECTANGULAR = 0x4651
+OBJECT_LIGHT_SPOT_OVERSHOOT = 0x4652
+OBJECT_LIGHT_SPOT_PROJECTOR = 0x4653
+OBJECT_LIGHT_EXCLUDE = 0x4654
+OBJECT_LIGHT_RANGE = 0x4655
+OBJECT_LIGHT_ROLL = 0x4656
+OBJECT_LIGHT_SPOT_ASPECT = 0x4657
+OBJECT_LIGHT_RAY_BIAS = 0x4658
+OBJECT_LIGHT_INNER_RANGE = 0x4659
+OBJECT_LIGHT_OUTER_RANGE = 0x465A
+OBJECT_LIGHT_MULTIPLIER = 0x465B
+OBJECT_LIGHT_AMBIENT_LIGHT = 0x4680
OBJECT_CAMERA = 0x4700 # This lets un know we are reading a camera object
@@ -596,7 +596,7 @@ def process_next_chunk(file, previous_chunk, importedObjects, IMAGE_SEARCH):
new_chunk.bytes_read += temp_chunk.bytes_read
- elif new_chunk.ID == OBJECT_LAMP: # Basic lamp support.
+ elif new_chunk.ID == OBJECT_LIGHT: # Basic lamp support.
temp_data = file.read(STRUCT_SIZE_3FLOAT)
@@ -604,7 +604,7 @@ def process_next_chunk(file, previous_chunk, importedObjects, IMAGE_SEARCH):
new_chunk.bytes_read += STRUCT_SIZE_3FLOAT
# no lamp in dict that would be confusing
- contextLamp[1] = bpy.data.lamps.new("Lamp", 'POINT')
+ contextLamp[1] = bpy.data.lights.new("Lamp", 'POINT')
contextLamp[0] = ob = bpy.data.objects.new("Lamp", contextLamp[1])
SCN.objects.link(ob)
@@ -949,8 +949,8 @@ def load_3ds(filepath,
axis_min = [1000000000] * 3
axis_max = [-1000000000] * 3
- global_clamp_size = IMPORT_CONSTRAIN_BOUNDS
- if global_clamp_size != 0.0:
+ global_clight_size = IMPORT_CONSTRAIN_BOUNDS
+ if global_clight_size != 0.0:
# Get all object bounds
for ob in importedObjects:
for v in ob.bound_box:
@@ -966,7 +966,7 @@ def load_3ds(filepath,
axis_max[2] - axis_min[2])
scale = 1.0
- while global_clamp_size < max_axis * scale:
+ while global_clight_size < max_axis * scale:
scale = scale / 10.0
scale_mat = mathutils.Matrix.Scale(scale, 4)
diff --git a/io_scene_fbx/__init__.py b/io_scene_fbx/__init__.py
index fddeda6c..d415305d 100644
--- a/io_scene_fbx/__init__.py
+++ b/io_scene_fbx/__init__.py
@@ -21,8 +21,8 @@
bl_info = {
"name": "FBX format",
"author": "Campbell Barton, Bastien Montagne, Jens Restemeier",
- "version": (3, 10, 0),
- "blender": (2, 79, 1),
+ "version": (4, 13, 1),
+ "blender": (2, 80, 0),
"location": "File > Import-Export",
"description": "FBX IO meshes, UV's, vertex colors, materials, textures, cameras, lamps and actions",
"warning": "",
@@ -52,27 +52,25 @@ from bpy.props import (
from bpy_extras.io_utils import (
ImportHelper,
ExportHelper,
- orientation_helper_factory,
+ orientation_helper,
path_reference_mode,
axis_conversion,
)
-IOFBXOrientationHelper = orientation_helper_factory("IOFBXOrientationHelper", axis_forward='-Z', axis_up='Y')
-
-
-class ImportFBX(bpy.types.Operator, ImportHelper, IOFBXOrientationHelper):
+@orientation_helper(axis_forward='-Z', axis_up='Y')
+class ImportFBX(bpy.types.Operator, ImportHelper):
"""Load a FBX file"""
bl_idname = "import_scene.fbx"
bl_label = "Import FBX"
bl_options = {'UNDO', 'PRESET'}
- directory = StringProperty()
+ directory: StringProperty()
filename_ext = ".fbx"
- filter_glob = StringProperty(default="*.fbx", options={'HIDDEN'})
+ filter_glob: StringProperty(default="*.fbx", options={'HIDDEN'})
- ui_tab = EnumProperty(
+ ui_tab: EnumProperty(
items=(('MAIN', "Main", "Main basic settings"),
('ARMATURE', "Armatures", "Armature-related settings"),
),
@@ -80,17 +78,17 @@ class ImportFBX(bpy.types.Operator, ImportHelper, IOFBXOrientationHelper):
description="Import options categories",
)
- use_manual_orientation = BoolProperty(
+ use_manual_orientation: BoolProperty(
name="Manual Orientation",
description="Specify orientation and scale, instead of using embedded data in FBX file",
default=False,
)
- global_scale = FloatProperty(
+ global_scale: FloatProperty(
name="Scale",
min=0.001, max=1000.0,
default=1.0,
)
- bake_space_transform = BoolProperty(
+ bake_space_transform: BoolProperty(
name="!EXPERIMENTAL! Apply Transform",
description="Bake space transform into object data, avoids getting unwanted rotations to objects when "
"target space is not aligned with Blender's space "
@@ -98,69 +96,69 @@ class ImportFBX(bpy.types.Operator, ImportHelper, IOFBXOrientationHelper):
default=False,
)
- use_custom_normals = BoolProperty(
+ use_custom_normals: BoolProperty(
name="Import Normals",
description="Import custom normals, if available (otherwise Blender will recompute them)",
default=True,
)
- use_image_search = BoolProperty(
+ use_image_search: BoolProperty(
name="Image Search",
description="Search subdirs for any associated images (WARNING: may be slow)",
default=True,
)
- use_alpha_decals = BoolProperty(
+ use_alpha_decals: BoolProperty(
name="Alpha Decals",
description="Treat materials with alpha as decals (no shadow casting)",
default=False,
)
- decal_offset = FloatProperty(
+ decal_offset: FloatProperty(
name="Decal Offset",
description="Displace geometry of alpha meshes",
min=0.0, max=1.0,
default=0.0,
)
- use_anim = BoolProperty(
+ use_anim: BoolProperty(
name="Import Animation",
description="Import FBX animation",
default=True,
)
- anim_offset = FloatProperty(
+ anim_offset: FloatProperty(
name="Animation Offset",
description="Offset to apply to animation during import, in frames",
default=1.0,
)
- use_custom_props = BoolProperty(
+ use_custom_props: BoolProperty(
name="Import User Properties",
description="Import user properties as custom properties",
default=True,
)
- use_custom_props_enum_as_string = BoolProperty(
+ use_custom_props_enum_as_string: BoolProperty(
name="Import Enums As Strings",
description="Store enumeration values as strings",
default=True,
)
- ignore_leaf_bones = BoolProperty(
+ ignore_leaf_bones: BoolProperty(
name="Ignore Leaf Bones",
description="Ignore the last bone at the end of each chain (used to mark the length of the previous bone)",
default=False,
)
- force_connect_children = BoolProperty(
+ force_connect_children: BoolProperty(
name="Force Connect Children",
description="Force connection of children bones to their parent, even if their computed head/tail "
"positions do not match (can be useful with pure-joints-type armatures)",
default=False,
)
- automatic_bone_orientation = BoolProperty(
+ automatic_bone_orientation: BoolProperty(
name="Automatic Bone Orientation",
description="Try to align the major bone axis with the bone children",
default=False,
)
- primary_bone_axis = EnumProperty(
+ primary_bone_axis: EnumProperty(
name="Primary Bone Axis",
items=(('X', "X Axis", ""),
('Y', "Y Axis", ""),
@@ -171,7 +169,7 @@ class ImportFBX(bpy.types.Operator, ImportHelper, IOFBXOrientationHelper):
),
default='Y',
)
- secondary_bone_axis = EnumProperty(
+ secondary_bone_axis: EnumProperty(
name="Secondary Bone Axis",
items=(('X', "X Axis", ""),
('Y', "Y Axis", ""),
@@ -183,7 +181,7 @@ class ImportFBX(bpy.types.Operator, ImportHelper, IOFBXOrientationHelper):
default='X',
)
- use_prepost_rot = BoolProperty(
+ use_prepost_rot: BoolProperty(
name="Use Pre/Post Rotation",
description="Use pre/post rotation from FBX transform (you may have to disable that in some cases)",
default=True,
@@ -228,25 +226,25 @@ class ImportFBX(bpy.types.Operator, ImportHelper, IOFBXOrientationHelper):
def execute(self, context):
keywords = self.as_keywords(ignore=("filter_glob", "directory", "ui_tab"))
- keywords["use_cycles"] = (context.scene.render.engine == 'CYCLES')
from . import import_fbx
return import_fbx.load(self, context, **keywords)
-class ExportFBX(bpy.types.Operator, ExportHelper, IOFBXOrientationHelper):
+@orientation_helper(axis_forward='-Z', axis_up='Y')
+class ExportFBX(bpy.types.Operator, ExportHelper):
"""Write a FBX file"""
bl_idname = "export_scene.fbx"
bl_label = "Export FBX"
bl_options = {'UNDO', 'PRESET'}
filename_ext = ".fbx"
- filter_glob = StringProperty(default="*.fbx", options={'HIDDEN'})
+ filter_glob: StringProperty(default="*.fbx", options={'HIDDEN'})
# List of operator properties, the attributes will be assigned
# to the class instance from the operator settings before calling.
- ui_tab = EnumProperty(
+ ui_tab: EnumProperty(
items=(('MAIN', "Main", "Main basic settings"),
('GEOMETRY', "Geometries", "Geometry-related settings"),
('ARMATURE', "Armatures", "Armature-related settings"),
@@ -256,24 +254,29 @@ class ExportFBX(bpy.types.Operator, ExportHelper, IOFBXOrientationHelper):
description="Export options categories",
)
- use_selection = BoolProperty(
+ use_selection: BoolProperty(
name="Selected Objects",
- description="Export selected objects on visible layers",
+ description="Export selected and visible objects only",
+ default=False,
+ )
+ use_active_collection: BoolProperty(
+ name="Active Collection",
+ description="Export only objects from the active collection (and its children)",
default=False,
)
- global_scale = FloatProperty(
+ global_scale: FloatProperty(
name="Scale",
description="Scale all data (Some importers do not support scaled armatures!)",
min=0.001, max=1000.0,
soft_min=0.01, soft_max=1000.0,
default=1.0,
)
- apply_unit_scale = BoolProperty(
+ apply_unit_scale: BoolProperty(
name="Apply Unit",
description="Take into account current Blender units settings (if unset, raw Blender Units values are used as-is)",
default=True,
)
- apply_scale_options = EnumProperty(
+ apply_scale_options: EnumProperty(
items=(('FBX_SCALE_NONE', "All Local",
"Apply custom scaling and units scaling to each object transformation, FBX scale remains at 1.0"),
('FBX_SCALE_UNITS', "FBX Units Scale",
@@ -288,7 +291,7 @@ class ExportFBX(bpy.types.Operator, ExportHelper, IOFBXOrientationHelper):
"(Blender uses FBX scale to detect units on import, "
"but many other applications do not handle the same way)",
)
- bake_space_transform = BoolProperty(
+ bake_space_transform: BoolProperty(
name="!EXPERIMENTAL! Apply Transform",
description="Bake space transform into object data, avoids getting unwanted rotations to objects when "
"target space is not aligned with Blender's space "
@@ -296,32 +299,32 @@ class ExportFBX(bpy.types.Operator, ExportHelper, IOFBXOrientationHelper):
default=False,
)
- object_types = EnumProperty(
+ object_types: EnumProperty(
name="Object Types",
options={'ENUM_FLAG'},
items=(('EMPTY', "Empty", ""),
('CAMERA', "Camera", ""),
- ('LAMP', "Lamp", ""),
+ ('LIGHT', "Lamp", ""),
('ARMATURE', "Armature", "WARNING: not supported in dupli/group instances"),
('MESH', "Mesh", ""),
('OTHER', "Other", "Other geometry types, like curve, metaball, etc. (converted to meshes)"),
),
description="Which kind of object to export",
- default={'EMPTY', 'CAMERA', 'LAMP', 'ARMATURE', 'MESH', 'OTHER'},
+ default={'EMPTY', 'CAMERA', 'LIGHT', 'ARMATURE', 'MESH', 'OTHER'},
)
- use_mesh_modifiers = BoolProperty(
+ use_mesh_modifiers: BoolProperty(
name="Apply Modifiers",
description="Apply modifiers to mesh objects (except Armature ones) - "
"WARNING: prevents exporting shape keys",
default=True,
)
- use_mesh_modifiers_render = BoolProperty(
+ use_mesh_modifiers_render: BoolProperty(
name="Use Modifiers Render Setting",
description="Use render settings when applying modifiers to mesh objects",
default=True,
)
- mesh_smooth_type = EnumProperty(
+ mesh_smooth_type: EnumProperty(
name="Smoothing",
items=(('OFF', "Normals Only", "Export only normals instead of writing edge or face smoothing data"),
('FACE', "Face", "Write face smoothing"),
@@ -331,29 +334,29 @@ class ExportFBX(bpy.types.Operator, ExportHelper, IOFBXOrientationHelper):
"(prefer 'Normals Only' option if your target importer understand split normals)",
default='OFF',
)
- use_mesh_edges = BoolProperty(
+ use_mesh_edges: BoolProperty(
name="Loose Edges",
description="Export loose edges (as two-vertices polygons)",
default=False,
)
- use_tspace = BoolProperty(
+ use_tspace: BoolProperty(
name="Tangent Space",
description="Add binormal and tangent vectors, together with normal they form the tangent space "
"(will only work correctly with tris/quads only meshes!)",
default=False,
)
- use_custom_props = BoolProperty(
+ use_custom_props: BoolProperty(
name="Custom Properties",
description="Export custom properties",
default=False,
)
- add_leaf_bones = BoolProperty(
+ add_leaf_bones: BoolProperty(
name="Add Leaf Bones",
description="Append a final bone to the end of each chain to specify last bone length "
"(use this when you intend to edit the armature from exported data)",
default=True # False for commit!
)
- primary_bone_axis = EnumProperty(
+ primary_bone_axis: EnumProperty(
name="Primary Bone Axis",
items=(('X', "X Axis", ""),
('Y', "Y Axis", ""),
@@ -364,7 +367,7 @@ class ExportFBX(bpy.types.Operator, ExportHelper, IOFBXOrientationHelper):
),
default='Y',
)
- secondary_bone_axis = EnumProperty(
+ secondary_bone_axis: EnumProperty(
name="Secondary Bone Axis",
items=(('X', "X Axis", ""),
('Y', "Y Axis", ""),
@@ -375,12 +378,12 @@ class ExportFBX(bpy.types.Operator, ExportHelper, IOFBXOrientationHelper):
),
default='X',
)
- use_armature_deform_only = BoolProperty(
+ use_armature_deform_only: BoolProperty(
name="Only Deform Bones",
description="Only write deforming bones (and non-deforming ones when they have deforming children)",
default=False,
)
- armature_nodetype = EnumProperty(
+ armature_nodetype: EnumProperty(
name="Armature FBXNode Type",
items=(('NULL', "Null", "'Null' FBX node, similar to Blender's Empty (default)"),
('ROOT', "Root", "'Root' FBX node, supposed to be the root of chains of bones..."),
@@ -391,68 +394,75 @@ class ExportFBX(bpy.types.Operator, ExportHelper, IOFBXOrientationHelper):
"perfectly in Blender...)",
default='NULL',
)
- bake_anim = BoolProperty(
+ bake_anim: BoolProperty(
name="Baked Animation",
description="Export baked keyframe animation",
default=True,
)
- bake_anim_use_all_bones = BoolProperty(
+ bake_anim_use_all_bones: BoolProperty(
name="Key All Bones",
description="Force exporting at least one key of animation for all bones "
"(needed with some target applications, like UE4)",
default=True,
)
- bake_anim_use_nla_strips = BoolProperty(
+ bake_anim_use_nla_strips: BoolProperty(
name="NLA Strips",
description="Export each non-muted NLA strip as a separated FBX's AnimStack, if any, "
"instead of global scene animation",
default=True,
)
- bake_anim_use_all_actions = BoolProperty(
+ bake_anim_use_all_actions: BoolProperty(
name="All Actions",
description="Export each action as a separated FBX's AnimStack, instead of global scene animation "
"(note that animated objects will get all actions compatible with them, "
"others will get no animation at all)",
default=True,
)
- bake_anim_force_startend_keying = BoolProperty(
+ bake_anim_force_startend_keying: BoolProperty(
name="Force Start/End Keying",
description="Always add a keyframe at start and end of actions for animated channels",
default=True,
)
- bake_anim_step = FloatProperty(
+ bake_anim_step: FloatProperty(
name="Sampling Rate",
description="How often to evaluate animated values (in frames)",
min=0.01, max=100.0,
soft_min=0.1, soft_max=10.0,
default=1.0,
)
- bake_anim_simplify_factor = FloatProperty(
+ bake_anim_simplify_factor: FloatProperty(
name="Simplify",
description="How much to simplify baked values (0.0 to disable, the higher the more simplified)",
min=0.0, max=100.0, # No simplification to up to 10% of current magnitude tolerance.
soft_min=0.0, soft_max=10.0,
default=1.0, # default: min slope: 0.005, max frame step: 10.
)
- path_mode = path_reference_mode
- embed_textures = BoolProperty(
+ path_mode: path_reference_mode
+ embed_textures: BoolProperty(
name="Embed Textures",
description="Embed textures in FBX binary file (only for \"Copy\" path mode!)",
default=False,
)
- batch_mode = EnumProperty(
+ batch_mode: EnumProperty(
name="Batch Mode",
items=(('OFF', "Off", "Active scene to file"),
('SCENE', "Scene", "Each scene as a file"),
- ('GROUP', "Group", "Each group as a file"),
+ ('COLLECTION', "Collection",
+ "Each collection (data-block ones) as a file, does not include content of children collections"),
+ ('SCENE_COLLECTION', "Scene Collections",
+ "Each collection (including master, non-data-block ones) of each scene as a file, "
+ "including content from children collections"),
+ ('ACTIVE_SCENE_COLLECTION', "Active Scene Collections",
+ "Each collection (including master, non-data-block one) of the active scene as a file, "
+ "including content from children collections"),
),
)
- use_batch_own_dir = BoolProperty(
+ use_batch_own_dir: BoolProperty(
name="Batch Own Dir",
description="Create a dir for each exported file",
default=True,
)
- use_metadata = BoolProperty(
+ use_metadata: BoolProperty(
name="Use Metadata",
default=True,
options={'HIDDEN'},
@@ -464,6 +474,7 @@ class ExportFBX(bpy.types.Operator, ExportHelper, IOFBXOrientationHelper):
layout.prop(self, "ui_tab", expand=True)
if self.ui_tab == 'MAIN':
layout.prop(self, "use_selection")
+ layout.prop(self, "use_active_collection")
col = layout.column(align=True)
row = col.row(align=True)
@@ -559,13 +570,13 @@ def register():
for cls in classes:
bpy.utils.register_class(cls)
- bpy.types.INFO_MT_file_import.append(menu_func_import)
- bpy.types.INFO_MT_file_export.append(menu_func_export)
+ bpy.types.TOPBAR_MT_file_import.append(menu_func_import)
+ bpy.types.TOPBAR_MT_file_export.append(menu_func_export)
def unregister():
- bpy.types.INFO_MT_file_import.remove(menu_func_import)
- bpy.types.INFO_MT_file_export.remove(menu_func_export)
+ bpy.types.TOPBAR_MT_file_import.remove(menu_func_import)
+ bpy.types.TOPBAR_MT_file_export.remove(menu_func_export)
for cls in classes:
bpy.utils.unregister_class(cls)
diff --git a/io_scene_fbx/export_fbx_bin.py b/io_scene_fbx/export_fbx_bin.py
index ed1109e7..6f6005cb 100644
--- a/io_scene_fbx/export_fbx_bin.py
+++ b/io_scene_fbx/export_fbx_bin.py
@@ -27,7 +27,6 @@ import math
import os
import time
-from collections import OrderedDict
from itertools import zip_longest, chain
if "bpy" in locals():
@@ -41,6 +40,7 @@ if "bpy" in locals():
import bpy
import bpy_extras
+from bpy_extras import node_shader_utils
from mathutils import Vector, Matrix
from . import encode_bin, data_types, fbx_utils
@@ -75,6 +75,7 @@ from .fbx_utils import (
get_blender_bindpose_key, get_blender_armature_skin_key, get_blender_bone_cluster_key,
get_blender_anim_id_base, get_blender_anim_stack_key, get_blender_anim_layer_key,
get_blender_anim_curve_node_key, get_blender_anim_curve_key,
+ get_blender_nodetexture_key,
# FBX element data.
elem_empty,
elem_data_single_bool, elem_data_single_int16, elem_data_single_int32, elem_data_single_int64,
@@ -110,7 +111,7 @@ convert_rad_to_deg_iter = units_convertor_iter("radian", "degree")
# TODO: check all those "default" values, they should match Blender's default as much as possible, I guess?
def fbx_template_def_globalsettings(scene, settings, override_defaults=None, nbr_users=0):
- props = OrderedDict()
+ props = {}
if override_defaults is not None:
props.update(override_defaults)
return FBXTemplate(b"GlobalSettings", b"", props, nbr_users, [False])
@@ -118,91 +119,91 @@ def fbx_template_def_globalsettings(scene, settings, override_defaults=None, nbr
def fbx_template_def_model(scene, settings, override_defaults=None, nbr_users=0):
gscale = settings.global_scale
- props = OrderedDict((
- # Name, Value, Type, Animatable
- (b"QuaternionInterpolate", (0, "p_enum", False)), # 0 = no quat interpolation.
- (b"RotationOffset", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
- (b"RotationPivot", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
- (b"ScalingOffset", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
- (b"ScalingPivot", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
- (b"TranslationActive", (False, "p_bool", False)),
- (b"TranslationMin", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
- (b"TranslationMax", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
- (b"TranslationMinX", (False, "p_bool", False)),
- (b"TranslationMinY", (False, "p_bool", False)),
- (b"TranslationMinZ", (False, "p_bool", False)),
- (b"TranslationMaxX", (False, "p_bool", False)),
- (b"TranslationMaxY", (False, "p_bool", False)),
- (b"TranslationMaxZ", (False, "p_bool", False)),
- (b"RotationOrder", (0, "p_enum", False)), # we always use 'XYZ' order.
- (b"RotationSpaceForLimitOnly", (False, "p_bool", False)),
- (b"RotationStiffnessX", (0.0, "p_double", False)),
- (b"RotationStiffnessY", (0.0, "p_double", False)),
- (b"RotationStiffnessZ", (0.0, "p_double", False)),
- (b"AxisLen", (10.0, "p_double", False)),
- (b"PreRotation", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
- (b"PostRotation", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
- (b"RotationActive", (False, "p_bool", False)),
- (b"RotationMin", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
- (b"RotationMax", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
- (b"RotationMinX", (False, "p_bool", False)),
- (b"RotationMinY", (False, "p_bool", False)),
- (b"RotationMinZ", (False, "p_bool", False)),
- (b"RotationMaxX", (False, "p_bool", False)),
- (b"RotationMaxY", (False, "p_bool", False)),
- (b"RotationMaxZ", (False, "p_bool", False)),
- (b"InheritType", (0, "p_enum", False)), # RrSs
- (b"ScalingActive", (False, "p_bool", False)),
- (b"ScalingMin", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
- (b"ScalingMax", ((1.0, 1.0, 1.0), "p_vector_3d", False)),
- (b"ScalingMinX", (False, "p_bool", False)),
- (b"ScalingMinY", (False, "p_bool", False)),
- (b"ScalingMinZ", (False, "p_bool", False)),
- (b"ScalingMaxX", (False, "p_bool", False)),
- (b"ScalingMaxY", (False, "p_bool", False)),
- (b"ScalingMaxZ", (False, "p_bool", False)),
- (b"GeometricTranslation", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
- (b"GeometricRotation", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
- (b"GeometricScaling", ((1.0, 1.0, 1.0), "p_vector_3d", False)),
- (b"MinDampRangeX", (0.0, "p_double", False)),
- (b"MinDampRangeY", (0.0, "p_double", False)),
- (b"MinDampRangeZ", (0.0, "p_double", False)),
- (b"MaxDampRangeX", (0.0, "p_double", False)),
- (b"MaxDampRangeY", (0.0, "p_double", False)),
- (b"MaxDampRangeZ", (0.0, "p_double", False)),
- (b"MinDampStrengthX", (0.0, "p_double", False)),
- (b"MinDampStrengthY", (0.0, "p_double", False)),
- (b"MinDampStrengthZ", (0.0, "p_double", False)),
- (b"MaxDampStrengthX", (0.0, "p_double", False)),
- (b"MaxDampStrengthY", (0.0, "p_double", False)),
- (b"MaxDampStrengthZ", (0.0, "p_double", False)),
- (b"PreferedAngleX", (0.0, "p_double", False)),
- (b"PreferedAngleY", (0.0, "p_double", False)),
- (b"PreferedAngleZ", (0.0, "p_double", False)),
- (b"LookAtProperty", (None, "p_object", False)),
- (b"UpVectorProperty", (None, "p_object", False)),
- (b"Show", (True, "p_bool", False)),
- (b"NegativePercentShapeSupport", (True, "p_bool", False)),
- (b"DefaultAttributeIndex", (-1, "p_integer", False)),
- (b"Freeze", (False, "p_bool", False)),
- (b"LODBox", (False, "p_bool", False)),
- (b"Lcl Translation", ((0.0, 0.0, 0.0), "p_lcl_translation", True)),
- (b"Lcl Rotation", ((0.0, 0.0, 0.0), "p_lcl_rotation", True)),
- (b"Lcl Scaling", ((1.0, 1.0, 1.0), "p_lcl_scaling", True)),
- (b"Visibility", (1.0, "p_visibility", True)),
- (b"Visibility Inheritance", (1, "p_visibility_inheritance", False)),
- ))
+ props = {
+ # Name, Value, Type, Animatable
+ b"QuaternionInterpolate": (0, "p_enum", False), # 0 = no quat interpolation.
+ b"RotationOffset": ((0.0, 0.0, 0.0), "p_vector_3d", False),
+ b"RotationPivot": ((0.0, 0.0, 0.0), "p_vector_3d", False),
+ b"ScalingOffset": ((0.0, 0.0, 0.0), "p_vector_3d", False),
+ b"ScalingPivot": ((0.0, 0.0, 0.0), "p_vector_3d", False),
+ b"TranslationActive": (False, "p_bool", False),
+ b"TranslationMin": ((0.0, 0.0, 0.0), "p_vector_3d", False),
+ b"TranslationMax": ((0.0, 0.0, 0.0), "p_vector_3d", False),
+ b"TranslationMinX": (False, "p_bool", False),
+ b"TranslationMinY": (False, "p_bool", False),
+ b"TranslationMinZ": (False, "p_bool", False),
+ b"TranslationMaxX": (False, "p_bool", False),
+ b"TranslationMaxY": (False, "p_bool", False),
+ b"TranslationMaxZ": (False, "p_bool", False),
+ b"RotationOrder": (0, "p_enum", False), # we always use 'XYZ' order.
+ b"RotationSpaceForLimitOnly": (False, "p_bool", False),
+ b"RotationStiffnessX": (0.0, "p_double", False),
+ b"RotationStiffnessY": (0.0, "p_double", False),
+ b"RotationStiffnessZ": (0.0, "p_double", False),
+ b"AxisLen": (10.0, "p_double", False),
+ b"PreRotation": ((0.0, 0.0, 0.0), "p_vector_3d", False),
+ b"PostRotation": ((0.0, 0.0, 0.0), "p_vector_3d", False),
+ b"RotationActive": (False, "p_bool", False),
+ b"RotationMin": ((0.0, 0.0, 0.0), "p_vector_3d", False),
+ b"RotationMax": ((0.0, 0.0, 0.0), "p_vector_3d", False),
+ b"RotationMinX": (False, "p_bool", False),
+ b"RotationMinY": (False, "p_bool", False),
+ b"RotationMinZ": (False, "p_bool", False),
+ b"RotationMaxX": (False, "p_bool", False),
+ b"RotationMaxY": (False, "p_bool", False),
+ b"RotationMaxZ": (False, "p_bool", False),
+ b"InheritType": (0, "p_enum", False), # RrSs
+ b"ScalingActive": (False, "p_bool", False),
+ b"ScalingMin": ((0.0, 0.0, 0.0), "p_vector_3d", False),
+ b"ScalingMax": ((1.0, 1.0, 1.0), "p_vector_3d", False),
+ b"ScalingMinX": (False, "p_bool", False),
+ b"ScalingMinY": (False, "p_bool", False),
+ b"ScalingMinZ": (False, "p_bool", False),
+ b"ScalingMaxX": (False, "p_bool", False),
+ b"ScalingMaxY": (False, "p_bool", False),
+ b"ScalingMaxZ": (False, "p_bool", False),
+ b"GeometricTranslation": ((0.0, 0.0, 0.0), "p_vector_3d", False),
+ b"GeometricRotation": ((0.0, 0.0, 0.0), "p_vector_3d", False),
+ b"GeometricScaling": ((1.0, 1.0, 1.0), "p_vector_3d", False),
+ b"MinDampRangeX": (0.0, "p_double", False),
+ b"MinDampRangeY": (0.0, "p_double", False),
+ b"MinDampRangeZ": (0.0, "p_double", False),
+ b"MaxDampRangeX": (0.0, "p_double", False),
+ b"MaxDampRangeY": (0.0, "p_double", False),
+ b"MaxDampRangeZ": (0.0, "p_double", False),
+ b"MinDampStrengthX": (0.0, "p_double", False),
+ b"MinDampStrengthY": (0.0, "p_double", False),
+ b"MinDampStrengthZ": (0.0, "p_double", False),
+ b"MaxDampStrengthX": (0.0, "p_double", False),
+ b"MaxDampStrengthY": (0.0, "p_double", False),
+ b"MaxDampStrengthZ": (0.0, "p_double", False),
+ b"PreferedAngleX": (0.0, "p_double", False),
+ b"PreferedAngleY": (0.0, "p_double", False),
+ b"PreferedAngleZ": (0.0, "p_double", False),
+ b"LookAtProperty": (None, "p_object", False),
+ b"UpVectorProperty": (None, "p_object", False),
+ b"Show": (True, "p_bool", False),
+ b"NegativePercentShapeSupport": (True, "p_bool", False),
+ b"DefaultAttributeIndex": (-1, "p_integer", False),
+ b"Freeze": (False, "p_bool", False),
+ b"LODBox": (False, "p_bool", False),
+ b"Lcl Translation": ((0.0, 0.0, 0.0), "p_lcl_translation", True),
+ b"Lcl Rotation": ((0.0, 0.0, 0.0), "p_lcl_rotation", True),
+ b"Lcl Scaling": ((1.0, 1.0, 1.0), "p_lcl_scaling", True),
+ b"Visibility": (1.0, "p_visibility", True),
+ b"Visibility Inheritance": (1, "p_visibility_inheritance", False),
+ }
if override_defaults is not None:
props.update(override_defaults)
return FBXTemplate(b"Model", b"FbxNode", props, nbr_users, [False])
def fbx_template_def_null(scene, settings, override_defaults=None, nbr_users=0):
- props = OrderedDict((
- (b"Color", ((0.8, 0.8, 0.8), "p_color_rgb", False)),
- (b"Size", (100.0, "p_double", False)),
- (b"Look", (1, "p_enum", False)), # Cross (0 is None, i.e. invisible?).
- ))
+ props = {
+ b"Color": ((0.8, 0.8, 0.8), "p_color_rgb", False),
+ b"Size": (100.0, "p_double", False),
+ b"Look": (1, "p_enum", False), # Cross (0 is None, i.e. invisible?).
+ }
if override_defaults is not None:
props.update(override_defaults)
return FBXTemplate(b"NodeAttribute", b"FbxNull", props, nbr_users, [False])
@@ -210,17 +211,17 @@ def fbx_template_def_null(scene, settings, override_defaults=None, nbr_users=0):
def fbx_template_def_light(scene, settings, override_defaults=None, nbr_users=0):
gscale = settings.global_scale
- props = OrderedDict((
- (b"LightType", (0, "p_enum", False)), # Point light.
- (b"CastLight", (True, "p_bool", False)),
- (b"Color", ((1.0, 1.0, 1.0), "p_color", True)),
- (b"Intensity", (100.0, "p_number", True)), # Times 100 compared to Blender values...
- (b"DecayType", (2, "p_enum", False)), # Quadratic.
- (b"DecayStart", (30.0 * gscale, "p_double", False)),
- (b"CastShadows", (True, "p_bool", False)),
- (b"ShadowColor", ((0.0, 0.0, 0.0), "p_color", True)),
- (b"AreaLightShape", (0, "p_enum", False)), # Rectangle.
- ))
+ props = {
+ b"LightType": (0, "p_enum", False), # Point light.
+ b"CastLight": (True, "p_bool", False),
+ b"Color": ((1.0, 1.0, 1.0), "p_color", True),
+ b"Intensity": (100.0, "p_number", True), # Times 100 compared to Blender values...
+ b"DecayType": (2, "p_enum", False), # Quadratic.
+ b"DecayStart": (30.0 * gscale, "p_double", False),
+ b"CastShadows": (True, "p_bool", False),
+ b"ShadowColor": ((0.0, 0.0, 0.0), "p_color", True),
+ b"AreaLightShape": (0, "p_enum", False), # Rectangle.
+ }
if override_defaults is not None:
props.update(override_defaults)
return FBXTemplate(b"NodeAttribute", b"FbxLight", props, nbr_users, [False])
@@ -228,137 +229,137 @@ def fbx_template_def_light(scene, settings, override_defaults=None, nbr_users=0)
def fbx_template_def_camera(scene, settings, override_defaults=None, nbr_users=0):
r = scene.render
- props = OrderedDict((
- (b"Color", ((0.8, 0.8, 0.8), "p_color_rgb", False)),
- (b"Position", ((0.0, 0.0, -50.0), "p_vector", True)),
- (b"UpVector", ((0.0, 1.0, 0.0), "p_vector", True)),
- (b"InterestPosition", ((0.0, 0.0, 0.0), "p_vector", True)),
- (b"Roll", (0.0, "p_roll", True)),
- (b"OpticalCenterX", (0.0, "p_opticalcenterx", True)),
- (b"OpticalCenterY", (0.0, "p_opticalcentery", True)),
- (b"BackgroundColor", ((0.63, 0.63, 0.63), "p_color", True)),
- (b"TurnTable", (0.0, "p_number", True)),
- (b"DisplayTurnTableIcon", (False, "p_bool", False)),
- (b"UseMotionBlur", (False, "p_bool", False)),
- (b"UseRealTimeMotionBlur", (True, "p_bool", False)),
- (b"Motion Blur Intensity", (1.0, "p_number", True)),
- (b"AspectRatioMode", (0, "p_enum", False)), # WindowSize.
- (b"AspectWidth", (320.0, "p_double", False)),
- (b"AspectHeight", (200.0, "p_double", False)),
- (b"PixelAspectRatio", (1.0, "p_double", False)),
- (b"FilmOffsetX", (0.0, "p_number", True)),
- (b"FilmOffsetY", (0.0, "p_number", True)),
- (b"FilmWidth", (0.816, "p_double", False)),
- (b"FilmHeight", (0.612, "p_double", False)),
- (b"FilmAspectRatio", (1.3333333333333333, "p_double", False)),
- (b"FilmSqueezeRatio", (1.0, "p_double", False)),
- (b"FilmFormatIndex", (0, "p_enum", False)), # Assuming this is ApertureFormat, 0 = custom.
- (b"PreScale", (1.0, "p_number", True)),
- (b"FilmTranslateX", (0.0, "p_number", True)),
- (b"FilmTranslateY", (0.0, "p_number", True)),
- (b"FilmRollPivotX", (0.0, "p_number", True)),
- (b"FilmRollPivotY", (0.0, "p_number", True)),
- (b"FilmRollValue", (0.0, "p_number", True)),
- (b"FilmRollOrder", (0, "p_enum", False)), # 0 = rotate first (default).
- (b"ApertureMode", (2, "p_enum", False)), # 2 = Vertical.
- (b"GateFit", (0, "p_enum", False)), # 0 = no resolution gate fit.
- (b"FieldOfView", (25.114999771118164, "p_fov", True)),
- (b"FieldOfViewX", (40.0, "p_fov_x", True)),
- (b"FieldOfViewY", (40.0, "p_fov_y", True)),
- (b"FocalLength", (34.89327621672628, "p_number", True)),
- (b"CameraFormat", (0, "p_enum", False)), # Custom camera format.
- (b"UseFrameColor", (False, "p_bool", False)),
- (b"FrameColor", ((0.3, 0.3, 0.3), "p_color_rgb", False)),
- (b"ShowName", (True, "p_bool", False)),
- (b"ShowInfoOnMoving", (True, "p_bool", False)),
- (b"ShowGrid", (True, "p_bool", False)),
- (b"ShowOpticalCenter", (False, "p_bool", False)),
- (b"ShowAzimut", (True, "p_bool", False)),
- (b"ShowTimeCode", (False, "p_bool", False)),
- (b"ShowAudio", (False, "p_bool", False)),
- (b"AudioColor", ((0.0, 1.0, 0.0), "p_vector_3d", False)), # Yep, vector3d, not corlorgb… :cry:
- (b"NearPlane", (10.0, "p_double", False)),
- (b"FarPlane", (4000.0, "p_double", False)),
- (b"AutoComputeClipPanes", (False, "p_bool", False)),
- (b"ViewCameraToLookAt", (True, "p_bool", False)),
- (b"ViewFrustumNearFarPlane", (False, "p_bool", False)),
- (b"ViewFrustumBackPlaneMode", (2, "p_enum", False)), # 2 = show back plane if texture added.
- (b"BackPlaneDistance", (4000.0, "p_number", True)),
- (b"BackPlaneDistanceMode", (1, "p_enum", False)), # 1 = relative to camera.
- (b"ViewFrustumFrontPlaneMode", (2, "p_enum", False)), # 2 = show front plane if texture added.
- (b"FrontPlaneDistance", (10.0, "p_number", True)),
- (b"FrontPlaneDistanceMode", (1, "p_enum", False)), # 1 = relative to camera.
- (b"LockMode", (False, "p_bool", False)),
- (b"LockInterestNavigation", (False, "p_bool", False)),
+ props = {
+ b"Color": ((0.8, 0.8, 0.8), "p_color_rgb", False),
+ b"Position": ((0.0, 0.0, -50.0), "p_vector", True),
+ b"UpVector": ((0.0, 1.0, 0.0), "p_vector", True),
+ b"InterestPosition": ((0.0, 0.0, 0.0), "p_vector", True),
+ b"Roll": (0.0, "p_roll", True),
+ b"OpticalCenterX": (0.0, "p_opticalcenterx", True),
+ b"OpticalCenterY": (0.0, "p_opticalcentery", True),
+ b"BackgroundColor": ((0.63, 0.63, 0.63), "p_color", True),
+ b"TurnTable": (0.0, "p_number", True),
+ b"DisplayTurnTableIcon": (False, "p_bool", False),
+ b"UseMotionBlur": (False, "p_bool", False),
+ b"UseRealTimeMotionBlur": (True, "p_bool", False),
+ b"Motion Blur Intensity": (1.0, "p_number", True),
+ b"AspectRatioMode": (0, "p_enum", False), # WindowSize.
+ b"AspectWidth": (320.0, "p_double", False),
+ b"AspectHeight": (200.0, "p_double", False),
+ b"PixelAspectRatio": (1.0, "p_double", False),
+ b"FilmOffsetX": (0.0, "p_number", True),
+ b"FilmOffsetY": (0.0, "p_number", True),
+ b"FilmWidth": (0.816, "p_double", False),
+ b"FilmHeight": (0.612, "p_double", False),
+ b"FilmAspectRatio": (1.3333333333333333, "p_double", False),
+ b"FilmSqueezeRatio": (1.0, "p_double", False),
+ b"FilmFormatIndex": (0, "p_enum", False), # Assuming this is ApertureFormat, 0 = custom.
+ b"PreScale": (1.0, "p_number", True),
+ b"FilmTranslateX": (0.0, "p_number", True),
+ b"FilmTranslateY": (0.0, "p_number", True),
+ b"FilmRollPivotX": (0.0, "p_number", True),
+ b"FilmRollPivotY": (0.0, "p_number", True),
+ b"FilmRollValue": (0.0, "p_number", True),
+ b"FilmRollOrder": (0, "p_enum", False), # 0 = rotate first (default).
+ b"ApertureMode": (2, "p_enum", False), # 2 = Vertical.
+ b"GateFit": (0, "p_enum", False), # 0 = no resolution gate fit.
+ b"FieldOfView": (25.114999771118164, "p_fov", True),
+ b"FieldOfViewX": (40.0, "p_fov_x", True),
+ b"FieldOfViewY": (40.0, "p_fov_y", True),
+ b"FocalLength": (34.89327621672628, "p_number", True),
+ b"CameraFormat": (0, "p_enum", False), # Custom camera format.
+ b"UseFrameColor": (False, "p_bool", False),
+ b"FrameColor": ((0.3, 0.3, 0.3), "p_color_rgb", False),
+ b"ShowName": (True, "p_bool", False),
+ b"ShowInfoOnMoving": (True, "p_bool", False),
+ b"ShowGrid": (True, "p_bool", False),
+ b"ShowOpticalCenter": (False, "p_bool", False),
+ b"ShowAzimut": (True, "p_bool", False),
+ b"ShowTimeCode": (False, "p_bool", False),
+ b"ShowAudio": (False, "p_bool", False),
+ b"AudioColor": ((0.0, 1.0, 0.0), "p_vector_3d", False), # Yep, vector3d, not corlorgb… :cry:
+ b"NearPlane": (10.0, "p_double", False),
+ b"FarPlane": (4000.0, "p_double", False),
+ b"AutoComputeClipPanes": (False, "p_bool", False),
+ b"ViewCameraToLookAt": (True, "p_bool", False),
+ b"ViewFrustumNearFarPlane": (False, "p_bool", False),
+ b"ViewFrustumBackPlaneMode": (2, "p_enum", False), # 2 = show back plane if texture added.
+ b"BackPlaneDistance": (4000.0, "p_number", True),
+ b"BackPlaneDistanceMode": (1, "p_enum", False), # 1 = relative to camera.
+ b"ViewFrustumFrontPlaneMode": (2, "p_enum", False), # 2 = show front plane if texture added.
+ b"FrontPlaneDistance": (10.0, "p_number", True),
+ b"FrontPlaneDistanceMode": (1, "p_enum", False), # 1 = relative to camera.
+ b"LockMode": (False, "p_bool", False),
+ b"LockInterestNavigation": (False, "p_bool", False),
# BackPlate... properties **arggggg!**
- (b"FitImage", (False, "p_bool", False)),
- (b"Crop", (False, "p_bool", False)),
- (b"Center", (True, "p_bool", False)),
- (b"KeepRatio", (True, "p_bool", False)),
+ b"FitImage": (False, "p_bool", False),
+ b"Crop": (False, "p_bool", False),
+ b"Center": (True, "p_bool", False),
+ b"KeepRatio": (True, "p_bool", False),
# End of BackPlate...
- (b"BackgroundAlphaTreshold", (0.5, "p_double", False)),
- (b"ShowBackplate", (True, "p_bool", False)),
- (b"BackPlaneOffsetX", (0.0, "p_number", True)),
- (b"BackPlaneOffsetY", (0.0, "p_number", True)),
- (b"BackPlaneRotation", (0.0, "p_number", True)),
- (b"BackPlaneScaleX", (1.0, "p_number", True)),
- (b"BackPlaneScaleY", (1.0, "p_number", True)),
- (b"Background Texture", (None, "p_object", False)),
- (b"FrontPlateFitImage", (True, "p_bool", False)),
- (b"FrontPlateCrop", (False, "p_bool", False)),
- (b"FrontPlateCenter", (True, "p_bool", False)),
- (b"FrontPlateKeepRatio", (True, "p_bool", False)),
- (b"Foreground Opacity", (1.0, "p_double", False)),
- (b"ShowFrontplate", (True, "p_bool", False)),
- (b"FrontPlaneOffsetX", (0.0, "p_number", True)),
- (b"FrontPlaneOffsetY", (0.0, "p_number", True)),
- (b"FrontPlaneRotation", (0.0, "p_number", True)),
- (b"FrontPlaneScaleX", (1.0, "p_number", True)),
- (b"FrontPlaneScaleY", (1.0, "p_number", True)),
- (b"Foreground Texture", (None, "p_object", False)),
- (b"DisplaySafeArea", (False, "p_bool", False)),
- (b"DisplaySafeAreaOnRender", (False, "p_bool", False)),
- (b"SafeAreaDisplayStyle", (1, "p_enum", False)), # 1 = rounded corners.
- (b"SafeAreaAspectRatio", (1.3333333333333333, "p_double", False)),
- (b"Use2DMagnifierZoom", (False, "p_bool", False)),
- (b"2D Magnifier Zoom", (100.0, "p_number", True)),
- (b"2D Magnifier X", (50.0, "p_number", True)),
- (b"2D Magnifier Y", (50.0, "p_number", True)),
- (b"CameraProjectionType", (0, "p_enum", False)), # 0 = perspective, 1 = orthogonal.
- (b"OrthoZoom", (1.0, "p_double", False)),
- (b"UseRealTimeDOFAndAA", (False, "p_bool", False)),
- (b"UseDepthOfField", (False, "p_bool", False)),
- (b"FocusSource", (0, "p_enum", False)), # 0 = camera interest, 1 = distance from camera interest.
- (b"FocusAngle", (3.5, "p_double", False)), # ???
- (b"FocusDistance", (200.0, "p_double", False)),
- (b"UseAntialiasing", (False, "p_bool", False)),
- (b"AntialiasingIntensity", (0.77777, "p_double", False)),
- (b"AntialiasingMethod", (0, "p_enum", False)), # 0 = oversampling, 1 = hardware.
- (b"UseAccumulationBuffer", (False, "p_bool", False)),
- (b"FrameSamplingCount", (7, "p_integer", False)),
- (b"FrameSamplingType", (1, "p_enum", False)), # 0 = uniform, 1 = stochastic.
- ))
+ b"BackgroundAlphaTreshold": (0.5, "p_double", False),
+ b"ShowBackplate": (True, "p_bool", False),
+ b"BackPlaneOffsetX": (0.0, "p_number", True),
+ b"BackPlaneOffsetY": (0.0, "p_number", True),
+ b"BackPlaneRotation": (0.0, "p_number", True),
+ b"BackPlaneScaleX": (1.0, "p_number", True),
+ b"BackPlaneScaleY": (1.0, "p_number", True),
+ b"Background Texture": (None, "p_object", False),
+ b"FrontPlateFitImage": (True, "p_bool", False),
+ b"FrontPlateCrop": (False, "p_bool", False),
+ b"FrontPlateCenter": (True, "p_bool", False),
+ b"FrontPlateKeepRatio": (True, "p_bool", False),
+ b"Foreground Opacity": (1.0, "p_double", False),
+ b"ShowFrontplate": (True, "p_bool", False),
+ b"FrontPlaneOffsetX": (0.0, "p_number", True),
+ b"FrontPlaneOffsetY": (0.0, "p_number", True),
+ b"FrontPlaneRotation": (0.0, "p_number", True),
+ b"FrontPlaneScaleX": (1.0, "p_number", True),
+ b"FrontPlaneScaleY": (1.0, "p_number", True),
+ b"Foreground Texture": (None, "p_object", False),
+ b"DisplaySafeArea": (False, "p_bool", False),
+ b"DisplaySafeAreaOnRender": (False, "p_bool", False),
+ b"SafeAreaDisplayStyle": (1, "p_enum", False), # 1 = rounded corners.
+ b"SafeAreaAspectRatio": (1.3333333333333333, "p_double", False),
+ b"Use2DMagnifierZoom": (False, "p_bool", False),
+ b"2D Magnifier Zoom": (100.0, "p_number", True),
+ b"2D Magnifier X": (50.0, "p_number", True),
+ b"2D Magnifier Y": (50.0, "p_number", True),
+ b"CameraProjectionType": (0, "p_enum", False), # 0 = perspective, 1 = orthogonal.
+ b"OrthoZoom": (1.0, "p_double", False),
+ b"UseRealTimeDOFAndAA": (False, "p_bool", False),
+ b"UseDepthOfField": (False, "p_bool", False),
+ b"FocusSource": (0, "p_enum", False), # 0 = camera interest, 1 = distance from camera interest.
+ b"FocusAngle": (3.5, "p_double", False), # ???
+ b"FocusDistance": (200.0, "p_double", False),
+ b"UseAntialiasing": (False, "p_bool", False),
+ b"AntialiasingIntensity": (0.77777, "p_double", False),
+ b"AntialiasingMethod": (0, "p_enum", False), # 0 = oversampling, 1 = hardware.
+ b"UseAccumulationBuffer": (False, "p_bool", False),
+ b"FrameSamplingCount": (7, "p_integer", False),
+ b"FrameSamplingType": (1, "p_enum", False), # 0 = uniform, 1 = stochastic.
+ }
if override_defaults is not None:
props.update(override_defaults)
return FBXTemplate(b"NodeAttribute", b"FbxCamera", props, nbr_users, [False])
def fbx_template_def_bone(scene, settings, override_defaults=None, nbr_users=0):
- props = OrderedDict()
+ props = {}
if override_defaults is not None:
props.update(override_defaults)
return FBXTemplate(b"NodeAttribute", b"LimbNode", props, nbr_users, [False])
def fbx_template_def_geometry(scene, settings, override_defaults=None, nbr_users=0):
- props = OrderedDict((
- (b"Color", ((0.8, 0.8, 0.8), "p_color_rgb", False)),
- (b"BBoxMin", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
- (b"BBoxMax", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
- (b"Primary Visibility", (True, "p_bool", False)),
- (b"Casts Shadows", (True, "p_bool", False)),
- (b"Receive Shadows", (True, "p_bool", False)),
- ))
+ props = {
+ b"Color": ((0.8, 0.8, 0.8), "p_color_rgb", False),
+ b"BBoxMin": ((0.0, 0.0, 0.0), "p_vector_3d", False),
+ b"BBoxMax": ((0.0, 0.0, 0.0), "p_vector_3d", False),
+ b"Primary Visibility": (True, "p_bool", False),
+ b"Casts Shadows": (True, "p_bool", False),
+ b"Receive Shadows": (True, "p_bool", False),
+ }
if override_defaults is not None:
props.update(override_defaults)
return FBXTemplate(b"Geometry", b"FbxMesh", props, nbr_users, [False])
@@ -366,37 +367,37 @@ def fbx_template_def_geometry(scene, settings, override_defaults=None, nbr_users
def fbx_template_def_material(scene, settings, override_defaults=None, nbr_users=0):
# WIP...
- props = OrderedDict((
- (b"ShadingModel", ("Phong", "p_string", False)),
- (b"MultiLayer", (False, "p_bool", False)),
+ props = {
+ b"ShadingModel": ("Phong", "p_string", False),
+ b"MultiLayer": (False, "p_bool", False),
# Lambert-specific.
- (b"EmissiveColor", ((0.0, 0.0, 0.0), "p_color", True)),
- (b"EmissiveFactor", (1.0, "p_number", True)),
- (b"AmbientColor", ((0.2, 0.2, 0.2), "p_color", True)),
- (b"AmbientFactor", (1.0, "p_number", True)),
- (b"DiffuseColor", ((0.8, 0.8, 0.8), "p_color", True)),
- (b"DiffuseFactor", (1.0, "p_number", True)),
- (b"TransparentColor", ((0.0, 0.0, 0.0), "p_color", True)),
- (b"TransparencyFactor", (0.0, "p_number", True)),
- (b"Opacity", (1.0, "p_number", True)),
- (b"NormalMap", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
- (b"Bump", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
- (b"BumpFactor", (1.0, "p_double", False)),
- (b"DisplacementColor", ((0.0, 0.0, 0.0), "p_color_rgb", False)),
- (b"DisplacementFactor", (1.0, "p_double", False)),
- (b"VectorDisplacementColor", ((0.0, 0.0, 0.0), "p_color_rgb", False)),
- (b"VectorDisplacementFactor", (1.0, "p_double", False)),
+ b"EmissiveColor": ((0.0, 0.0, 0.0), "p_color", True),
+ b"EmissiveFactor": (1.0, "p_number", True),
+ b"AmbientColor": ((0.2, 0.2, 0.2), "p_color", True),
+ b"AmbientFactor": (1.0, "p_number", True),
+ b"DiffuseColor": ((0.8, 0.8, 0.8), "p_color", True),
+ b"DiffuseFactor": (1.0, "p_number", True),
+ b"TransparentColor": ((0.0, 0.0, 0.0), "p_color", True),
+ b"TransparencyFactor": (0.0, "p_number", True),
+ b"Opacity": (1.0, "p_number", True),
+ b"NormalMap": ((0.0, 0.0, 0.0), "p_vector_3d", False),
+ b"Bump": ((0.0, 0.0, 0.0), "p_vector_3d", False),
+ b"BumpFactor": (1.0, "p_double", False),
+ b"DisplacementColor": ((0.0, 0.0, 0.0), "p_color_rgb", False),
+ b"DisplacementFactor": (1.0, "p_double", False),
+ b"VectorDisplacementColor": ((0.0, 0.0, 0.0), "p_color_rgb", False),
+ b"VectorDisplacementFactor": (1.0, "p_double", False),
# Phong-specific.
- (b"SpecularColor", ((0.2, 0.2, 0.2), "p_color", True)),
- (b"SpecularFactor", (1.0, "p_number", True)),
+ b"SpecularColor": ((0.2, 0.2, 0.2), "p_color", True),
+ b"SpecularFactor": (1.0, "p_number", True),
# Not sure about the name, importer uses this (but ShininessExponent for tex prop name!)
# And in fbx exported by sdk, you have one in template, the other in actual material!!! :/
# For now, using both.
- (b"Shininess", (20.0, "p_number", True)),
- (b"ShininessExponent", (20.0, "p_number", True)),
- (b"ReflectionColor", ((0.0, 0.0, 0.0), "p_color", True)),
- (b"ReflectionFactor", (1.0, "p_number", True)),
- ))
+ b"Shininess": (20.0, "p_number", True),
+ b"ShininessExponent": (20.0, "p_number", True),
+ b"ReflectionColor": ((0.0, 0.0, 0.0), "p_color", True),
+ b"ReflectionFactor": (1.0, "p_number", True),
+ }
if override_defaults is not None:
props.update(override_defaults)
return FBXTemplate(b"Material", b"FbxSurfacePhong", props, nbr_users, [False])
@@ -405,26 +406,26 @@ def fbx_template_def_material(scene, settings, override_defaults=None, nbr_users
def fbx_template_def_texture_file(scene, settings, override_defaults=None, nbr_users=0):
# WIP...
# XXX Not sure about all names!
- props = OrderedDict((
- (b"TextureTypeUse", (0, "p_enum", False)), # Standard.
- (b"AlphaSource", (2, "p_enum", False)), # Black (i.e. texture's alpha), XXX name guessed!.
- (b"Texture alpha", (1.0, "p_double", False)),
- (b"PremultiplyAlpha", (True, "p_bool", False)),
- (b"CurrentTextureBlendMode", (1, "p_enum", False)), # Additive...
- (b"CurrentMappingType", (0, "p_enum", False)), # UV.
- (b"UVSet", ("default", "p_string", False)), # UVMap name.
- (b"WrapModeU", (0, "p_enum", False)), # Repeat.
- (b"WrapModeV", (0, "p_enum", False)), # Repeat.
- (b"UVSwap", (False, "p_bool", False)),
- (b"Translation", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
- (b"Rotation", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
- (b"Scaling", ((1.0, 1.0, 1.0), "p_vector_3d", False)),
- (b"TextureRotationPivot", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
- (b"TextureScalingPivot", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
+ props = {
+ b"TextureTypeUse": (0, "p_enum", False), # Standard.
+ b"AlphaSource": (2, "p_enum", False), # Black (i.e. texture's alpha), XXX name guessed!.
+ b"Texture alpha": (1.0, "p_double", False),
+ b"PremultiplyAlpha": (True, "p_bool", False),
+ b"CurrentTextureBlendMode": (1, "p_enum", False), # Additive...
+ b"CurrentMappingType": (0, "p_enum", False), # UV.
+ b"UVSet": ("default", "p_string", False), # UVMap name.
+ b"WrapModeU": (0, "p_enum", False), # Repeat.
+ b"WrapModeV": (0, "p_enum", False), # Repeat.
+ b"UVSwap": (False, "p_bool", False),
+ b"Translation": ((0.0, 0.0, 0.0), "p_vector_3d", False),
+ b"Rotation": ((0.0, 0.0, 0.0), "p_vector_3d", False),
+ b"Scaling": ((1.0, 1.0, 1.0), "p_vector_3d", False),
+ b"TextureRotationPivot": ((0.0, 0.0, 0.0), "p_vector_3d", False),
+ b"TextureScalingPivot": ((0.0, 0.0, 0.0), "p_vector_3d", False),
# Not sure about those two...
- (b"UseMaterial", (False, "p_bool", False)),
- (b"UseMipMap", (False, "p_bool", False)),
- ))
+ b"UseMaterial": (False, "p_bool", False),
+ b"UseMipMap": (False, "p_bool", False),
+ }
if override_defaults is not None:
props.update(override_defaults)
return FBXTemplate(b"Texture", b"FbxFileTexture", props, nbr_users, [False])
@@ -432,86 +433,86 @@ def fbx_template_def_texture_file(scene, settings, override_defaults=None, nbr_u
def fbx_template_def_video(scene, settings, override_defaults=None, nbr_users=0):
# WIP...
- props = OrderedDict((
+ props = {
# All pictures.
- (b"Width", (0, "p_integer", False)),
- (b"Height", (0, "p_integer", False)),
- (b"Path", ("", "p_string_url", False)),
- (b"AccessMode", (0, "p_enum", False)), # Disk (0=Disk, 1=Mem, 2=DiskAsync).
+ b"Width": (0, "p_integer", False),
+ b"Height": (0, "p_integer", False),
+ b"Path": ("", "p_string_url", False),
+ b"AccessMode": (0, "p_enum", False), # Disk (0=Disk, 1=Mem, 2=DiskAsync).
# All videos.
- (b"StartFrame", (0, "p_integer", False)),
- (b"StopFrame", (0, "p_integer", False)),
- (b"Offset", (0, "p_timestamp", False)),
- (b"PlaySpeed", (0.0, "p_double", False)),
- (b"FreeRunning", (False, "p_bool", False)),
- (b"Loop", (False, "p_bool", False)),
- (b"InterlaceMode", (0, "p_enum", False)), # None, i.e. progressive.
+ b"StartFrame": (0, "p_integer", False),
+ b"StopFrame": (0, "p_integer", False),
+ b"Offset": (0, "p_timestamp", False),
+ b"PlaySpeed": (0.0, "p_double", False),
+ b"FreeRunning": (False, "p_bool", False),
+ b"Loop": (False, "p_bool", False),
+ b"InterlaceMode": (0, "p_enum", False), # None, i.e. progressive.
# Image sequences.
- (b"ImageSequence", (False, "p_bool", False)),
- (b"ImageSequenceOffset", (0, "p_integer", False)),
- (b"FrameRate", (0.0, "p_double", False)),
- (b"LastFrame", (0, "p_integer", False)),
- ))
+ b"ImageSequence": (False, "p_bool", False),
+ b"ImageSequenceOffset": (0, "p_integer", False),
+ b"FrameRate": (0.0, "p_double", False),
+ b"LastFrame": (0, "p_integer", False),
+ }
if override_defaults is not None:
props.update(override_defaults)
return FBXTemplate(b"Video", b"FbxVideo", props, nbr_users, [False])
def fbx_template_def_pose(scene, settings, override_defaults=None, nbr_users=0):
- props = OrderedDict()
+ props = {}
if override_defaults is not None:
props.update(override_defaults)
return FBXTemplate(b"Pose", b"", props, nbr_users, [False])
def fbx_template_def_deformer(scene, settings, override_defaults=None, nbr_users=0):
- props = OrderedDict()
+ props = {}
if override_defaults is not None:
props.update(override_defaults)
return FBXTemplate(b"Deformer", b"", props, nbr_users, [False])
def fbx_template_def_animstack(scene, settings, override_defaults=None, nbr_users=0):
- props = OrderedDict((
- (b"Description", ("", "p_string", False)),
- (b"LocalStart", (0, "p_timestamp", False)),
- (b"LocalStop", (0, "p_timestamp", False)),
- (b"ReferenceStart", (0, "p_timestamp", False)),
- (b"ReferenceStop", (0, "p_timestamp", False)),
- ))
+ props = {
+ b"Description": ("", "p_string", False),
+ b"LocalStart": (0, "p_timestamp", False),
+ b"LocalStop": (0, "p_timestamp", False),
+ b"ReferenceStart": (0, "p_timestamp", False),
+ b"ReferenceStop": (0, "p_timestamp", False),
+ }
if override_defaults is not None:
props.update(override_defaults)
return FBXTemplate(b"AnimationStack", b"FbxAnimStack", props, nbr_users, [False])
def fbx_template_def_animlayer(scene, settings, override_defaults=None, nbr_users=0):
- props = OrderedDict((
- (b"Weight", (100.0, "p_number", True)),
- (b"Mute", (False, "p_bool", False)),
- (b"Solo", (False, "p_bool", False)),
- (b"Lock", (False, "p_bool", False)),
- (b"Color", ((0.8, 0.8, 0.8), "p_color_rgb", False)),
- (b"BlendMode", (0, "p_enum", False)),
- (b"RotationAccumulationMode", (0, "p_enum", False)),
- (b"ScaleAccumulationMode", (0, "p_enum", False)),
- (b"BlendModeBypass", (0, "p_ulonglong", False)),
- ))
+ props = {
+ b"Weight": (100.0, "p_number", True),
+ b"Mute": (False, "p_bool", False),
+ b"Solo": (False, "p_bool", False),
+ b"Lock": (False, "p_bool", False),
+ b"Color": ((0.8, 0.8, 0.8), "p_color_rgb", False),
+ b"BlendMode": (0, "p_enum", False),
+ b"RotationAccumulationMode": (0, "p_enum", False),
+ b"ScaleAccumulationMode": (0, "p_enum", False),
+ b"BlendModeBypass": (0, "p_ulonglong", False),
+ }
if override_defaults is not None:
props.update(override_defaults)
return FBXTemplate(b"AnimationLayer", b"FbxAnimLayer", props, nbr_users, [False])
def fbx_template_def_animcurvenode(scene, settings, override_defaults=None, nbr_users=0):
- props = OrderedDict((
- (FBX_ANIM_PROPSGROUP_NAME.encode(), (None, "p_compound", False)),
- ))
+ props = {
+ FBX_ANIM_PROPSGROUP_NAME.encode(): (None, "p_compound", False),
+ }
if override_defaults is not None:
props.update(override_defaults)
return FBXTemplate(b"AnimationCurveNode", b"FbxAnimCurveNode", props, nbr_users, [False])
def fbx_template_def_animcurve(scene, settings, override_defaults=None, nbr_users=0):
- props = OrderedDict()
+ props = {}
if override_defaults is not None:
props.update(override_defaults)
return FBXTemplate(b"AnimationCurve", b"", props, nbr_users, [False])
@@ -581,13 +582,13 @@ def fbx_data_empty_elements(root, empty, scene_data):
# No custom properties, already saved with object (Model).
-def fbx_data_lamp_elements(root, lamp, scene_data):
+def fbx_data_light_elements(root, lamp, scene_data):
"""
Write the Lamp data block.
"""
gscale = scene_data.settings.global_scale
- lamp_key = scene_data.data_lamps[lamp]
+ light_key = scene_data.data_lights[lamp]
do_light = True
decay_type = FBX_LIGHT_DECAY_TYPES['CONSTANT']
do_shadow = False
@@ -595,11 +596,11 @@ def fbx_data_lamp_elements(root, lamp, scene_data):
if lamp.type not in {'HEMI'}:
if lamp.type not in {'SUN', 'AREA'}:
decay_type = FBX_LIGHT_DECAY_TYPES[lamp.falloff_type]
- do_light = (not lamp.use_only_shadow) and (lamp.use_specular or lamp.use_diffuse)
- do_shadow = lamp.shadow_method not in {'NOSHADOW'}
+ do_light = True
+ do_shadow = lamp.use_shadow
shadow_color = lamp.shadow_color
- light = elem_data_single_int64(root, b"NodeAttribute", get_fbx_uuid_from_key(lamp_key))
+ light = elem_data_single_int64(root, b"NodeAttribute", get_fbx_uuid_from_key(light_key))
light.add_string(fbx_name_class(lamp.name.encode(), b"NodeAttribute"))
light.add_string(b"Light")
@@ -639,8 +640,8 @@ def fbx_data_camera_elements(root, cam_obj, scene_data):
# Real data now, good old camera!
# Object transform info.
loc, rot, scale, matrix, matrix_rot = cam_obj.fbx_object_tx(scene_data)
- up = matrix_rot * Vector((0.0, 1.0, 0.0))
- to = matrix_rot * Vector((0.0, 0.0, -1.0))
+ up = matrix_rot @ Vector((0.0, 1.0, 0.0))
+ to = matrix_rot @ Vector((0.0, 0.0, -1.0))
# Render settings.
# TODO We could export much more...
render = scene_data.scene.render
@@ -1035,7 +1036,7 @@ def fbx_data_mesh_elements(root, me_obj, scene_data, done_meshes):
# t_lnw = array.array(data_types.ARRAY_FLOAT64, (0.0,)) * len(me.loops)
uv_names = [uvlayer.name for uvlayer in me.uv_layers]
for name in uv_names:
- me.calc_tangents(name)
+ me.calc_tangents(uvmap=name)
for idx, uvlayer in enumerate(me.uv_layers):
name = uvlayer.name
# Loop bitangents (aka binormals).
@@ -1121,38 +1122,39 @@ def fbx_data_mesh_elements(root, me_obj, scene_data, done_meshes):
del _uvtuples_gen
# Face's materials.
- me_fbxmats_idx = scene_data.mesh_mat_indices.get(me)
- if me_fbxmats_idx is not None:
- me_blmats = me.materials
- if me_fbxmats_idx and me_blmats:
- lay_mat = elem_data_single_int32(geom, b"LayerElementMaterial", 0)
- elem_data_single_int32(lay_mat, b"Version", FBX_GEOMETRY_MATERIAL_VERSION)
- elem_data_single_string(lay_mat, b"Name", b"")
- nbr_mats = len(me_fbxmats_idx)
+ me_fbxmaterials_idx = scene_data.mesh_material_indices.get(me)
+ if me_fbxmaterials_idx is not None:
+ me_blmaterials = me.materials
+ if me_fbxmaterials_idx and me_blmaterials:
+ lay_ma = elem_data_single_int32(geom, b"LayerElementMaterial", 0)
+ elem_data_single_int32(lay_ma, b"Version", FBX_GEOMETRY_MATERIAL_VERSION)
+ elem_data_single_string(lay_ma, b"Name", b"")
+ nbr_mats = len(me_fbxmaterials_idx)
if nbr_mats > 1:
t_pm = array.array(data_types.ARRAY_INT32, (0,)) * len(me.polygons)
me.polygons.foreach_get("material_index", t_pm)
# We have to validate mat indices, and map them to FBX indices.
# Note a mat might not be in me_fbxmats_idx (e.g. node mats are ignored).
- blmats_to_fbxmats_idxs = [me_fbxmats_idx[m] for m in me_blmats if m in me_fbxmats_idx]
- mat_idx_limit = len(blmats_to_fbxmats_idxs)
- def_mat = blmats_to_fbxmats_idxs[0]
- _gen = (blmats_to_fbxmats_idxs[m] if m < mat_idx_limit else def_mat for m in t_pm)
+ blmaterials_to_fbxmaterials_idxs = [me_fbxmaterials_idx[m]
+ for m in me_blmaterials if m in me_fbxmaterials_idx]
+ ma_idx_limit = len(blmaterials_to_fbxmaterials_idxs)
+ def_ma = blmaterials_to_fbxmaterials_idxs[0]
+ _gen = (blmaterials_to_fbxmaterials_idxs[m] if m < ma_idx_limit else def_ma for m in t_pm)
t_pm = array.array(data_types.ARRAY_INT32, _gen)
- elem_data_single_string(lay_mat, b"MappingInformationType", b"ByPolygon")
+ elem_data_single_string(lay_ma, b"MappingInformationType", b"ByPolygon")
# XXX Logically, should be "Direct" reference type, since we do not have any index array, and have one
# value per polygon...
# But looks like FBX expects it to be IndexToDirect here (maybe because materials are already
# indices??? *sigh*).
- elem_data_single_string(lay_mat, b"ReferenceInformationType", b"IndexToDirect")
- elem_data_single_int32_array(lay_mat, b"Materials", t_pm)
+ elem_data_single_string(lay_ma, b"ReferenceInformationType", b"IndexToDirect")
+ elem_data_single_int32_array(lay_ma, b"Materials", t_pm)
del t_pm
else:
- elem_data_single_string(lay_mat, b"MappingInformationType", b"AllSame")
- elem_data_single_string(lay_mat, b"ReferenceInformationType", b"IndexToDirect")
- elem_data_single_int32_array(lay_mat, b"Materials", [0])
+ elem_data_single_string(lay_ma, b"MappingInformationType", b"AllSame")
+ elem_data_single_string(lay_ma, b"ReferenceInformationType", b"IndexToDirect")
+ elem_data_single_int32_array(lay_ma, b"Materials", [0])
# And the "layer TOC"...
@@ -1181,10 +1183,10 @@ def fbx_data_mesh_elements(root, me_obj, scene_data, done_meshes):
lay_uv = elem_empty(layer, b"LayerElement")
elem_data_single_string(lay_uv, b"Type", b"LayerElementUV")
elem_data_single_int32(lay_uv, b"TypedIndex", 0)
- if me_fbxmats_idx is not None:
- lay_mat = elem_empty(layer, b"LayerElement")
- elem_data_single_string(lay_mat, b"Type", b"LayerElementMaterial")
- elem_data_single_int32(lay_mat, b"TypedIndex", 0)
+ if me_fbxmaterials_idx is not None:
+ lay_ma = elem_empty(layer, b"LayerElement")
+ elem_data_single_string(lay_ma, b"Type", b"LayerElementMaterial")
+ elem_data_single_int32(lay_ma, b"TypedIndex", 0)
# Add other uv and/or vcol layers...
for vcolidx, uvidx, tspaceidx in zip_longest(range(1, vcolnumber), range(1, uvnumber), range(1, tspacenumber),
@@ -1214,76 +1216,70 @@ def fbx_data_mesh_elements(root, me_obj, scene_data, done_meshes):
done_meshes.add(me_key)
-def check_skip_material(mat):
- """Simple helper to check whether we actually support exporting that material or not"""
- return mat.type not in {'SURFACE'}
-
-
-def fbx_data_material_elements(root, mat, scene_data):
+def fbx_data_material_elements(root, ma, scene_data):
"""
Write the Material data block.
"""
+
ambient_color = (0.0, 0.0, 0.0)
if scene_data.data_world:
- ambient_color = next(iter(scene_data.data_world.keys())).ambient_color
+ ambient_color = next(iter(scene_data.data_world.keys())).color
- mat_key, _objs = scene_data.data_materials[mat]
- skip_mat = check_skip_material(mat)
- node_mat = mat.use_nodes
- mat_type = b"Phong"
- # Approximation...
- if not skip_mat and not node_mat and mat.specular_shader not in {'COOKTORR', 'PHONG', 'BLINN'}:
- mat_type = b"Lambert"
+ ma_wrap = node_shader_utils.PrincipledBSDFWrapper(ma, is_readonly=True)
+ ma_key, _objs = scene_data.data_materials[ma]
+ ma_type = b"Phong"
- fbx_mat = elem_data_single_int64(root, b"Material", get_fbx_uuid_from_key(mat_key))
- fbx_mat.add_string(fbx_name_class(mat.name.encode(), b"Material"))
- fbx_mat.add_string(b"")
+ fbx_ma = elem_data_single_int64(root, b"Material", get_fbx_uuid_from_key(ma_key))
+ fbx_ma.add_string(fbx_name_class(ma.name.encode(), b"Material"))
+ fbx_ma.add_string(b"")
- elem_data_single_int32(fbx_mat, b"Version", FBX_MATERIAL_VERSION)
+ elem_data_single_int32(fbx_ma, b"Version", FBX_MATERIAL_VERSION)
# those are not yet properties, it seems...
- elem_data_single_string(fbx_mat, b"ShadingModel", mat_type)
- elem_data_single_int32(fbx_mat, b"MultiLayer", 0) # Should be bool...
+ elem_data_single_string(fbx_ma, b"ShadingModel", ma_type)
+ elem_data_single_int32(fbx_ma, b"MultiLayer", 0) # Should be bool...
tmpl = elem_props_template_init(scene_data.templates, b"Material")
- props = elem_properties(fbx_mat)
-
- if not skip_mat:
- elem_props_template_set(tmpl, props, "p_string", b"ShadingModel", mat_type.decode())
- elem_props_template_set(tmpl, props, "p_color", b"DiffuseColor", mat.diffuse_color)
- elem_props_template_set(tmpl, props, "p_number", b"DiffuseFactor", mat.diffuse_intensity)
- if not node_mat:
- elem_props_template_set(tmpl, props, "p_color", b"EmissiveColor", mat.diffuse_color)
- elem_props_template_set(tmpl, props, "p_number", b"EmissiveFactor", mat.emit)
- elem_props_template_set(tmpl, props, "p_color", b"AmbientColor", ambient_color)
- elem_props_template_set(tmpl, props, "p_number", b"AmbientFactor", mat.ambient)
- elem_props_template_set(tmpl, props, "p_color", b"TransparentColor",
- mat.diffuse_color if mat.use_transparency else (1.0, 1.0, 1.0))
- elem_props_template_set(tmpl, props, "p_number", b"TransparencyFactor",
- 1.0 - mat.alpha if mat.use_transparency else 0.0)
- elem_props_template_set(tmpl, props, "p_number", b"Opacity", mat.alpha if mat.use_transparency else 1.0)
- elem_props_template_set(tmpl, props, "p_vector_3d", b"NormalMap", (0.0, 0.0, 0.0))
- # Not sure about those...
- """
- b"Bump": ((0.0, 0.0, 0.0), "p_vector_3d"),
- b"BumpFactor": (1.0, "p_double"),
- b"DisplacementColor": ((0.0, 0.0, 0.0), "p_color_rgb"),
- b"DisplacementFactor": (0.0, "p_double"),
- """
- if mat_type == b"Phong":
- elem_props_template_set(tmpl, props, "p_color", b"SpecularColor", mat.specular_color)
- elem_props_template_set(tmpl, props, "p_number", b"SpecularFactor", mat.specular_intensity / 2.0)
- # See Material template about those two!
- elem_props_template_set(tmpl, props, "p_number", b"Shininess", (mat.specular_hardness - 1.0) / 5.10)
- elem_props_template_set(tmpl, props, "p_number", b"ShininessExponent", (mat.specular_hardness - 1.0) / 5.10)
- elem_props_template_set(tmpl, props, "p_color", b"ReflectionColor", mat.mirror_color)
- elem_props_template_set(tmpl, props, "p_number", b"ReflectionFactor",
- mat.raytrace_mirror.reflect_factor if mat.raytrace_mirror.use else 0.0)
+ props = elem_properties(fbx_ma)
+
+ elem_props_template_set(tmpl, props, "p_string", b"ShadingModel", ma_type.decode())
+ elem_props_template_set(tmpl, props, "p_color", b"DiffuseColor", ma_wrap.base_color)
+ # Not in Principled BSDF, so assuming always 1
+ elem_props_template_set(tmpl, props, "p_number", b"DiffuseFactor", 1.0)
+ # Not in Principled BSDF, so assuming always 0
+ elem_props_template_set(tmpl, props, "p_color", b"EmissiveColor", ma_wrap.base_color)
+ elem_props_template_set(tmpl, props, "p_number", b"EmissiveFactor", 0.0)
+ # Not in Principled BSDF, so assuming always 0
+ elem_props_template_set(tmpl, props, "p_color", b"AmbientColor", ambient_color)
+ elem_props_template_set(tmpl, props, "p_number", b"AmbientFactor", 0.0)
+ elem_props_template_set(tmpl, props, "p_color", b"TransparentColor", ma_wrap.base_color)
+ elem_props_template_set(tmpl, props, "p_number", b"TransparencyFactor", ma_wrap.transmission)
+ elem_props_template_set(tmpl, props, "p_number", b"Opacity", 1.0 - ma_wrap.transmission)
+ elem_props_template_set(tmpl, props, "p_vector_3d", b"NormalMap", (0.0, 0.0, 0.0))
+ # Not sure about those...
+ """
+ b"Bump": ((0.0, 0.0, 0.0), "p_vector_3d"),
+ b"BumpFactor": (1.0, "p_double"),
+ b"DisplacementColor": ((0.0, 0.0, 0.0), "p_color_rgb"),
+ b"DisplacementFactor": (0.0, "p_double"),
+ """
+ # TODO: use specular tint?
+ elem_props_template_set(tmpl, props, "p_color", b"SpecularColor", ma_wrap.base_color)
+ elem_props_template_set(tmpl, props, "p_number", b"SpecularFactor", ma_wrap.specular / 2.0)
+ # See Material template about those two!
+ # XXX Totally empirical conversion, trying to adapt it
+ # (from 0.0 - 100.0 FBX shininess range to 1.0 - 0.0 Principled BSDF range)...
+ shininess = (1.0 - ma_wrap.roughness) * 10
+ shininess *= shininess
+ elem_props_template_set(tmpl, props, "p_number", b"Shininess", shininess)
+ elem_props_template_set(tmpl, props, "p_number", b"ShininessExponent", shininess)
+ elem_props_template_set(tmpl, props, "p_color", b"ReflectionColor", ma_wrap.base_color)
+ elem_props_template_set(tmpl, props, "p_number", b"ReflectionFactor", ma_wrap.metallic)
elem_props_template_finalize(tmpl, props)
# Custom properties.
if scene_data.settings.use_custom_props:
- fbx_data_element_custom_properties(props, mat)
+ fbx_data_element_custom_properties(props, ma)
def _gen_vid_path(img, scene_data):
@@ -1294,7 +1290,7 @@ def _gen_vid_path(img, scene_data):
return fname_abs, fname_rel
-def fbx_data_texture_file_elements(root, tex, scene_data):
+def fbx_data_texture_file_elements(root, blender_tex_key, scene_data):
"""
Write the (file) Texture data block.
"""
@@ -1302,45 +1298,50 @@ def fbx_data_texture_file_elements(root, tex, scene_data):
# Textures do not seem to use properties as much as they could.
# For now assuming most logical and simple stuff.
- tex_key, _mats = scene_data.data_textures[tex]
- img = tex.texture.image
+ ma, sock_name = blender_tex_key
+ ma_wrap = node_shader_utils.PrincipledBSDFWrapper(ma, is_readonly=True)
+ tex_key, _fbx_prop = scene_data.data_textures[blender_tex_key]
+ tex = getattr(ma_wrap, sock_name)
+ img = tex.image
fname_abs, fname_rel = _gen_vid_path(img, scene_data)
fbx_tex = elem_data_single_int64(root, b"Texture", get_fbx_uuid_from_key(tex_key))
- fbx_tex.add_string(fbx_name_class(tex.name.encode(), b"Texture"))
+ fbx_tex.add_string(fbx_name_class(sock_name.encode(), b"Texture"))
fbx_tex.add_string(b"")
elem_data_single_string(fbx_tex, b"Type", b"TextureVideoClip")
elem_data_single_int32(fbx_tex, b"Version", FBX_TEXTURE_VERSION)
- elem_data_single_string(fbx_tex, b"TextureName", fbx_name_class(tex.name.encode(), b"Texture"))
+ elem_data_single_string(fbx_tex, b"TextureName", fbx_name_class(sock_name.encode(), b"Texture"))
elem_data_single_string(fbx_tex, b"Media", fbx_name_class(img.name.encode(), b"Video"))
elem_data_single_string_unicode(fbx_tex, b"FileName", fname_abs)
elem_data_single_string_unicode(fbx_tex, b"RelativeFilename", fname_rel)
alpha_source = 0 # None
if img.use_alpha:
- if tex.texture.use_calculate_alpha:
- alpha_source = 1 # RGBIntensity as alpha.
- else:
- alpha_source = 2 # Black, i.e. alpha channel.
+ # ~ if tex.texture.use_calculate_alpha:
+ # ~ alpha_source = 1 # RGBIntensity as alpha.
+ # ~ else:
+ # ~ alpha_source = 2 # Black, i.e. alpha channel.
+ alpha_source = 2 # Black, i.e. alpha channel.
# BlendMode not useful for now, only affects layered textures afaics.
mapping = 0 # UV.
uvset = None
- if tex.texture_coords in {'ORCO'}: # XXX Others?
- if tex.mapping in {'FLAT'}:
+ if tex.texcoords == 'ORCO': # XXX Others?
+ if tex.projection == 'FLAT':
mapping = 1 # Planar
- elif tex.mapping in {'CUBE'}:
+ elif tex.projection == 'CUBE':
mapping = 4 # Box
- elif tex.mapping in {'TUBE'}:
+ elif tex.projection == 'TUBE':
mapping = 3 # Cylindrical
- elif tex.mapping in {'SPHERE'}:
+ elif tex.projection == 'SPHERE':
mapping = 2 # Spherical
- elif tex.texture_coords in {'UV'}:
+ elif tex.texcoords == 'UV':
mapping = 0 # UV
# Yuck, UVs are linked by mere names it seems... :/
- uvset = tex.uv_layer
+ # XXX TODO how to get that now???
+ # uvset = tex.uv_layer
wrap_mode = 1 # Clamp
- if tex.texture.extension in {'REPEAT'}:
+ if tex.extension == 'REPEAT':
wrap_mode = 0 # Repeat
tmpl = elem_props_template_init(scene_data.templates, b"TextureFile")
@@ -1353,16 +1354,15 @@ def fbx_data_texture_file_elements(root, tex, scene_data):
elem_props_template_set(tmpl, props, "p_string", b"UVSet", uvset)
elem_props_template_set(tmpl, props, "p_enum", b"WrapModeU", wrap_mode)
elem_props_template_set(tmpl, props, "p_enum", b"WrapModeV", wrap_mode)
- elem_props_template_set(tmpl, props, "p_vector_3d", b"Translation", tex.offset)
- elem_props_template_set(tmpl, props, "p_vector_3d", b"Scaling", tex.scale)
+ elem_props_template_set(tmpl, props, "p_vector_3d", b"Translation", tex.translation)
+ elem_props_template_set(tmpl, props, "p_vector_3d", b"Rotation", (-r for r in tex.rotation))
+ elem_props_template_set(tmpl, props, "p_vector_3d", b"Scaling", (((1.0 / s) if s != 0.0 else 1.0) for s in tex.scale))
# UseMaterial should always be ON imho.
elem_props_template_set(tmpl, props, "p_bool", b"UseMaterial", True)
- elem_props_template_set(tmpl, props, "p_bool", b"UseMipMap", tex.texture.use_mipmap)
+ elem_props_template_set(tmpl, props, "p_bool", b"UseMipMap", False)
elem_props_template_finalize(tmpl, props)
- # Custom properties.
- if scene_data.settings.use_custom_props:
- fbx_data_element_custom_properties(props, tex.texture)
+ # No custom properties, since that's not a data-block anymore.
def fbx_data_video_elements(root, vid, scene_data):
@@ -1472,7 +1472,7 @@ def fbx_data_armature_elements(root, arm_obj, scene_data):
bo_vg_idx = {bo_obj.bdata.name: ob.vertex_groups[bo_obj.bdata.name].index
for bo_obj in clusters.keys() if bo_obj.bdata.name in ob.vertex_groups}
valid_idxs = set(bo_vg_idx.values())
- vgroups = {vg.index: OrderedDict() for vg in ob.vertex_groups}
+ vgroups = {vg.index: {} for vg in ob.vertex_groups}
verts_vgroups = (sorted(((vg.group, vg.weight) for vg in v.groups if vg.weight and vg.group in valid_idxs),
key=lambda e: e[1], reverse=True)
for v in me.vertices)
@@ -1507,7 +1507,7 @@ def fbx_data_armature_elements(root, arm_obj, scene_data):
# http://area.autodesk.com/forum/autodesk-fbx/fbx-sdk/why-the-values-return-
# by-fbxcluster-gettransformmatrix-x-not-same-with-the-value-in-ascii-fbx-file/
elem_data_single_float64_array(fbx_clstr, b"Transform",
- matrix4_to_array(mat_world_bones[bo_obj].inverted_safe() * mat_world_obj))
+ matrix4_to_array(mat_world_bones[bo_obj].inverted_safe() @ mat_world_obj))
elem_data_single_float64_array(fbx_clstr, b"TransformLink", matrix4_to_array(mat_world_bones[bo_obj]))
elem_data_single_float64_array(fbx_clstr, b"TransformAssociateModel", matrix4_to_array(mat_world_arm))
@@ -1580,7 +1580,7 @@ def fbx_data_object_elements(root, ob_obj, scene_data):
obj_type = b"Null"
elif (ob_obj.type in BLENDER_OBJECT_TYPES_MESHLIKE):
obj_type = b"Mesh"
- elif (ob_obj.type == 'LAMP'):
+ elif (ob_obj.type == 'LIGHT'):
obj_type = b"Light"
elif (ob_obj.type == 'CAMERA'):
obj_type = b"Camera"
@@ -1727,46 +1727,30 @@ def fbx_data_animation_elements(root, scene_data):
# ##### Top-level FBX data container. #####
-def fbx_mat_properties_from_texture(tex):
- """
- Returns a set of FBX metarial properties that are affected by the given texture.
- Quite obviously, this is a fuzzy and far-from-perfect mapping! Amounts of influence are completely lost, e.g.
- Note tex is actually expected to be a texture slot.
- """
- # Mapping Blender -> FBX (blend_use_name, blend_fact_name, fbx_name).
- blend_to_fbx = (
- # Lambert & Phong...
- ("diffuse", "diffuse", b"DiffuseFactor"),
- ("color_diffuse", "diffuse_color", b"DiffuseColor"),
- ("alpha", "alpha", b"TransparencyFactor"),
- ("diffuse", "diffuse", b"TransparentColor"), # Uses diffuse color in Blender!
- ("emit", "emit", b"EmissiveFactor"),
- ("diffuse", "diffuse", b"EmissiveColor"), # Uses diffuse color in Blender!
- ("ambient", "ambient", b"AmbientFactor"),
- # ("", "", b"AmbientColor"), # World stuff in Blender, for now ignore...
- ("normal", "normal", b"NormalMap"),
- # Note: unsure about those... :/
- # ("", "", b"Bump"),
- # ("", "", b"BumpFactor"),
- # ("", "", b"DisplacementColor"),
- # ("", "", b"DisplacementFactor"),
- # Phong only.
- ("specular", "specular", b"SpecularFactor"),
- ("color_spec", "specular_color", b"SpecularColor"),
- # See Material template about those two!
- ("hardness", "hardness", b"Shininess"),
- ("hardness", "hardness", b"ShininessExponent"),
- ("mirror", "mirror", b"ReflectionColor"),
- ("raymir", "raymir", b"ReflectionFactor"),
- )
-
- tex_fbx_props = set()
- for use_map_name, name_factor, fbx_prop_name in blend_to_fbx:
- # Always export enabled textures, even if they have a null influence...
- if getattr(tex, "use_map_" + use_map_name):
- tex_fbx_props.add(fbx_prop_name)
-
- return tex_fbx_props
+# Mapping Blender -> FBX (principled_socket_name, fbx_name).
+PRINCIPLED_TEXTURE_SOCKETS_TO_FBX = (
+ # ("diffuse", "diffuse", b"DiffuseFactor"),
+ ("base_color_texture", b"DiffuseColor"),
+ ("transmission_texture", b"TransparencyFactor"),
+ # ("base_color_texture", b"TransparentColor"), # Uses diffuse color in Blender!
+ # ("emit", "emit", b"EmissiveFactor"),
+ # ("diffuse", "diffuse", b"EmissiveColor"), # Uses diffuse color in Blender!
+ # ("ambient", "ambient", b"AmbientFactor"),
+ # ("", "", b"AmbientColor"), # World stuff in Blender, for now ignore...
+ ("normalmap_texture", b"NormalMap"),
+ # Note: unsure about those... :/
+ # ("", "", b"Bump"),
+ # ("", "", b"BumpFactor"),
+ # ("", "", b"DisplacementColor"),
+ # ("", "", b"DisplacementFactor"),
+ ("specular_texture", b"SpecularFactor"),
+ # ("base_color", b"SpecularColor"), # TODO: use tint?
+ # See Material template about those two!
+ ("roughness_texture", b"Shininess"),
+ ("roughness_texture", b"ShininessExponent"),
+ # ("mirror", "mirror", b"ReflectionColor"),
+ ("metallic_texture", b"ReflectionFactor"),
+)
def fbx_skeleton_from_armature(scene, settings, arm_obj, objects, data_meshes,
@@ -1781,7 +1765,7 @@ def fbx_skeleton_from_armature(scene, settings, arm_obj, objects, data_meshes,
data_empties[arm_obj] = get_blender_empty_key(arm_obj.bdata)
arm_data = arm_obj.bdata.data
- bones = OrderedDict()
+ bones = {}
for bo in arm_obj.bones:
if settings.use_armature_deform_only:
if bo.bdata.use_deform:
@@ -1795,7 +1779,7 @@ def fbx_skeleton_from_armature(scene, settings, arm_obj, objects, data_meshes,
else:
bones[bo] = True
- bones = OrderedDict((bo, None) for bo, use in bones.items() if use)
+ bones = {bo: None for bo, use in bones.items() if use}
if not bones:
return
@@ -1823,8 +1807,8 @@ def fbx_skeleton_from_armature(scene, settings, arm_obj, objects, data_meshes,
# Note: bindpose have no relations at all (no connections), so no need for any preprocess for them.
# Create skin & clusters relations (note skins are connected to geometry, *not* model!).
_key, me, _free = data_meshes[ob_obj]
- clusters = OrderedDict((bo, get_blender_bone_cluster_key(arm_obj.bdata, me, bo.bdata)) for bo in bones)
- data_deformers_skin.setdefault(arm_obj, OrderedDict())[me] = (get_blender_armature_skin_key(arm_obj.bdata, me),
+ clusters = {bo: get_blender_bone_cluster_key(arm_obj.bdata, me, bo.bdata) for bo in bones}
+ data_deformers_skin.setdefault(arm_obj, {})[me] = (get_blender_armature_skin_key(arm_obj.bdata, me),
ob_obj, clusters)
# We don't want a regular parent relationship for those in FBX...
@@ -1859,9 +1843,9 @@ def fbx_generate_leaf_bones(settings, data_bones):
bone_length = (parent.bdata.tail_local - parent.bdata.head_local).length
matrix = Matrix.Translation((0, bone_length, 0))
if settings.bone_correction_matrix_inv:
- matrix = settings.bone_correction_matrix_inv * matrix
+ matrix = settings.bone_correction_matrix_inv @ matrix
if settings.bone_correction_matrix:
- matrix = matrix * settings.bone_correction_matrix
+ matrix = matrix @ settings.bone_correction_matrix
leaf_bones.append((node_name, parent_uuid, node_uuid, attr_uuid, matrix, hide, size))
return leaf_bones
@@ -1874,6 +1858,7 @@ def fbx_animations_do(scene_data, ref_id, f_start, f_end, start_zero, objects=No
bake_step = scene_data.settings.bake_anim_step
simplify_fac = scene_data.settings.bake_anim_simplify_factor
scene = scene_data.scene
+ depsgraph = scene_data.depsgraph
force_keying = scene_data.settings.bake_anim_use_all_bones
force_sek = scene_data.settings.bake_anim_force_startend_keying
@@ -1884,16 +1869,14 @@ def fbx_animations_do(scene_data, ref_id, f_start, f_end, start_zero, objects=No
continue
if ob_obj.type == 'ARMATURE':
objects |= {bo_obj for bo_obj in ob_obj.bones if bo_obj in scene_data.objects}
- ob_obj.dupli_list_create(scene, 'RENDER')
- for dp_obj in ob_obj.dupli_list:
+ for dp_obj in ob_obj.dupli_list_gen(depsgraph):
if dp_obj in scene_data.objects:
objects.add(dp_obj)
- ob_obj.dupli_list_clear()
else:
objects = scene_data.objects
back_currframe = scene.frame_current
- animdata_ob = OrderedDict()
+ animdata_ob = {}
p_rots = {}
for ob_obj in objects:
@@ -1909,8 +1892,8 @@ def fbx_animations_do(scene_data, ref_id, f_start, f_end, start_zero, objects=No
p_rots[ob_obj] = rot
force_key = (simplify_fac == 0.0)
+ animdata_shapes = {}
- animdata_shapes = OrderedDict()
for me, (me_key, _shapes_key, shapes) in scene_data.data_deformers_shape.items():
# Ignore absolute shape keys for now!
if not me.shape_keys.use_relative:
@@ -1921,7 +1904,7 @@ def fbx_animations_do(scene_data, ref_id, f_start, f_end, start_zero, objects=No
acnode.add_group(me_key, shape.name, shape.name, (shape.name,))
animdata_shapes[channel_key] = (acnode, me, shape)
- animdata_cameras = OrderedDict()
+ animdata_cameras = {}
for cam_obj, cam_key in scene_data.data_cameras.items():
cam = cam_obj.bdata.data
acnode = AnimationCurveNodeWrapper(cam_key, 'CAMERA_FOCAL', force_key, force_sek, (cam.lens,))
@@ -1930,10 +1913,10 @@ def fbx_animations_do(scene_data, ref_id, f_start, f_end, start_zero, objects=No
currframe = f_start
while currframe <= f_end:
real_currframe = currframe - f_start if start_zero else currframe
- scene.frame_set(int(currframe), currframe - int(currframe))
+ scene.frame_set(int(currframe), subframe=currframe - int(currframe))
- for ob_obj in animdata_ob:
- ob_obj.dupli_list_create(scene, 'RENDER')
+ for dp_obj in ob_obj.dupli_list_gen(depsgraph):
+ pass # Merely updating dupli matrix of ObjectWrapper...
for ob_obj, (anim_loc, anim_rot, anim_scale) in animdata_ob.items():
# We compute baked loc/rot/scale for all objects (rot being euler-compat with previous value!).
p_rot = p_rots.get(ob_obj, None)
@@ -1942,17 +1925,15 @@ def fbx_animations_do(scene_data, ref_id, f_start, f_end, start_zero, objects=No
anim_loc.add_keyframe(real_currframe, loc)
anim_rot.add_keyframe(real_currframe, tuple(convert_rad_to_deg_iter(rot)))
anim_scale.add_keyframe(real_currframe, scale)
- for ob_obj in objects:
- ob_obj.dupli_list_clear()
for anim_shape, me, shape in animdata_shapes.values():
anim_shape.add_keyframe(real_currframe, (shape.value * 100.0,))
for anim_camera, camera in animdata_cameras.values():
anim_camera.add_keyframe(real_currframe, (camera.lens,))
currframe += bake_step
- scene.frame_set(back_currframe, 0.0)
+ scene.frame_set(back_currframe, subframe=0.0)
- animations = OrderedDict()
+ animations = {}
# And now, produce final data (usable by FBX export code)
# Objects-like loc/rot/scale...
@@ -1962,34 +1943,28 @@ def fbx_animations_do(scene_data, ref_id, f_start, f_end, start_zero, objects=No
if not anim:
continue
for obj_key, group_key, group, fbx_group, fbx_gname in anim.get_final_data(scene, ref_id, force_keep):
- anim_data = animations.get(obj_key)
- if anim_data is None:
- anim_data = animations[obj_key] = ("dummy_unused_key", OrderedDict())
+ anim_data = animations.setdefault(obj_key, ("dummy_unused_key", {}))
anim_data[1][fbx_group] = (group_key, group, fbx_gname)
# And meshes' shape keys.
for channel_key, (anim_shape, me, shape) in animdata_shapes.items():
- final_keys = OrderedDict()
+ final_keys = {}
anim_shape.simplify(simplify_fac, bake_step, force_keep)
if not anim_shape:
continue
for elem_key, group_key, group, fbx_group, fbx_gname in anim_shape.get_final_data(scene, ref_id, force_keep):
- anim_data = animations.get(elem_key)
- if anim_data is None:
- anim_data = animations[elem_key] = ("dummy_unused_key", OrderedDict())
- anim_data[1][fbx_group] = (group_key, group, fbx_gname)
+ anim_data = animations.setdefault(elem_key, ("dummy_unused_key", {}))
+ anim_data[1][fbx_group] = (group_key, group, fbx_gname)
# And cameras' lens keys.
for cam_key, (anim_camera, camera) in animdata_cameras.items():
- final_keys = OrderedDict()
+ final_keys = {}
anim_camera.simplify(simplify_fac, bake_step, force_keep)
if not anim_camera:
continue
for elem_key, group_key, group, fbx_group, fbx_gname in anim_camera.get_final_data(scene, ref_id, force_keep):
- anim_data = animations.get(elem_key)
- if anim_data is None:
- anim_data = animations[elem_key] = ("dummy_unused_key", OrderedDict())
- anim_data[1][fbx_group] = (group_key, group, fbx_gname)
+ anim_data = animations.setdefault(elem_key, ("dummy_unused_key", {}))
+ anim_data[1][fbx_group] = (group_key, group, fbx_gname)
astack_key = get_blender_anim_stack_key(scene, ref_id)
alayer_key = get_blender_anim_layer_key(scene, ref_id)
@@ -2055,7 +2030,7 @@ def fbx_animations(scene_data):
add_anim(animations, animated,
fbx_animations_do(scene_data, strip, strip.frame_start, strip.frame_end, True, force_keep=True))
strip.mute = True
- scene.frame_set(scene.frame_current, 0.0)
+ scene.frame_set(scene.frame_current, subframe=0.0)
for strip in strips:
strip.mute = False
@@ -2082,14 +2057,14 @@ def fbx_animations(scene_data):
'location', 'rotation_quaternion', 'rotation_axis_angle', 'rotation_euler', 'rotation_mode', 'scale',
'delta_location', 'delta_rotation_euler', 'delta_rotation_quaternion', 'delta_scale',
'lock_location', 'lock_rotation', 'lock_rotation_w', 'lock_rotations_4d', 'lock_scale',
- 'tag', 'layers', 'select', 'track_axis', 'up_axis', 'active_material', 'active_material_index',
- 'matrix_parent_inverse', 'empty_draw_type', 'empty_draw_size', 'empty_image_offset', 'pass_index',
- 'color', 'hide', 'hide_select', 'hide_render', 'use_slow_parent', 'slow_parent_offset',
- 'use_extra_recalc_object', 'use_extra_recalc_data', 'dupli_type', 'use_dupli_frames_speed',
- 'use_dupli_vertices_rotation', 'use_dupli_faces_scale', 'dupli_faces_scale', 'dupli_group',
- 'dupli_frames_start', 'dupli_frames_end', 'dupli_frames_on', 'dupli_frames_off',
- 'draw_type', 'show_bounds', 'draw_bounds_type', 'show_name', 'show_axis', 'show_texture_space',
- 'show_wire', 'show_all_edges', 'show_transparent', 'show_x_ray',
+ 'tag', 'track_axis', 'up_axis', 'active_material', 'active_material_index',
+ 'matrix_parent_inverse', 'empty_display_type', 'empty_display_size', 'empty_image_offset', 'pass_index',
+ 'color', 'hide_viewport', 'hide_select', 'hide_render', 'use_slow_parent', 'slow_parent_offset',
+ 'instance_type', 'use_instance_frames_speed',
+ 'use_instance_vertices_rotation', 'use_instance_faces_scale', 'instance_faces_scale',
+ 'instance_frames_start', 'instance_frames_end', 'instance_frames_on', 'instance_frames_off',
+ 'display_type', 'show_bounds', 'display_bounds_type', 'show_name', 'show_axis', 'show_texture_space',
+ 'show_wire', 'show_all_edges', 'show_transparent', 'show_in_front',
'show_only_shape_key', 'use_shape_key_edit_mode', 'active_shape_key_index',
)
for p in props:
@@ -2134,7 +2109,7 @@ def fbx_animations(scene_data):
pbo.matrix_basis = mat.copy()
ob.animation_data.action = org_act
restore_object(ob, ob_copy)
- scene.frame_set(scene.frame_current, 0.0)
+ scene.frame_set(scene.frame_current, subframe=0.0)
if pbones_matrices is not ...:
for pbo, mat in zip(ob.pose.bones, pbones_matrices):
@@ -2142,19 +2117,19 @@ def fbx_animations(scene_data):
ob.animation_data.action = org_act
bpy.data.objects.remove(ob_copy)
- scene.frame_set(scene.frame_current, 0.0)
+ scene.frame_set(scene.frame_current, subframe=0.0)
# Global (containing everything) animstack, only if not exporting NLA strips and/or all actions.
if not scene_data.settings.bake_anim_use_nla_strips and not scene_data.settings.bake_anim_use_all_actions:
add_anim(animations, animated, fbx_animations_do(scene_data, None, scene.frame_start, scene.frame_end, False))
# Be sure to update all matrices back to org state!
- scene.frame_set(scene.frame_current, 0.0)
+ scene.frame_set(scene.frame_current, subframe=0.0)
return animations, animated, frame_start, frame_end
-def fbx_data_from_scene(scene, settings):
+def fbx_data_from_scene(scene, depsgraph, settings):
"""
Do some pre-processing over scene's data...
"""
@@ -2169,34 +2144,32 @@ def fbx_data_from_scene(scene, settings):
# This is rather simple for now, maybe we could end generating templates with most-used values
# instead of default ones?
- objects = OrderedDict() # Because we do not have any ordered set...
+ objects = {} # Because we do not have any ordered set...
for ob in settings.context_objects:
if ob.type not in objtypes:
continue
ob_obj = ObjectWrapper(ob)
objects[ob_obj] = None
# Duplis...
- ob_obj.dupli_list_create(scene, 'RENDER')
- for dp_obj in ob_obj.dupli_list:
+ for dp_obj in ob_obj.dupli_list_gen(depsgraph):
if dp_obj.type not in dp_objtypes:
continue
objects[dp_obj] = None
- ob_obj.dupli_list_clear()
perfmon.step("FBX export prepare: Wrapping Data (lamps, cameras, empties)...")
- data_lamps = OrderedDict((ob_obj.bdata.data, get_blenderID_key(ob_obj.bdata.data))
- for ob_obj in objects if ob_obj.type == 'LAMP')
+ data_lights = {ob_obj.bdata.data: get_blenderID_key(ob_obj.bdata.data)
+ for ob_obj in objects if ob_obj.type == 'LIGHT'}
# Unfortunately, FBX camera data contains object-level data (like position, orientation, etc.)...
- data_cameras = OrderedDict((ob_obj, get_blenderID_key(ob_obj.bdata.data))
- for ob_obj in objects if ob_obj.type == 'CAMERA')
+ data_cameras = {ob_obj: get_blenderID_key(ob_obj.bdata.data)
+ for ob_obj in objects if ob_obj.type == 'CAMERA'}
# Yep! Contains nothing, but needed!
- data_empties = OrderedDict((ob_obj, get_blender_empty_key(ob_obj.bdata))
- for ob_obj in objects if ob_obj.type == 'EMPTY')
+ data_empties = {ob_obj: get_blender_empty_key(ob_obj.bdata)
+ for ob_obj in objects if ob_obj.type == 'EMPTY'}
perfmon.step("FBX export prepare: Wrapping Meshes...")
- data_meshes = OrderedDict()
+ data_meshes = {}
for ob_obj in objects:
if ob_obj.type not in BLENDER_OBJECT_TYPES_MESHLIKE:
continue
@@ -2221,21 +2194,22 @@ def fbx_data_from_scene(scene, settings):
# No need to create a new mesh in this case, if no modifier is active!
for mod in ob.modifiers:
# For meshes, when armature export is enabled, disable Armature modifiers here!
+ # XXX Temp hacks here since currently we only have access to a viewport depsgraph...
if mod.type == 'ARMATURE' and 'ARMATURE' in settings.object_types:
- tmp_mods.append((mod, mod.show_render))
+ tmp_mods.append((mod, mod.show_render, mod.show_viewport))
mod.show_render = False
- if mod.show_render:
+ mod.show_viewport = False
+ if mod.show_render or mod.show_viewport:
use_org_data = False
if not use_org_data:
tmp_me = ob.to_mesh(
- scene,
- apply_modifiers=settings.use_mesh_modifiers,
- settings='RENDER' if settings.use_mesh_modifiers_render else 'PREVIEW',
- )
+ depsgraph,
+ apply_modifiers=settings.use_mesh_modifiers)
data_meshes[ob_obj] = (get_blenderID_key(tmp_me), tmp_me, True)
# Re-enable temporary disabled modifiers.
- for mod, show_render in tmp_mods:
+ for mod, show_render, show_viewport in tmp_mods:
mod.show_render = show_render
+ mod.show_viewport = show_viewport
if use_org_data:
data_meshes[ob_obj] = (get_blenderID_key(ob.data), ob.data, False)
@@ -2246,7 +2220,7 @@ def fbx_data_from_scene(scene, settings):
perfmon.step("FBX export prepare: Wrapping ShapeKeys...")
# ShapeKeys.
- data_deformers_shape = OrderedDict()
+ data_deformers_shape = {}
geom_mat_co = settings.global_matrix if settings.bake_space_transform else None
for me_key, me, _free in data_meshes.values():
if not (me.shape_keys and len(me.shape_keys.key_blocks) > 1): # We do not want basis-only relative skeys...
@@ -2284,13 +2258,13 @@ def fbx_data_from_scene(scene, settings):
continue
channel_key, geom_key = get_blender_mesh_shape_channel_key(me, shape)
data = (channel_key, geom_key, shape_verts_co, shape_verts_idx)
- data_deformers_shape.setdefault(me, (me_key, shapes_key, OrderedDict()))[2][shape] = data
+ data_deformers_shape.setdefault(me, (me_key, shapes_key, {}))[2][shape] = data
perfmon.step("FBX export prepare: Wrapping Armatures...")
# Armatures!
- data_deformers_skin = OrderedDict()
- data_bones = OrderedDict()
+ data_deformers_skin = {}
+ data_bones = {}
arm_parents = set()
for ob_obj in tuple(objects):
if not (ob_obj.is_object and ob_obj.type in {'ARMATURE'}):
@@ -2307,71 +2281,51 @@ def fbx_data_from_scene(scene, settings):
# Some world settings are embedded in FBX materials...
if scene.world:
- data_world = OrderedDict(((scene.world, get_blenderID_key(scene.world)),))
+ data_world = {scene.world: get_blenderID_key(scene.world)}
else:
- data_world = OrderedDict()
+ data_world = {}
perfmon.step("FBX export prepare: Wrapping Materials...")
- # TODO: Check all the mat stuff works even when mats are linked to Objects
+ # TODO: Check all the material stuff works even when they are linked to Objects
# (we can then have the same mesh used with different materials...).
# *Should* work, as FBX always links its materials to Models (i.e. objects).
# XXX However, material indices would probably break...
- data_materials = OrderedDict()
+ data_materials = {}
for ob_obj in objects:
# If obj is not a valid object for materials, wrapper will just return an empty tuple...
- for mat_s in ob_obj.material_slots:
- mat = mat_s.material
- if mat is None:
+ for ma_s in ob_obj.material_slots:
+ ma = ma_s.material
+ if ma is None:
continue # Empty slots!
# Note theoretically, FBX supports any kind of materials, even GLSL shaders etc.
# However, I doubt anything else than Lambert/Phong is really portable!
- # We support any kind of 'surface' shader though, better to have some kind of default Lambert than nothing.
- # Note we want to keep a 'dummy' empty mat even when we can't really support it, see T41396.
- mat_data = data_materials.get(mat)
- if mat_data is not None:
- mat_data[1].append(ob_obj)
- else:
- data_materials[mat] = (get_blenderID_key(mat), [ob_obj])
+ # Note we want to keep a 'dummy' empty material even when we can't really support it, see T41396.
+ ma_data = data_materials.setdefault(ma, (get_blenderID_key(ma), []))
+ ma_data[1].append(ob_obj)
perfmon.step("FBX export prepare: Wrapping Textures...")
# Note FBX textures also hold their mapping info.
# TODO: Support layers?
- data_textures = OrderedDict()
+ data_textures = {}
# FbxVideo also used to store static images...
- data_videos = OrderedDict()
+ data_videos = {}
# For now, do not use world textures, don't think they can be linked to anything FBX wise...
- for mat in data_materials.keys():
- if check_skip_material(mat):
- continue
- for tex, use_tex in zip(mat.texture_slots, mat.use_textures):
- if tex is None or tex.texture is None or not use_tex:
- continue
- # For now, only consider image textures.
- # Note FBX does has support for procedural, but this is not portable at all (opaque blob),
- # so not useful for us.
- # TODO I think ENVIRONMENT_MAP should be usable in FBX as well, but for now let it aside.
- # if tex.texture.type not in {'IMAGE', 'ENVIRONMENT_MAP'}:
- if tex.texture.type not in {'IMAGE'}:
- continue
- img = tex.texture.image
- if img is None:
+ for ma in data_materials.keys():
+ # Note: with nodal shaders, we'll could be generating much more textures, but that's kind of unavoidable,
+ # given that textures actually do not exist anymore in material context in Blender...
+ ma_wrap = node_shader_utils.PrincipledBSDFWrapper(ma, is_readonly=True)
+ for sock_name, fbx_name in PRINCIPLED_TEXTURE_SOCKETS_TO_FBX:
+ tex = getattr(ma_wrap, sock_name)
+ if tex is None or tex.image is None:
continue
- # Find out whether we can actually use this texture for this material, in FBX context.
- tex_fbx_props = fbx_mat_properties_from_texture(tex)
- if not tex_fbx_props:
- continue
- tex_data = data_textures.get(tex)
- if tex_data is not None:
- tex_data[1][mat] = tex_fbx_props
- else:
- data_textures[tex] = (get_blenderID_key(tex), OrderedDict(((mat, tex_fbx_props),)))
- vid_data = data_videos.get(img)
- if vid_data is not None:
- vid_data[1].append(tex)
- else:
- data_videos[img] = (get_blenderID_key(img), [tex])
+ blender_tex_key = (ma, sock_name)
+ data_textures[blender_tex_key] = (get_blender_nodetexture_key(*blender_tex_key), fbx_name)
+
+ img = tex.image
+ vid_data = data_videos.setdefault(img, (get_blenderID_key(img), []))
+ vid_data[1].append(blender_tex_key)
perfmon.step("FBX export prepare: Wrapping Animations...")
@@ -2385,8 +2339,8 @@ def fbx_data_from_scene(scene, settings):
# Kind of hack, we need a temp scene_data for object's space handling to bake animations...
tmp_scdata = FBXExportData(
None, None, None,
- settings, scene, objects, None, None, 0.0, 0.0,
- data_empties, data_lamps, data_cameras, data_meshes, None,
+ settings, scene, depsgraph, objects, None, None, 0.0, 0.0,
+ data_empties, data_lights, data_cameras, data_meshes, None,
data_bones, data_leaf_bones, data_deformers_skin, data_deformers_shape,
data_world, data_materials, data_textures, data_videos,
)
@@ -2396,14 +2350,14 @@ def fbx_data_from_scene(scene, settings):
perfmon.step("FBX export prepare: Generating templates...")
- templates = OrderedDict()
+ templates = {}
templates[b"GlobalSettings"] = fbx_template_def_globalsettings(scene, settings, nbr_users=1)
if data_empties:
templates[b"Null"] = fbx_template_def_null(scene, settings, nbr_users=len(data_empties))
- if data_lamps:
- templates[b"Light"] = fbx_template_def_light(scene, settings, nbr_users=len(data_lamps))
+ if data_lights:
+ templates[b"Light"] = fbx_template_def_light(scene, settings, nbr_users=len(data_lights))
if data_cameras:
templates[b"Camera"] = fbx_template_def_camera(scene, settings, nbr_users=len(data_cameras))
@@ -2501,9 +2455,9 @@ def fbx_data_from_scene(scene, settings):
bo_data_key = data_bones[ob_obj]
connections.append((b"OO", get_fbx_uuid_from_key(bo_data_key), ob_obj.fbx_uuid, None))
else:
- if ob_obj.type == 'LAMP':
- lamp_key = data_lamps[ob_obj.bdata.data]
- connections.append((b"OO", get_fbx_uuid_from_key(lamp_key), ob_obj.fbx_uuid, None))
+ if ob_obj.type == 'LIGHT':
+ light_key = data_lights[ob_obj.bdata.data]
+ connections.append((b"OO", get_fbx_uuid_from_key(light_key), ob_obj.fbx_uuid, None))
elif ob_obj.type == 'CAMERA':
cam_key = data_cameras[ob_obj]
connections.append((b"OO", get_fbx_uuid_from_key(cam_key), ob_obj.fbx_uuid, None))
@@ -2543,35 +2497,33 @@ def fbx_data_from_scene(scene, settings):
connections.append((b"OO", bo_obj.fbx_uuid, get_fbx_uuid_from_key(clstr_key), None))
# Materials
- mesh_mat_indices = OrderedDict()
+ mesh_material_indices = {}
_objs_indices = {}
- for mat, (mat_key, ob_objs) in data_materials.items():
+ for ma, (ma_key, ob_objs) in data_materials.items():
for ob_obj in ob_objs:
- connections.append((b"OO", get_fbx_uuid_from_key(mat_key), ob_obj.fbx_uuid, None))
- # Get index of this mat for this object (or dupliobject).
- # Mat indices for mesh faces are determined by their order in 'mat to ob' connections.
- # Only mats for meshes currently...
- # Note in case of dupliobjects a same me/mat idx will be generated several times...
+ connections.append((b"OO", get_fbx_uuid_from_key(ma_key), ob_obj.fbx_uuid, None))
+ # Get index of this material for this object (or dupliobject).
+ # Material indices for mesh faces are determined by their order in 'ma to ob' connections.
+ # Only materials for meshes currently...
+ # Note in case of dupliobjects a same me/ma idx will be generated several times...
# Should not be an issue in practice, and it's needed in case we export duplis but not the original!
if ob_obj.type not in BLENDER_OBJECT_TYPES_MESHLIKE:
continue
_mesh_key, me, _free = data_meshes[ob_obj]
idx = _objs_indices[ob_obj] = _objs_indices.get(ob_obj, -1) + 1
- mesh_mat_indices.setdefault(me, OrderedDict())[mat] = idx
+ mesh_material_indices.setdefault(me, {})[ma] = idx
del _objs_indices
# Textures
- for tex, (tex_key, mats) in data_textures.items():
- for mat, fbx_mat_props in mats.items():
- mat_key, _ob_objs = data_materials[mat]
- for fbx_prop in fbx_mat_props:
- # texture -> material properties
- connections.append((b"OP", get_fbx_uuid_from_key(tex_key), get_fbx_uuid_from_key(mat_key), fbx_prop))
+ for (ma, sock_name), (tex_key, fbx_prop) in data_textures.items():
+ ma_key, _ob_objs = data_materials[ma]
+ # texture -> material properties
+ connections.append((b"OP", get_fbx_uuid_from_key(tex_key), get_fbx_uuid_from_key(ma_key), fbx_prop))
# Images
- for vid, (vid_key, texs) in data_videos.items():
- for tex in texs:
- tex_key, _texs = data_textures[tex]
+ for vid, (vid_key, blender_tex_keys) in data_videos.items():
+ for blender_tex_key in blender_tex_keys:
+ tex_key, _fbx_prop = data_textures[blender_tex_key]
connections.append((b"OO", get_fbx_uuid_from_key(vid_key), get_fbx_uuid_from_key(tex_key), None))
# Animations
@@ -2603,8 +2555,8 @@ def fbx_data_from_scene(scene, settings):
return FBXExportData(
templates, templates_users, connections,
- settings, scene, objects, animations, animated, frame_start, frame_end,
- data_empties, data_lamps, data_cameras, data_meshes, mesh_mat_indices,
+ settings, scene, depsgraph, objects, animations, animated, frame_start, frame_end,
+ data_empties, data_lights, data_cameras, data_meshes, mesh_material_indices,
data_bones, data_leaf_bones, data_deformers_skin, data_deformers_shape,
data_world, data_materials, data_textures, data_videos,
)
@@ -2813,10 +2765,10 @@ def fbx_objects_elements(root, scene_data):
for empty in scene_data.data_empties:
fbx_data_empty_elements(objects, empty, scene_data)
- perfmon.step("FBX export fetch lamps (%d)..." % len(scene_data.data_lamps))
+ perfmon.step("FBX export fetch lamps (%d)..." % len(scene_data.data_lights))
- for lamp in scene_data.data_lamps:
- fbx_data_lamp_elements(objects, lamp, scene_data)
+ for lamp in scene_data.data_lights:
+ fbx_data_light_elements(objects, lamp, scene_data)
perfmon.step("FBX export fetch cameras (%d)..." % len(scene_data.data_cameras))
@@ -2837,12 +2789,10 @@ def fbx_objects_elements(root, scene_data):
if ob_obj.is_dupli:
continue
fbx_data_object_elements(objects, ob_obj, scene_data)
- ob_obj.dupli_list_create(scene_data.scene, 'RENDER')
- for dp_obj in ob_obj.dupli_list:
+ for dp_obj in ob_obj.dupli_list_gen(scene_data.depsgraph):
if dp_obj not in scene_data.objects:
continue
fbx_data_object_elements(objects, dp_obj, scene_data)
- ob_obj.dupli_list_clear()
perfmon.step("FBX export fetch remaining...")
@@ -2854,11 +2804,11 @@ def fbx_objects_elements(root, scene_data):
if scene_data.data_leaf_bones:
fbx_data_leaf_bone_elements(objects, scene_data)
- for mat in scene_data.data_materials:
- fbx_data_material_elements(objects, mat, scene_data)
+ for ma in scene_data.data_materials:
+ fbx_data_material_elements(objects, ma, scene_data)
- for tex in scene_data.data_textures:
- fbx_data_texture_file_elements(objects, tex, scene_data)
+ for blender_tex_key in scene_data.data_textures:
+ fbx_data_texture_file_elements(objects, blender_tex_key, scene_data)
for vid in scene_data.data_videos:
fbx_data_video_elements(objects, vid, scene_data)
@@ -2907,7 +2857,7 @@ def fbx_takes_elements(root, scene_data):
# ##### "Main" functions. #####
# This func can be called with just the filepath
-def save_single(operator, scene, filepath="",
+def save_single(operator, scene, depsgraph, filepath="",
global_matrix=Matrix(),
apply_unit_scale=False,
global_scale=1.0,
@@ -2945,7 +2895,7 @@ def save_single(operator, scene, filepath="",
ObjectWrapper.cache_clear()
if object_types is None:
- object_types = {'EMPTY', 'CAMERA', 'LAMP', 'ARMATURE', 'MESH', 'OTHER'}
+ object_types = {'EMPTY', 'CAMERA', 'LIGHT', 'ARMATURE', 'MESH', 'OTHER'}
if 'OTHER' in object_types:
object_types |= BLENDER_OTHER_OBJECT_TYPES
@@ -2953,12 +2903,12 @@ def save_single(operator, scene, filepath="",
# Default Blender unit is equivalent to meter, while FBX one is centimeter...
unit_scale = units_blender_to_fbx_factor(scene) if apply_unit_scale else 100.0
if apply_scale_options == 'FBX_SCALE_NONE':
- global_matrix = Matrix.Scale(unit_scale * global_scale, 4) * global_matrix
+ global_matrix = Matrix.Scale(unit_scale * global_scale, 4) @ global_matrix
unit_scale = 1.0
elif apply_scale_options == 'FBX_SCALE_UNITS':
- global_matrix = Matrix.Scale(global_scale, 4) * global_matrix
+ global_matrix = Matrix.Scale(global_scale, 4) @ global_matrix
elif apply_scale_options == 'FBX_SCALE_CUSTOM':
- global_matrix = Matrix.Scale(unit_scale, 4) * global_matrix
+ global_matrix = Matrix.Scale(unit_scale, 4) @ global_matrix
unit_scale = global_scale
else: # if apply_scale_options == 'FBX_SCALE_ALL':
unit_scale = global_scale * unit_scale
@@ -3014,7 +2964,7 @@ def save_single(operator, scene, filepath="",
start_time = time.process_time()
# Generate some data about exported scene...
- scene_data = fbx_data_from_scene(scene, settings)
+ scene_data = fbx_data_from_scene(scene, depsgraph, settings)
root = elem_empty(None, b"") # Root element has no id, as it is not saved per se!
@@ -3058,7 +3008,6 @@ def save_single(operator, scene, filepath="",
def defaults_unity3d():
return {
# These options seem to produce the same result as the old Ascii exporter in Unity3D:
- "version": 'BIN7400',
"axis_up": 'Y',
"axis_forward": '-Z',
"global_matrix": Matrix.Rotation(-math.pi / 2.0, 4, 'X'),
@@ -3097,18 +3046,19 @@ def defaults_unity3d():
def save(operator, context,
filepath="",
use_selection=False,
+ use_active_collection=False,
batch_mode='OFF',
use_batch_own_dir=False,
**kwargs
):
"""
- This is a wrapper around save_single, which handles multi-scenes (or groups) cases, when batch-exporting a whole
- .blend file.
+ This is a wrapper around save_single, which handles multi-scenes (or collections) cases, when batch-exporting
+ a whole .blend file.
"""
- ret = None
+ ret = {'FINISHED'}
- active_object = context.scene.objects.active
+ active_object = context.view_layer.objects.active
org_mode = None
if active_object and active_object.mode != 'OBJECT' and bpy.ops.object.mode_set.poll():
@@ -3117,37 +3067,59 @@ def save(operator, context,
if batch_mode == 'OFF':
kwargs_mod = kwargs.copy()
- if use_selection:
- kwargs_mod["context_objects"] = context.selected_objects
+ if use_active_collection:
+ if use_selection:
+ ctx_objects = tuple(obj
+ for obj in context.view_layer.active_layer_collection.collection.all_objects
+ if obj.select_get())
+ else:
+ ctx_objects = context.view_layer.active_layer_collection.collection.all_objects
else:
- kwargs_mod["context_objects"] = context.scene.objects
+ if use_selection:
+ ctx_objects = context.selected_objects
+ else:
+ ctx_objects = context.view_layer.objects
+ kwargs_mod["context_objects"] = ctx_objects
- ret = save_single(operator, context.scene, filepath, **kwargs_mod)
+ ret = save_single(operator, context.scene, context.depsgraph, filepath, **kwargs_mod)
else:
+ # XXX We need a way to generate a depsgraph for inactive view_layers first...
+ # XXX Also, what to do in case of batch-exporting scenes, when there is more than one view layer?
+ # Scenes have no concept of 'active' view layer, that's on window level...
fbxpath = filepath
prefix = os.path.basename(fbxpath)
if prefix:
fbxpath = os.path.dirname(fbxpath)
- if batch_mode == 'GROUP':
- data_seq = tuple(grp for grp in bpy.data.groups if grp.objects)
+ if batch_mode == 'COLLECTION':
+ data_seq = tuple((coll, coll.name, 'objects') for coll in bpy.data.collections if coll.objects)
+ elif batch_mode in {'SCENE_COLLECTION', 'ACTIVE_SCENE_COLLECTION'}:
+ scenes = [context.scene] if batch_mode == 'ACTIVE_SCENE_COLLECTION' else bpy.data.scenes
+ data_seq = []
+ for scene in scenes:
+ if not scene.objects:
+ continue
+ # Needed to avoid having tens of 'Master Collection' entries.
+ todo_collections = [(scene.collection, "_".join((scene.name, scene.collection.name)))]
+ while todo_collections:
+ coll, coll_name = todo_collections.pop()
+ todo_collections.extend(((c, c.name) for c in coll.children if c.all_objects))
+ data_seq.append((coll, coll_name, 'all_objects'))
else:
- data_seq = bpy.data.scenes
+ data_seq = tuple((scene, scene.name, 'objects') for scene in bpy.data.scenes if scene.objects)
# call this function within a loop with BATCH_ENABLE == False
- # no scene switching done at the moment.
- # orig_sce = context.scene
new_fbxpath = fbxpath # own dir option modifies, we need to keep an original
- for data in data_seq: # scene or group
- newname = "_".join((prefix, bpy.path.clean_name(data.name))) if prefix else bpy.path.clean_name(data.name)
+ for data, data_name, data_obj_propname in data_seq: # scene or collection
+ newname = "_".join((prefix, bpy.path.clean_name(data_name))) if prefix else bpy.path.clean_name(data_name)
if use_batch_own_dir:
new_fbxpath = os.path.join(fbxpath, newname)
- # path may already exist
- # TODO - might exist but be a file. unlikely but should probably account for it.
-
+ # path may already exist... and be a file.
+ while os.path.isfile(new_fbxpath):
+ new_fbxpath = "_".join((new_fbxpath, "dir"))
if not os.path.exists(new_fbxpath):
os.makedirs(new_fbxpath)
@@ -3155,18 +3127,14 @@ def save(operator, context,
print('\nBatch exporting %s as...\n\t%r' % (data, filepath))
- if batch_mode == 'GROUP': # group
- # group, so objects update properly, add a dummy scene.
+ if batch_mode in {'COLLECTION', 'SCENE_COLLECTION', 'ACTIVE_SCENE_COLLECTION'}:
+ # Collection, so that objects update properly, add a dummy scene.
scene = bpy.data.scenes.new(name="FBX_Temp")
- scene.layers = [True] * 20
- # bpy.data.scenes.active = scene # XXX, cant switch
src_scenes = {} # Count how much each 'source' scenes are used.
- for ob_base in data.objects:
- for src_sce in ob_base.users_scene:
- if src_sce not in src_scenes:
- src_scenes[src_sce] = 0
- src_scenes[src_sce] += 1
- scene.objects.link(ob_base)
+ for obj in getattr(data, data_obj_propname):
+ for src_sce in obj.users_scene:
+ src_scenes[src_sce] = src_scenes.setdefault(src_sce, 0) + 1
+ scene.collection.objects.link(obj)
# Find the 'most used' source scene, and use its unit settings. This is somewhat weak, but should work
# fine in most cases, and avoids stupid issues like T41931.
@@ -3186,20 +3154,17 @@ def save(operator, context,
scene = data
kwargs_batch = kwargs.copy()
- kwargs_batch["context_objects"] = data.objects
+ kwargs_batch["context_objects"] = getattr(data, data_obj_propname)
- save_single(operator, scene, filepath, **kwargs_batch)
+ save_single(operator, scene, scene.view_layers[0].depsgraph, filepath, **kwargs_batch)
- if batch_mode == 'GROUP':
- # remove temp group scene
+ if batch_mode in {'COLLECTION', 'SCENE_COLLECTION', 'ACTIVE_SCENE_COLLECTION'}:
+ # Remove temp collection scene.
bpy.data.scenes.remove(scene)
- # no active scene changing!
- # bpy.data.scenes.active = orig_sce
-
- ret = {'FINISHED'} # so the script wont run after we have batch exported.
-
- if active_object and org_mode and bpy.ops.object.mode_set.poll():
- bpy.ops.object.mode_set(mode=org_mode)
+ if active_object and org_mode:
+ context.view_layer.objects.active = active_object
+ if bpy.ops.object.mode_set.poll():
+ bpy.ops.object.mode_set(mode=org_mode)
return ret
diff --git a/io_scene_fbx/fbx_utils.py b/io_scene_fbx/fbx_utils.py
index 0ed26b6a..19f32800 100644
--- a/io_scene_fbx/fbx_utils.py
+++ b/io_scene_fbx/fbx_utils.py
@@ -24,13 +24,13 @@
import math
import time
-from collections import namedtuple, OrderedDict
+from collections import namedtuple
from collections.abc import Iterable
from itertools import zip_longest, chain
import bpy
import bpy_extras
-from bpy.types import Object, Bone, PoseBone, DupliObject
+from bpy.types import Object, Bone, PoseBone, DepsgraphObjectInstance
from mathutils import Vector, Matrix
from . import encode_bin, data_types
@@ -71,7 +71,7 @@ FBX_ANIM_PROPSGROUP_NAME = "d"
FBX_KTIME = 46186158000 # This is the number of "ktimes" in one second (yep, precision over the nanosecond...)
-MAT_CONVERT_LAMP = Matrix.Rotation(math.pi / 2.0, 4, 'X') # Blender is -Z, FBX is -Y.
+MAT_CONVERT_LIGHT = Matrix.Rotation(math.pi / 2.0, 4, 'X') # Blender is -Z, FBX is -Y.
MAT_CONVERT_CAMERA = Matrix.Rotation(math.pi / 2.0, 4, 'Y') # Blender is -Z, FBX is +X.
# XXX I can't get this working :(
# MAT_CONVERT_BONE = Matrix.Rotation(math.pi / 2.0, 4, 'Z') # Blender is +Y, FBX is -X.
@@ -271,14 +271,14 @@ def similar_values_iter(v1, v2, e=1e-6):
def vcos_transformed_gen(raw_cos, m=None):
# Note: we could most likely get much better performances with numpy, but will leave this as TODO for now.
gen = zip(*(iter(raw_cos),) * 3)
- return gen if m is None else (m * Vector(v) for v in gen)
+ return gen if m is None else (m @ Vector(v) for v in gen)
def nors_transformed_gen(raw_nors, m=None):
# Great, now normals are also expected 4D!
# XXX Back to 3D normals for now!
# gen = zip(*(iter(raw_nors),) * 3 + (_infinite_gen(1.0),))
gen = zip(*(iter(raw_nors),) * 3)
- return gen if m is None else (m * Vector(v) for v in gen)
+ return gen if m is None else (m @ Vector(v) for v in gen)
# ##### UIDs code. #####
@@ -324,7 +324,7 @@ def _key_to_uuid(uuids, key):
def get_fbx_uuid_from_key(key):
"""
- Return an UUID for given key, which is assumed hasable.
+ Return an UUID for given key, which is assumed to be hashable.
"""
uuid = _keys_to_uuids.get(key, None)
if uuid is None:
@@ -431,6 +431,10 @@ def get_blender_anim_curve_key(scene, ref_id, obj_key, fbx_prop_name, fbx_prop_i
fbx_prop_item_name, "AnimCurve"))
+def get_blender_nodetexture_key(ma, socket_names):
+ return "|".join((get_blenderID_key(ma), *socket_names))
+
+
# ##### Element generators. #####
# Note: elem may be None, in this case the element is not added to any parent.
@@ -614,12 +618,12 @@ def elem_props_template_init(templates, template_type):
"""
Init a writing template of given type, for *one* element's properties.
"""
- ret = OrderedDict()
+ ret = {}
tmpl = templates.get(template_type)
if tmpl is not None:
written = tmpl.written[0]
props = tmpl.properties
- ret = OrderedDict((name, [val, ptype, anim, written]) for name, (val, ptype, anim) in props.items())
+ ret = {name: [val, ptype, anim, written] for name, (val, ptype, anim) in props.items()}
return ret
@@ -671,14 +675,11 @@ def fbx_templates_generate(root, fbx_templates):
# for Lights, Cameras, LibNodes, etc.).
ref_templates = {(tmpl.type_name, tmpl.prop_type_name): tmpl for tmpl in fbx_templates.values()}
- templates = OrderedDict()
+ templates = {}
for type_name, prop_type_name, properties, nbr_users, _written in fbx_templates.values():
- tmpl = templates.get(type_name)
- if tmpl is None:
- templates[type_name] = [OrderedDict(((prop_type_name, (properties, nbr_users)),)), nbr_users]
- else:
- tmpl[0][prop_type_name] = (properties, nbr_users)
- tmpl[1] += nbr_users
+ tmpl = templates.setdefault(type_name, [{}, 0])
+ tmpl[0][prop_type_name] = (properties, nbr_users)
+ tmpl[1] += nbr_users
for type_name, (subprops, nbr_users) in templates.items():
template = elem_data_single_string(root, b"ObjectType", type_name)
@@ -844,7 +845,7 @@ class AnimationCurveNodeWrapper:
for elem_key, fbx_group, fbx_gname, fbx_props in \
zip(self.elem_keys, self.fbx_group, self.fbx_gname, self.fbx_props):
group_key = get_blender_anim_curve_node_key(scene, ref_id, elem_key, fbx_group)
- group = OrderedDict()
+ group = {}
for c, def_val, fbx_item in zip(curves, self.default_values, fbx_props):
fbx_item = FBX_ANIM_PROPSGROUP_NAME + "|" + fbx_item
curve_key = get_blender_anim_curve_key(scene, ref_id, elem_key, fbx_group, fbx_item)
@@ -856,7 +857,7 @@ class AnimationCurveNodeWrapper:
# ##### FBX objects generators. #####
-# FBX Model-like data (i.e. Blender objects, dupliobjects and bones) are wrapped in ObjectWrapper.
+# FBX Model-like data (i.e. Blender objects, depsgraph instances and bones) are wrapped in ObjectWrapper.
# This allows us to have a (nearly) same code FBX-wise for all those types.
# The wrapper tries to stay as small as possible, by mostly using callbacks (property(get...))
# to actual Blender data it contains.
@@ -870,9 +871,13 @@ class MetaObjectWrapper(type):
dup_mat = None
if isinstance(bdata, Object):
key = get_blenderID_key(bdata)
- elif isinstance(bdata, DupliObject):
- key = "|".join((get_blenderID_key((bdata.id_data, bdata.object)), cls._get_dup_num_id(bdata)))
- dup_mat = bdata.matrix.copy()
+ elif isinstance(bdata, DepsgraphObjectInstance):
+ if bdata.is_instance:
+ key = "|".join((get_blenderID_key((bdata.parent.original, bdata.instance_object.original)),
+ cls._get_dup_num_id(bdata)))
+ dup_mat = bdata.matrix_world.copy()
+ else:
+ key = get_blenderID_key(bdata.object.original)
else: # isinstance(bdata, (Bone, PoseBone)):
if isinstance(bdata, PoseBone):
bdata = armature.data.bones[bdata.name]
@@ -883,9 +888,9 @@ class MetaObjectWrapper(type):
cache = cls._cache = {}
instance = cache.get(key)
if instance is not None:
- # Duplis hack: since duplis are not persistent in Blender (we have to re-create them to get updated
+ # Duplis hack: since dupli instances are not persistent in Blender (we have to re-create them to get updated
# info like matrix...), we *always* need to reset that matrix when calling ObjectWrapper() (all
- # other data is supposed valid during whole cache live, so we can skip resetting it).
+ # other data is supposed valid during whole cache live span, so we can skip resetting it).
instance._dupli_matrix = dup_mat
return instance
@@ -902,7 +907,7 @@ class ObjectWrapper(metaclass=MetaObjectWrapper):
This class provides a same common interface for all (FBX-wise) object-like elements:
* Blender Object
* Blender Bone and PoseBone
- * Blender DupliObject
+ * Blender DepsgraphObjectInstance (for dulis).
Note since a same Blender object might be 'mapped' to several FBX models (esp. with duplis),
we need to use a key to identify each.
"""
@@ -918,24 +923,42 @@ class ObjectWrapper(metaclass=MetaObjectWrapper):
@staticmethod
def _get_dup_num_id(bdata):
- return ".".join(str(i) for i in bdata.persistent_id if i != 2147483647)
+ INVALID_IDS = {2147483647, 0}
+ pids = tuple(bdata.persistent_id)
+ idx_valid = 0
+ prev_i = ...
+ for idx, i in enumerate(pids[::-1]):
+ if i not in INVALID_IDS or (idx == len(pids) and i == 0 and prev_i != 0):
+ idx_valid = len(pids) - idx
+ break
+ prev_i = i
+ return ".".join(str(i) for i in pids[:idx_valid])
def __init__(self, bdata, armature=None):
"""
- bdata might be an Object, DupliObject, Bone or PoseBone.
+ bdata might be an Object (deprecated), DepsgraphObjectInstance, Bone or PoseBone.
If Bone or PoseBone, armature Object must be provided.
"""
- if isinstance(bdata, Object):
+ # Note: DepsgraphObjectInstance are purely runtime data, they become invalid as soon as we step to the next item!
+ # Hence we have to immediately copy *all* needed data...
+ if isinstance(bdata, Object): # DEPRECATED
self._tag = 'OB'
self.name = get_blenderID_name(bdata)
self.bdata = bdata
self._ref = None
- elif isinstance(bdata, DupliObject):
- self._tag = 'DP'
- self.name = "|".join((get_blenderID_name((bdata.id_data, bdata.object)),
- "Dupli", self._get_dup_num_id(bdata)))
- self.bdata = bdata.object
- self._ref = bdata.id_data
+ elif isinstance(bdata, DepsgraphObjectInstance):
+ if bdata.is_instance:
+ # Note that dupli instance matrix is set by meta-class initialization.
+ self._tag = 'DP'
+ self.name = "|".join((get_blenderID_name((bdata.parent.original, bdata.instance_object.original)),
+ "Dupli", self._get_dup_num_id(bdata)))
+ self.bdata = bdata.instance_object.original
+ self._ref = bdata.parent.original
+ else:
+ self._tag = 'OB'
+ self.name = get_blenderID_name(bdata)
+ self.bdata = bdata.object.original
+ self._ref = None
else: # isinstance(bdata, (Bone, PoseBone)):
if isinstance(bdata, PoseBone):
bdata = armature.data.bones[bdata.name]
@@ -951,13 +974,17 @@ class ObjectWrapper(metaclass=MetaObjectWrapper):
def __hash__(self):
return hash(self.key)
+ def __repr__(self):
+ return self.key
+
# #### Common to all _tag values.
def get_fbx_uuid(self):
return get_fbx_uuid_from_key(self.key)
fbx_uuid = property(get_fbx_uuid)
+ # XXX Not sure how much that’s useful now... :/
def get_hide(self):
- return self.bdata.hide
+ return self.bdata.hide_viewport if self._tag in {'OB', 'DP'} else self.bdata.hide
hide = property(get_hide)
def get_parent(self):
@@ -974,7 +1001,7 @@ class ObjectWrapper(metaclass=MetaObjectWrapper):
# Mere object parenting.
return ObjectWrapper(self.bdata.parent)
elif self._tag == 'DP':
- return ObjectWrapper(self.bdata.parent or self._ref)
+ return ObjectWrapper(self._ref)
else: # self._tag == 'BO'
return ObjectWrapper(self.bdata.parent, self._ref) or ObjectWrapper(self._ref)
parent = property(get_parent)
@@ -983,12 +1010,12 @@ class ObjectWrapper(metaclass=MetaObjectWrapper):
if self._tag == 'OB':
return self.bdata.matrix_local.copy()
elif self._tag == 'DP':
- return self._ref.matrix_world.inverted_safe() * self._dupli_matrix
+ return self._ref.matrix_world.inverted_safe() @ self._dupli_matrix
else: # 'BO', current pose
# PoseBone.matrix is in armature space, bring in back in real local one!
par = self.bdata.parent
par_mat_inv = self._ref.pose.bones[par.name].matrix.inverted_safe() if par else Matrix()
- return par_mat_inv * self._ref.pose.bones[self.bdata.name].matrix
+ return par_mat_inv @ self._ref.pose.bones[self.bdata.name].matrix
matrix_local = property(get_matrix_local)
def get_matrix_global(self):
@@ -997,7 +1024,7 @@ class ObjectWrapper(metaclass=MetaObjectWrapper):
elif self._tag == 'DP':
return self._dupli_matrix
else: # 'BO', current pose
- return self._ref.matrix_world * self._ref.pose.bones[self.bdata.name].matrix
+ return self._ref.matrix_world @ self._ref.pose.bones[self.bdata.name].matrix
matrix_global = property(get_matrix_global)
def get_matrix_rest_local(self):
@@ -1005,14 +1032,14 @@ class ObjectWrapper(metaclass=MetaObjectWrapper):
# Bone.matrix_local is in armature space, bring in back in real local one!
par = self.bdata.parent
par_mat_inv = par.matrix_local.inverted_safe() if par else Matrix()
- return par_mat_inv * self.bdata.matrix_local
+ return par_mat_inv @ self.bdata.matrix_local
else:
return self.matrix_local.copy()
matrix_rest_local = property(get_matrix_rest_local)
def get_matrix_rest_global(self):
if self._tag == 'BO':
- return self._ref.matrix_world * self.bdata.matrix_local
+ return self._ref.matrix_world @ self.bdata.matrix_local
else:
return self.matrix_global.copy()
matrix_rest_global = property(get_matrix_rest_global)
@@ -1065,41 +1092,41 @@ class ObjectWrapper(metaclass=MetaObjectWrapper):
if self._tag == 'BO':
# If we have a bone parent we need to undo the parent correction.
if not is_global and scene_data.settings.bone_correction_matrix_inv and parent and parent.is_bone:
- matrix = scene_data.settings.bone_correction_matrix_inv * matrix
+ matrix = scene_data.settings.bone_correction_matrix_inv @ matrix
# Apply the bone correction.
if scene_data.settings.bone_correction_matrix:
- matrix = matrix * scene_data.settings.bone_correction_matrix
- elif self.bdata.type == 'LAMP':
- matrix = matrix * MAT_CONVERT_LAMP
+ matrix = matrix @ scene_data.settings.bone_correction_matrix
+ elif self.bdata.type == 'LIGHT':
+ matrix = matrix @ MAT_CONVERT_LIGHT
elif self.bdata.type == 'CAMERA':
- matrix = matrix * MAT_CONVERT_CAMERA
+ matrix = matrix @ MAT_CONVERT_CAMERA
if self._tag in {'DP', 'OB'} and parent:
if parent._tag == 'BO':
# In bone parent case, we get transformation in **bone tip** space (sigh).
# Have to bring it back into bone root, which is FBX expected value.
- matrix = Matrix.Translation((0, (parent.bdata.tail - parent.bdata.head).length, 0)) * matrix
+ matrix = Matrix.Translation((0, (parent.bdata.tail - parent.bdata.head).length, 0)) @ matrix
# Our matrix is in local space, time to bring it in its final desired space.
if parent:
if is_global:
# Move matrix to global Blender space.
- matrix = (parent.matrix_rest_global if rest else parent.matrix_global) * matrix
+ matrix = (parent.matrix_rest_global if rest else parent.matrix_global) @ matrix
elif parent.use_bake_space_transform(scene_data):
# Blender's and FBX's local space of parent may differ if we use bake_space_transform...
# Apply parent's *Blender* local space...
- matrix = (parent.matrix_rest_local if rest else parent.matrix_local) * matrix
+ matrix = (parent.matrix_rest_local if rest else parent.matrix_local) @ matrix
# ...and move it back into parent's *FBX* local space.
par_mat = parent.fbx_object_matrix(scene_data, rest=rest, local_space=True)
- matrix = par_mat.inverted_safe() * matrix
+ matrix = par_mat.inverted_safe() @ matrix
if self.use_bake_space_transform(scene_data):
# If we bake the transforms we need to post-multiply inverse global transform.
# This means that the global transform will not apply to children of this transform.
- matrix = matrix * scene_data.settings.global_matrix_inv
+ matrix = matrix @ scene_data.settings.global_matrix_inv
if is_global:
# In any case, pre-multiply the global matrix to get it in FBX global space!
- matrix = scene_data.settings.global_matrix * matrix
+ matrix = scene_data.settings.global_matrix @ matrix
return matrix
@@ -1164,19 +1191,11 @@ class ObjectWrapper(metaclass=MetaObjectWrapper):
return True
# #### Duplis...
- def dupli_list_create(self, scene, settings='PREVIEW'):
- if self._tag == 'OB' and self.bdata.is_duplicator:
- self.bdata.dupli_list_create(scene, settings)
-
- def dupli_list_clear(self):
- if self._tag == 'OB'and self.bdata.is_duplicator:
- self.bdata.dupli_list_clear()
-
- def get_dupli_list(self):
- if self._tag == 'OB'and self.bdata.is_duplicator:
- return (ObjectWrapper(dup) for dup in self.bdata.dupli_list)
+ def dupli_list_gen(self, depsgraph):
+ if self._tag == 'OB' and self.bdata.is_instancer:
+ return (ObjectWrapper(dup) for dup in depsgraph.object_instances
+ if dup.parent and ObjectWrapper(dup.parent.original) == self)
return ()
- dupli_list = property(get_dupli_list)
def fbx_name_class(name, cls):
@@ -1213,8 +1232,8 @@ FBXExportSettings = namedtuple("FBXExportSettings", (
# * animations.
FBXExportData = namedtuple("FBXExportData", (
"templates", "templates_users", "connections",
- "settings", "scene", "objects", "animations", "animated", "frame_start", "frame_end",
- "data_empties", "data_lamps", "data_cameras", "data_meshes", "mesh_mat_indices",
+ "settings", "scene", "depsgraph", "objects", "animations", "animated", "frame_start", "frame_end",
+ "data_empties", "data_lights", "data_cameras", "data_meshes", "mesh_material_indices",
"data_bones", "data_leaf_bones", "data_deformers_skin", "data_deformers_shape",
"data_world", "data_materials", "data_textures", "data_videos",
))
@@ -1223,11 +1242,11 @@ FBXExportData = namedtuple("FBXExportData", (
FBXImportSettings = namedtuple("FBXImportSettings", (
"report", "to_axes", "global_matrix", "global_scale",
"bake_space_transform", "global_matrix_inv", "global_matrix_inv_transposed",
- "use_custom_normals", "use_cycles", "use_image_search",
+ "use_custom_normals", "use_image_search",
"use_alpha_decals", "decal_offset",
"use_anim", "anim_offset",
"use_custom_props", "use_custom_props_enum_as_string",
- "cycles_material_wrap_map", "image_cache",
+ "nodal_material_wrap_map", "image_cache",
"ignore_leaf_bones", "force_connect_children", "automatic_bone_orientation", "bone_correction_matrix",
"use_prepost_rot",
))
diff --git a/io_scene_fbx/import_fbx.py b/io_scene_fbx/import_fbx.py
index addb8ae6..f35a6387 100644
--- a/io_scene_fbx/import_fbx.py
+++ b/io_scene_fbx/import_fbx.py
@@ -60,7 +60,7 @@ fbx_elem_nil = None
convert_deg_to_rad_iter = units_convertor_iter("degree", "radian")
MAT_CONVERT_BONE = fbx_utils.MAT_CONVERT_BONE.inverted()
-MAT_CONVERT_LAMP = fbx_utils.MAT_CONVERT_LAMP.inverted()
+MAT_CONVERT_LIGHT = fbx_utils.MAT_CONVERT_LIGHT.inverted()
MAT_CONVERT_CAMERA = fbx_utils.MAT_CONVERT_CAMERA.inverted()
@@ -369,7 +369,7 @@ def blen_read_custom_properties(fbx_obj, blen_obj, settings):
def blen_read_object_transform_do(transform_data):
# This is a nightmare. FBX SDK uses Maya way to compute the transformation matrix of a node - utterly simple:
#
- # WorldTransform = ParentWorldTransform * T * Roff * Rp * Rpre * R * Rpost * Rp-1 * Soff * Sp * S * Sp-1
+ # WorldTransform = ParentWorldTransform @ T @ Roff @ Rp @ Rpre @ R @ Rpost @ Rp-1 @ Soff @ Sp @ S @ Sp-1
#
# Where all those terms are 4 x 4 matrices that contain:
# WorldTransform: Transformation matrix of the node in global space.
@@ -389,7 +389,7 @@ def blen_read_object_transform_do(transform_data):
# But it was still too simple, and FBX notion of compatibility is... quite specific. So we also have to
# support 3DSMax way:
#
- # WorldTransform = ParentWorldTransform * T * R * S * OT * OR * OS
+ # WorldTransform = ParentWorldTransform @ T @ R @ S @ OT @ OR @ OS
#
# Where all those terms are 4 x 4 matrices that contain:
# WorldTransform: Transformation matrix of the node in global space
@@ -414,7 +414,7 @@ def blen_read_object_transform_do(transform_data):
# rotation
to_rot = lambda rot, rot_ord: Euler(convert_deg_to_rad_iter(rot), rot_ord).to_matrix().to_4x4()
- lcl_rot = to_rot(transform_data.rot, transform_data.rot_ord) * transform_data.rot_alt_mat
+ lcl_rot = to_rot(transform_data.rot, transform_data.rot_ord) @ transform_data.rot_alt_mat
pre_rot = to_rot(transform_data.pre_rot, transform_data.rot_ord)
pst_rot = to_rot(transform_data.pst_rot, transform_data.rot_ord)
geom_rot = to_rot(transform_data.geom_rot, transform_data.rot_ord)
@@ -431,21 +431,21 @@ def blen_read_object_transform_do(transform_data):
geom_scale[0][0], geom_scale[1][1], geom_scale[2][2] = transform_data.geom_sca
base_mat = (
- lcl_translation *
- rot_ofs *
- rot_piv *
- pre_rot *
- lcl_rot *
- pst_rot *
- rot_piv.inverted_safe() *
- sca_ofs *
- sca_piv *
- lcl_scale *
+ lcl_translation @
+ rot_ofs @
+ rot_piv @
+ pre_rot @
+ lcl_rot @
+ pst_rot @
+ rot_piv.inverted_safe() @
+ sca_ofs @
+ sca_piv @
+ lcl_scale @
sca_piv.inverted_safe()
)
- geom_mat = geom_loc * geom_rot * geom_scale
+ geom_mat = geom_loc @ geom_rot @ geom_scale
# We return mat without 'geometric transforms' too, because it is to be used for children, sigh...
- return (base_mat * geom_mat, base_mat, geom_mat)
+ return (base_mat @ geom_mat, base_mat, geom_mat)
# XXX This might be weak, now that we can add vgroups from both bones and shapes, name collisions become
@@ -457,7 +457,7 @@ def add_vgroup_to_objects(vg_indices, vg_weights, vg_name, objects):
# We replace/override here...
vg = obj.vertex_groups.get(vg_name)
if vg is None:
- vg = obj.vertex_groups.new(vg_name)
+ vg = obj.vertex_groups.new(name=vg_name)
for i, w in zip(vg_indices, vg_weights):
vg.add((i,), w, 'REPLACE')
@@ -601,7 +601,7 @@ def blen_read_animations_action_item(action, item, cnodes, fps, anim_offset):
else: # Euler
props[1] = (bl_obj.path_from_id("rotation_euler"), 3, grpname or "Euler Rotation")
- blen_curves = [action.fcurves.new(prop, channel, grpname)
+ blen_curves = [action.fcurves.new(prop, index=channel, action_group=grpname)
for prop, nbr_channels, grpname in props for channel in range(nbr_channels)]
if isinstance(item, Material):
@@ -613,7 +613,7 @@ def blen_read_animations_action_item(action, item, cnodes, fps, anim_offset):
value[channel] = v
for fc, v in zip(blen_curves, value):
- fc.keyframe_points.insert(frame, v, {'NEEDED', 'FAST'}).interpolation = 'LINEAR'
+ fc.keyframe_points.insert(frame, v, options={'NEEDED', 'FAST'}).interpolation = 'LINEAR'
elif isinstance(item, ShapeKey):
for frame, values in blen_read_animations_curves_iter(fbx_curves, anim_offset, 0, fps):
@@ -624,7 +624,7 @@ def blen_read_animations_action_item(action, item, cnodes, fps, anim_offset):
value = v / 100.0
for fc, v in zip(blen_curves, (value,)):
- fc.keyframe_points.insert(frame, v, {'NEEDED', 'FAST'}).interpolation = 'LINEAR'
+ fc.keyframe_points.insert(frame, v, options={'NEEDED', 'FAST'}).interpolation = 'LINEAR'
elif isinstance(item, Camera):
for frame, values in blen_read_animations_curves_iter(fbx_curves, anim_offset, 0, fps):
@@ -635,7 +635,7 @@ def blen_read_animations_action_item(action, item, cnodes, fps, anim_offset):
value = v
for fc, v in zip(blen_curves, (value,)):
- fc.keyframe_points.insert(frame, v, {'NEEDED', 'FAST'}).interpolation = 'LINEAR'
+ fc.keyframe_points.insert(frame, v, options={'NEEDED', 'FAST'}).interpolation = 'LINEAR'
else: # Object or PoseBone:
if item.is_bone:
@@ -661,19 +661,19 @@ def blen_read_animations_action_item(action, item, cnodes, fps, anim_offset):
# compensate for changes in the local matrix during processing
if item.anim_compensation_matrix:
- mat = mat * item.anim_compensation_matrix
+ mat = mat @ item.anim_compensation_matrix
# apply pre- and post matrix
# post-matrix will contain any correction for lights, camera and bone orientation
# pre-matrix will contain any correction for a parent's correction matrix or the global matrix
if item.pre_matrix:
- mat = item.pre_matrix * mat
+ mat = item.pre_matrix @ mat
if item.post_matrix:
- mat = mat * item.post_matrix
+ mat = mat @ item.post_matrix
# And now, remove that rest pose matrix from current mat (also in parent space).
if restmat_inv:
- mat = restmat_inv * mat
+ mat = restmat_inv @ mat
# Now we have a virtual matrix of transform from AnimCurves, we can insert keyframes!
loc, rot, sca = mat.decompose()
@@ -686,7 +686,7 @@ def blen_read_animations_action_item(action, item, cnodes, fps, anim_offset):
rot = rot.to_euler(rot_mode, rot_prev)
rot_prev = rot
for fc, value in zip(blen_curves, chain(loc, rot, sca)):
- fc.keyframe_points.insert(frame, value, {'NEEDED', 'FAST'}).interpolation = 'LINEAR'
+ fc.keyframe_points.insert(frame, value, options={'NEEDED', 'FAST'}).interpolation = 'LINEAR'
# Since we inserted our keyframes in 'FAST' mode, we have to update the fcurves now.
for fc in blen_curves:
@@ -1006,8 +1006,7 @@ def blen_read_geom_layer_uv(fbx_obj, mesh):
fbx_layer_data = elem_prop_first(elem_find_first(fbx_layer, b'UV'))
fbx_layer_index = elem_prop_first(elem_find_first(fbx_layer, b'UVIndex'))
- uv_tex = mesh.uv_textures.new(name=fbx_layer_name)
- uv_lay = mesh.uv_layers[-1]
+ uv_lay = mesh.uv_layers.new(name=fbx_layer_name)
blen_data = uv_lay.data
# some valid files omit this data
@@ -1087,7 +1086,6 @@ def blen_read_geom_layer_smooth(fbx_obj, mesh):
)
# We only set sharp edges here, not face smoothing itself...
mesh.use_auto_smooth = True
- mesh.show_edge_sharp = True
return False
elif fbx_layer_mapping == b'ByPolygon':
blen_data = mesh.polygons
@@ -1165,7 +1163,7 @@ def blen_read_geom(fbx_tmpl, fbx_obj, settings):
if geom_mat_co is not None:
def _vcos_transformed_gen(raw_cos, m=None):
# Note: we could most likely get much better performances with numpy, but will leave this as TODO for now.
- return chain(*(m * Vector(v) for v in zip(*(iter(raw_cos),) * 3)))
+ return chain(*(m @ Vector(v) for v in zip(*(iter(raw_cos),) * 3)))
fbx_verts = array.array(fbx_verts.typecode, _vcos_transformed_gen(fbx_verts, geom_mat_co))
if fbx_verts is None:
@@ -1242,7 +1240,7 @@ def blen_read_geom(fbx_tmpl, fbx_obj, settings):
ok_normals = blen_read_geom_layer_normal(fbx_obj, mesh)
else:
def nortrans(v):
- return geom_mat_no * Vector(v)
+ return geom_mat_no @ Vector(v)
ok_normals = blen_read_geom_layer_normal(fbx_obj, mesh, nortrans)
mesh.validate(clean_customdata=False) # *Very* important to not remove lnors here!
@@ -1257,7 +1255,6 @@ def blen_read_geom(fbx_tmpl, fbx_obj, settings):
mesh.normals_split_custom_set(tuple(zip(*(iter(clnors),) * 3)))
mesh.use_auto_smooth = True
- mesh.show_edge_sharp = True
else:
mesh.calc_normals()
@@ -1319,9 +1316,12 @@ def blen_read_shape(fbx_tmpl, fbx_sdata, fbx_bcdata, meshes, scene):
# Material
def blen_read_material(fbx_tmpl, fbx_obj, settings):
+ from bpy_extras import node_shader_utils
+ from math import sqrt
+
elem_name_utf8 = elem_name_ensure_class(fbx_obj, b'Material')
- cycles_material_wrap_map = settings.cycles_material_wrap_map
+ nodal_material_wrap_map = settings.nodal_material_wrap_map
ma = bpy.data.materials.new(name=elem_name_utf8)
const_color_white = 1.0, 1.0, 1.0
@@ -1329,43 +1329,23 @@ def blen_read_material(fbx_tmpl, fbx_obj, settings):
fbx_props = (elem_find_first(fbx_obj, b'Properties70'),
elem_find_first(fbx_tmpl, b'Properties70', fbx_elem_nil))
- ma_diff = elem_props_get_color_rgb(fbx_props, b'DiffuseColor', const_color_white)
- ma_spec = elem_props_get_color_rgb(fbx_props, b'SpecularColor', const_color_white)
- ma_alpha = elem_props_get_number(fbx_props, b'Opacity', 1.0)
- ma_spec_intensity = ma.specular_intensity = elem_props_get_number(fbx_props, b'SpecularFactor', 0.25) * 2.0
- ma_spec_hardness = elem_props_get_number(fbx_props, b'Shininess', 9.6)
- ma_refl_factor = elem_props_get_number(fbx_props, b'ReflectionFactor', 0.0)
- ma_refl_color = elem_props_get_color_rgb(fbx_props, b'ReflectionColor', const_color_white)
-
- if settings.use_cycles:
- from modules import cycles_shader_compat
- # viewport color
- ma.diffuse_color = ma_diff
-
- ma_wrap = cycles_shader_compat.CyclesShaderWrapper(ma)
- ma_wrap.diffuse_color_set(ma_diff)
- ma_wrap.specular_color_set([c * ma_spec_intensity for c in ma_spec])
- ma_wrap.hardness_value_set(((ma_spec_hardness + 3.0) / 5.0) - 0.65)
- ma_wrap.alpha_value_set(ma_alpha)
- ma_wrap.reflect_factor_set(ma_refl_factor)
- ma_wrap.reflect_color_set(ma_refl_color)
-
- cycles_material_wrap_map[ma] = ma_wrap
- else:
- # TODO, number BumpFactor isnt used yet
- ma.diffuse_color = ma_diff
- ma.specular_color = ma_spec
- ma.alpha = ma_alpha
- if ma_alpha < 1.0:
- ma.use_transparency = True
- ma.transparency_method = 'RAYTRACE'
- ma.specular_intensity = ma_spec_intensity
- ma.specular_hardness = ma_spec_hardness * 5.10 + 1.0
-
- if ma_refl_factor != 0.0:
- ma.raytrace_mirror.use = True
- ma.raytrace_mirror.reflect_factor = ma_refl_factor
- ma.mirror_color = ma_refl_color
+ ma_wrap = node_shader_utils.PrincipledBSDFWrapper(ma, is_readonly=False, use_nodes=True)
+ ma_wrap.base_color = elem_props_get_color_rgb(fbx_props, b'DiffuseColor', const_color_white)
+ # No specular color in Principled BSDF shader, assumed to be either white or take some tint from diffuse one...
+ # TODO: add way to handle tint option (guesstimate from spec color + intensity...)?
+ ma_wrap.specular = elem_props_get_number(fbx_props, b'SpecularFactor', 0.25) * 2.0
+ # XXX Totally empirical conversion, trying to adapt it
+ # (from 1.0 - 0.0 Principled BSDF range to 0.0 - 100.0 FBX shininess range)...
+ fbx_shininess = elem_props_get_number(fbx_props, b'Shininess', 20.0)
+ ma_wrap.roughness = 1.0 - (sqrt(fbx_shininess) / 10.0)
+ ma_wrap.transmission = 1.0 - elem_props_get_number(fbx_props, b'Opacity', 1.0)
+ ma_wrap.metallic = elem_props_get_number(fbx_props, b'ReflectionFactor', 0.0)
+ # We have no metallic (a.k.a. reflection) color...
+ # elem_props_get_color_rgb(fbx_props, b'ReflectionColor', const_color_white)
+ # (x / 7.142) is only a guess, cycles usable range is (0.0 -> 0.5)
+ ma_wrap.normalmap_strength = elem_props_get_number(fbx_props, b'BumpFactor', 2.5) / 7.142
+
+ nodal_material_wrap_map[ma] = ma_wrap
if settings.use_custom_props:
blen_read_custom_properties(fbx_obj, ma, settings)
@@ -1479,7 +1459,7 @@ def blen_read_light(fbx_tmpl, fbx_obj, global_scale):
1: 'SUN',
2: 'SPOT'}.get(elem_props_get_enum(fbx_props, b'LightType', 0), 'POINT')
- lamp = bpy.data.lamps.new(name=elem_name_utf8, type=light_type)
+ lamp = bpy.data.lights.new(name=elem_name_utf8, type=light_type)
if light_type == 'SPOT':
spot_size = elem_props_get_number(fbx_props, b'OuterAngle', None)
@@ -1494,11 +1474,14 @@ def blen_read_light(fbx_tmpl, fbx_obj, global_scale):
spot_blend = elem_props_get_number(fbx_props, b'HotSpot', 45.0)
lamp.spot_blend = 1.0 - (spot_blend / spot_size)
- # TODO, cycles
+ # TODO, cycles nodes???
lamp.color = elem_props_get_color_rgb(fbx_props, b'Color', (1.0, 1.0, 1.0))
lamp.energy = elem_props_get_number(fbx_props, b'Intensity', 100.0) / 100.0
lamp.distance = elem_props_get_number(fbx_props, b'DecayStart', 25.0) * global_scale
- lamp.shadow_method = ('RAY_SHADOW' if elem_props_get_bool(fbx_props, b'CastShadow', True) else 'NOSHADOW')
+ lamp.use_shadow = elem_props_get_bool(fbx_props, b'CastShadow', True)
+ if hasattr(lamp, "cycles"):
+ lamp.cycles.cast_shadow = lamp.use_shadow
+ # Keeping this for now, but this is not used nor exposed anymore afaik...
lamp.shadow_color = elem_props_get_color_rgb(fbx_props, b'ShadowColor', (0.0, 0.0, 0.0))
return lamp
@@ -1612,7 +1595,7 @@ class FbxImportHelperNode:
self.pre_matrix = settings.global_matrix
if parent_correction_inv:
- self.pre_matrix = parent_correction_inv * (self.pre_matrix if self.pre_matrix else Matrix())
+ self.pre_matrix = parent_correction_inv @ (self.pre_matrix if self.pre_matrix else Matrix())
correction_matrix = None
@@ -1700,12 +1683,12 @@ class FbxImportHelperNode:
if self.fbx_type == b'Camera':
correction_matrix = MAT_CONVERT_CAMERA
elif self.fbx_type == b'Light':
- correction_matrix = MAT_CONVERT_LAMP
+ correction_matrix = MAT_CONVERT_LIGHT
self.post_matrix = correction_matrix
if self.do_bake_transform(settings):
- self.post_matrix = settings.global_matrix_inv * (self.post_matrix if self.post_matrix else Matrix())
+ self.post_matrix = settings.global_matrix_inv @ (self.post_matrix if self.post_matrix else Matrix())
# process children
correction_matrix_inv = correction_matrix.inverted_safe() if correction_matrix else None
@@ -1782,29 +1765,29 @@ class FbxImportHelperNode:
def get_world_matrix_as_parent(self):
matrix = self.parent.get_world_matrix_as_parent() if self.parent else Matrix()
if self.matrix_as_parent:
- matrix = matrix * self.matrix_as_parent
+ matrix = matrix @ self.matrix_as_parent
return matrix
def get_world_matrix(self):
matrix = self.parent.get_world_matrix_as_parent() if self.parent else Matrix()
if self.matrix:
- matrix = matrix * self.matrix
+ matrix = matrix @ self.matrix
return matrix
def get_matrix(self):
matrix = self.matrix if self.matrix else Matrix()
if self.pre_matrix:
- matrix = self.pre_matrix * matrix
+ matrix = self.pre_matrix @ matrix
if self.post_matrix:
- matrix = matrix * self.post_matrix
+ matrix = matrix @ self.post_matrix
return matrix
def get_bind_matrix(self):
matrix = self.bind_matrix if self.bind_matrix else Matrix()
if self.pre_matrix:
- matrix = self.pre_matrix * matrix
+ matrix = self.pre_matrix @ matrix
if self.post_matrix:
- matrix = matrix * self.post_matrix
+ matrix = matrix @ self.post_matrix
return matrix
def make_bind_pose_local(self, parent_matrix=None):
@@ -1812,13 +1795,13 @@ class FbxImportHelperNode:
parent_matrix = Matrix()
if self.bind_matrix:
- bind_matrix = parent_matrix.inverted_safe() * self.bind_matrix
+ bind_matrix = parent_matrix.inverted_safe() @ self.bind_matrix
else:
bind_matrix = self.matrix.copy() if self.matrix else None
self.bind_matrix = bind_matrix
if bind_matrix:
- parent_matrix = parent_matrix * bind_matrix
+ parent_matrix = parent_matrix @ bind_matrix
for child in self.children:
child.make_bind_pose_local(parent_matrix)
@@ -1838,8 +1821,8 @@ class FbxImportHelperNode:
child.collect_skeleton_meshes(meshes)
for m in meshes:
old_matrix = m.matrix
- m.matrix = armature_matrix_inv * m.get_world_matrix()
- m.anim_compensation_matrix = old_matrix.inverted_safe() * m.matrix
+ m.matrix = armature_matrix_inv @ m.get_world_matrix()
+ m.anim_compensation_matrix = old_matrix.inverted_safe() @ m.matrix
m.is_global_animation = True
m.parent = self
self.meshes = meshes
@@ -1914,7 +1897,7 @@ class FbxImportHelperNode:
bone.tail = bone_tail
# And rotate/move it to its final "rest pose".
- bone_matrix = parent_matrix * self.get_bind_matrix().normalized()
+ bone_matrix = parent_matrix @ self.get_bind_matrix().normalized()
bone.matrix = bone_matrix
@@ -1927,7 +1910,7 @@ class FbxImportHelperNode:
if child.is_leaf and force_connect_children:
# Arggggggggggggggggg! We do not want to create this bone, but we need its 'virtual head' location
# to orient current one!!!
- child_head = (bone_matrix * child.get_bind_matrix().normalized()).translation
+ child_head = (bone_matrix @ child.get_bind_matrix().normalized()).translation
child_connect(bone, None, child_head, connect_ctx)
elif child.is_bone and not child.ignore:
child_bone = child.build_skeleton(arm, bone_matrix, bone_size,
@@ -1958,7 +1941,7 @@ class FbxImportHelperNode:
# Misc Attributes
obj.color[0:3] = elem_props_get_color_rgb(fbx_props, b'Color', (0.8, 0.8, 0.8))
- obj.hide = not bool(elem_props_get_visibility(fbx_props, b'Visibility', 1.0))
+ obj.hide_viewport = not bool(elem_props_get_visibility(fbx_props, b'Visibility', 1.0))
obj.matrix_basis = self.get_matrix()
@@ -1967,12 +1950,12 @@ class FbxImportHelperNode:
return obj
- def build_skeleton_children(self, fbx_tmpl, settings, scene):
+ def build_skeleton_children(self, fbx_tmpl, settings, scene, view_layer):
if self.is_bone:
for child in self.children:
if child.ignore:
continue
- child.build_skeleton_children(fbx_tmpl, settings, scene)
+ child.build_skeleton_children(fbx_tmpl, settings, scene, view_layer)
return None
else:
# child is not a bone
@@ -1984,11 +1967,11 @@ class FbxImportHelperNode:
for child in self.children:
if child.ignore:
continue
- child.build_skeleton_children(fbx_tmpl, settings, scene)
+ child.build_skeleton_children(fbx_tmpl, settings, scene, view_layer)
# instance in scene
- obj_base = scene.objects.link(obj)
- obj_base.select = True
+ view_layer.active_layer_collection.collection.objects.link(obj)
+ obj.select_set(True)
return obj
@@ -2007,7 +1990,7 @@ class FbxImportHelperNode:
# Blender attaches to the end of a bone, while FBX attaches to the start.
# bone_child_matrix corrects for that.
if child.pre_matrix:
- child.pre_matrix = self.bone_child_matrix * child.pre_matrix
+ child.pre_matrix = self.bone_child_matrix @ child.pre_matrix
else:
child.pre_matrix = self.bone_child_matrix
@@ -2027,7 +2010,7 @@ class FbxImportHelperNode:
def set_pose_matrix(self, arm):
pose_bone = arm.bl_obj.pose.bones[self.bl_bone]
- pose_bone.matrix_basis = self.get_bind_matrix().inverted_safe() * self.get_matrix()
+ pose_bone.matrix_basis = self.get_bind_matrix().inverted_safe() @ self.get_matrix()
for child in self.children:
if child.ignore:
@@ -2094,7 +2077,7 @@ class FbxImportHelperNode:
if child.is_bone and not child.ignore:
child.set_bone_weights()
- def build_hierarchy(self, fbx_tmpl, settings, scene):
+ def build_hierarchy(self, fbx_tmpl, settings, scene, view_layer):
if self.is_armature:
# create when linking since we need object data
elem_name_utf8 = self.fbx_name
@@ -2114,15 +2097,15 @@ class FbxImportHelperNode:
blen_read_custom_properties(self.fbx_elem, arm, settings)
# instance in scene
- obj_base = scene.objects.link(arm)
- obj_base.select = True
+ view_layer.active_layer_collection.collection.objects.link(arm)
+ arm.select_set(True)
# Add bones:
# Switch to Edit mode.
- scene.objects.active = arm
- is_hidden = arm.hide
- arm.hide = False # Can't switch to Edit mode hidden objects...
+ view_layer.objects.active = arm
+ is_hidden = arm.hide_viewport
+ arm.hide_viewport = False # Can't switch to Edit mode hidden objects...
bpy.ops.object.mode_set(mode='EDIT')
for child in self.children:
@@ -2133,7 +2116,7 @@ class FbxImportHelperNode:
bpy.ops.object.mode_set(mode='OBJECT')
- arm.hide = is_hidden
+ arm.hide_viewport = is_hidden
# Set pose matrix
for child in self.children:
@@ -2146,7 +2129,7 @@ class FbxImportHelperNode:
for child in self.children:
if child.ignore:
continue
- child_obj = child.build_skeleton_children(fbx_tmpl, settings, scene)
+ child_obj = child.build_skeleton_children(fbx_tmpl, settings, scene, view_layer)
return arm
elif self.fbx_elem and not self.is_bone:
@@ -2154,16 +2137,16 @@ class FbxImportHelperNode:
# walk through children
for child in self.children:
- child.build_hierarchy(fbx_tmpl, settings, scene)
+ child.build_hierarchy(fbx_tmpl, settings, scene, view_layer)
# instance in scene
- obj_base = scene.objects.link(obj)
- obj_base.select = True
+ view_layer.active_layer_collection.collection.objects.link(obj)
+ obj.select_set(True)
return obj
else:
for child in self.children:
- child.build_hierarchy(fbx_tmpl, settings, scene)
+ child.build_hierarchy(fbx_tmpl, settings, scene, view_layer)
return None
@@ -2192,16 +2175,16 @@ class FbxImportHelperNode:
# which we obviously cannot do in Blender. :/
if amat is None:
amat = self.bind_matrix
- amat = settings.global_matrix * (Matrix() if amat is None else amat)
+ amat = settings.global_matrix @ (Matrix() if amat is None else amat)
if self.matrix_geom:
- amat = amat * self.matrix_geom
- mmat = settings.global_matrix * mmat
+ amat = amat @ self.matrix_geom
+ mmat = settings.global_matrix @ mmat
if mesh.matrix_geom:
- mmat = mmat * mesh.matrix_geom
+ mmat = mmat @ mesh.matrix_geom
# Now that we have armature and mesh in there (global) bind 'state' (matrix),
# we can compute inverse parenting matrix of the mesh.
- me_obj.matrix_parent_inverse = amat.inverted_safe() * mmat * me_obj.matrix_basis.inverted_safe()
+ me_obj.matrix_parent_inverse = amat.inverted_safe() @ mmat @ me_obj.matrix_basis.inverted_safe()
mod = mesh.bl_obj.modifiers.new(arm.name, 'ARMATURE')
mod.object = arm
@@ -2249,7 +2232,6 @@ def load(operator, context, filepath="",
global_scale=1.0,
bake_space_transform=False,
use_custom_normals=True,
- use_cycles=True,
use_image_search=False,
use_alpha_decals=False,
decal_offset=0.0,
@@ -2311,10 +2293,8 @@ def load(operator, context, filepath="",
basedir = os.path.dirname(filepath)
- cycles_material_wrap_map = {}
+ nodal_material_wrap_map = {}
image_cache = {}
- if not use_cycles:
- texture_cache = {}
# Tables: (FBX_byte_id -> [FBX_data, None or Blender_datablock])
fbx_table_nodes = {}
@@ -2325,6 +2305,7 @@ def load(operator, context, filepath="",
material_decals = None
scene = context.scene
+ view_layer = context.view_layer
# #### Get some info from GlobalSettings.
@@ -2350,7 +2331,7 @@ def load(operator, context, filepath="",
elem_props_get_integer(fbx_settings_props, b'CoordAxisSign', 1))
axis_key = (axis_up, axis_forward, axis_coord)
axis_up, axis_forward = {v: k for k, v in RIGHT_HAND_AXES.items()}.get(axis_key, ('Z', 'Y'))
- global_matrix = (Matrix.Scale(global_scale, 4) *
+ global_matrix = (Matrix.Scale(global_scale, 4) @
axis_conversion(from_forward=axis_forward, from_up=axis_up).to_4x4())
# To cancel out unwanted rotation/scale on nodes.
@@ -2381,11 +2362,11 @@ def load(operator, context, filepath="",
settings = FBXImportSettings(
operator.report, (axis_up, axis_forward), global_matrix, global_scale,
bake_space_transform, global_matrix_inv, global_matrix_inv_transposed,
- use_custom_normals, use_cycles, use_image_search,
+ use_custom_normals, use_image_search,
use_alpha_decals, decal_offset,
use_anim, anim_offset,
use_custom_props, use_custom_props_enum_as_string,
- cycles_material_wrap_map, image_cache,
+ nodal_material_wrap_map, image_cache,
ignore_leaf_bones, force_connect_children, automatic_bone_orientation, bone_correction_matrix,
use_prepost_rot,
)
@@ -2682,7 +2663,7 @@ def load(operator, context, filepath="",
armature_matrix = tx_arm
if tx_bone:
- mesh_matrix = tx_bone * mesh_matrix
+ mesh_matrix = tx_bone @ mesh_matrix
helper_node.bind_matrix = tx_bone # overwrite the bind matrix
# Get the meshes driven by this cluster: (Shouldn't that be only one?)
@@ -2725,7 +2706,7 @@ def load(operator, context, filepath="",
root_helper.find_correction_matrix(settings)
# build the Object/Armature/Bone hierarchy
- root_helper.build_hierarchy(fbx_tmpl, settings, scene)
+ root_helper.build_hierarchy(fbx_tmpl, settings, scene, view_layer)
# Link the Object/Armature/Bone hierarchy
root_helper.link_hierarchy(fbx_tmpl, settings, scene)
@@ -2857,8 +2838,7 @@ def load(operator, context, filepath="",
continue
mat = fbx_item[1]
items.append((mat, lnk_prop))
- if settings.use_cycles:
- print("WARNING! Importing material's animation is not supported for Cycles materials...")
+ print("WARNING! Importing material's animation is not supported for Nodal materials...")
for al_uuid, al_ctype in fbx_connection_map.get(acn_uuid, ()):
if al_ctype.props[0] != b'OO':
continue
@@ -2919,17 +2899,17 @@ def load(operator, context, filepath="",
# So we have to be careful not to re-add endlessly the same material to a mesh!
# This can easily happen with 'baked' dupliobjects, see T44386.
# TODO: add an option to link materials to objects in Blender instead?
- done_mats = set()
+ done_materials = set()
for (fbx_lnk, fbx_lnk_item, fbx_lnk_type) in connection_filter_forward(fbx_uuid, b'Model'):
# link materials
fbx_lnk_uuid = elem_uuid(fbx_lnk)
for (fbx_lnk_material, material, fbx_lnk_material_type) in connection_filter_reverse(fbx_lnk_uuid, b'Material'):
- if material not in done_mats:
+ if material not in done_materials:
mesh.materials.append(material)
- done_mats.add(material)
+ done_materials.add(material)
- # We have to validate mesh polygons' mat_idx, see T41015!
+ # We have to validate mesh polygons' ma_idx, see T41015!
# Some FBX seem to have an extra 'default' material which is not defined in FBX file.
if mesh.validate_material_indices():
print("WARNING: mesh '%s' had invalid material indices, those were reset to first material" % mesh.name)
@@ -2943,51 +2923,36 @@ def load(operator, context, filepath="",
fbx_tmpl = fbx_template_get((b'Material', b'KFbxSurfacePhong'))
# b'KFbxSurfaceLambert'
- # textures that use this material
- def texture_bumpfac_get(fbx_obj):
- assert(fbx_obj.id == b'Material')
- fbx_props = (elem_find_first(fbx_obj, b'Properties70'),
- elem_find_first(fbx_tmpl, b'Properties70', fbx_elem_nil))
- # (x / 7.142) is only a guess, cycles usable range is (0.0 -> 0.5)
- return elem_props_get_number(fbx_props, b'BumpFactor', 2.5) / 7.142
-
- def texture_mapping_get(fbx_obj):
+ def texture_mapping_set(fbx_obj, node_texture):
assert(fbx_obj.id == b'Texture')
fbx_props = (elem_find_first(fbx_obj, b'Properties70'),
elem_find_first(fbx_tmpl, b'Properties70', fbx_elem_nil))
- return (elem_props_get_vector_3d(fbx_props, b'Translation', (0.0, 0.0, 0.0)),
- elem_props_get_vector_3d(fbx_props, b'Rotation', (0.0, 0.0, 0.0)),
- elem_props_get_vector_3d(fbx_props, b'Scaling', (1.0, 1.0, 1.0)),
- (bool(elem_props_get_enum(fbx_props, b'WrapModeU', 0)),
- bool(elem_props_get_enum(fbx_props, b'WrapModeV', 0))))
-
- if not use_cycles:
- # Simple function to make a new mtex and set defaults
- def material_mtex_new(material, image, tex_map):
- tex = texture_cache.get(image)
- if tex is None:
- tex = bpy.data.textures.new(name=image.name, type='IMAGE')
- tex.image = image
- texture_cache[image] = tex
-
- # copy custom properties from image object to texture
- for key, value in image.items():
- tex[key] = value
-
- # delete custom properties on the image object
- for key in image.keys():
- del image[key]
-
- mtex = material.texture_slots.add()
- mtex.texture = tex
- mtex.texture_coords = 'UV'
- mtex.use_map_color_diffuse = False
-
- # No rotation here...
- mtex.offset[:] = tex_map[0]
- mtex.scale[:] = tex_map[2]
- return mtex
+ loc = elem_props_get_vector_3d(fbx_props, b'Translation', (0.0, 0.0, 0.0))
+ rot = tuple(-r for r in elem_props_get_vector_3d(fbx_props, b'Rotation', (0.0, 0.0, 0.0)))
+ scale = tuple(((1.0 / s) if s != 0.0 else 1.0)
+ for s in elem_props_get_vector_3d(fbx_props, b'Scaling', (1.0, 1.0, 1.0)))
+ clamp_uv = (bool(elem_props_get_enum(fbx_props, b'WrapModeU', 0)),
+ bool(elem_props_get_enum(fbx_props, b'WrapModeV', 0)))
+
+ if (loc == (0.0, 0.0, 0.0) and
+ rot == (0.0, 0.0, 0.0) and
+ scale == (1.0, 1.0, 1.0) and
+ clamp_uv == (False, False)):
+ return
+
+ node_texture.translation = loc
+ node_texture.rotation = rot
+ node_texture.scale = scale
+
+ # awkward conversion UV clamping to min/max
+ node_texture.min = (0.0, 0.0, 0.0)
+ node_texture.max = (1.0, 1.0, 1.0)
+ node_texture.use_min = node_texture.use_max = clamp_uv[0] or clamp_uv[1]
+ if clamp_uv[0] != clamp_uv[1]:
+ # use bool as index
+ node_texture.min[not clamp[0]] = -1e9
+ node_texture.max[not clamp[0]] = 1e9
for fbx_uuid, fbx_item in fbx_table_nodes.items():
fbx_obj, blen_data = fbx_item
@@ -2999,112 +2964,44 @@ def load(operator, context, filepath="",
image,
fbx_lnk_type) in connection_filter_reverse(fbx_uuid, b'Texture'):
- if use_cycles:
- if fbx_lnk_type.props[0] == b'OP':
- lnk_type = fbx_lnk_type.props[3]
-
- ma_wrap = cycles_material_wrap_map[material]
-
- # tx/rot/scale
- tex_map = texture_mapping_get(fbx_lnk)
- if (tex_map[0] == (0.0, 0.0, 0.0) and
- tex_map[1] == (0.0, 0.0, 0.0) and
- tex_map[2] == (1.0, 1.0, 1.0) and
- tex_map[3] == (False, False)):
- use_mapping = False
- else:
- use_mapping = True
- tex_map_kw = {
- "translation": tex_map[0],
- "rotation": [-i for i in tex_map[1]],
- "scale": [((1.0 / i) if i != 0.0 else 1.0) for i in tex_map[2]],
- "clamp": tex_map[3],
- }
-
- if lnk_type in {b'DiffuseColor', b'3dsMax|maps|texmap_diffuse'}:
- ma_wrap.diffuse_image_set(image)
- if use_mapping:
- ma_wrap.diffuse_mapping_set(**tex_map_kw)
- elif lnk_type == b'SpecularColor':
- ma_wrap.specular_image_set(image)
- if use_mapping:
- ma_wrap.specular_mapping_set(**tex_map_kw)
- elif lnk_type in {b'ReflectionColor', b'3dsMax|maps|texmap_reflection'}:
- ma_wrap.reflect_image_set(image)
- if use_mapping:
- ma_wrap.reflect_mapping_set(**tex_map_kw)
- elif lnk_type == b'TransparentColor': # alpha
- ma_wrap.alpha_image_set(image)
- if use_mapping:
- ma_wrap.alpha_mapping_set(**tex_map_kw)
- if use_alpha_decals:
- material_decals.add(material)
- elif lnk_type == b'DiffuseFactor':
- pass # TODO
- elif lnk_type == b'ShininessExponent':
- ma_wrap.hardness_image_set(image)
- if use_mapping:
- ma_wrap.hardness_mapping_set(**tex_map_kw)
- # XXX, applications abuse bump!
- elif lnk_type in {b'NormalMap', b'Bump', b'3dsMax|maps|texmap_bump'}:
- ma_wrap.normal_image_set(image)
- ma_wrap.normal_factor_set(texture_bumpfac_get(fbx_obj))
- if use_mapping:
- ma_wrap.normal_mapping_set(**tex_map_kw)
- """
- elif lnk_type == b'Bump':
- ma_wrap.bump_image_set(image)
- ma_wrap.bump_factor_set(texture_bumpfac_get(fbx_obj))
- if use_mapping:
- ma_wrap.bump_mapping_set(**tex_map_kw)
- """
- else:
- print("WARNING: material link %r ignored" % lnk_type)
-
- material_images.setdefault(material, {})[lnk_type] = (image, tex_map)
- else:
- if fbx_lnk_type.props[0] == b'OP':
- lnk_type = fbx_lnk_type.props[3]
-
- # tx/rot/scale (rot is ignored here!).
- tex_map = texture_mapping_get(fbx_lnk)
-
- mtex = material_mtex_new(material, image, tex_map)
-
- if lnk_type in {b'DiffuseColor', b'3dsMax|maps|texmap_diffuse'}:
- mtex.use_map_color_diffuse = True
- mtex.blend_type = 'MULTIPLY'
- elif lnk_type == b'SpecularColor':
- mtex.use_map_color_spec = True
- mtex.blend_type = 'MULTIPLY'
- elif lnk_type in {b'ReflectionColor', b'3dsMax|maps|texmap_reflection'}:
- mtex.use_map_raymir = True
- elif lnk_type == b'TransparentColor': # alpha
- material.use_transparency = True
- material.transparency_method = 'RAYTRACE'
- material.alpha = 0.0
- mtex.use_map_alpha = True
- mtex.alpha_factor = 1.0
- if use_alpha_decals:
- material_decals.add(material)
- elif lnk_type == b'DiffuseFactor':
- mtex.use_map_diffuse = True
- elif lnk_type == b'ShininessExponent':
- mtex.use_map_hardness = True
- # XXX, applications abuse bump!
- elif lnk_type in {b'NormalMap', b'Bump', b'3dsMax|maps|texmap_bump'}:
- mtex.texture.use_normal_map = True # not ideal!
- mtex.use_map_normal = True
- mtex.normal_factor = texture_bumpfac_get(fbx_obj)
- """
- elif lnk_type == b'Bump':
- mtex.use_map_normal = True
- mtex.normal_factor = texture_bumpfac_get(fbx_obj)
- """
- else:
- print("WARNING: material link %r ignored" % lnk_type)
+ if fbx_lnk_type.props[0] == b'OP':
+ lnk_type = fbx_lnk_type.props[3]
+
+ ma_wrap = nodal_material_wrap_map[material]
+
+ if lnk_type in {b'DiffuseColor', b'3dsMax|maps|texmap_diffuse'}:
+ ma_wrap.base_color_texture.image = image
+ texture_mapping_set(fbx_lnk, ma_wrap.base_color_texture)
+ elif lnk_type in {b'SpecularColor', b'SpecularFactor'}:
+ # Intensity actually, not color...
+ ma_wrap.specular_texture.image = image
+ texture_mapping_set(fbx_lnk, ma_wrap.specular_texture)
+ elif lnk_type in {b'ReflectionColor', b'ReflectionFactor', b'3dsMax|maps|texmap_reflection'}:
+ # Intensity actually, not color...
+ ma_wrap.metallic_texture.image = image
+ texture_mapping_set(fbx_lnk, ma_wrap.metallic_texture)
+ elif lnk_type in {b'TransparentColor', b'TransparentFactor'}:
+ # Transparency... sort of...
+ ma_wrap.transmission_texture.image = image
+ texture_mapping_set(fbx_lnk, ma_wrap.transmission_texture)
+ if use_alpha_decals:
+ material_decals.add(material)
+ elif lnk_type == b'ShininessExponent':
+ # That is probably reversed compared to expected results? TODO...
+ ma_wrap.roughness_texture.image = image
+ texture_mapping_set(fbx_lnk, ma_wrap.roughness_texture)
+ # XXX, applications abuse bump!
+ elif lnk_type in {b'NormalMap', b'Bump', b'3dsMax|maps|texmap_bump'}:
+ ma_wrap.normalmap_texture.image = image
+ texture_mapping_set(fbx_lnk, ma_wrap.normalmap_texture)
+ """
+ elif lnk_type == b'Bump':
+ # TODO displacement...
+ """
+ else:
+ print("WARNING: material link %r ignored" % lnk_type)
- material_images.setdefault(material, {})[lnk_type] = (image, tex_map)
+ material_images.setdefault(material, {})[lnk_type] = image
# Check if the diffuse image has an alpha channel,
# if so, use the alpha channel.
@@ -3115,30 +3012,21 @@ def load(operator, context, filepath="",
if fbx_obj.id != b'Material':
continue
material = fbx_table_nodes.get(fbx_uuid, (None, None))[1]
- image, tex_map = material_images.get(material, {}).get(b'DiffuseColor', (None, None))
+ image = material_images.get(material, {}).get(b'DiffuseColor', None)
# do we have alpha?
if image and image.depth == 32:
if use_alpha_decals:
material_decals.add(material)
- if use_cycles:
- ma_wrap = cycles_material_wrap_map[material]
- if ma_wrap.node_bsdf_alpha.mute:
- ma_wrap.alpha_image_set_from_diffuse()
- else:
- if not any((True for mtex in material.texture_slots if mtex and mtex.use_map_alpha)):
- mtex = material_mtex_new(material, image, tex_map)
+ ma_wrap = nodal_material_wrap_map[material]
+ ma_wrap.transmission_texture.use_alpha = True
+ ma_wrap.transmission_texture.copy_from(ma_wrap.base_color_texture)
- material.use_transparency = True
- material.transparency_method = 'RAYTRACE'
- material.alpha = 0.0
- mtex.use_map_alpha = True
- mtex.alpha_factor = 1.0
-
- # propagate mapping from diffuse to all other channels which have none defined.
- if use_cycles:
- ma_wrap = cycles_material_wrap_map[material]
- ma_wrap.mapping_set_from_diffuse()
+ # Propagate mapping from diffuse to all other channels which have none defined.
+ # XXX Commenting for now, I do not really understand the logic here, why should diffuse mapping
+ # be applied to all others if not defined for them???
+ # ~ ma_wrap = nodal_material_wrap_map[material]
+ # ~ ma_wrap.mapping_set_from_diffuse()
_(); del _
@@ -3161,14 +3049,8 @@ def load(operator, context, filepath="",
v.co += v.normal * decal_offset
break
- if use_cycles:
- for obj in (obj for obj in bpy.data.objects if obj.data == mesh):
- obj.cycles_visibility.shadow = False
- else:
- for material in mesh.materials:
- if material in material_decals:
- # receive but dont cast shadows
- material.use_raytrace = False
+ for obj in (obj for obj in bpy.data.objects if obj.data == mesh):
+ obj.cycles_visibility.shadow = False
_(); del _
perfmon.level_down()
diff --git a/io_scene_gltf2/__init__.py b/io_scene_gltf2/__init__.py
new file mode 100755
index 00000000..0318919a
--- /dev/null
+++ b/io_scene_gltf2/__init__.py
@@ -0,0 +1,520 @@
+# Copyright 2018 The glTF-Blender-IO authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+#
+# Imports
+#
+
+import os
+import bpy
+from bpy_extras.io_utils import ImportHelper, ExportHelper
+from bpy.types import Operator, AddonPreferences
+
+from .io.com.gltf2_io_debug import Log
+
+from bpy.props import (CollectionProperty,
+ StringProperty,
+ BoolProperty,
+ EnumProperty,
+ FloatProperty,
+ IntProperty)
+
+#
+# Globals
+#
+
+bl_info = {
+ 'name': 'glTF 2.0 format',
+ 'author': 'Julien Duroure, Norbert Nopper, Urs Hanselmann Moritz Becher, Benjamin Schmithüsen',
+ "version": (0, 0, 1),
+ 'blender': (2, 80, 0),
+ 'location': 'File > Import-Export',
+ 'description': 'Import-Export as glTF 2.0',
+ 'warning': '',
+ 'wiki_url': "https://github.com/KhronosGroup/glTF-Blender-IO",
+ 'tracker_url': "https://github.com/KhronosGroup/glTF-Blender-IO/issues/",
+ 'support': 'OFFICIAL',
+ 'category': 'Import-Export'}
+
+
+#
+# Functions / Classes.
+#
+
+
+class ExportGLTF2_Base:
+
+ # TODO: refactor to avoid boilerplate
+
+ bl_options = {'UNDO', 'PRESET'}
+
+ export_format: EnumProperty(
+ name='Format',
+ items=(('GLB', 'glTF Binary (.glb)',
+ 'Exports a single file, with all data packed in binary form. '
+ 'Most efficient and portable, but more difficult to edit later'),
+ ('GLTF_EMBEDDED', 'glTF Embedded (.gltf)',
+ 'Exports a single file, with all data packed in JSON. '
+ 'Less efficient than binary, but easier to edit later'),
+ ('GLTF_SEPARATE', 'glTF Separate (.gltf + .bin + textures)',
+ 'Exports multiple files, with separate JSON, binary and texture data. '
+ 'Easiest to edit later')),
+ description=(
+ 'Output format and embedding options. Binary is most efficient, '
+ 'but JSON (embedded or separate) may be easier to edit later'
+ ),
+ default='GLB'
+ )
+
+ ui_tab: EnumProperty(
+ items=(('GENERAL', "General", "General settings"),
+ ('MESHES', "Meshes", "Mesh settings"),
+ ('OBJECTS', "Objects", "Object settings"),
+ ('MATERIALS', "Materials", "Material settings"),
+ ('ANIMATION', "Animation", "Animation settings")),
+ name="ui_tab",
+ description="Export setting categories",
+ )
+
+ export_copyright: StringProperty(
+ name='Copyright',
+ description='Legal rights and conditions for the model',
+ default=''
+ )
+
+ export_texcoords: BoolProperty(
+ name='UVs',
+ description='Export UVs (texture coordinates) with meshes',
+ default=True
+ )
+
+ export_normals: BoolProperty(
+ name='Normals',
+ description='Export vertex normals with meshes',
+ default=True
+ )
+
+ export_tangents: BoolProperty(
+ name='Tangents',
+ description='Export vertex tangents with meshes',
+ default=False
+ )
+
+ export_materials: BoolProperty(
+ name='Materials',
+ description='Export materials',
+ default=True
+ )
+
+ export_colors: BoolProperty(
+ name='Vertex Colors',
+ description='Export vertex colors with meshes',
+ default=True
+ )
+
+ export_cameras: BoolProperty(
+ name='Cameras',
+ description='Export cameras',
+ default=False
+ )
+
+ export_selected: BoolProperty(
+ name='Selected Objects',
+ description='Export selected objects only',
+ default=False
+ )
+
+ # export_layers: BoolProperty(
+ # name='All layers',
+ # description='Export all layers, rather than just the first',
+ # default=True
+ # )
+
+ export_extras: BoolProperty(
+ name='Custom Properties',
+ description='Export custom properties as glTF extras',
+ default=False
+ )
+
+ export_yup: BoolProperty(
+ name='+Y Up',
+ description='Export using glTF convention, +Y up',
+ default=True
+ )
+
+ export_apply: BoolProperty(
+ name='Apply Modifiers',
+ description='Apply modifiers to mesh objects',
+ default=False
+ )
+
+ export_animations: BoolProperty(
+ name='Animations',
+ description='Exports active actions and NLA tracks as glTF animations',
+ default=True
+ )
+
+ export_frame_range: BoolProperty(
+ name='Limit to Playback Range',
+ description='Clips animations to selected playback range',
+ default=True
+ )
+
+ export_frame_step: IntProperty(
+ name='Sampling Rate',
+ description='How often to evaluate animated values (in frames)',
+ default=1,
+ min=1,
+ max=120
+ )
+
+ export_move_keyframes: BoolProperty(
+ name='Keyframes Start at 0',
+ description='Keyframes start at 0, instead of 1',
+ default=True
+ )
+
+ export_force_sampling: BoolProperty(
+ name='Always Sample Animations',
+ description='Apply sampling to all animations',
+ default=False
+ )
+
+ export_current_frame: BoolProperty(
+ name='Use Current Frame',
+ description='Export the scene in the current animation frame',
+ default=True
+ )
+
+ export_skins: BoolProperty(
+ name='Skinning',
+ description='Export skinning (armature) data',
+ default=True
+ )
+
+ export_bake_skins: BoolProperty(
+ name='Bake Skinning Constraints',
+ description='Apply skinning constraints to armatures',
+ default=False
+ )
+
+ export_all_influences: BoolProperty(
+ name='Include All Bone Influences',
+ description='Allow >4 joint vertex influences. Models may appear incorrectly in many viewers',
+ default=False
+ )
+
+ export_morph: BoolProperty(
+ name='Shape Keys',
+ description='Export shape keys (morph targets)',
+ default=True
+ )
+
+ export_morph_normal: BoolProperty(
+ name='Shape Key Normals',
+ description='Export vertex normals with shape keys (morph targets)',
+ default=True
+ )
+
+ export_morph_tangent: BoolProperty(
+ name='Shape Key Tangents',
+ description='Export vertex tangents with shape keys (morph targets)',
+ default=False
+ )
+
+ export_lights: BoolProperty(
+ name='Punctual Lights',
+ description='Export directional, point, and spot lights. '
+ 'Uses "KHR_lights_punctual" glTF extension',
+ default=False
+ )
+
+ export_texture_transform: BoolProperty(
+ name='Texture Transforms',
+ description='Export texture or UV position, rotation, and scale. '
+ 'Uses "KHR_texture_transform" glTF extension',
+ default=False
+ )
+
+ export_displacement: BoolProperty(
+ name='Displacement Textures (EXPERIMENTAL)',
+ description='EXPERIMENTAL: Export displacement textures. '
+ 'Uses incomplete "KHR_materials_displacement" glTF extension',
+ default=False
+ )
+
+ will_save_settings: BoolProperty(default=False)
+
+ # Custom scene property for saving settings
+ scene_key = "glTF2ExportSettings"
+
+ #
+
+ def invoke(self, context, event):
+ settings = context.scene.get(self.scene_key)
+ self.will_save_settings = False
+ if settings:
+ try:
+ for (k, v) in settings.items():
+ setattr(self, k, v)
+ self.will_save_settings = True
+
+ except (AttributeError, TypeError):
+ self.report({"ERROR"}, "Loading export settings failed. Removed corrupted settings")
+ del context.scene[self.scene_key]
+
+ return ExportHelper.invoke(self, context, event)
+
+ def save_settings(self, context):
+ # find all export_ props
+ all_props = self.properties
+ export_props = {x: getattr(self, x) for x in dir(all_props)
+ if x.startswith("export_") and all_props.get(x) is not None}
+
+ context.scene[self.scene_key] = export_props
+
+ def execute(self, context):
+ import datetime
+ from .blender.exp import gltf2_blender_export
+
+ if self.will_save_settings:
+ self.save_settings(context)
+
+ if self.export_format == 'GLB':
+ self.filename_ext = '.glb'
+ else:
+ self.filename_ext = '.gltf'
+
+ # All custom export settings are stored in this container.
+ export_settings = {}
+
+ export_settings['timestamp'] = datetime.datetime.now()
+
+ export_settings['gltf_filepath'] = bpy.path.ensure_ext(self.filepath, self.filename_ext)
+ export_settings['gltf_filedirectory'] = os.path.dirname(export_settings['gltf_filepath']) + '/'
+
+ export_settings['gltf_format'] = self.export_format
+ export_settings['gltf_copyright'] = self.export_copyright
+ export_settings['gltf_texcoords'] = self.export_texcoords
+ export_settings['gltf_normals'] = self.export_normals
+ export_settings['gltf_tangents'] = self.export_tangents and self.export_normals
+ export_settings['gltf_materials'] = self.export_materials
+ export_settings['gltf_colors'] = self.export_colors
+ export_settings['gltf_cameras'] = self.export_cameras
+ export_settings['gltf_selected'] = self.export_selected
+ export_settings['gltf_layers'] = True #self.export_layers
+ export_settings['gltf_extras'] = self.export_extras
+ export_settings['gltf_yup'] = self.export_yup
+ export_settings['gltf_apply'] = self.export_apply
+ export_settings['gltf_animations'] = self.export_animations
+ if self.export_animations:
+ export_settings['gltf_current_frame'] = False
+ export_settings['gltf_frame_range'] = self.export_frame_range
+ export_settings['gltf_move_keyframes'] = self.export_move_keyframes
+ export_settings['gltf_force_sampling'] = self.export_force_sampling
+ else:
+ export_settings['gltf_current_frame'] = self.export_current_frame
+ export_settings['gltf_frame_range'] = False
+ export_settings['gltf_move_keyframes'] = False
+ export_settings['gltf_force_sampling'] = False
+ export_settings['gltf_skins'] = self.export_skins
+ if self.export_skins:
+ export_settings['gltf_bake_skins'] = self.export_bake_skins
+ export_settings['gltf_all_vertex_influences'] = self.export_all_influences
+ else:
+ export_settings['gltf_bake_skins'] = False
+ export_settings['gltf_all_vertex_influences'] = False
+ export_settings['gltf_frame_step'] = self.export_frame_step
+ export_settings['gltf_morph'] = self.export_morph
+ if self.export_morph:
+ export_settings['gltf_morph_normal'] = self.export_morph_normal
+ else:
+ export_settings['gltf_morph_normal'] = False
+ if self.export_morph and self.export_morph_normal:
+ export_settings['gltf_morph_tangent'] = self.export_morph_tangent
+ else:
+ export_settings['gltf_morph_tangent'] = False
+
+ export_settings['gltf_lights'] = self.export_lights
+ export_settings['gltf_texture_transform'] = self.export_texture_transform
+ export_settings['gltf_displacement'] = self.export_displacement
+
+ export_settings['gltf_binary'] = bytearray()
+ export_settings['gltf_binaryfilename'] = os.path.splitext(os.path.basename(self.filepath))[0] + '.bin'
+
+ return gltf2_blender_export.save(context, export_settings)
+
+ def draw(self, context):
+ self.layout.prop(self, 'ui_tab', expand=True)
+ if self.ui_tab == 'GENERAL':
+ self.draw_general_settings()
+ elif self.ui_tab == 'MESHES':
+ self.draw_mesh_settings()
+ elif self.ui_tab == 'OBJECTS':
+ self.draw_object_settings()
+ elif self.ui_tab == 'MATERIALS':
+ self.draw_material_settings()
+ elif self.ui_tab == 'ANIMATION':
+ self.draw_animation_settings()
+
+ def draw_general_settings(self):
+ col = self.layout.box().column()
+ col.prop(self, 'export_format')
+ col.prop(self, 'export_selected')
+ col.prop(self, 'export_apply')
+ col.prop(self, 'export_yup')
+ col.prop(self, 'export_extras')
+ col.prop(self, 'export_copyright')
+
+ def draw_mesh_settings(self):
+ col = self.layout.box().column()
+ col.prop(self, 'export_texcoords')
+ col.prop(self, 'export_normals')
+ if self.export_normals:
+ col.prop(self, 'export_tangents')
+ col.prop(self, 'export_colors')
+
+ def draw_object_settings(self):
+ col = self.layout.box().column()
+ col.prop(self, 'export_cameras')
+ col.prop(self, 'export_lights')
+
+ def draw_material_settings(self):
+ col = self.layout.box().column()
+ col.prop(self, 'export_materials')
+ col.prop(self, 'export_texture_transform')
+
+ def draw_animation_settings(self):
+ col = self.layout.box().column()
+ col.prop(self, 'export_animations')
+ if self.export_animations:
+ col.prop(self, 'export_frame_range')
+ col.prop(self, 'export_frame_step')
+ col.prop(self, 'export_move_keyframes')
+ col.prop(self, 'export_force_sampling')
+ else:
+ col.prop(self, 'export_current_frame')
+ col.prop(self, 'export_skins')
+ if self.export_skins:
+ col.prop(self, 'export_bake_skins')
+ col.prop(self, 'export_all_influences')
+ col.prop(self, 'export_morph')
+ if self.export_morph:
+ col.prop(self, 'export_morph_normal')
+ if self.export_morph_normal:
+ col.prop(self, 'export_morph_tangent')
+
+
+class ExportGLTF2(bpy.types.Operator, ExportGLTF2_Base, ExportHelper):
+ """Export scene as glTF 2.0 file"""
+ bl_idname = 'export_scene.gltf'
+ bl_label = 'glTF 2.0 (.glb/.gltf)'
+
+ filename_ext = ''
+
+ filter_glob: StringProperty(default='*.glb;*.gltf', options={'HIDDEN'})
+
+
+def menu_func_export(self, context):
+ self.layout.operator(ExportGLTF2.bl_idname, text='glTF 2.0 (.glb/.gltf)')
+
+
+class ImportGLTF2(Operator, ImportHelper):
+ bl_idname = 'import_scene.gltf'
+ bl_label = 'glTF 2.0 (.glb/.gltf)'
+
+ filter_glob: StringProperty(default="*.glb;*.gltf", options={'HIDDEN'})
+
+ loglevel: EnumProperty(
+ items=Log.get_levels(),
+ name="Log Level",
+ description="Set level of log to display",
+ default=Log.default())
+
+ import_pack_images: BoolProperty(
+ name='Pack images',
+ description='Pack all images into .blend file',
+ default=True
+ )
+
+ import_shading: EnumProperty(
+ name="Shading",
+ items=(("NORMALS", "Use Normal Data", ""),
+ ("FLAT", "Flat Shading", ""),
+ ("SMOOTH", "Smooth Shading", "")),
+ description="How normals are computed during import",
+ default="NORMALS")
+
+ def draw(self, context):
+ layout = self.layout
+
+ layout.prop(self, 'loglevel')
+ layout.prop(self, 'import_pack_images')
+ layout.prop(self, 'import_shading')
+
+ def execute(self, context):
+ return self.import_gltf2(context)
+
+ def import_gltf2(self, context):
+ from .io.imp.gltf2_io_gltf import glTFImporter
+ from .blender.imp.gltf2_blender_gltf import BlenderGlTF
+
+ import_settings = self.as_keywords()
+
+ self.gltf_importer = glTFImporter(self.filepath, import_settings)
+ success, txt = self.gltf_importer.read()
+ if not success:
+ self.report({'ERROR'}, txt)
+ return {'CANCELLED'}
+ success, txt = self.gltf_importer.checks()
+ if not success:
+ self.report({'ERROR'}, txt)
+ return {'CANCELLED'}
+ self.gltf_importer.log.critical("Data are loaded, start creating Blender stuff")
+ BlenderGlTF.create(self.gltf_importer)
+ self.gltf_importer.log.critical("glTF import is now finished")
+ self.gltf_importer.log.removeHandler(self.gltf_importer.log_handler)
+
+ return {'FINISHED'}
+
+
+def menu_func_import(self, context):
+ self.layout.operator(ImportGLTF2.bl_idname, text=ImportGLTF2.bl_label)
+
+
+classes = (
+ ExportGLTF2,
+ ImportGLTF2
+)
+
+
+def register():
+ for c in classes:
+ bpy.utils.register_class(c)
+ # bpy.utils.register_module(__name__)
+
+ # add to the export / import menu
+ bpy.types.TOPBAR_MT_file_export.append(menu_func_export)
+ bpy.types.TOPBAR_MT_file_import.append(menu_func_import)
+
+
+def unregister():
+ for c in classes:
+ bpy.utils.unregister_class(c)
+ # bpy.utils.unregister_module(__name__)
+
+ # remove from the export / import menu
+ bpy.types.TOPBAR_MT_file_export.remove(menu_func_export)
+ bpy.types.TOPBAR_MT_file_import.remove(menu_func_import)
+
diff --git a/io_scene_gltf2/blender/com/gltf2_blender_conversion.py b/io_scene_gltf2/blender/com/gltf2_blender_conversion.py
new file mode 100755
index 00000000..95fa292d
--- /dev/null
+++ b/io_scene_gltf2/blender/com/gltf2_blender_conversion.py
@@ -0,0 +1,42 @@
+# Copyright 2018 The glTF-Blender-IO authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from mathutils import Matrix, Quaternion
+
+def matrix_gltf_to_blender(mat_input):
+ """Matrix from glTF format to Blender format."""
+ mat = Matrix([mat_input[0:4], mat_input[4:8], mat_input[8:12], mat_input[12:16]])
+ mat.transpose()
+ return mat
+
+def loc_gltf_to_blender(loc):
+ """Location."""
+ return loc
+
+def scale_gltf_to_blender(scale):
+ """Scaling."""
+ return scale
+
+def quaternion_gltf_to_blender(q):
+ """Quaternion from glTF to Blender."""
+ return Quaternion([q[3], q[0], q[1], q[2]])
+
+def scale_to_matrix(scale):
+ """Scale to matrix."""
+ mat = Matrix()
+ for i in range(3):
+ mat[i][i] = scale[i]
+
+ return mat
+
diff --git a/io_scene_gltf2/blender/com/gltf2_blender_data_path.py b/io_scene_gltf2/blender/com/gltf2_blender_data_path.py
new file mode 100755
index 00000000..c5ce4025
--- /dev/null
+++ b/io_scene_gltf2/blender/com/gltf2_blender_data_path.py
@@ -0,0 +1,28 @@
+# Copyright 2018 The glTF-Blender-IO authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+def get_target_property_name(data_path: str) -> str:
+ """Retrieve target property."""
+ return data_path.rsplit('.', 1)[-1]
+
+
+def get_target_object_path(data_path: str) -> str:
+ """Retrieve target object data path without property"""
+ path_split = data_path.rsplit('.', 1)
+ self_targeting = len(path_split) < 2
+ if self_targeting:
+ return ""
+ return path_split[0]
+
diff --git a/io_scene_gltf2/blender/com/gltf2_blender_image.py b/io_scene_gltf2/blender/com/gltf2_blender_image.py
new file mode 100755
index 00000000..7564070d
--- /dev/null
+++ b/io_scene_gltf2/blender/com/gltf2_blender_image.py
@@ -0,0 +1,32 @@
+# Copyright 2018 The glTF-Blender-IO authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+#
+# Imports
+#
+
+from ...io.com.gltf2_io_image import create_img_from_pixels
+
+
+def create_img_from_blender_image(blender_image):
+ """
+ Create a new image object using the given blender image.
+
+ Returns the created image object.
+ """
+ if blender_image is None:
+ return None
+
+ return create_img_from_pixels(blender_image.size[0], blender_image.size[1], blender_image.pixels[:])
+
diff --git a/io_scene_gltf2/blender/com/gltf2_blender_image_util.py b/io_scene_gltf2/blender/com/gltf2_blender_image_util.py
new file mode 100755
index 00000000..e2563a52
--- /dev/null
+++ b/io_scene_gltf2/blender/com/gltf2_blender_image_util.py
@@ -0,0 +1,121 @@
+# Copyright 2018 The glTF-Blender-IO authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import os
+import shutil
+import bpy
+import zlib
+import struct
+from io_scene_gltf2.blender.exp import gltf2_blender_get
+
+
+def create_image_file(context, blender_image, dst_path, file_format):
+ """Create JPEG or PNG file from a given Blender image."""
+ # Check, if source image exists e.g. does not exist if image is packed.
+ file_exists = 1
+ try:
+ src_path = bpy.path.abspath(blender_image.filepath, library=blender_image.library)
+ file = open(src_path)
+ except IOError:
+ file_exists = 0
+ else:
+ file.close()
+
+ if file_exists == 0:
+ # Image does not exist on disk ...
+ blender_image.filepath = dst_path
+ # ... so save it.
+ blender_image.save()
+
+ elif file_format == blender_image.file_format:
+ # Copy source image to destination, keeping original format.
+
+ src_path = bpy.path.abspath(blender_image.filepath, library=blender_image.library)
+
+ # Required for comapre.
+ src_path = src_path.replace('\\', '/')
+ dst_path = dst_path.replace('\\', '/')
+
+ # Check that source and destination path are not the same using os.path.abspath
+ # because bpy.path.abspath seems to not always return an absolute path
+ if os.path.abspath(dst_path) != os.path.abspath(src_path):
+ shutil.copyfile(src_path, dst_path)
+
+ else:
+ # Render a new image to destination, converting to target format.
+
+ # TODO: Reusing the existing scene means settings like exposure are applied on export,
+ # which we don't want, but I'm not sure how to create a new Scene object through the
+ # Python API. See: https://github.com/KhronosGroup/glTF-Blender-Exporter/issues/184.
+
+ tmp_file_format = context.scene.render.image_settings.file_format
+ tmp_color_depth = context.scene.render.image_settings.color_depth
+
+ context.scene.render.image_settings.file_format = file_format
+ context.scene.render.image_settings.color_depth = '8'
+ blender_image.save_render(dst_path, context.scene)
+
+ context.scene.render.image_settings.file_format = tmp_file_format
+ context.scene.render.image_settings.color_depth = tmp_color_depth
+
+
+def create_image_data(context, export_settings, blender_image, file_format):
+ """Create JPEG or PNG byte array from a given Blender image."""
+ if blender_image is None:
+ return None
+
+ if file_format == 'PNG':
+ return _create_png_data(blender_image)
+ else:
+ return _create_jpg_data(context, export_settings, blender_image)
+
+
+def _create_jpg_data(context, export_settings, blender_image):
+ """Create a JPEG byte array from a given Blender image."""
+ uri = gltf2_blender_get.get_image_uri(export_settings, blender_image)
+ path = export_settings['gltf_filedirectory'] + uri
+
+ create_image_file(context, blender_image, path, 'JPEG')
+
+ jpg_data = open(path, 'rb').read()
+ os.remove(path)
+
+ return jpg_data
+
+
+def _create_png_data(blender_image):
+ """Create a PNG byte array from a given Blender image."""
+ width, height = blender_image.size
+
+ buf = bytearray([int(channel * 255.0) for channel in blender_image.pixels])
+
+ #
+ # Taken from 'blender-thumbnailer.py' in Blender.
+ #
+
+ # reverse the vertical line order and add null bytes at the start
+ width_byte_4 = width * 4
+ raw_data = b"".join(
+ b'\x00' + buf[span:span + width_byte_4] for span in range((height - 1) * width * 4, -1, - width_byte_4))
+
+ def png_pack(png_tag, data):
+ chunk_head = png_tag + data
+ return struct.pack("!I", len(data)) + chunk_head + struct.pack("!I", 0xFFFFFFFF & zlib.crc32(chunk_head))
+
+ return b"".join([
+ b'\x89PNG\r\n\x1a\n',
+ png_pack(b'IHDR', struct.pack("!2I5B", width, height, 8, 6, 0, 0, 0)),
+ png_pack(b'IDAT', zlib.compress(raw_data, 9)),
+ png_pack(b'IEND', b'')])
+
diff --git a/io_scene_gltf2/blender/com/gltf2_blender_json.py b/io_scene_gltf2/blender/com/gltf2_blender_json.py
new file mode 100755
index 00000000..fbf833c1
--- /dev/null
+++ b/io_scene_gltf2/blender/com/gltf2_blender_json.py
@@ -0,0 +1,38 @@
+# Copyright 2018 The glTF-Blender-IO authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import json
+import bpy
+
+
+class BlenderJSONEncoder(json.JSONEncoder):
+ """Blender JSON Encoder."""
+
+ def default(self, obj):
+ if isinstance(obj, bpy.types.ID):
+ return dict(
+ name=obj.name,
+ type=obj.__class__.__name__
+ )
+ return super(BlenderJSONEncoder, self).default(obj)
+
+
+def is_json_convertible(data):
+ """Test, if a data set can be expressed as JSON."""
+ try:
+ json.dumps(data, cls=BlenderJSONEncoder)
+ return True
+ except:
+ return False
+
diff --git a/io_scene_gltf2/blender/com/gltf2_blender_material_helpers.py b/io_scene_gltf2/blender/com/gltf2_blender_material_helpers.py
new file mode 100755
index 00000000..05f35954
--- /dev/null
+++ b/io_scene_gltf2/blender/com/gltf2_blender_material_helpers.py
@@ -0,0 +1,59 @@
+# Copyright 2018 The glTF-Blender-IO authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+def get_output_node(node_tree):
+ """Retrive output node."""
+ output = [node for node in node_tree.nodes if node.type == 'OUTPUT_MATERIAL'][0]
+ return output
+
+
+def get_output_surface_input(node_tree):
+ """Retrieve surface input of output node."""
+ output_node = get_output_node(node_tree)
+ return output_node.inputs['Surface']
+
+
+def get_diffuse_texture(node_tree):
+ """Retrieve diffuse texture node."""
+ for node in node_tree.nodes:
+ print(node.name)
+ if node.label == 'BASE COLOR':
+ return node
+
+ return None
+
+
+def get_preoutput_node_output(node_tree):
+ """Retrieve node just before output node."""
+ output_node = get_output_node(node_tree)
+ preoutput_node = output_node.inputs['Surface'].links[0].from_node
+
+ # Pre output node is Principled BSDF or any BSDF => BSDF
+ if 'BSDF' in preoutput_node.type:
+ return preoutput_node.outputs['BSDF']
+ elif 'SHADER' in preoutput_node.type:
+ return preoutput_node.outputs['Shader']
+ else:
+ print(preoutput_node.type)
+
+
+def get_base_color_node(node_tree):
+ """Returns the last node of the diffuse block."""
+ for node in node_tree.nodes:
+ if node.label == 'BASE COLOR':
+ return node
+
+ return None
+
diff --git a/io_scene_gltf2/blender/com/gltf2_blender_math.py b/io_scene_gltf2/blender/com/gltf2_blender_math.py
new file mode 100755
index 00000000..dd15ce2f
--- /dev/null
+++ b/io_scene_gltf2/blender/com/gltf2_blender_math.py
@@ -0,0 +1,172 @@
+# Copyright 2018 The glTF-Blender-IO authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import bpy
+import typing
+import math
+from mathutils import Matrix, Vector, Quaternion, Euler
+
+from io_scene_gltf2.blender.com.gltf2_blender_data_path import get_target_property_name
+
+
+def multiply(a, b):
+ """Multiplication."""
+ return a @ b
+
+
+def list_to_mathutils(values: typing.List[float], data_path: str) -> typing.Union[Vector, Quaternion, Euler]:
+ """Transform a list to blender py object."""
+ target = get_target_property_name(data_path)
+
+ if target == 'delta_location':
+ return Vector(values) # TODO Should be Vector(values) - Vector(something)?
+ elif target == 'delta_rotation_euler':
+ return Euler(values).to_quaternion() # TODO Should be multiply(Euler(values).to_quaternion(), something)?
+ elif target == 'location':
+ return Vector(values)
+ elif target == 'rotation_axis_angle':
+ angle = values[0]
+ axis = values[1:]
+ return Quaternion(axis, math.radians(angle))
+ elif target == 'rotation_euler':
+ return Euler(values).to_quaternion()
+ elif target == 'rotation_quaternion':
+ return Quaternion(values)
+ elif target == 'scale':
+ return Vector(values)
+ elif target == 'value':
+ return values
+
+ return values
+
+
+def mathutils_to_gltf(x: typing.Union[Vector, Quaternion]) -> typing.List[float]:
+ """Transform a py object to glTF list."""
+ if isinstance(x, Vector):
+ return list(x)
+ if isinstance(x, Quaternion):
+ # Blender has w-first quaternion notation
+ return [x[1], x[2], x[3], x[0]]
+ else:
+ return list(x)
+
+
+def to_yup() -> Matrix:
+ """Transform to Yup."""
+ return Matrix(
+ ((1.0, 0.0, 0.0, 0.0),
+ (0.0, 0.0, 1.0, 0.0),
+ (0.0, -1.0, 0.0, 0.0),
+ (0.0, 0.0, 0.0, 1.0))
+ )
+
+
+to_zup = to_yup
+
+
+def swizzle_yup(v: typing.Union[Vector, Quaternion], data_path: str) -> typing.Union[Vector, Quaternion]:
+ """Manage Yup."""
+ target = get_target_property_name(data_path)
+ swizzle_func = {
+ "delta_location": swizzle_yup_location,
+ "delta_rotation_euler": swizzle_yup_rotation,
+ "location": swizzle_yup_location,
+ "rotation_axis_angle": swizzle_yup_rotation,
+ "rotation_euler": swizzle_yup_rotation,
+ "rotation_quaternion": swizzle_yup_rotation,
+ "scale": swizzle_yup_scale,
+ "value": swizzle_yup_value
+ }.get(target)
+
+ if swizzle_func is None:
+ raise RuntimeError("Cannot transform values at {}".format(data_path))
+
+ return swizzle_func(v)
+
+
+def swizzle_yup_location(loc: Vector) -> Vector:
+ """Manage Yup location."""
+ return Vector((loc[0], loc[2], -loc[1]))
+
+
+def swizzle_yup_rotation(rot: Quaternion) -> Quaternion:
+ """Manage Yup rotation."""
+ return Quaternion((rot[0], rot[1], rot[3], -rot[2]))
+
+
+def swizzle_yup_scale(scale: Vector) -> Vector:
+ """Manage Yup scale."""
+ return Vector((scale[0], scale[2], scale[1]))
+
+
+def swizzle_yup_value(value: typing.Any) -> typing.Any:
+ """Manage Yup value."""
+ return value
+
+
+def transform(v: typing.Union[Vector, Quaternion], data_path: str, transform: Matrix = Matrix.Identity(4)) -> typing \
+ .Union[Vector, Quaternion]:
+ """Manage transformations."""
+ target = get_target_property_name(data_path)
+ transform_func = {
+ "delta_location": transform_location,
+ "delta_rotation_euler": transform_rotation,
+ "location": transform_location,
+ "rotation_axis_angle": transform_rotation,
+ "rotation_euler": transform_rotation,
+ "rotation_quaternion": transform_rotation,
+ "scale": transform_scale,
+ "value": transform_value
+ }.get(target)
+
+ if transform_func is None:
+ raise RuntimeError("Cannot transform values at {}".format(data_path))
+
+ return transform_func(v, transform)
+
+
+def transform_location(location: Vector, transform: Matrix = Matrix.Identity(4)) -> Vector:
+ """Transform location."""
+ m = Matrix.Translation(location)
+ m = multiply(transform, m)
+ return m.to_translation()
+
+
+def transform_rotation(rotation: Quaternion, transform: Matrix = Matrix.Identity(4)) -> Quaternion:
+ """Transform rotation."""
+ m = rotation.to_matrix().to_4x4()
+ m = multiply(transform, m)
+ return m.to_quaternion()
+
+
+def transform_scale(scale: Vector, transform: Matrix = Matrix.Identity(4)) -> Vector:
+ """Transform scale."""
+ m = Matrix.Identity(4)
+ m[0][0] = scale.x
+ m[1][1] = scale.y
+ m[2][2] = scale.z
+ m = multiply(transform, m)
+
+ return m.to_scale()
+
+
+def transform_value(value: Vector, _: Matrix = Matrix.Identity(4)) -> Vector:
+ """Transform value."""
+ return value
+
+
+def round_if_near(value: float, target: float) -> float:
+ """If value is very close to target, round to target."""
+ return value if abs(value - target) > 2.0e-6 else target
+
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_animate.py b/io_scene_gltf2/blender/exp/gltf2_blender_animate.py
new file mode 100755
index 00000000..e4b11487
--- /dev/null
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_animate.py
@@ -0,0 +1,638 @@
+# Copyright 2018 The glTF-Blender-IO authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+#
+# Imports
+#
+
+import bpy
+from . import gltf2_blender_export_keys
+from . import gltf2_blender_extract
+from mathutils import Matrix, Quaternion, Euler
+
+
+#
+# Globals
+#
+
+JOINT_NODE = 'JOINT'
+
+NEEDS_CONVERSION = 'CONVERSION_NEEDED'
+CUBIC_INTERPOLATION = 'CUBICSPLINE'
+LINEAR_INTERPOLATION = 'LINEAR'
+STEP_INTERPOLATION = 'STEP'
+BEZIER_INTERPOLATION = 'BEZIER'
+CONSTANT_INTERPOLATION = 'CONSTANT'
+
+
+#
+# Functions
+#
+
+def animate_get_interpolation(export_settings, blender_fcurve_list):
+ """
+ Retrieve the glTF interpolation, depending on a fcurve list.
+
+ Blender allows mixing and more variations of interpolations.
+ In such a case, a conversion is needed.
+ """
+ if export_settings[gltf2_blender_export_keys.FORCE_SAMPLING]:
+ return NEEDS_CONVERSION
+
+ #
+
+ interpolation = None
+
+ keyframe_count = None
+
+ for blender_fcurve in blender_fcurve_list:
+ if blender_fcurve is None:
+ continue
+
+ #
+
+ current_keyframe_count = len(blender_fcurve.keyframe_points)
+
+ if keyframe_count is None:
+ keyframe_count = current_keyframe_count
+
+ if current_keyframe_count > 0 > blender_fcurve.keyframe_points[0].co[0]:
+ return NEEDS_CONVERSION
+
+ if keyframe_count != current_keyframe_count:
+ return NEEDS_CONVERSION
+
+ #
+
+ for blender_keyframe in blender_fcurve.keyframe_points:
+ is_bezier = blender_keyframe.interpolation == BEZIER_INTERPOLATION
+ is_linear = blender_keyframe.interpolation == LINEAR_INTERPOLATION
+ is_constant = blender_keyframe.interpolation == CONSTANT_INTERPOLATION
+
+ if interpolation is None:
+ if is_bezier:
+ interpolation = CUBIC_INTERPOLATION
+ elif is_linear:
+ interpolation = LINEAR_INTERPOLATION
+ elif is_constant:
+ interpolation = STEP_INTERPOLATION
+ else:
+ interpolation = NEEDS_CONVERSION
+ return interpolation
+ else:
+ if is_bezier and interpolation != CUBIC_INTERPOLATION:
+ interpolation = NEEDS_CONVERSION
+ return interpolation
+ elif is_linear and interpolation != LINEAR_INTERPOLATION:
+ interpolation = NEEDS_CONVERSION
+ return interpolation
+ elif is_constant and interpolation != STEP_INTERPOLATION:
+ interpolation = NEEDS_CONVERSION
+ return interpolation
+ elif not is_bezier and not is_linear and not is_constant:
+ interpolation = NEEDS_CONVERSION
+ return interpolation
+
+ if interpolation is None:
+ interpolation = NEEDS_CONVERSION
+
+ return interpolation
+
+
+def animate_convert_rotation_axis_angle(axis_angle):
+ """Convert an axis angle to a quaternion rotation."""
+ q = Quaternion((axis_angle[1], axis_angle[2], axis_angle[3]), axis_angle[0])
+
+ return [q.x, q.y, q.z, q.w]
+
+
+def animate_convert_rotation_euler(euler, rotation_mode):
+ """Convert an euler angle to a quaternion rotation."""
+ rotation = Euler((euler[0], euler[1], euler[2]), rotation_mode).to_quaternion()
+
+ return [rotation.x, rotation.y, rotation.z, rotation.w]
+
+
+def animate_convert_keys(key_list):
+ """Convert Blender key frames to glTF time keys depending on the applied frames per second."""
+ times = []
+
+ for key in key_list:
+ times.append(key / bpy.context.scene.render.fps)
+
+ return times
+
+
+def animate_gather_keys(export_settings, fcurve_list, interpolation):
+ """
+ Merge and sort several key frames to one set.
+
+ If an interpolation conversion is needed, the sample key frames are created as well.
+ """
+ keys = []
+
+ frame_start = bpy.context.scene.frame_start
+ frame_end = bpy.context.scene.frame_end
+
+ if interpolation == NEEDS_CONVERSION:
+ start = None
+ end = None
+
+ for blender_fcurve in fcurve_list:
+ if blender_fcurve is None:
+ continue
+
+ if start is None:
+ start = blender_fcurve.range()[0]
+ else:
+ start = min(start, blender_fcurve.range()[0])
+
+ if end is None:
+ end = blender_fcurve.range()[1]
+ else:
+ end = max(end, blender_fcurve.range()[1])
+
+ #
+
+ add_epsilon_keyframe = False
+ for blender_keyframe in blender_fcurve.keyframe_points:
+ if add_epsilon_keyframe:
+ key = blender_keyframe.co[0] - 0.001
+
+ if key not in keys:
+ keys.append(key)
+
+ add_epsilon_keyframe = False
+
+ if blender_keyframe.interpolation == CONSTANT_INTERPOLATION:
+ add_epsilon_keyframe = True
+
+ if add_epsilon_keyframe:
+ key = end - 0.001
+
+ if key not in keys:
+ keys.append(key)
+
+ key = start
+ while key <= end:
+ if not export_settings[gltf2_blender_export_keys.FRAME_RANGE] or (frame_start <= key <= frame_end):
+ keys.append(key)
+ key += export_settings[gltf2_blender_export_keys.FRAME_STEP]
+
+ keys.sort()
+
+ else:
+ for blender_fcurve in fcurve_list:
+ if blender_fcurve is None:
+ continue
+
+ for blender_keyframe in blender_fcurve.keyframe_points:
+ key = blender_keyframe.co[0]
+ if not export_settings[gltf2_blender_export_keys.FRAME_RANGE] or (frame_start <= key <= frame_end):
+ if key not in keys:
+ keys.append(key)
+
+ keys.sort()
+
+ return keys
+
+
+def animate_location(export_settings, location, interpolation, node_type, node_name, action_name, matrix_correction,
+ matrix_basis):
+ """Calculate/gather the key value pairs for location transformations."""
+ joint_cache = export_settings[gltf2_blender_export_keys.JOINT_CACHE][action_name]
+ if not joint_cache.get(node_name):
+ joint_cache[node_name] = {}
+
+ keys = animate_gather_keys(export_settings, location, interpolation)
+
+ times = animate_convert_keys(keys)
+
+ result = {}
+ result_in_tangent = {}
+ result_out_tangent = {}
+
+ keyframe_index = 0
+ for timeIndex, time in enumerate(times):
+ translation = [0.0, 0.0, 0.0]
+ in_tangent = [0.0, 0.0, 0.0]
+ out_tangent = [0.0, 0.0, 0.0]
+
+ if node_type == JOINT_NODE:
+ if joint_cache[node_name].get(keys[keyframe_index]):
+ translation, tmp_rotation, tmp_scale = joint_cache[node_name][keys[keyframe_index]]
+ else:
+ bpy.context.scene.frame_set(keys[keyframe_index])
+
+ matrix = matrix_correction * matrix_basis
+
+ translation, tmp_rotation, tmp_scale = matrix.decompose()
+
+ joint_cache[node_name][keys[keyframe_index]] = [translation, tmp_rotation, tmp_scale]
+ else:
+ channel_index = 0
+ for blender_fcurve in location:
+
+ if blender_fcurve is not None:
+
+ if interpolation == CUBIC_INTERPOLATION:
+ blender_key_frame = blender_fcurve.keyframe_points[keyframe_index]
+
+ translation[channel_index] = blender_key_frame.co[1]
+
+ if timeIndex == 0:
+ in_tangent_value = 0.0
+ else:
+ factor = 3.0 / (time - times[timeIndex - 1])
+ in_tangent_value = (blender_key_frame.co[1] - blender_key_frame.handle_left[1]) * factor
+
+ if timeIndex == len(times) - 1:
+ out_tangent_value = 0.0
+ else:
+ factor = 3.0 / (times[timeIndex + 1] - time)
+ out_tangent_value = (blender_key_frame.handle_right[1] - blender_key_frame.co[1]) * factor
+
+ in_tangent[channel_index] = in_tangent_value
+ out_tangent[channel_index] = out_tangent_value
+ else:
+ value = blender_fcurve.evaluate(keys[keyframe_index])
+
+ translation[channel_index] = value
+
+ channel_index += 1
+
+ # handle parent inverse
+ matrix = Matrix.Translation(translation)
+ matrix = matrix_correction * matrix
+ translation = matrix.to_translation()
+
+ translation = gltf2_blender_extract.convert_swizzle_location(translation, export_settings)
+ in_tangent = gltf2_blender_extract.convert_swizzle_location(in_tangent, export_settings)
+ out_tangent = gltf2_blender_extract.convert_swizzle_location(out_tangent, export_settings)
+
+ result[time] = translation
+ result_in_tangent[time] = in_tangent
+ result_out_tangent[time] = out_tangent
+
+ keyframe_index += 1
+
+ return result, result_in_tangent, result_out_tangent
+
+
+def animate_rotation_axis_angle(export_settings, rotation_axis_angle, interpolation, node_type, node_name, action_name,
+ matrix_correction, matrix_basis):
+ """Calculate/gather the key value pairs for axis angle transformations."""
+ joint_cache = export_settings[gltf2_blender_export_keys.JOINT_CACHE][action_name]
+ if not joint_cache.get(node_name):
+ joint_cache[node_name] = {}
+
+ keys = animate_gather_keys(export_settings, rotation_axis_angle, interpolation)
+
+ times = animate_convert_keys(keys)
+
+ result = {}
+
+ keyframe_index = 0
+ for time in times:
+ axis_angle_rotation = [1.0, 0.0, 0.0, 0.0]
+
+ if node_type == JOINT_NODE:
+ if joint_cache[node_name].get(keys[keyframe_index]):
+ tmp_location, rotation, tmp_scale = joint_cache[node_name][keys[keyframe_index]]
+ else:
+ bpy.context.scene.frame_set(keys[keyframe_index])
+
+ matrix = matrix_correction * matrix_basis
+
+ tmp_location, rotation, tmp_scale = matrix.decompose()
+
+ joint_cache[node_name][keys[keyframe_index]] = [tmp_location, rotation, tmp_scale]
+ else:
+ channel_index = 0
+ for blender_fcurve in rotation_axis_angle:
+ if blender_fcurve is not None:
+ value = blender_fcurve.evaluate(keys[keyframe_index])
+
+ axis_angle_rotation[channel_index] = value
+
+ channel_index += 1
+
+ rotation = animate_convert_rotation_axis_angle(axis_angle_rotation)
+
+ # handle parent inverse
+ rotation = Quaternion((rotation[3], rotation[0], rotation[1], rotation[2]))
+ matrix = rotation.to_matrix().to_4x4()
+ matrix = matrix_correction * matrix
+ rotation = matrix.to_quaternion()
+
+ # Bring back to internal Quaternion notation.
+ rotation = gltf2_blender_extract.convert_swizzle_rotation(
+ [rotation[0], rotation[1], rotation[2], rotation[3]], export_settings)
+
+ # Bring back to glTF Quaternion notation.
+ rotation = [rotation[1], rotation[2], rotation[3], rotation[0]]
+
+ result[time] = rotation
+
+ keyframe_index += 1
+
+ return result
+
+
+def animate_rotation_euler(export_settings, rotation_euler, rotation_mode, interpolation, node_type, node_name,
+ action_name, matrix_correction, matrix_basis):
+ """Calculate/gather the key value pairs for euler angle transformations."""
+ joint_cache = export_settings[gltf2_blender_export_keys.JOINT_CACHE][action_name]
+ if not joint_cache.get(node_name):
+ joint_cache[node_name] = {}
+
+ keys = animate_gather_keys(export_settings, rotation_euler, interpolation)
+
+ times = animate_convert_keys(keys)
+
+ result = {}
+
+ keyframe_index = 0
+ for time in times:
+ euler_rotation = [0.0, 0.0, 0.0]
+
+ if node_type == JOINT_NODE:
+ if joint_cache[node_name].get(keys[keyframe_index]):
+ tmp_location, rotation, tmp_scale = joint_cache[node_name][keys[keyframe_index]]
+ else:
+ bpy.context.scene.frame_set(keys[keyframe_index])
+
+ matrix = matrix_correction * matrix_basis
+
+ tmp_location, rotation, tmp_scale = matrix.decompose()
+
+ joint_cache[node_name][keys[keyframe_index]] = [tmp_location, rotation, tmp_scale]
+ else:
+ channel_index = 0
+ for blender_fcurve in rotation_euler:
+ if blender_fcurve is not None:
+ value = blender_fcurve.evaluate(keys[keyframe_index])
+
+ euler_rotation[channel_index] = value
+
+ channel_index += 1
+
+ rotation = animate_convert_rotation_euler(euler_rotation, rotation_mode)
+
+ # handle parent inverse
+ rotation = Quaternion((rotation[3], rotation[0], rotation[1], rotation[2]))
+ matrix = rotation.to_matrix().to_4x4()
+ matrix = matrix_correction * matrix
+ rotation = matrix.to_quaternion()
+
+ # Bring back to internal Quaternion notation.
+ rotation = gltf2_blender_extract.convert_swizzle_rotation(
+ [rotation[0], rotation[1], rotation[2], rotation[3]], export_settings)
+
+ # Bring back to glTF Quaternion notation.
+ rotation = [rotation[1], rotation[2], rotation[3], rotation[0]]
+
+ result[time] = rotation
+
+ keyframe_index += 1
+
+ return result
+
+
+def animate_rotation_quaternion(export_settings, rotation_quaternion, interpolation, node_type, node_name, action_name,
+ matrix_correction, matrix_basis):
+ """Calculate/gather the key value pairs for quaternion transformations."""
+ joint_cache = export_settings[gltf2_blender_export_keys.JOINT_CACHE][action_name]
+ if not joint_cache.get(node_name):
+ joint_cache[node_name] = {}
+
+ keys = animate_gather_keys(export_settings, rotation_quaternion, interpolation)
+
+ times = animate_convert_keys(keys)
+
+ result = {}
+ result_in_tangent = {}
+ result_out_tangent = {}
+
+ keyframe_index = 0
+ for timeIndex, time in enumerate(times):
+ rotation = [1.0, 0.0, 0.0, 0.0]
+ in_tangent = [1.0, 0.0, 0.0, 0.0]
+ out_tangent = [1.0, 0.0, 0.0, 0.0]
+
+ if node_type == JOINT_NODE:
+ if joint_cache[node_name].get(keys[keyframe_index]):
+ tmp_location, rotation, tmp_scale = joint_cache[node_name][keys[keyframe_index]]
+ else:
+ bpy.context.scene.frame_set(keys[keyframe_index])
+
+ matrix = matrix_correction * matrix_basis
+
+ tmp_location, rotation, tmp_scale = matrix.decompose()
+
+ joint_cache[node_name][keys[keyframe_index]] = [tmp_location, rotation, tmp_scale]
+ else:
+ channel_index = 0
+ for blender_fcurve in rotation_quaternion:
+
+ if blender_fcurve is not None:
+ if interpolation == CUBIC_INTERPOLATION:
+ blender_key_frame = blender_fcurve.keyframe_points[keyframe_index]
+
+ rotation[channel_index] = blender_key_frame.co[1]
+
+ if timeIndex == 0:
+ in_tangent_value = 0.0
+ else:
+ factor = 3.0 / (time - times[timeIndex - 1])
+ in_tangent_value = (blender_key_frame.co[1] - blender_key_frame.handle_left[1]) * factor
+
+ if timeIndex == len(times) - 1:
+ out_tangent_value = 0.0
+ else:
+ factor = 3.0 / (times[timeIndex + 1] - time)
+ out_tangent_value = (blender_key_frame.handle_right[1] - blender_key_frame.co[1]) * factor
+
+ in_tangent[channel_index] = in_tangent_value
+ out_tangent[channel_index] = out_tangent_value
+ else:
+ value = blender_fcurve.evaluate(keys[keyframe_index])
+
+ rotation[channel_index] = value
+
+ channel_index += 1
+
+ rotation = Quaternion((rotation[0], rotation[1], rotation[2], rotation[3]))
+ in_tangent = gltf2_blender_extract.convert_swizzle_rotation(in_tangent, export_settings)
+ out_tangent = gltf2_blender_extract.convert_swizzle_rotation(out_tangent, export_settings)
+
+ # handle parent inverse
+ matrix = rotation.to_matrix().to_4x4()
+ matrix = matrix_correction * matrix
+ rotation = matrix.to_quaternion()
+
+ # Bring back to internal Quaternion notation.
+ rotation = gltf2_blender_extract.convert_swizzle_rotation(
+ [rotation[0], rotation[1], rotation[2], rotation[3]], export_settings)
+
+ # Bring to glTF Quaternion notation.
+ rotation = [rotation[1], rotation[2], rotation[3], rotation[0]]
+ in_tangent = [in_tangent[1], in_tangent[2], in_tangent[3], in_tangent[0]]
+ out_tangent = [out_tangent[1], out_tangent[2], out_tangent[3], out_tangent[0]]
+
+ result[time] = rotation
+ result_in_tangent[time] = in_tangent
+ result_out_tangent[time] = out_tangent
+
+ keyframe_index += 1
+
+ return result, result_in_tangent, result_out_tangent
+
+
+def animate_scale(export_settings, scale, interpolation, node_type, node_name, action_name, matrix_correction,
+ matrix_basis):
+ """Calculate/gather the key value pairs for scale transformations."""
+ joint_cache = export_settings[gltf2_blender_export_keys.JOINT_CACHE][action_name]
+ if not joint_cache.get(node_name):
+ joint_cache[node_name] = {}
+
+ keys = animate_gather_keys(export_settings, scale, interpolation)
+
+ times = animate_convert_keys(keys)
+
+ result = {}
+ result_in_tangent = {}
+ result_out_tangent = {}
+
+ keyframe_index = 0
+ for timeIndex, time in enumerate(times):
+ scale_data = [1.0, 1.0, 1.0]
+ in_tangent = [0.0, 0.0, 0.0]
+ out_tangent = [0.0, 0.0, 0.0]
+
+ if node_type == JOINT_NODE:
+ if joint_cache[node_name].get(keys[keyframe_index]):
+ tmp_location, tmp_rotation, scale_data = joint_cache[node_name][keys[keyframe_index]]
+ else:
+ bpy.context.scene.frame_set(keys[keyframe_index])
+
+ matrix = matrix_correction * matrix_basis
+
+ tmp_location, tmp_rotation, scale_data = matrix.decompose()
+
+ joint_cache[node_name][keys[keyframe_index]] = [tmp_location, tmp_rotation, scale_data]
+ else:
+ channel_index = 0
+ for blender_fcurve in scale:
+
+ if blender_fcurve is not None:
+ if interpolation == CUBIC_INTERPOLATION:
+ blender_key_frame = blender_fcurve.keyframe_points[keyframe_index]
+
+ scale_data[channel_index] = blender_key_frame.co[1]
+
+ if timeIndex == 0:
+ in_tangent_value = 0.0
+ else:
+ factor = 3.0 / (time - times[timeIndex - 1])
+ in_tangent_value = (blender_key_frame.co[1] - blender_key_frame.handle_left[1]) * factor
+
+ if timeIndex == len(times) - 1:
+ out_tangent_value = 0.0
+ else:
+ factor = 3.0 / (times[timeIndex + 1] - time)
+ out_tangent_value = (blender_key_frame.handle_right[1] - blender_key_frame.co[1]) * factor
+
+ in_tangent[channel_index] = in_tangent_value
+ out_tangent[channel_index] = out_tangent_value
+ else:
+ value = blender_fcurve.evaluate(keys[keyframe_index])
+
+ scale_data[channel_index] = value
+
+ channel_index += 1
+
+ scale_data = gltf2_blender_extract.convert_swizzle_scale(scale_data, export_settings)
+ in_tangent = gltf2_blender_extract.convert_swizzle_scale(in_tangent, export_settings)
+ out_tangent = gltf2_blender_extract.convert_swizzle_scale(out_tangent, export_settings)
+
+ # handle parent inverse
+ matrix = Matrix()
+ matrix[0][0] = scale_data.x
+ matrix[1][1] = scale_data.y
+ matrix[2][2] = scale_data.z
+ matrix = matrix_correction * matrix
+ scale_data = matrix.to_scale()
+
+ result[time] = scale_data
+ result_in_tangent[time] = in_tangent
+ result_out_tangent[time] = out_tangent
+
+ keyframe_index += 1
+
+ return result, result_in_tangent, result_out_tangent
+
+
+def animate_value(export_settings, value_parameter, interpolation,
+ node_type, node_name, matrix_correction, matrix_basis):
+ """Calculate/gather the key value pairs for scalar anaimations."""
+ keys = animate_gather_keys(export_settings, value_parameter, interpolation)
+
+ times = animate_convert_keys(keys)
+
+ result = {}
+ result_in_tangent = {}
+ result_out_tangent = {}
+
+ keyframe_index = 0
+ for timeIndex, time in enumerate(times):
+ value_data = []
+ in_tangent = []
+ out_tangent = []
+
+ for blender_fcurve in value_parameter:
+
+ if blender_fcurve is not None:
+ if interpolation == CUBIC_INTERPOLATION:
+ blender_key_frame = blender_fcurve.keyframe_points[keyframe_index]
+
+ value_data.append(blender_key_frame.co[1])
+
+ if timeIndex == 0:
+ in_tangent_value = 0.0
+ else:
+ factor = 3.0 / (time - times[timeIndex - 1])
+ in_tangent_value = (blender_key_frame.co[1] - blender_key_frame.handle_left[1]) * factor
+
+ if timeIndex == len(times) - 1:
+ out_tangent_value = 0.0
+ else:
+ factor = 3.0 / (times[timeIndex + 1] - time)
+ out_tangent_value = (blender_key_frame.handle_right[1] - blender_key_frame.co[1]) * factor
+
+ in_tangent.append(in_tangent_value)
+ out_tangent.append(out_tangent_value)
+ else:
+ value = blender_fcurve.evaluate(keys[keyframe_index])
+
+ value_data.append(value)
+
+ result[time] = value_data
+ result_in_tangent[time] = in_tangent
+ result_out_tangent[time] = out_tangent
+
+ keyframe_index += 1
+
+ return result, result_in_tangent, result_out_tangent
+
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_export.py b/io_scene_gltf2/blender/exp/gltf2_blender_export.py
new file mode 100755
index 00000000..418453c2
--- /dev/null
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_export.py
@@ -0,0 +1,128 @@
+# Copyright 2018 The glTF-Blender-IO authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import bpy
+import sys
+import traceback
+
+from io_scene_gltf2.blender.com import gltf2_blender_json
+from io_scene_gltf2.blender.exp import gltf2_blender_export_keys
+from io_scene_gltf2.blender.exp import gltf2_blender_gather
+from io_scene_gltf2.blender.exp.gltf2_blender_gltf2_exporter import GlTF2Exporter
+from io_scene_gltf2.io.com.gltf2_io_debug import print_console, print_newline
+from io_scene_gltf2.io.exp import gltf2_io_export
+
+
+def save(context, export_settings):
+ """Start the glTF 2.0 export and saves to content either to a .gltf or .glb file."""
+ if bpy.context.active_object is not None:
+ bpy.ops.object.mode_set(mode='OBJECT')
+
+ __notify_start(context)
+ json, buffer = __export(export_settings)
+ __write_file(json, buffer, export_settings)
+ __notify_end(context)
+ return {'FINISHED'}
+
+
+def __export(export_settings):
+ export_settings['gltf_channelcache'] = dict()
+ exporter = GlTF2Exporter(__get_copyright(export_settings))
+ __add_root_objects(exporter, export_settings)
+ buffer = __create_buffer(exporter, export_settings)
+ exporter.finalize_images(export_settings[gltf2_blender_export_keys.FILE_DIRECTORY])
+ json = __fix_json(exporter.glTF.to_dict())
+
+ return json, buffer
+
+
+def __get_copyright(export_settings):
+ if export_settings[gltf2_blender_export_keys.COPYRIGHT]:
+ return export_settings[gltf2_blender_export_keys.COPYRIGHT]
+ return None
+
+
+def __add_root_objects(exporter, export_settings):
+ scenes, animations = gltf2_blender_gather.gather_gltf2(export_settings)
+ for scene in scenes:
+ exporter.add_scene(scene)
+ for animation in animations:
+ exporter.add_animation(animation)
+
+
+def __create_buffer(exporter, export_settings):
+ buffer = bytes()
+ if export_settings[gltf2_blender_export_keys.FORMAT] == 'GLB':
+ buffer = exporter.finalize_buffer(export_settings[gltf2_blender_export_keys.FILE_DIRECTORY], is_glb=True)
+ else:
+ if export_settings[gltf2_blender_export_keys.FORMAT] == 'GLTF_EMBEDDED':
+ exporter.finalize_buffer(export_settings[gltf2_blender_export_keys.FILE_DIRECTORY])
+ else:
+ exporter.finalize_buffer(export_settings[gltf2_blender_export_keys.FILE_DIRECTORY],
+ export_settings[gltf2_blender_export_keys.BINARY_FILENAME])
+
+ return buffer
+
+
+def __fix_json(obj):
+ # TODO: move to custom JSON encoder
+ fixed = obj
+ if isinstance(obj, dict):
+ fixed = {}
+ for key, value in obj.items():
+ if value is None:
+ continue
+ elif isinstance(value, list) and len(value) == 0:
+ continue
+ fixed[key] = __fix_json(value)
+ elif isinstance(obj, list):
+ fixed = []
+ for value in obj:
+ fixed.append(__fix_json(value))
+ elif isinstance(obj, float):
+ # force floats to int, if they are integers (prevent INTEGER_WRITTEN_AS_FLOAT validator warnings)
+ if int(obj) == obj:
+ return int(obj)
+ return fixed
+
+
+def __write_file(json, buffer, export_settings):
+ try:
+ gltf2_io_export.save_gltf(
+ json,
+ export_settings,
+ gltf2_blender_json.BlenderJSONEncoder,
+ buffer)
+ except AssertionError as e:
+ _, _, tb = sys.exc_info()
+ traceback.print_tb(tb) # Fixed format
+ tb_info = traceback.extract_tb(tb)
+ for tbi in tb_info:
+ filename, line, func, text = tbi
+ print_console('ERROR', 'An error occurred on line {} in statement {}'.format(line, text))
+ print_console('ERROR', str(e))
+ raise e
+
+
+def __notify_start(context):
+ print_console('INFO', 'Starting glTF 2.0 export')
+ context.window_manager.progress_begin(0, 100)
+ context.window_manager.progress_update(0)
+
+
+def __notify_end(context):
+ print_console('INFO', 'Finished glTF 2.0 export')
+ context.window_manager.progress_end()
+ print_newline()
+
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_export_keys.py b/io_scene_gltf2/blender/exp/gltf2_blender_export_keys.py
new file mode 100755
index 00000000..ca9ca139
--- /dev/null
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_export_keys.py
@@ -0,0 +1,63 @@
+# Copyright 2018 The glTF-Blender-IO authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+FILTERED_VERTEX_GROUPS = 'filtered_vertex_groups'
+FILTERED_MESHES = 'filtered_meshes'
+FILTERED_IMAGES = 'filtered_images'
+FILTERED_IMAGES_USE_ALPHA = 'filtered_images_use_alpha'
+FILTERED_MERGED_IMAGES = 'filtered_merged_images'
+FILTERED_TEXTURES = 'filtered_textures'
+FILTERED_MATERIALS = 'filtered_materials'
+FILTERED_LIGHTS = 'filtered_lights'
+TEMPORARY_MESHES = 'temporary_meshes'
+FILTERED_OBJECTS = 'filtered_objects'
+FILTERED_CAMERAS = 'filtered_cameras'
+
+APPLY = 'gltf_apply'
+LAYERS = 'gltf_layers'
+SELECTED = 'gltf_selected'
+SKINS = 'gltf_skins'
+DISPLACEMENT = 'gltf_displacement'
+FORCE_SAMPLING = 'gltf_force_sampling'
+FRAME_RANGE = 'gltf_frame_range'
+FRAME_STEP = 'gltf_frame_step'
+JOINT_CACHE = 'gltf_joint_cache'
+COPYRIGHT = 'gltf_copyright'
+FORMAT = 'gltf_format'
+FILE_DIRECTORY = 'gltf_filedirectory'
+BINARY_FILENAME = 'gltf_binaryfilename'
+YUP = 'gltf_yup'
+MORPH = 'gltf_morph'
+BAKE_SKINS = 'gltf_bake_skins'
+TEX_COORDS = 'gltf_texcoords'
+COLORS = 'gltf_colors'
+NORMALS = 'gltf_normals'
+TANGENTS = 'gltf_tangents'
+MORPH_TANGENT = 'gltf_morph_tangent'
+MORPH_NORMAL = 'gltf_morph_normal'
+MOVE_KEYFRAMES = 'gltf_move_keyframes'
+MATERIALS = 'gltf_materials'
+EXTRAS = 'gltf_extras'
+CAMERAS = 'gltf_cameras'
+LIGHTS = 'gltf_lights'
+ANIMATIONS = 'gltf_animations'
+EMBED_IMAGES = 'gltf_embed_images'
+BINARY = 'gltf_binary'
+EMBED_BUFFERS = 'gltf_embed_buffers'
+TEXTURE_TRANSFORM = 'gltf_texture_transform'
+USE_NO_COLOR = 'gltf_use_no_color'
+
+METALLIC_ROUGHNESS_IMAGE = "metallic_roughness_image"
+GROUP_INDEX = 'group_index'
+
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_extract.py b/io_scene_gltf2/blender/exp/gltf2_blender_extract.py
new file mode 100755
index 00000000..87c9d426
--- /dev/null
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_extract.py
@@ -0,0 +1,1116 @@
+# Copyright 2018 The glTF-Blender-IO authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+#
+# Imports
+#
+
+from mathutils import Vector, Quaternion
+from mathutils.geometry import tessellate_polygon
+
+from . import gltf2_blender_export_keys
+from ...io.com.gltf2_io_debug import print_console
+from io_scene_gltf2.blender.exp import gltf2_blender_gather_skins
+
+#
+# Globals
+#
+
+INDICES_ID = 'indices'
+MATERIAL_ID = 'material'
+ATTRIBUTES_ID = 'attributes'
+
+COLOR_PREFIX = 'COLOR_'
+MORPH_TANGENT_PREFIX = 'MORPH_TANGENT_'
+MORPH_NORMAL_PREFIX = 'MORPH_NORMAL_'
+MORPH_POSITION_PREFIX = 'MORPH_POSITION_'
+TEXCOORD_PREFIX = 'TEXCOORD_'
+WEIGHTS_PREFIX = 'WEIGHTS_'
+JOINTS_PREFIX = 'JOINTS_'
+
+TANGENT_ATTRIBUTE = 'TANGENT'
+NORMAL_ATTRIBUTE = 'NORMAL'
+POSITION_ATTRIBUTE = 'POSITION'
+
+GLTF_MAX_COLORS = 2
+
+
+#
+# Classes
+#
+
+class ShapeKey:
+ def __init__(self, shape_key, vertex_normals, polygon_normals):
+ self.shape_key = shape_key
+ self.vertex_normals = vertex_normals
+ self.polygon_normals = polygon_normals
+
+
+#
+# Functions
+#
+
+def convert_swizzle_location(loc, export_settings):
+ """Convert a location from Blender coordinate system to glTF coordinate system."""
+ if export_settings[gltf2_blender_export_keys.YUP]:
+ return Vector((loc[0], loc[2], -loc[1]))
+ else:
+ return Vector((loc[0], loc[1], loc[2]))
+
+
+def convert_swizzle_tangent(tan, export_settings):
+ """Convert a tangent from Blender coordinate system to glTF coordinate system."""
+ if tan[0] == 0.0 and tan[1] == 0.0 and tan[2] == 0.0:
+ print_console('WARNING', 'Tangent has zero length.')
+
+ if export_settings[gltf2_blender_export_keys.YUP]:
+ return Vector((tan[0], tan[2], -tan[1], 1.0))
+ else:
+ return Vector((tan[0], tan[1], tan[2], 1.0))
+
+
+def convert_swizzle_rotation(rot, export_settings):
+ """
+ Convert a quaternion rotation from Blender coordinate system to glTF coordinate system.
+
+ 'w' is still at first position.
+ """
+ if export_settings[gltf2_blender_export_keys.YUP]:
+ return Quaternion((rot[0], rot[1], rot[3], -rot[2]))
+ else:
+ return Quaternion((rot[0], rot[1], rot[2], rot[3]))
+
+
+def convert_swizzle_scale(scale, export_settings):
+ """Convert a scale from Blender coordinate system to glTF coordinate system."""
+ if export_settings[gltf2_blender_export_keys.YUP]:
+ return Vector((scale[0], scale[2], scale[1]))
+ else:
+ return Vector((scale[0], scale[1], scale[2]))
+
+
+def decompose_transition(matrix, context, export_settings):
+ translation, rotation, scale = matrix.decompose()
+ """Decompose a matrix depending if it is associated to a joint or node."""
+ if context == 'NODE':
+ translation = convert_swizzle_location(translation, export_settings)
+ rotation = convert_swizzle_rotation(rotation, export_settings)
+ scale = convert_swizzle_scale(scale, export_settings)
+
+ # Put w at the end.
+ rotation = Quaternion((rotation[1], rotation[2], rotation[3], rotation[0]))
+
+ return translation, rotation, scale
+
+
+def color_srgb_to_scene_linear(c):
+ """
+ Convert from sRGB to scene linear color space.
+
+ Source: Cycles addon implementation, node_color.h.
+ """
+ if c < 0.04045:
+ return 0.0 if c < 0.0 else c * (1.0 / 12.92)
+ else:
+ return pow((c + 0.055) * (1.0 / 1.055), 2.4)
+
+
+def extract_primitive_floor(a, indices, use_tangents):
+ """Shift indices, that the first one starts with 0. It is assumed, that the indices are packed."""
+ attributes = {
+ POSITION_ATTRIBUTE: [],
+ NORMAL_ATTRIBUTE: []
+ }
+
+ if use_tangents:
+ attributes[TANGENT_ATTRIBUTE] = []
+
+ result_primitive = {
+ MATERIAL_ID: a[MATERIAL_ID],
+ INDICES_ID: [],
+ ATTRIBUTES_ID: attributes
+ }
+
+ source_attributes = a[ATTRIBUTES_ID]
+
+ #
+
+ tex_coord_index = 0
+ process_tex_coord = True
+ while process_tex_coord:
+ tex_coord_id = TEXCOORD_PREFIX + str(tex_coord_index)
+
+ if source_attributes.get(tex_coord_id) is not None:
+ attributes[tex_coord_id] = []
+ tex_coord_index += 1
+ else:
+ process_tex_coord = False
+
+ tex_coord_max = tex_coord_index
+
+ #
+
+ color_index = 0
+ process_color = True
+ while process_color:
+ color_id = COLOR_PREFIX + str(color_index)
+
+ if source_attributes.get(color_id) is not None:
+ attributes[color_id] = []
+ color_index += 1
+ else:
+ process_color = False
+
+ color_max = color_index
+
+ #
+
+ bone_index = 0
+ process_bone = True
+ while process_bone:
+ joint_id = JOINTS_PREFIX + str(bone_index)
+ weight_id = WEIGHTS_PREFIX + str(bone_index)
+
+ if source_attributes.get(joint_id) is not None:
+ attributes[joint_id] = []
+ attributes[weight_id] = []
+ bone_index += 1
+ else:
+ process_bone = False
+
+ bone_max = bone_index
+
+ #
+
+ morph_index = 0
+ process_morph = True
+ while process_morph:
+ morph_position_id = MORPH_POSITION_PREFIX + str(morph_index)
+ morph_normal_id = MORPH_NORMAL_PREFIX + str(morph_index)
+ morph_tangent_id = MORPH_TANGENT_PREFIX + str(morph_index)
+
+ if source_attributes.get(morph_position_id) is not None:
+ attributes[morph_position_id] = []
+ attributes[morph_normal_id] = []
+ if use_tangents:
+ attributes[morph_tangent_id] = []
+ morph_index += 1
+ else:
+ process_morph = False
+
+ morph_max = morph_index
+
+ #
+
+ min_index = min(indices)
+ max_index = max(indices)
+
+ for old_index in indices:
+ result_primitive[INDICES_ID].append(old_index - min_index)
+
+ for old_index in range(min_index, max_index + 1):
+ for vi in range(0, 3):
+ attributes[POSITION_ATTRIBUTE].append(source_attributes[POSITION_ATTRIBUTE][old_index * 3 + vi])
+ attributes[NORMAL_ATTRIBUTE].append(source_attributes[NORMAL_ATTRIBUTE][old_index * 3 + vi])
+
+ if use_tangents:
+ for vi in range(0, 4):
+ attributes[TANGENT_ATTRIBUTE].append(source_attributes[TANGENT_ATTRIBUTE][old_index * 4 + vi])
+
+ for tex_coord_index in range(0, tex_coord_max):
+ tex_coord_id = TEXCOORD_PREFIX + str(tex_coord_index)
+ for vi in range(0, 2):
+ attributes[tex_coord_id].append(source_attributes[tex_coord_id][old_index * 2 + vi])
+
+ for color_index in range(0, color_max):
+ color_id = COLOR_PREFIX + str(color_index)
+ for vi in range(0, 4):
+ attributes[color_id].append(source_attributes[color_id][old_index * 4 + vi])
+
+ for bone_index in range(0, bone_max):
+ joint_id = JOINTS_PREFIX + str(bone_index)
+ weight_id = WEIGHTS_PREFIX + str(bone_index)
+ for vi in range(0, 4):
+ attributes[joint_id].append(source_attributes[joint_id][old_index * 4 + vi])
+ attributes[weight_id].append(source_attributes[weight_id][old_index * 4 + vi])
+
+ for morph_index in range(0, morph_max):
+ morph_position_id = MORPH_POSITION_PREFIX + str(morph_index)
+ morph_normal_id = MORPH_NORMAL_PREFIX + str(morph_index)
+ morph_tangent_id = MORPH_TANGENT_PREFIX + str(morph_index)
+ for vi in range(0, 3):
+ attributes[morph_position_id].append(source_attributes[morph_position_id][old_index * 3 + vi])
+ attributes[morph_normal_id].append(source_attributes[morph_normal_id][old_index * 3 + vi])
+ if use_tangents:
+ for vi in range(0, 4):
+ attributes[morph_tangent_id].append(source_attributes[morph_tangent_id][old_index * 4 + vi])
+
+ return result_primitive
+
+
+def extract_primitive_pack(a, indices, use_tangents):
+ """Pack indices, that the first one starts with 0. Current indices can have gaps."""
+ attributes = {
+ POSITION_ATTRIBUTE: [],
+ NORMAL_ATTRIBUTE: []
+ }
+
+ if use_tangents:
+ attributes[TANGENT_ATTRIBUTE] = []
+
+ result_primitive = {
+ MATERIAL_ID: a[MATERIAL_ID],
+ INDICES_ID: [],
+ ATTRIBUTES_ID: attributes
+ }
+
+ source_attributes = a[ATTRIBUTES_ID]
+
+ #
+
+ tex_coord_index = 0
+ process_tex_coord = True
+ while process_tex_coord:
+ tex_coord_id = TEXCOORD_PREFIX + str(tex_coord_index)
+
+ if source_attributes.get(tex_coord_id) is not None:
+ attributes[tex_coord_id] = []
+ tex_coord_index += 1
+ else:
+ process_tex_coord = False
+
+ tex_coord_max = tex_coord_index
+
+ #
+
+ color_index = 0
+ process_color = True
+ while process_color:
+ color_id = COLOR_PREFIX + str(color_index)
+
+ if source_attributes.get(color_id) is not None:
+ attributes[color_id] = []
+ color_index += 1
+ else:
+ process_color = False
+
+ color_max = color_index
+
+ #
+
+ bone_index = 0
+ process_bone = True
+ while process_bone:
+ joint_id = JOINTS_PREFIX + str(bone_index)
+ weight_id = WEIGHTS_PREFIX + str(bone_index)
+
+ if source_attributes.get(joint_id) is not None:
+ attributes[joint_id] = []
+ attributes[weight_id] = []
+ bone_index += 1
+ else:
+ process_bone = False
+
+ bone_max = bone_index
+
+ #
+
+ morph_index = 0
+ process_morph = True
+ while process_morph:
+ morph_position_id = MORPH_POSITION_PREFIX + str(morph_index)
+ morph_normal_id = MORPH_NORMAL_PREFIX + str(morph_index)
+ morph_tangent_id = MORPH_TANGENT_PREFIX + str(morph_index)
+
+ if source_attributes.get(morph_position_id) is not None:
+ attributes[morph_position_id] = []
+ attributes[morph_normal_id] = []
+ if use_tangents:
+ attributes[morph_tangent_id] = []
+ morph_index += 1
+ else:
+ process_morph = False
+
+ morph_max = morph_index
+
+ #
+
+ old_to_new_indices = {}
+ new_to_old_indices = {}
+
+ new_index = 0
+ for old_index in indices:
+ if old_to_new_indices.get(old_index) is None:
+ old_to_new_indices[old_index] = new_index
+ new_to_old_indices[new_index] = old_index
+ new_index += 1
+
+ result_primitive[INDICES_ID].append(old_to_new_indices[old_index])
+
+ end_new_index = new_index
+
+ for new_index in range(0, end_new_index):
+ old_index = new_to_old_indices[new_index]
+
+ for vi in range(0, 3):
+ attributes[POSITION_ATTRIBUTE].append(source_attributes[POSITION_ATTRIBUTE][old_index * 3 + vi])
+ attributes[NORMAL_ATTRIBUTE].append(source_attributes[NORMAL_ATTRIBUTE][old_index * 3 + vi])
+
+ if use_tangents:
+ for vi in range(0, 4):
+ attributes[TANGENT_ATTRIBUTE].append(source_attributes[TANGENT_ATTRIBUTE][old_index * 4 + vi])
+
+ for tex_coord_index in range(0, tex_coord_max):
+ tex_coord_id = TEXCOORD_PREFIX + str(tex_coord_index)
+ for vi in range(0, 2):
+ attributes[tex_coord_id].append(source_attributes[tex_coord_id][old_index * 2 + vi])
+
+ for color_index in range(0, color_max):
+ color_id = COLOR_PREFIX + str(color_index)
+ for vi in range(0, 4):
+ attributes[color_id].append(source_attributes[color_id][old_index * 4 + vi])
+
+ for bone_index in range(0, bone_max):
+ joint_id = JOINTS_PREFIX + str(bone_index)
+ weight_id = WEIGHTS_PREFIX + str(bone_index)
+ for vi in range(0, 4):
+ attributes[joint_id].append(source_attributes[joint_id][old_index * 4 + vi])
+ attributes[weight_id].append(source_attributes[weight_id][old_index * 4 + vi])
+
+ for morph_index in range(0, morph_max):
+ morph_position_id = MORPH_POSITION_PREFIX + str(morph_index)
+ morph_normal_id = MORPH_NORMAL_PREFIX + str(morph_index)
+ morph_tangent_id = MORPH_TANGENT_PREFIX + str(morph_index)
+ for vi in range(0, 3):
+ attributes[morph_position_id].append(source_attributes[morph_position_id][old_index * 3 + vi])
+ attributes[morph_normal_id].append(source_attributes[morph_normal_id][old_index * 3 + vi])
+ if use_tangents:
+ for vi in range(0, 4):
+ attributes[morph_tangent_id].append(source_attributes[morph_tangent_id][old_index * 4 + vi])
+
+ return result_primitive
+
+
+def extract_primitives(glTF, blender_mesh, blender_vertex_groups, modifiers, export_settings):
+ """
+ Extract primitives from a mesh. Polygons are triangulated and sorted by material.
+
+ Furthermore, primitives are split up, if the indices range is exceeded.
+ Finally, triangles are also split up/duplicated, if face normals are used instead of vertex normals.
+ """
+ print_console('INFO', 'Extracting primitive')
+
+ use_tangents = False
+ if blender_mesh.uv_layers.active and len(blender_mesh.uv_layers) > 0:
+ try:
+ blender_mesh.calc_tangents()
+ use_tangents = True
+ except Exception:
+ print_console('WARNING', 'Could not calculate tangents. Please try to triangulate the mesh first.')
+
+ #
+
+ material_map = {}
+
+ #
+ # Gathering position, normal and tex_coords.
+ #
+ no_material_attributes = {
+ POSITION_ATTRIBUTE: [],
+ NORMAL_ATTRIBUTE: []
+ }
+
+ if use_tangents:
+ no_material_attributes[TANGENT_ATTRIBUTE] = []
+
+ #
+ # Directory of materials with its primitive.
+ #
+ no_material_primitives = {
+ MATERIAL_ID: '',
+ INDICES_ID: [],
+ ATTRIBUTES_ID: no_material_attributes
+ }
+
+ material_name_to_primitives = {'': no_material_primitives}
+
+ #
+
+ vertex_index_to_new_indices = {}
+
+ material_map[''] = vertex_index_to_new_indices
+
+ #
+ # Create primitive for each material.
+ #
+ for blender_material in blender_mesh.materials:
+ if blender_material is None:
+ continue
+
+ attributes = {
+ POSITION_ATTRIBUTE: [],
+ NORMAL_ATTRIBUTE: []
+ }
+
+ if use_tangents:
+ attributes[TANGENT_ATTRIBUTE] = []
+
+ primitive = {
+ MATERIAL_ID: blender_material.name,
+ INDICES_ID: [],
+ ATTRIBUTES_ID: attributes
+ }
+
+ material_name_to_primitives[blender_material.name] = primitive
+
+ #
+
+ vertex_index_to_new_indices = {}
+
+ material_map[blender_material.name] = vertex_index_to_new_indices
+
+ tex_coord_max = 0
+ if blender_mesh.uv_layers.active:
+ tex_coord_max = len(blender_mesh.uv_layers)
+
+ #
+
+ vertex_colors = {}
+
+ color_index = 0
+ for vertex_color in blender_mesh.vertex_colors:
+ vertex_color_name = COLOR_PREFIX + str(color_index)
+ vertex_colors[vertex_color_name] = vertex_color
+
+ color_index += 1
+ if color_index >= GLTF_MAX_COLORS:
+ break
+ color_max = color_index
+
+ #
+
+ bone_max = 0
+ for blender_polygon in blender_mesh.polygons:
+ for loop_index in blender_polygon.loop_indices:
+ vertex_index = blender_mesh.loops[loop_index].vertex_index
+ bones_count = len(blender_mesh.vertices[vertex_index].groups)
+ if bones_count > 0:
+ if bones_count % 4 == 0:
+ bones_count -= 1
+ bone_max = max(bone_max, bones_count // 4 + 1)
+
+ #
+
+ morph_max = 0
+
+ blender_shape_keys = []
+
+ if blender_mesh.shape_keys is not None:
+ morph_max = len(blender_mesh.shape_keys.key_blocks) - 1
+
+ for blender_shape_key in blender_mesh.shape_keys.key_blocks:
+ if blender_shape_key != blender_shape_key.relative_key:
+ blender_shape_keys.append(ShapeKey(
+ blender_shape_key,
+ blender_shape_key.normals_vertex_get(), # calculate vertex normals for this shape key
+ blender_shape_key.normals_polygon_get())) # calculate polygon normals for this shape key
+
+ #
+ # Convert polygon to primitive indices and eliminate invalid ones. Assign to material.
+ #
+ for blender_polygon in blender_mesh.polygons:
+ export_color = True
+
+ #
+
+ if blender_polygon.material_index < 0 or blender_polygon.material_index >= len(blender_mesh.materials) or \
+ blender_mesh.materials[blender_polygon.material_index] is None:
+ primitive = material_name_to_primitives['']
+ vertex_index_to_new_indices = material_map['']
+ else:
+ primitive = material_name_to_primitives[blender_mesh.materials[blender_polygon.material_index].name]
+ vertex_index_to_new_indices = material_map[blender_mesh.materials[blender_polygon.material_index].name]
+ #
+
+ attributes = primitive[ATTRIBUTES_ID]
+
+ face_normal = blender_polygon.normal
+ face_tangent = Vector((0.0, 0.0, 0.0))
+ face_bitangent = Vector((0.0, 0.0, 0.0))
+ if use_tangents:
+ for loop_index in blender_polygon.loop_indices:
+ temp_vertex = blender_mesh.loops[loop_index]
+ face_tangent += temp_vertex.tangent
+ face_bitangent += temp_vertex.bitangent
+
+ face_tangent.normalize()
+ face_bitangent.normalize()
+
+ #
+
+ indices = primitive[INDICES_ID]
+
+ loop_index_list = []
+
+ if len(blender_polygon.loop_indices) == 3:
+ loop_index_list.extend(blender_polygon.loop_indices)
+ elif len(blender_polygon.loop_indices) > 3:
+ # Triangulation of polygon. Using internal function, as non-convex polygons could exist.
+ polyline = []
+
+ for loop_index in blender_polygon.loop_indices:
+ vertex_index = blender_mesh.loops[loop_index].vertex_index
+ v = blender_mesh.vertices[vertex_index].co
+ polyline.append(Vector((v[0], v[1], v[2])))
+
+ triangles = tessellate_polygon((polyline,))
+
+ for triangle in triangles:
+ loop_index_list.append(blender_polygon.loop_indices[triangle[0]])
+ loop_index_list.append(blender_polygon.loop_indices[triangle[2]])
+ loop_index_list.append(blender_polygon.loop_indices[triangle[1]])
+ else:
+ continue
+
+ for loop_index in loop_index_list:
+ vertex_index = blender_mesh.loops[loop_index].vertex_index
+
+ if vertex_index_to_new_indices.get(vertex_index) is None:
+ vertex_index_to_new_indices[vertex_index] = []
+
+ #
+
+ v = None
+ n = None
+ t = None
+ b = None
+ uvs = []
+ colors = []
+ joints = []
+ weights = []
+
+ target_positions = []
+ target_normals = []
+ target_tangents = []
+
+ vertex = blender_mesh.vertices[vertex_index]
+
+ v = convert_swizzle_location(vertex.co, export_settings)
+ if blender_polygon.use_smooth:
+ n = convert_swizzle_location(vertex.normal, export_settings)
+ if use_tangents:
+ t = convert_swizzle_tangent(blender_mesh.loops[loop_index].tangent, export_settings)
+ b = convert_swizzle_location(blender_mesh.loops[loop_index].bitangent, export_settings)
+ else:
+ n = convert_swizzle_location(face_normal, export_settings)
+ if use_tangents:
+ t = convert_swizzle_tangent(face_tangent, export_settings)
+ b = convert_swizzle_location(face_bitangent, export_settings)
+
+ if use_tangents:
+ tv = Vector((t[0], t[1], t[2]))
+ bv = Vector((b[0], b[1], b[2]))
+ nv = Vector((n[0], n[1], n[2]))
+
+ if (nv.cross(tv)).dot(bv) < 0.0:
+ t[3] = -1.0
+
+ if blender_mesh.uv_layers.active:
+ for tex_coord_index in range(0, tex_coord_max):
+ uv = blender_mesh.uv_layers[tex_coord_index].data[loop_index].uv
+ uvs.append([uv.x, 1.0 - uv.y])
+
+ #
+
+ if color_max > 0 and export_color:
+ for color_index in range(0, color_max):
+ color_name = COLOR_PREFIX + str(color_index)
+ color = vertex_colors[color_name].data[loop_index].color
+ colors.append([
+ color_srgb_to_scene_linear(color[0]),
+ color_srgb_to_scene_linear(color[1]),
+ color_srgb_to_scene_linear(color[2]),
+ 1.0
+ ])
+
+ #
+
+ bone_count = 0
+
+ if blender_vertex_groups is not None and vertex.groups is not None and len(vertex.groups) > 0 and export_settings[gltf2_blender_export_keys.SKINS]:
+ joint = []
+ weight = []
+ for group_element in vertex.groups:
+
+ if len(joint) == 4:
+ bone_count += 1
+ joints.append(joint)
+ weights.append(weight)
+ joint = []
+ weight = []
+
+ #
+
+ vertex_group_index = group_element.group
+ vertex_group_name = blender_vertex_groups[vertex_group_index].name
+
+ #
+
+ joint_index = 0
+
+ if modifiers is not None:
+ modifiers_dict = {m.type: m for m in modifiers}
+ if "ARMATURE" in modifiers_dict:
+ armature = modifiers_dict["ARMATURE"].object
+ skin = gltf2_blender_gather_skins.gather_skin(armature, export_settings)
+ for index, j in enumerate(skin.joints):
+ if j.name == vertex_group_name:
+ joint_index = index
+
+ joint_weight = group_element.weight
+
+ #
+ joint.append(joint_index)
+ weight.append(joint_weight)
+
+ if len(joint) > 0:
+ bone_count += 1
+
+ for fill in range(0, 4 - len(joint)):
+ joint.append(0)
+ weight.append(0.0)
+
+ joints.append(joint)
+ weights.append(weight)
+
+ for fill in range(0, bone_max - bone_count):
+ joints.append([0, 0, 0, 0])
+ weights.append([0.0, 0.0, 0.0, 0.0])
+
+ #
+
+ if morph_max > 0 and export_settings[gltf2_blender_export_keys.MORPH]:
+ for morph_index in range(0, morph_max):
+ blender_shape_key = blender_shape_keys[morph_index]
+
+ v_morph = convert_swizzle_location(blender_shape_key.shape_key.data[vertex_index].co,
+ export_settings)
+
+ # Store delta.
+ v_morph -= v
+
+ target_positions.append(v_morph)
+
+ #
+
+ n_morph = None
+
+ if blender_polygon.use_smooth:
+ temp_normals = blender_shape_key.vertex_normals
+ n_morph = (temp_normals[vertex_index * 3 + 0], temp_normals[vertex_index * 3 + 1],
+ temp_normals[vertex_index * 3 + 2])
+ else:
+ temp_normals = blender_shape_key.polygon_normals
+ n_morph = (
+ temp_normals[blender_polygon.index * 3 + 0], temp_normals[blender_polygon.index * 3 + 1],
+ temp_normals[blender_polygon.index * 3 + 2])
+
+ n_morph = convert_swizzle_location(n_morph, export_settings)
+
+ # Store delta.
+ n_morph -= n
+
+ target_normals.append(n_morph)
+
+ #
+
+ if use_tangents:
+ rotation = n_morph.rotation_difference(n)
+
+ t_morph = Vector((t[0], t[1], t[2]))
+
+ t_morph.rotate(rotation)
+
+ target_tangents.append(t_morph)
+
+ #
+ #
+
+ create = True
+
+ for current_new_index in vertex_index_to_new_indices[vertex_index]:
+ found = True
+
+ for i in range(0, 3):
+ if attributes[POSITION_ATTRIBUTE][current_new_index * 3 + i] != v[i]:
+ found = False
+ break
+
+ if attributes[NORMAL_ATTRIBUTE][current_new_index * 3 + i] != n[i]:
+ found = False
+ break
+
+ if use_tangents:
+ for i in range(0, 4):
+ if attributes[TANGENT_ATTRIBUTE][current_new_index * 4 + i] != t[i]:
+ found = False
+ break
+
+ if not found:
+ continue
+
+ for tex_coord_index in range(0, tex_coord_max):
+ uv = uvs[tex_coord_index]
+
+ tex_coord_id = TEXCOORD_PREFIX + str(tex_coord_index)
+ for i in range(0, 2):
+ if attributes[tex_coord_id][current_new_index * 2 + i] != uv[i]:
+ found = False
+ break
+
+ if export_color:
+ for color_index in range(0, color_max):
+ color = colors[color_index]
+
+ color_id = COLOR_PREFIX + str(color_index)
+ for i in range(0, 3):
+ # Alpha is always 1.0 - see above.
+ current_color = attributes[color_id][current_new_index * 4 + i]
+ if color_srgb_to_scene_linear(current_color) != color[i]:
+ found = False
+ break
+
+ if export_settings[gltf2_blender_export_keys.SKINS]:
+ for bone_index in range(0, bone_max):
+ joint = joints[bone_index]
+ weight = weights[bone_index]
+
+ joint_id = JOINTS_PREFIX + str(bone_index)
+ weight_id = WEIGHTS_PREFIX + str(bone_index)
+ for i in range(0, 4):
+ if attributes[joint_id][current_new_index * 4 + i] != joint[i]:
+ found = False
+ break
+ if attributes[weight_id][current_new_index * 4 + i] != weight[i]:
+ found = False
+ break
+
+ if export_settings[gltf2_blender_export_keys.MORPH]:
+ for morph_index in range(0, morph_max):
+ target_position = target_positions[morph_index]
+ target_normal = target_normals[morph_index]
+ if use_tangents:
+ target_tangent = target_tangents[morph_index]
+
+ target_position_id = MORPH_POSITION_PREFIX + str(morph_index)
+ target_normal_id = MORPH_NORMAL_PREFIX + str(morph_index)
+ target_tangent_id = MORPH_TANGENT_PREFIX + str(morph_index)
+ for i in range(0, 3):
+ if attributes[target_position_id][current_new_index * 3 + i] != target_position[i]:
+ found = False
+ break
+ if attributes[target_normal_id][current_new_index * 3 + i] != target_normal[i]:
+ found = False
+ break
+ if use_tangents:
+ if attributes[target_tangent_id][current_new_index * 3 + i] != target_tangent[i]:
+ found = False
+ break
+
+ if found:
+ indices.append(current_new_index)
+
+ create = False
+ break
+
+ if not create:
+ continue
+
+ new_index = 0
+
+ if primitive.get('max_index') is not None:
+ new_index = primitive['max_index'] + 1
+
+ primitive['max_index'] = new_index
+
+ vertex_index_to_new_indices[vertex_index].append(new_index)
+
+ #
+ #
+
+ indices.append(new_index)
+
+ #
+
+ attributes[POSITION_ATTRIBUTE].extend(v)
+ attributes[NORMAL_ATTRIBUTE].extend(n)
+ if use_tangents:
+ attributes[TANGENT_ATTRIBUTE].extend(t)
+
+ if blender_mesh.uv_layers.active:
+ for tex_coord_index in range(0, tex_coord_max):
+ tex_coord_id = TEXCOORD_PREFIX + str(tex_coord_index)
+
+ if attributes.get(tex_coord_id) is None:
+ attributes[tex_coord_id] = []
+
+ attributes[tex_coord_id].extend(uvs[tex_coord_index])
+
+ if export_color:
+ for color_index in range(0, color_max):
+ color_id = COLOR_PREFIX + str(color_index)
+
+ if attributes.get(color_id) is None:
+ attributes[color_id] = []
+
+ attributes[color_id].extend(colors[color_index])
+
+ if export_settings[gltf2_blender_export_keys.SKINS]:
+ for bone_index in range(0, bone_max):
+ joint_id = JOINTS_PREFIX + str(bone_index)
+
+ if attributes.get(joint_id) is None:
+ attributes[joint_id] = []
+
+ attributes[joint_id].extend(joints[bone_index])
+
+ weight_id = WEIGHTS_PREFIX + str(bone_index)
+
+ if attributes.get(weight_id) is None:
+ attributes[weight_id] = []
+
+ attributes[weight_id].extend(weights[bone_index])
+
+ if export_settings[gltf2_blender_export_keys.MORPH]:
+ for morph_index in range(0, morph_max):
+ target_position_id = MORPH_POSITION_PREFIX + str(morph_index)
+
+ if attributes.get(target_position_id) is None:
+ attributes[target_position_id] = []
+
+ attributes[target_position_id].extend(target_positions[morph_index])
+
+ target_normal_id = MORPH_NORMAL_PREFIX + str(morph_index)
+
+ if attributes.get(target_normal_id) is None:
+ attributes[target_normal_id] = []
+
+ attributes[target_normal_id].extend(target_normals[morph_index])
+
+ if use_tangents:
+ target_tangent_id = MORPH_TANGENT_PREFIX + str(morph_index)
+
+ if attributes.get(target_tangent_id) is None:
+ attributes[target_tangent_id] = []
+
+ attributes[target_tangent_id].extend(target_tangents[morph_index])
+
+ #
+ # Add primitive plus split them if needed.
+ #
+
+ result_primitives = []
+
+ for material_name, primitive in material_name_to_primitives.items():
+ export_color = True
+
+ #
+
+ indices = primitive[INDICES_ID]
+
+ if len(indices) == 0:
+ continue
+
+ position = primitive[ATTRIBUTES_ID][POSITION_ATTRIBUTE]
+ normal = primitive[ATTRIBUTES_ID][NORMAL_ATTRIBUTE]
+ if use_tangents:
+ tangent = primitive[ATTRIBUTES_ID][TANGENT_ATTRIBUTE]
+ tex_coords = []
+ for tex_coord_index in range(0, tex_coord_max):
+ tex_coords.append(primitive[ATTRIBUTES_ID][TEXCOORD_PREFIX + str(tex_coord_index)])
+ colors = []
+ if export_color:
+ for color_index in range(0, color_max):
+ tex_coords.append(primitive[ATTRIBUTES_ID][COLOR_PREFIX + str(color_index)])
+ joints = []
+ weights = []
+ if export_settings[gltf2_blender_export_keys.SKINS]:
+ for bone_index in range(0, bone_max):
+ joints.append(primitive[ATTRIBUTES_ID][JOINTS_PREFIX + str(bone_index)])
+ weights.append(primitive[ATTRIBUTES_ID][WEIGHTS_PREFIX + str(bone_index)])
+
+ target_positions = []
+ target_normals = []
+ target_tangents = []
+ if export_settings[gltf2_blender_export_keys.MORPH]:
+ for morph_index in range(0, morph_max):
+ target_positions.append(primitive[ATTRIBUTES_ID][MORPH_POSITION_PREFIX + str(morph_index)])
+ target_normals.append(primitive[ATTRIBUTES_ID][MORPH_NORMAL_PREFIX + str(morph_index)])
+ if use_tangents:
+ target_tangents.append(primitive[ATTRIBUTES_ID][MORPH_TANGENT_PREFIX + str(morph_index)])
+
+ #
+
+ count = len(indices)
+
+ if count == 0:
+ continue
+
+ max_index = max(indices)
+
+ #
+
+ range_indices = 65536
+
+ #
+
+ if max_index >= range_indices:
+ #
+ # Splitting result_primitives.
+ #
+
+ # At start, all indices are pending.
+ pending_attributes = {
+ POSITION_ATTRIBUTE: [],
+ NORMAL_ATTRIBUTE: []
+ }
+
+ if use_tangents:
+ pending_attributes[TANGENT_ATTRIBUTE] = []
+
+ pending_primitive = {
+ MATERIAL_ID: material_name,
+ INDICES_ID: [],
+ ATTRIBUTES_ID: pending_attributes
+ }
+
+ pending_primitive[INDICES_ID].extend(indices)
+
+ pending_attributes[POSITION_ATTRIBUTE].extend(position)
+ pending_attributes[NORMAL_ATTRIBUTE].extend(normal)
+ if use_tangents:
+ pending_attributes[TANGENT_ATTRIBUTE].extend(tangent)
+ tex_coord_index = 0
+ for tex_coord in tex_coords:
+ pending_attributes[TEXCOORD_PREFIX + str(tex_coord_index)] = tex_coord
+ tex_coord_index += 1
+ if export_color:
+ color_index = 0
+ for color in colors:
+ pending_attributes[COLOR_PREFIX + str(color_index)] = color
+ color_index += 1
+ if export_settings[gltf2_blender_export_keys.SKINS]:
+ joint_index = 0
+ for joint in joints:
+ pending_attributes[JOINTS_PREFIX + str(joint_index)] = joint
+ joint_index += 1
+ weight_index = 0
+ for weight in weights:
+ pending_attributes[WEIGHTS_PREFIX + str(weight_index)] = weight
+ weight_index += 1
+ if export_settings[gltf2_blender_export_keys.MORPH]:
+ morph_index = 0
+ for target_position in target_positions:
+ pending_attributes[MORPH_POSITION_PREFIX + str(morph_index)] = target_position
+ morph_index += 1
+ morph_index = 0
+ for target_normal in target_normals:
+ pending_attributes[MORPH_NORMAL_PREFIX + str(morph_index)] = target_normal
+ morph_index += 1
+ if use_tangents:
+ morph_index = 0
+ for target_tangent in target_tangents:
+ pending_attributes[MORPH_TANGENT_PREFIX + str(morph_index)] = target_tangent
+ morph_index += 1
+
+ pending_indices = pending_primitive[INDICES_ID]
+
+ # Continue until all are processed.
+ while len(pending_indices) > 0:
+
+ process_indices = pending_primitive[INDICES_ID]
+ max_index = max(process_indices)
+
+ pending_indices = []
+
+ #
+ #
+
+ all_local_indices = []
+
+ for i in range(0, (max_index // range_indices) + 1):
+ all_local_indices.append([])
+
+ #
+ #
+
+ # For all faces ...
+ for face_index in range(0, len(process_indices), 3):
+
+ written = False
+
+ face_min_index = min(process_indices[face_index + 0], process_indices[face_index + 1],
+ process_indices[face_index + 2])
+ face_max_index = max(process_indices[face_index + 0], process_indices[face_index + 1],
+ process_indices[face_index + 2])
+
+ # ... check if it can be but in a range of maximum indices.
+ for i in range(0, (max_index // range_indices) + 1):
+ offset = i * range_indices
+
+ # Yes, so store the primitive with its indices.
+ if face_min_index >= offset and face_max_index < offset + range_indices:
+ all_local_indices[i].extend(
+ [process_indices[face_index + 0], process_indices[face_index + 1],
+ process_indices[face_index + 2]])
+
+ written = True
+ break
+
+ # If not written, the triangle face has indices from different ranges.
+ if not written:
+ pending_indices.extend([process_indices[face_index + 0], process_indices[face_index + 1],
+ process_indices[face_index + 2]])
+
+ # Only add result_primitives, which do have indices in it.
+ for local_indices in all_local_indices:
+ if len(local_indices) > 0:
+ current_primitive = extract_primitive_floor(pending_primitive, local_indices, use_tangents)
+
+ result_primitives.append(current_primitive)
+
+ print_console('DEBUG', 'Adding primitive with splitting. Indices: ' + str(
+ len(current_primitive[INDICES_ID])) + ' Vertices: ' + str(
+ len(current_primitive[ATTRIBUTES_ID][POSITION_ATTRIBUTE]) // 3))
+
+ # Process primitive faces having indices in several ranges.
+ if len(pending_indices) > 0:
+ pending_primitive = extract_primitive_pack(pending_primitive, pending_indices, use_tangents)
+
+ print_console('DEBUG', 'Creating temporary primitive for splitting')
+
+ else:
+ #
+ # No splitting needed.
+ #
+ result_primitives.append(primitive)
+
+ print_console('DEBUG', 'Adding primitive without splitting. Indices: ' + str(
+ len(primitive[INDICES_ID])) + ' Vertices: ' + str(
+ len(primitive[ATTRIBUTES_ID][POSITION_ATTRIBUTE]) // 3))
+
+ print_console('INFO', 'Primitives created: ' + str(len(result_primitives)))
+
+ return result_primitives
+
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_filter.py b/io_scene_gltf2/blender/exp/gltf2_blender_filter.py
new file mode 100755
index 00000000..6a4e18f9
--- /dev/null
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_filter.py
@@ -0,0 +1,455 @@
+# Copyright 2018 The glTF-Blender-IO authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+#
+# Imports
+#
+
+import bpy
+from . import gltf2_blender_export_keys
+from . import gltf2_blender_get
+from ...io.com.gltf2_io_debug import print_console
+from ..com.gltf2_blender_image import create_img_from_blender_image
+from ...io.com import gltf2_io_image
+
+#
+# Globals
+#
+
+PREVIEW = 'PREVIEW'
+GLOSSINESS = 'glTF Specular Glossiness'
+ROUGHNESS = 'glTF Metallic Roughness'
+
+
+#
+# Functions
+#
+
+def filter_merge_image(export_settings, blender_image):
+ metallic_channel = gltf2_blender_get.get_image_material_usage_to_socket(blender_image, "Metallic")
+ roughness_channel = gltf2_blender_get.get_image_material_usage_to_socket(blender_image, "Roughness")
+
+ if metallic_channel < 0 and roughness_channel < 0:
+ return False
+
+ output = export_settings[gltf2_blender_export_keys.METALLIC_ROUGHNESS_IMAGE]
+ if export_settings.get(export_keys.METALLIC_ROUGHNESS_IMAGE) is None:
+ width = blender_image.image.size[0]
+ height = blender_image.image.size[1]
+ output = gltf2_io_image.create_img(width, height, r=1.0, g=1.0, b=1.0, a=1.0)
+
+ source = create_img_from_blender_image(blender_image.image)
+
+ if metallic_channel >= 0:
+ gltf2_io_image.copy_img_channel(output, dst_channel=2, src_image=source, src_channel=metallic_channel)
+ output.name = blender_image.image.name + output.name
+ if roughness_channel >= 0:
+ gltf2_io_image.copy_img_channel(output, dst_channel=1, src_image=source, src_channel=roughness_channel)
+ if metallic_channel < 0:
+ output.name = output.name + blender_image.image.name
+ return True
+
+
+def filter_used_materials():
+ """Gather and return all unfiltered, valid Blender materials."""
+ materials = []
+
+ for blender_material in bpy.data.materials:
+ if blender_material.node_tree and blender_material.use_nodes:
+ for currentNode in blender_material.node_tree.nodes:
+ if isinstance(currentNode, bpy.types.ShaderNodeGroup):
+ if currentNode.node_tree.name.startswith(ROUGHNESS):
+ materials.append(blender_material)
+ elif currentNode.node_tree.name.startswith(GLOSSINESS):
+ materials.append(blender_material)
+ elif isinstance(currentNode, bpy.types.ShaderNodeBsdfPrincipled):
+ materials.append(blender_material)
+ else:
+ materials.append(blender_material)
+
+ return materials
+
+
+def filter_apply(export_settings):
+ """
+ Gathers and filters the objects and assets to export.
+
+ Also filters out invalid, deleted and not exportable elements.
+ """
+ filtered_objects = []
+ implicit_filtered_objects = []
+
+ for blender_object in bpy.data.objects:
+
+ if blender_object.users == 0:
+ continue
+
+ if export_settings[gltf2_blender_export_keys.SELECTED] and blender_object.select_get() is False:
+ continue
+
+ if not export_settings[gltf2_blender_export_keys.LAYERS] and not blender_object.layers[0]:
+ continue
+
+ filtered_objects.append(blender_object)
+
+ if export_settings[gltf2_blender_export_keys.SELECTED] or not export_settings[gltf2_blender_export_keys.LAYERS]:
+ current_parent = blender_object.parent
+ while current_parent:
+ if current_parent not in implicit_filtered_objects:
+ implicit_filtered_objects.append(current_parent)
+
+ current_parent = current_parent.parent
+
+ export_settings[gltf2_blender_export_keys.FILTERED_OBJECTS] = filtered_objects
+
+ # Meshes
+
+ filtered_meshes = {}
+ filtered_vertex_groups = {}
+ temporary_meshes = []
+
+ for blender_mesh in bpy.data.meshes:
+
+ if blender_mesh.users == 0:
+ continue
+
+ current_blender_mesh = blender_mesh
+
+ current_blender_object = None
+
+ skip = True
+
+ for blender_object in filtered_objects:
+
+ current_blender_object = blender_object
+
+ if current_blender_object.type != 'MESH':
+ continue
+
+ if current_blender_object.data == current_blender_mesh:
+
+ skip = False
+
+ use_auto_smooth = current_blender_mesh.use_auto_smooth
+
+ if use_auto_smooth:
+
+ if current_blender_mesh.shape_keys is None:
+ current_blender_object = current_blender_object.copy()
+ else:
+ use_auto_smooth = False
+
+ print_console('WARNING',
+ 'Auto smooth and shape keys cannot be exported in parallel. '
+ 'Falling back to non auto smooth.')
+
+ if export_settings[gltf2_blender_export_keys.APPLY] or use_auto_smooth:
+ # TODO: maybe add to new exporter
+ if not export_settings[gltf2_blender_export_keys.APPLY]:
+ current_blender_object.modifiers.clear()
+
+ if use_auto_smooth:
+ blender_modifier = current_blender_object.modifiers.new('Temporary_Auto_Smooth', 'EDGE_SPLIT')
+
+ blender_modifier.split_angle = current_blender_mesh.auto_smooth_angle
+ blender_modifier.use_edge_angle = not current_blender_mesh.has_custom_normals
+
+ current_blender_mesh = current_blender_object.to_mesh(bpy.context.scene, True, PREVIEW)
+ temporary_meshes.append(current_blender_mesh)
+
+ break
+
+ if skip:
+ continue
+
+ filtered_meshes[blender_mesh.name] = current_blender_mesh
+ filtered_vertex_groups[blender_mesh.name] = current_blender_object.vertex_groups
+
+ # Curves
+
+ for blender_curve in bpy.data.curves:
+
+ if blender_curve.users == 0:
+ continue
+
+ current_blender_curve = blender_curve
+
+ current_blender_mesh = None
+
+ current_blender_object = None
+
+ skip = True
+
+ for blender_object in filtered_objects:
+
+ current_blender_object = blender_object
+
+ if current_blender_object.type not in ('CURVE', 'FONT'):
+ continue
+
+ if current_blender_object.data == current_blender_curve:
+
+ skip = False
+
+ current_blender_object = current_blender_object.copy()
+
+ if not export_settings[gltf2_blender_export_keys.APPLY]:
+ current_blender_object.modifiers.clear()
+
+ current_blender_mesh = current_blender_object.to_mesh(bpy.context.scene, True, PREVIEW)
+ temporary_meshes.append(current_blender_mesh)
+
+ break
+
+ if skip:
+ continue
+
+ filtered_meshes[blender_curve.name] = current_blender_mesh
+ filtered_vertex_groups[blender_curve.name] = current_blender_object.vertex_groups
+
+ #
+
+ export_settings[gltf2_blender_export_keys.FILTERED_MESHES] = filtered_meshes
+ export_settings[gltf2_blender_export_keys.FILTERED_VERTEX_GROUPS] = filtered_vertex_groups
+ export_settings[gltf2_blender_export_keys.TEMPORARY_MESHES] = temporary_meshes
+
+ #
+
+ filtered_materials = []
+
+ for blender_material in filter_used_materials():
+
+ if blender_material.users == 0:
+ continue
+
+ for mesh_name, blender_mesh in filtered_meshes.items():
+ for compare_blender_material in blender_mesh.materials:
+ if compare_blender_material == blender_material and blender_material not in filtered_materials:
+ filtered_materials.append(blender_material)
+
+ #
+
+ for blender_object in filtered_objects:
+ if blender_object.material_slots:
+ for blender_material_slot in blender_object.material_slots:
+ if blender_material_slot.link == 'DATA':
+ continue
+
+ if blender_material_slot.material not in filtered_materials:
+ filtered_materials.append(blender_material_slot.material)
+
+ export_settings[gltf2_blender_export_keys.FILTERED_MATERIALS] = filtered_materials
+
+ #
+
+ filtered_textures = []
+ filtered_merged_textures = []
+
+ temp_filtered_texture_names = []
+
+ for blender_material in filtered_materials:
+ if blender_material.node_tree and blender_material.use_nodes:
+
+ per_material_textures = []
+
+ for blender_node in blender_material.node_tree.nodes:
+
+ if is_valid_node(blender_node) and blender_node not in filtered_textures:
+ add_node = False
+ add_merged_node = False
+ for blender_socket in blender_node.outputs:
+ if blender_socket.is_linked:
+ for blender_link in blender_socket.links:
+ if isinstance(blender_link.to_node, bpy.types.ShaderNodeGroup):
+ is_roughness = blender_link.to_node.node_tree.name.startswith(ROUGHNESS)
+ is_glossiness = blender_link.to_node.node_tree.name.startswith(GLOSSINESS)
+ if is_roughness or is_glossiness:
+ add_node = True
+ break
+ elif isinstance(blender_link.to_node, bpy.types.ShaderNodeBsdfPrincipled):
+ add_node = True
+ break
+ elif isinstance(blender_link.to_node, bpy.types.ShaderNodeNormalMap):
+ add_node = True
+ break
+ elif isinstance(blender_link.to_node, bpy.types.ShaderNodeSeparateRGB):
+ add_merged_node = True
+ break
+
+ if add_node or add_merged_node:
+ break
+
+ if add_node:
+ filtered_textures.append(blender_node)
+ # TODO: Add displacement texture, as not stored in node tree.
+
+ if add_merged_node:
+ if len(per_material_textures) == 0:
+ filtered_merged_textures.append(per_material_textures)
+
+ per_material_textures.append(blender_node)
+
+ else:
+
+ for blender_texture_slot in blender_material.texture_slots:
+
+ if is_valid_texture_slot(blender_texture_slot) and \
+ blender_texture_slot not in filtered_textures and \
+ blender_texture_slot.name not in temp_filtered_texture_names:
+ accept = False
+
+ if blender_texture_slot.use_map_color_diffuse:
+ accept = True
+
+ if blender_texture_slot.use_map_ambient:
+ accept = True
+ if blender_texture_slot.use_map_emit:
+ accept = True
+ if blender_texture_slot.use_map_normal:
+ accept = True
+
+ if export_settings[gltf2_blender_export_keys.DISPLACEMENT]:
+ if blender_texture_slot.use_map_displacement:
+ accept = True
+
+ if accept:
+ filtered_textures.append(blender_texture_slot)
+ temp_filtered_texture_names.append(blender_texture_slot.name)
+
+ export_settings[gltf2_blender_export_keys.FILTERED_TEXTURES] = filtered_textures
+
+ #
+
+ filtered_images = []
+ filtered_merged_images = []
+ filtered_images_use_alpha = {}
+
+ for blender_texture in filtered_textures:
+
+ if isinstance(blender_texture, bpy.types.ShaderNodeTexImage):
+ if is_valid_image(blender_texture.image) and blender_texture.image not in filtered_images:
+ filtered_images.append(blender_texture.image)
+ alpha_socket = blender_texture.outputs.get('Alpha')
+ if alpha_socket is not None and alpha_socket.is_linked:
+ filtered_images_use_alpha[blender_texture.image.name] = True
+
+ else:
+ if is_valid_image(blender_texture.texture.image) and blender_texture.texture.image not in filtered_images:
+ filtered_images.append(blender_texture.texture.image)
+ if blender_texture.use_map_alpha:
+ filtered_images_use_alpha[blender_texture.texture.image.name] = True
+
+ #
+
+ for per_material_textures in filtered_merged_textures:
+
+ export_settings[gltf2_blender_export_keys.METALLIC_ROUGHNESS_IMAGE] = None
+
+ for blender_texture in per_material_textures:
+
+ if isinstance(blender_texture, bpy.types.ShaderNodeTexImage):
+ if is_valid_image(blender_texture.image) and blender_texture.image not in filtered_images:
+ filter_merge_image(export_settings, blender_texture)
+
+ img = export_settings.get(export_keys.METALLIC_ROUGHNESS_IMAGE)
+ if img is not None:
+ filtered_merged_images.append(img)
+ export_settings[gltf2_blender_export_keys.FILTERED_TEXTURES].append(img)
+
+ export_settings[gltf2_blender_export_keys.FILTERED_MERGED_IMAGES] = filtered_merged_images
+ export_settings[gltf2_blender_export_keys.FILTERED_IMAGES] = filtered_images
+ export_settings[gltf2_blender_export_keys.FILTERED_IMAGES_USE_ALPHA] = filtered_images_use_alpha
+
+ #
+
+ filtered_cameras = []
+
+ for blender_camera in bpy.data.cameras:
+
+ if blender_camera.users == 0:
+ continue
+
+ if export_settings[gltf2_blender_export_keys.SELECTED]:
+ if blender_camera not in filtered_objects:
+ continue
+
+ filtered_cameras.append(blender_camera)
+
+ export_settings[gltf2_blender_export_keys.FILTERED_CAMERAS] = filtered_cameras
+
+ #
+ #
+
+ filtered_lights = []
+
+ for blender_light in bpy.data.lamps:
+
+ if blender_light.users == 0:
+ continue
+
+ if export_settings[gltf2_blender_export_keys.SELECTED]:
+ if blender_light not in filtered_objects:
+ continue
+
+ if blender_light.type == 'HEMI':
+ continue
+
+ filtered_lights.append(blender_light)
+
+ export_settings[gltf2_blender_export_keys.FILTERED_LIGHTS] = filtered_lights
+
+ #
+ #
+
+ for implicit_object in implicit_filtered_objects:
+ if implicit_object not in filtered_objects:
+ filtered_objects.append(implicit_object)
+
+ #
+ #
+ #
+
+ group_index = {}
+
+ if export_settings[gltf2_blender_export_keys.SKINS]:
+ for blender_object in filtered_objects:
+ if blender_object.type != 'ARMATURE' or len(blender_object.pose.bones) == 0:
+ continue
+ for blender_bone in blender_object.pose.bones:
+ group_index[blender_bone.name] = len(group_index)
+
+ export_settings[gltf2_blender_export_keys.GROUP_INDEX] = group_index
+
+
+def is_valid_node(blender_node):
+ return isinstance(blender_node, bpy.types.ShaderNodeTexImage) and is_valid_image(blender_node.image)
+
+
+def is_valid_image(image):
+ return image is not None and \
+ image.users != 0 and \
+ image.size[0] > 0 and \
+ image.size[1] > 0
+
+
+def is_valid_texture_slot(blender_texture_slot):
+ return blender_texture_slot is not None and \
+ blender_texture_slot.texture and \
+ blender_texture_slot.texture.users != 0 and \
+ blender_texture_slot.texture.type == 'IMAGE' and \
+ blender_texture_slot.texture.image is not None and \
+ blender_texture_slot.texture.image.users != 0 and \
+ blender_texture_slot.texture.image.size[0] > 0 and \
+ blender_texture_slot.texture.image.size[1] > 0
+
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather.py
new file mode 100755
index 00000000..217930e1
--- /dev/null
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather.py
@@ -0,0 +1,69 @@
+# Copyright 2018 The glTF-Blender-IO authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import bpy
+
+from io_scene_gltf2.io.com import gltf2_io
+from io_scene_gltf2.blender.exp import gltf2_blender_gather_nodes
+from io_scene_gltf2.blender.exp import gltf2_blender_gather_animations
+from io_scene_gltf2.blender.exp.gltf2_blender_gather_cache import cached
+from io_scene_gltf2.blender.exp import gltf2_blender_generate_extras
+from io_scene_gltf2.blender.exp import gltf2_blender_export_keys
+
+
+def gather_gltf2(export_settings):
+ """
+ Gather glTF properties from the current state of blender.
+
+ :return: list of scene graphs to be added to the glTF export
+ """
+ scenes = []
+ animations = [] # unfortunately animations in gltf2 are just as 'root' as scenes.
+ for blender_scene in bpy.data.scenes:
+ scenes.append(__gather_scene(blender_scene, export_settings))
+ animations += __gather_animations(blender_scene, export_settings)
+
+ return scenes, animations
+
+
+@cached
+def __gather_scene(blender_scene, export_settings):
+ scene = gltf2_io.Scene(
+ extensions=None,
+ extras=__gather_extras(blender_scene, export_settings),
+ name=blender_scene.name,
+ nodes=[]
+ )
+
+ for blender_object in blender_scene.objects:
+ if blender_object.parent is None:
+ node = gltf2_blender_gather_nodes.gather_node(blender_object, export_settings)
+ if node is not None:
+ scene.nodes.append(node)
+
+ return scene
+
+
+def __gather_animations(blender_scene, export_settings):
+ animations = []
+ for blender_object in blender_scene.objects:
+ animations += gltf2_blender_gather_animations.gather_animations(blender_object, export_settings)
+ return animations
+
+
+def __gather_extras(blender_object, export_settings):
+ if export_settings[gltf2_blender_export_keys.EXTRAS]:
+ return gltf2_blender_generate_extras.generate_extras(blender_object)
+ return None
+
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_channel_target.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_channel_target.py
new file mode 100755
index 00000000..2e4ac1d7
--- /dev/null
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_channel_target.py
@@ -0,0 +1,84 @@
+# Copyright 2018 The glTF-Blender-IO authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+import bpy
+import typing
+from io_scene_gltf2.io.com import gltf2_io
+from io_scene_gltf2.blender.exp.gltf2_blender_gather_cache import cached
+from io_scene_gltf2.blender.exp import gltf2_blender_gather_nodes
+from io_scene_gltf2.blender.exp import gltf2_blender_gather_joints
+
+
+@cached
+def gather_animation_channel_target(channels: typing.Tuple[bpy.types.FCurve],
+ blender_object: bpy.types.Object,
+ export_settings
+ ) -> gltf2_io.AnimationChannelTarget:
+ return gltf2_io.AnimationChannelTarget(
+ extensions=__gather_extensions(channels, blender_object, export_settings),
+ extras=__gather_extras(channels, blender_object, export_settings),
+ node=__gather_node(channels, blender_object, export_settings),
+ path=__gather_path(channels, blender_object, export_settings)
+ )
+
+
+def __gather_extensions(channels: typing.Tuple[bpy.types.FCurve],
+ blender_object: bpy.types.Object,
+ export_settings
+ ) -> typing.Any:
+ return None
+
+
+def __gather_extras(channels: typing.Tuple[bpy.types.FCurve],
+ blender_object: bpy.types.Object,
+ export_settings
+ ) -> typing.Any:
+ return None
+
+
+def __gather_node(channels: typing.Tuple[bpy.types.FCurve],
+ blender_object: bpy.types.Object,
+ export_settings
+ ) -> gltf2_io.Node:
+ if blender_object.type == "ARMATURE":
+ # TODO: get joint from fcurve data_path and gather_joint
+ blender_bone = blender_object.path_resolve(channels[0].data_path.rsplit('.', 1)[0])
+ if isinstance(blender_bone, bpy.types.PoseBone):
+ return gltf2_blender_gather_joints.gather_joint(blender_bone, export_settings)
+
+ return gltf2_blender_gather_nodes.gather_node(blender_object, export_settings)
+
+
+def __gather_path(channels: typing.Tuple[bpy.types.FCurve],
+ blender_object: bpy.types.Object,
+ export_settings
+ ) -> str:
+ target = channels[0].data_path.split('.')[-1]
+ path = {
+ "delta_location": "translation",
+ "delta_rotation_euler": "rotation",
+ "location": "translation",
+ "rotation_axis_angle": "rotation",
+ "rotation_euler": "rotation",
+ "rotation_quaternion": "rotation",
+ "scale": "scale",
+ "value": "weights"
+ }.get(target)
+
+ if target is None:
+ raise RuntimeError("Cannot export an animation with {} target".format(target))
+
+ return path
+
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_channels.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_channels.py
new file mode 100755
index 00000000..808c970d
--- /dev/null
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_channels.py
@@ -0,0 +1,131 @@
+# Copyright 2018 The glTF-Blender-IO authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import bpy
+import typing
+
+from ..com.gltf2_blender_data_path import get_target_object_path, get_target_property_name
+from io_scene_gltf2.io.com import gltf2_io
+from io_scene_gltf2.io.com import gltf2_io_debug
+from io_scene_gltf2.blender.exp.gltf2_blender_gather_cache import cached
+from io_scene_gltf2.blender.exp import gltf2_blender_gather_animation_samplers
+from io_scene_gltf2.blender.exp import gltf2_blender_gather_animation_channel_target
+
+
+@cached
+def gather_animation_channels(blender_action: bpy.types.Action,
+ blender_object: bpy.types.Object,
+ export_settings
+ ) -> typing.List[gltf2_io.AnimationChannel]:
+ channels = []
+
+ for channel_group in __get_channel_groups(blender_action, blender_object):
+ channel = __gather_animation_channel(channel_group, blender_object, export_settings)
+ if channel is not None:
+ channels.append(channel)
+
+ return channels
+
+
+def __gather_animation_channel(channels: typing.Tuple[bpy.types.FCurve],
+ blender_object: bpy.types.Object,
+ export_settings
+ ) -> typing.Union[gltf2_io.AnimationChannel, None]:
+ if not __filter_animation_channel(channels, blender_object, export_settings):
+ return None
+
+ return gltf2_io.AnimationChannel(
+ extensions=__gather_extensions(channels, blender_object, export_settings),
+ extras=__gather_extras(channels, blender_object, export_settings),
+ sampler=__gather_sampler(channels, blender_object, export_settings),
+ target=__gather_target(channels, blender_object, export_settings)
+ )
+
+
+def __filter_animation_channel(channels: typing.Tuple[bpy.types.FCurve],
+ blender_object: bpy.types.Object,
+ export_settings
+ ) -> bool:
+ return True
+
+
+def __gather_extensions(channels: typing.Tuple[bpy.types.FCurve],
+ blender_object: bpy.types.Object,
+ export_settings
+ ) -> typing.Any:
+ return None
+
+
+def __gather_extras(channels: typing.Tuple[bpy.types.FCurve],
+ blender_object: bpy.types.Object,
+ export_settings
+ ) -> typing.Any:
+ return None
+
+
+def __gather_sampler(channels: typing.Tuple[bpy.types.FCurve],
+ blender_object: bpy.types.Object,
+ export_settings
+ ) -> gltf2_io.AnimationSampler:
+ return gltf2_blender_gather_animation_samplers.gather_animation_sampler(
+ channels,
+ blender_object,
+ export_settings
+ )
+
+
+def __gather_target(channels: typing.Tuple[bpy.types.FCurve],
+ blender_object: bpy.types.Object,
+ export_settings
+ ) -> gltf2_io.AnimationChannelTarget:
+ return gltf2_blender_gather_animation_channel_target.gather_animation_channel_target(
+ channels, blender_object, export_settings)
+
+
+def __get_channel_groups(blender_action: bpy.types.Action, blender_object: bpy.types.Object):
+ targets = {}
+ for fcurve in blender_action.fcurves:
+ target_property = get_target_property_name(fcurve.data_path)
+ object_path = get_target_object_path(fcurve.data_path)
+
+ # find the object affected by this action
+ if not object_path:
+ target = blender_object
+ else:
+ try:
+ target = blender_object.path_resolve(object_path)
+ except ValueError:
+ # if the object is a mesh and the action target path can not be resolved, we know that this is a morph
+ # animation.
+ if blender_object.type == "MESH":
+ # if you need the specific shape key for some reason, this is it:
+ # shape_key = blender_object.data.shape_keys.path_resolve(object_path)
+ target = blender_object.data.shape_keys
+ else:
+ gltf2_io_debug.print_console("WARNING", "Can not export animations with target {}".format(object_path))
+ continue
+
+ # group channels by target object and affected property of the target
+ target_properties = targets.get(target, {})
+ channels = target_properties.get(target_property, [])
+ channels.append(fcurve)
+ target_properties[target_property] = channels
+ targets[target] = target_properties
+
+ groups = []
+ for p in targets.values():
+ groups += list(p.values())
+
+ return map(tuple, groups)
+
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_sampler_keyframes.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_sampler_keyframes.py
new file mode 100755
index 00000000..6ef7fb0b
--- /dev/null
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_sampler_keyframes.py
@@ -0,0 +1,200 @@
+# Copyright 2018 The glTF-Blender-IO authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import bpy
+import mathutils
+import typing
+
+from io_scene_gltf2.blender.exp.gltf2_blender_gather_cache import cached
+from io_scene_gltf2.blender.com import gltf2_blender_math
+from . import gltf2_blender_export_keys
+from io_scene_gltf2.io.com import gltf2_io_debug
+
+
+class Keyframe:
+ def __init__(self, channels: typing.Tuple[bpy.types.FCurve], time: float):
+ self.seconds = time / bpy.context.scene.render.fps
+ self.__target = channels[0].data_path.split('.')[-1]
+ self.__indices = [c.array_index for c in channels]
+
+ # Data holders for virtual properties
+ self.__value = None
+ self.__in_tangent = None
+ self.__out_tangent = None
+
+ def __get_target_len(self):
+ length = {
+ "delta_location": 3,
+ "delta_rotation_euler": 3,
+ "location": 3,
+ "rotation_axis_angle": 4,
+ "rotation_euler": 3,
+ "rotation_quaternion": 4,
+ "scale": 3,
+ "value": 1
+ }.get(self.__target)
+
+ if length is None:
+ raise RuntimeError("Unknown target type {}".format(self.__target))
+
+ return length
+
+ def __set_indexed(self, value):
+ # 'value' targets don't use keyframe.array_index
+ if self.__target == "value":
+ return value
+ # Sometimes blender animations only reference a subset of components of a data target. Keyframe should always
+ # contain a complete Vector/ Quaternion --> use the array_index value of the keyframe to set components in such
+ # structures
+ result = [0.0] * self.__get_target_len()
+ for i, v in zip(self.__indices, value):
+ result[i] = v
+ result = gltf2_blender_math.list_to_mathutils(result, self.__target)
+ return result
+
+ @property
+ def value(self) -> typing.Union[mathutils.Vector, mathutils.Euler, mathutils.Quaternion, typing.List[float]]:
+ return self.__value
+
+ @value.setter
+ def value(self, value: typing.List[float]):
+ self.__value = self.__set_indexed(value)
+
+ @property
+ def in_tangent(self) -> typing.Union[mathutils.Vector, mathutils.Euler, mathutils.Quaternion, typing.List[float]]:
+ return self.__in_tangent
+
+ @in_tangent.setter
+ def in_tangent(self, value: typing.List[float]):
+ self.__in_tangent = self.__set_indexed(value)
+
+ @property
+ def out_tangent(self) -> typing.Union[mathutils.Vector, mathutils.Euler, mathutils.Quaternion, typing.List[float]]:
+ return self.__in_tangent
+
+ @out_tangent.setter
+ def out_tangent(self, value: typing.List[float]):
+ self.__out_tangent = self.__set_indexed(value)
+
+
+# cache for performance reasons
+@cached
+def gather_keyframes(channels: typing.Tuple[bpy.types.FCurve], export_settings) \
+ -> typing.List[Keyframe]:
+ """Convert the blender action groups' fcurves to keyframes for use in glTF."""
+ # Find the start and end of the whole action group
+ ranges = [channel.range() for channel in channels]
+
+ start = min([channel.range()[0] for channel in channels])
+ end = max([channel.range()[1] for channel in channels])
+
+ keyframes = []
+ if needs_baking(channels, export_settings):
+ # Bake the animation, by evaluating it at a high frequency
+ # TODO: maybe baking can also be done with FCurve.convert_to_samples
+ time = start
+ # TODO: make user controllable
+ step = 1.0 / bpy.context.scene.render.fps
+ while time <= end:
+ key = Keyframe(channels, time)
+ key.value = [c.evaluate(time) for c in channels]
+ keyframes.append(key)
+ time += step
+ else:
+ # Just use the keyframes as they are specified in blender
+ times = [keyframe.co[0] for keyframe in channels[0].keyframe_points]
+ for i, time in enumerate(times):
+ key = Keyframe(channels, time)
+ # key.value = [c.keyframe_points[i].co[0] for c in action_group.channels]
+ key.value = [c.evaluate(time) for c in channels]
+
+ # compute tangents for cubic spline interpolation
+ if channels[0].keyframe_points[0].interpolation == "BEZIER":
+ # Construct the in tangent
+ if time == times[0]:
+ # start in-tangent has zero length
+ key.in_tangent = [0.0 for _ in channels]
+ else:
+ # otherwise construct an in tangent from the keyframes control points
+
+ key.in_tangent = [
+ 3.0 * (c.keyframe_points[i].co[1] - c.keyframe_points[i].handle_left[1]
+ ) / (time - times[i - 1])
+ for c in channels
+ ]
+ # Construct the out tangent
+ if time == times[-1]:
+ # end out-tangent has zero length
+ key.out_tangent = [0.0 for _ in channels]
+ else:
+ # otherwise construct an out tangent from the keyframes control points
+ key.out_tangent = [
+ 3.0 * (c.keyframe_points[i].handle_right[1] - c.keyframe_points[i].co[1]
+ ) / (times[i + 1] - time)
+ for c in channels
+ ]
+ keyframes.append(key)
+
+ return keyframes
+
+
+def needs_baking(channels: typing.Tuple[bpy.types.FCurve],
+ export_settings
+ ) -> bool:
+ """
+ Check if baking is needed.
+
+ Some blender animations need to be baked as they can not directly be expressed in glTF.
+ """
+ def all_equal(lst):
+ return lst[1:] == lst[:-1]
+
+
+ if export_settings[gltf2_blender_export_keys.FORCE_SAMPLING]:
+ return True
+
+ interpolation = channels[0].keyframe_points[0].interpolation
+ if interpolation not in ["BEZIER", "LINEAR", "CONSTANT"]:
+ gltf2_io_debug.print_console("WARNING",
+ "Baking animation because of an unsupported interpolation method: {}".format(
+ interpolation)
+ )
+ return True
+
+ if any(any(k.interpolation != interpolation for k in c.keyframe_points) for c in channels):
+ # There are different interpolation methods in one action group
+ gltf2_io_debug.print_console("WARNING",
+ "Baking animation because there are different "
+ "interpolation methods in one channel"
+ )
+ return True
+
+ if not all_equal([len(c.keyframe_points) for c in channels]):
+ gltf2_io_debug.print_console("WARNING",
+ "Baking animation because the number of keyframes is not "
+ "equal for all channel tracks")
+ return True
+
+ if len(channels[0].keyframe_points) <= 1:
+ # we need to bake to 'STEP', as at least two keyframes are required to interpolate
+ return True
+
+ if not all(all_equal(key_times) for key_times in zip([[k.co[0] for k in c.keyframe_points] for c in channels])):
+ # The channels have differently located keyframes
+ gltf2_io_debug.print_console("WARNING",
+ "Baking animation because of differently located keyframes in one channel")
+ return True
+
+ return False
+
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_samplers.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_samplers.py
new file mode 100755
index 00000000..6846128d
--- /dev/null
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_samplers.py
@@ -0,0 +1,169 @@
+# Copyright 2018 The glTF-Blender-IO authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+import bpy
+import mathutils
+import typing
+import math
+
+from . import gltf2_blender_export_keys
+from mathutils import Matrix
+from io_scene_gltf2.blender.com.gltf2_blender_data_path import get_target_property_name, get_target_object_path
+from io_scene_gltf2.io.com import gltf2_io
+from io_scene_gltf2.blender.exp.gltf2_blender_gather_cache import cached
+from io_scene_gltf2.io.exp import gltf2_io_binary_data
+from io_scene_gltf2.io.com import gltf2_io_constants
+from io_scene_gltf2.blender.com import gltf2_blender_math
+from io_scene_gltf2.blender.exp import gltf2_blender_gather_animation_sampler_keyframes
+
+
+@cached
+def gather_animation_sampler(channels: typing.Tuple[bpy.types.FCurve],
+ blender_object: bpy.types.Object,
+ export_settings
+ ) -> gltf2_io.AnimationSampler:
+ return gltf2_io.AnimationSampler(
+ extensions=__gather_extensions(channels, blender_object, export_settings),
+ extras=__gather_extras(channels, blender_object, export_settings),
+ input=__gather_input(channels, blender_object, export_settings),
+ interpolation=__gather_interpolation(channels, blender_object, export_settings),
+ output=__gather_output(channels, blender_object, export_settings)
+ )
+
+
+def __gather_extensions(channels: typing.Tuple[bpy.types.FCurve],
+ blender_object: bpy.types.Object,
+ export_settings
+ ) -> typing.Any:
+ return None
+
+
+def __gather_extras(channels: typing.Tuple[bpy.types.FCurve],
+ blender_object: bpy.types.Object,
+ export_settings
+ ) -> typing.Any:
+ return None
+
+
+def __gather_input(channels: typing.Tuple[bpy.types.FCurve],
+ blender_object: bpy.types.Object,
+ export_settings
+ ) -> gltf2_io.Accessor:
+ """Gather the key time codes."""
+ keyframes = gltf2_blender_gather_animation_sampler_keyframes.gather_keyframes(channels, export_settings)
+ times = [k.seconds for k in keyframes]
+
+ return gltf2_io.Accessor(
+ buffer_view=gltf2_io_binary_data.BinaryData.from_list(times, gltf2_io_constants.ComponentType.Float),
+ byte_offset=None,
+ component_type=gltf2_io_constants.ComponentType.Float,
+ count=len(times),
+ extensions=None,
+ extras=None,
+ max=[max(times)],
+ min=[min(times)],
+ name=None,
+ normalized=None,
+ sparse=None,
+ type=gltf2_io_constants.DataType.Scalar
+ )
+
+
+def __gather_interpolation(channels: typing.Tuple[bpy.types.FCurve],
+ blender_object: bpy.types.Object,
+ export_settings
+ ) -> str:
+ if gltf2_blender_gather_animation_sampler_keyframes.needs_baking(channels, export_settings):
+ return 'STEP'
+
+ blender_keyframe = channels[0].keyframe_points[0]
+
+ # Select the interpolation method. Any unsupported method will fallback to STEP
+ return {
+ "BEZIER": "CUBICSPLINE",
+ "LINEAR": "LINEAR",
+ "CONSTANT": "STEP"
+ }[blender_keyframe.interpolation]
+
+
+def __gather_output(channels: typing.Tuple[bpy.types.FCurve],
+ blender_object: bpy.types.Object,
+ export_settings
+ ) -> gltf2_io.Accessor:
+ """Gather the data of the keyframes."""
+ keyframes = gltf2_blender_gather_animation_sampler_keyframes.gather_keyframes(channels, export_settings)
+
+ target_datapath = channels[0].data_path
+
+ transform = blender_object.matrix_parent_inverse
+
+ isYup = export_settings[gltf2_blender_export_keys.YUP]
+
+ if blender_object.type == "ARMATURE":
+ bone = blender_object.path_resolve(get_target_object_path(target_datapath))
+ if isinstance(bone, bpy.types.PoseBone):
+ if bone.parent is not None:
+ parent_transform = bone.parent.bone.matrix_local
+ transform = gltf2_blender_math.multiply(transform, parent_transform.inverted())
+ # if not isYup:
+ # transform = gltf2_blender_math.multiply(transform, gltf2_blender_math.to_zup())
+ else:
+ # only apply the y-up conversion to root bones, as child bones already are in the y-up space
+ if isYup:
+ transform = gltf2_blender_math.multiply(transform, gltf2_blender_math.to_yup())
+ local_transform = bone.bone.matrix_local
+ transform = gltf2_blender_math.multiply(transform, local_transform)
+
+ values = []
+ for keyframe in keyframes:
+ # Transform the data and extract
+ value = gltf2_blender_math.transform(keyframe.value, target_datapath, transform)
+ if isYup and not blender_object.type == "ARMATURE":
+ value = gltf2_blender_math.swizzle_yup(value, target_datapath)
+ keyframe_value = gltf2_blender_math.mathutils_to_gltf(value)
+ if keyframe.in_tangent is not None:
+ in_tangent = gltf2_blender_math.transform(keyframe.in_tangent, target_datapath, transform)
+ if isYup and not blender_object.type == "ARMATURE":
+ in_tangent = gltf2_blender_math.swizzle_yup(in_tangent, target_datapath)
+ keyframe_value = gltf2_blender_math.mathutils_to_gltf(in_tangent) + keyframe_value
+ if keyframe.out_tangent is not None:
+ out_tangent = gltf2_blender_math.transform(keyframe.out_tangent, target_datapath, transform)
+ if isYup and not blender_object.type == "ARMATURE":
+ out_tangent = gltf2_blender_math.swizzle_yup(out_tangent, target_datapath)
+ keyframe_value = keyframe_value + gltf2_blender_math.mathutils_to_gltf(out_tangent)
+ values += keyframe_value
+
+ component_type = gltf2_io_constants.ComponentType.Float
+ if get_target_property_name(target_datapath) == "value":
+ # channels with 'weight' targets must have scalar accessors
+ data_type = gltf2_io_constants.DataType.Scalar
+ else:
+ data_type = gltf2_io_constants.DataType.vec_type_from_num(len(keyframes[0].value))
+
+ return gltf2_io.Accessor(
+ buffer_view=gltf2_io_binary_data.BinaryData.from_list(values, component_type),
+ byte_offset=None,
+ component_type=component_type,
+ count=len(values) // gltf2_io_constants.DataType.num_elements(data_type),
+ extensions=None,
+ extras=None,
+ max=None,
+ min=None,
+ name=None,
+ normalized=None,
+ sparse=None,
+ type=data_type
+ )
+
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_animations.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_animations.py
new file mode 100755
index 00000000..bfbc03ed
--- /dev/null
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_animations.py
@@ -0,0 +1,169 @@
+# Copyright 2018 The glTF-Blender-IO authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import bpy
+import typing
+
+from io_scene_gltf2.io.com import gltf2_io
+from io_scene_gltf2.blender.exp import gltf2_blender_gather_animation_channels
+
+
+def gather_animations(blender_object: bpy.types.Object, export_settings) -> typing.List[gltf2_io.Animation]:
+ """
+ Gather all animations which contribute to the objects property.
+
+ :param blender_object: The blender object which is animated
+ :param export_settings:
+ :return: A list of glTF2 animations
+ """
+ animations = []
+
+ # Collect all 'actions' affecting this object. There is a direct mapping between blender actions and glTF animations
+ blender_actions = __get_blender_actions(blender_object)
+
+ # Export all collected actions.
+ for blender_action in blender_actions:
+ animation = __gather_animation(blender_action, blender_object, export_settings)
+ if animation is not None:
+ animations.append(animation)
+
+ return animations
+
+
+def __gather_animation(blender_action: bpy.types.Action,
+ blender_object: bpy.types.Object,
+ export_settings
+ ) -> typing.Optional[gltf2_io.Animation]:
+ if not __filter_animation(blender_action, blender_object, export_settings):
+ return None
+
+ animation = gltf2_io.Animation(
+ channels=__gather_channels(blender_action, blender_object, export_settings),
+ extensions=__gather_extensions(blender_action, blender_object, export_settings),
+ extras=__gather_extras(blender_action, blender_object, export_settings),
+ name=__gather_name(blender_action, blender_object, export_settings),
+ samplers=__gather_samplers(blender_action, blender_object, export_settings)
+ )
+
+ # To allow reuse of samplers in one animation,
+ __link_samplers(animation, export_settings)
+
+ if not animation.channels:
+ return None
+
+ return animation
+
+
+def __filter_animation(blender_action: bpy.types.Action,
+ blender_object: bpy.types.Object,
+ export_settings
+ ) -> bool:
+ if blender_action.users == 0:
+ return False
+
+ return True
+
+
+def __gather_channels(blender_action: bpy.types.Action,
+ blender_object: bpy.types.Object,
+ export_settings
+ ) -> typing.List[gltf2_io.AnimationChannel]:
+ return gltf2_blender_gather_animation_channels.gather_animation_channels(
+ blender_action, blender_object, export_settings)
+
+
+def __gather_extensions(blender_action: bpy.types.Action,
+ blender_object: bpy.types.Object,
+ export_settings
+ ) -> typing.Any:
+ return None
+
+
+def __gather_extras(blender_action: bpy.types.Action,
+ blender_object: bpy.types.Object,
+ export_settings
+ ) -> typing.Any:
+ return None
+
+
+def __gather_name(blender_action: bpy.types.Action,
+ blender_object: bpy.types.Object,
+ export_settings
+ ) -> typing.Optional[str]:
+ return blender_action.name
+
+
+def __gather_samplers(blender_action: bpy.types.Action,
+ blender_object: bpy.types.Object,
+ export_settings
+ ) -> typing.List[gltf2_io.AnimationSampler]:
+ # We need to gather the samplers after gathering all channels --> populate this list in __link_samplers
+ return []
+
+
+def __link_samplers(animation: gltf2_io.Animation, export_settings):
+ """
+ Move animation samplers to their own list and store their indices at their previous locations.
+
+ After gathering, samplers are stored in the channels properties of the animation and need to be moved
+ to their own list while storing an index into this list at the position where they previously were.
+ This behaviour is similar to that of the glTFExporter that traverses all nodes
+ :param animation:
+ :param export_settings:
+ :return:
+ """
+ # TODO: move this to some util module and update gltf2 exporter also
+ T = typing.TypeVar('T')
+
+ def __append_unique_and_get_index(l: typing.List[T], item: T):
+ if item in l:
+ return l.index(item)
+ else:
+ index = len(l)
+ l.append(item)
+ return index
+
+ for i, channel in enumerate(animation.channels):
+ animation.channels[i].sampler = __append_unique_and_get_index(animation.samplers, channel.sampler)
+
+
+def __get_blender_actions(blender_object: bpy.types.Object
+ ) -> typing.List[bpy.types.Action]:
+ blender_actions = []
+
+ if blender_object.animation_data is not None:
+ # Collect active action.
+ if blender_object.animation_data.action is not None:
+ blender_actions.append(blender_object.animation_data.action)
+
+ # Collect associated strips from NLA tracks.
+ for track in blender_object.animation_data.nla_tracks:
+ # Multi-strip tracks do not export correctly yet (they need to be baked),
+ # so skip them for now and only write single-strip tracks.
+ if track.strips is None or len(track.strips) != 1:
+ continue
+ for strip in track.strips:
+ blender_actions.append(strip.action)
+
+ if blender_object.type == "MESH" \
+ and blender_object.data is not None \
+ and blender_object.data.shape_keys is not None \
+ and blender_object.data.shape_keys.animation_data is not None:
+ blender_actions.append(blender_object.data.shape_keys.animation_data.action)
+
+ # Remove duplicate actions.
+ blender_actions = list(set(blender_actions))
+
+ return blender_actions
+
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_cache.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_cache.py
new file mode 100755
index 00000000..5b00a98b
--- /dev/null
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_cache.py
@@ -0,0 +1,60 @@
+# Copyright 2018 The glTF-Blender-IO authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import functools
+
+
+def cached(func):
+ """
+ Decorate the cache gather functions results.
+
+ The gather function is only executed if its result isn't in the cache yet
+ :param func: the function to be decorated. It will have a static __cache member afterwards
+ :return:
+ """
+ @functools.wraps(func)
+ def wrapper_cached(*args, **kwargs):
+ assert len(args) >= 2 and 0 <= len(kwargs) <= 1, "Wrong signature for cached function"
+ cache_key_args = args
+ # make a shallow copy of the keyword arguments so that 'export_settings' can be removed
+ cache_key_kwargs = dict(kwargs)
+ if kwargs.get("export_settings"):
+ export_settings = kwargs["export_settings"]
+ # 'export_settings' should not be cached
+ del cache_key_kwargs["export_settings"]
+ else:
+ export_settings = args[-1]
+ cache_key_args = args[:-1]
+
+ # we make a tuple from the function arguments so that they can be used as a key to the cache
+ cache_key = tuple(cache_key_args + tuple(cache_key_kwargs.values()))
+
+ # invalidate cache if export settings have changed
+ if not hasattr(func, "__export_settings") or export_settings != func.__export_settings:
+ func.__cache = {}
+ func.__export_settings = export_settings
+ # use or fill cache
+ if cache_key in func.__cache:
+ return func.__cache[cache_key]
+ else:
+ result = func(*args)
+ func.__cache[cache_key] = result
+ return result
+ return wrapper_cached
+
+
+# TODO: replace "cached" with "unique" in all cases where the caching is functional and not only for performance reasons
+call_or_fetch = cached
+unique = cached
+
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_cameras.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_cameras.py
new file mode 100755
index 00000000..9640a7ce
--- /dev/null
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_cameras.py
@@ -0,0 +1,122 @@
+# Copyright 2018 The glTF-Blender-IO authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from . import gltf2_blender_export_keys
+from io_scene_gltf2.blender.exp.gltf2_blender_gather_cache import cached
+from io_scene_gltf2.io.com import gltf2_io
+
+import bpy
+import math
+
+
+@cached
+def gather_camera(blender_object, export_settings):
+ if not __filter_camera(blender_object, export_settings):
+ return None
+
+ return gltf2_io.Camera(
+ extensions=__gather_extensions(blender_object, export_settings),
+ extras=__gather_extras(blender_object, export_settings),
+ name=__gather_name(blender_object, export_settings),
+ orthographic=__gather_orthographic(blender_object, export_settings),
+ perspective=__gather_perspective(blender_object, export_settings),
+ type=__gather_type(blender_object, export_settings)
+ )
+
+
+def __filter_camera(blender_object, export_settings):
+ if blender_object.type != 'CAMERA':
+ return False
+ if not __gather_type(blender_object, export_settings):
+ return False
+
+ return True
+
+
+def __gather_extensions(blender_object, export_settings):
+ return None
+
+
+def __gather_extras(blender_object, export_settings):
+ return None
+
+
+def __gather_name(blender_object, export_settings):
+ return blender_object.data.name
+
+
+def __gather_orthographic(blender_object, export_settings):
+ if __gather_type(blender_object, export_settings) == "orthographic":
+ orthographic = gltf2_io.CameraOrthographic(
+ extensions=None,
+ extras=None,
+ xmag=None,
+ ymag=None,
+ zfar=None,
+ znear=None
+ )
+ blender_camera = blender_object.data
+
+ orthographic.xmag = blender_camera.ortho_scale
+ orthographic.ymag = blender_camera.ortho_scale
+
+ orthographic.znear = blender_camera.clip_start
+ orthographic.zfar = blender_camera.clip_end
+
+ return orthographic
+ return None
+
+
+def __gather_perspective(blender_object, export_settings):
+ if __gather_type(blender_object, export_settings) == "perspective":
+ perspective = gltf2_io.CameraPerspective(
+ aspect_ratio=None,
+ extensions=None,
+ extras=None,
+ yfov=None,
+ zfar=None,
+ znear=None
+ )
+ blender_camera = blender_object.data
+
+ width = bpy.context.scene.render.pixel_aspect_x * bpy.context.scene.render.resolution_x
+ height = bpy.context.scene.render.pixel_aspect_y * bpy.context.scene.render.resolution_y
+ perspective.aspectRatio = width / height
+
+ if width >= height:
+ if blender_camera.sensor_fit != 'VERTICAL':
+ perspective.yfov = 2.0 * math.atan(math.tan(blender_camera.angle * 0.5) / perspective.aspectRatio)
+ else:
+ perspective.yfov = blender_camera.angle
+ else:
+ if blender_camera.sensor_fit != 'HORIZONTAL':
+ perspective.yfov = blender_camera.angle
+ else:
+ perspective.yfov = 2.0 * math.atan(math.tan(blender_camera.angle * 0.5) / perspective.aspectRatio)
+
+ perspective.znear = blender_camera.clip_start
+ perspective.zfar = blender_camera.clip_end
+
+ return perspective
+ return None
+
+
+def __gather_type(blender_object, export_settings):
+ camera = blender_object.data
+ if camera.type == 'PERSP':
+ return "perspective"
+ elif camera.type == 'ORTHO':
+ return "orthographic"
+ return None
+
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_image.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_image.py
new file mode 100755
index 00000000..b6131a59
--- /dev/null
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_image.py
@@ -0,0 +1,177 @@
+# Copyright 2018 The glTF-Blender-IO authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import bpy
+import typing
+import os
+import numpy as np
+
+from . import gltf2_blender_export_keys
+from io_scene_gltf2.io.com import gltf2_io
+from io_scene_gltf2.blender.exp import gltf2_blender_search_node_tree
+from io_scene_gltf2.io.exp import gltf2_io_binary_data
+from io_scene_gltf2.io.exp import gltf2_io_image_data
+
+
+def gather_image(
+ blender_shader_sockets_or_texture_slots: typing.Union[typing.Tuple[bpy.types.NodeSocket],
+ typing.Tuple[bpy.types.Texture]],
+ export_settings):
+ if not __filter_image(blender_shader_sockets_or_texture_slots, export_settings):
+ return None
+ image = gltf2_io.Image(
+ buffer_view=__gather_buffer_view(blender_shader_sockets_or_texture_slots, export_settings),
+ extensions=__gather_extensions(blender_shader_sockets_or_texture_slots, export_settings),
+ extras=__gather_extras(blender_shader_sockets_or_texture_slots, export_settings),
+ mime_type=__gather_mime_type(blender_shader_sockets_or_texture_slots, export_settings),
+ name=__gather_name(blender_shader_sockets_or_texture_slots, export_settings),
+ uri=__gather_uri(blender_shader_sockets_or_texture_slots, export_settings)
+ )
+ return image
+
+
+def __filter_image(sockets_or_slots, export_settings):
+ if not sockets_or_slots:
+ return False
+ return True
+
+
+def __gather_buffer_view(sockets_or_slots, export_settings):
+ if export_settings[gltf2_blender_export_keys.FORMAT] != 'GLTF_SEPARATE':
+ image = __get_image_data(sockets_or_slots, export_settings)
+ return gltf2_io_binary_data.BinaryData(
+ data=image.to_image_data(__gather_mime_type(sockets_or_slots, export_settings)))
+ return None
+
+
+def __gather_extensions(sockets_or_slots, export_settings):
+ return None
+
+
+def __gather_extras(sockets_or_slots, export_settings):
+ return None
+
+
+def __gather_mime_type(sockets_or_slots, export_settings):
+ return 'image/png'
+ # return 'image/jpeg'
+
+
+def __gather_name(sockets_or_slots, export_settings):
+ if __is_socket(sockets_or_slots):
+ node = __get_tex_from_socket(sockets_or_slots[0])
+ if node is not None:
+ return node.shader_node.image.name
+ elif isinstance(sockets_or_slots[0], bpy.types.MaterialTextureSlot):
+ return sockets_or_slots[0].name
+ return None
+
+
+def __gather_uri(sockets_or_slots, export_settings):
+ if export_settings[gltf2_blender_export_keys.FORMAT] == 'GLTF_SEPARATE':
+ # as usual we just store the data in place instead of already resolving the references
+ return __get_image_data(sockets_or_slots, export_settings)
+ return None
+
+
+def __is_socket(sockets_or_slots):
+ return isinstance(sockets_or_slots[0], bpy.types.NodeSocket)
+
+
+def __is_slot(sockets_or_slots):
+ return isinstance(sockets_or_slots[0], bpy.types.MaterialTextureSlot)
+
+
+def __get_image_data(sockets_or_slots, export_settings):
+ # For shared ressources, such as images, we just store the portion of data that is needed in the glTF property
+ # in a helper class. During generation of the glTF in the exporter these will then be combined to actual binary
+ # ressources.
+ def split_pixels_by_channels(image: bpy.types.Image, export_settings) -> typing.List[typing.List[float]]:
+ channelcache = export_settings['gltf_channelcache']
+ if image.name in channelcache:
+ return channelcache[image.name]
+
+ pixels = np.array(image.pixels)
+ pixels = pixels.reshape((pixels.shape[0] // image.channels, image.channels))
+ channels = np.split(pixels, pixels.shape[1], axis=1)
+
+ channelcache[image.name] = channels
+
+ return channels
+
+ if __is_socket(sockets_or_slots):
+ results = [__get_tex_from_socket(socket) for socket in sockets_or_slots]
+ image = None
+ for result, socket in zip(results, sockets_or_slots):
+ # rudimentarily try follow the node tree to find the correct image data.
+ channel = None
+ for elem in result.path:
+ if isinstance(elem.from_node, bpy.types.ShaderNodeSeparateRGB):
+ channel = {
+ 'R': 0,
+ 'G': 1,
+ 'B': 2
+ }[elem.from_socket.name]
+
+ if channel is not None:
+ pixels = [split_pixels_by_channels(result.shader_node.image, export_settings)[channel]]
+ else:
+ pixels = split_pixels_by_channels(result.shader_node.image, export_settings)
+ channel = 0
+
+ file_name = os.path.splitext(result.shader_node.image.name)[0]
+
+ image_data = gltf2_io_image_data.ImageData(
+ file_name,
+ result.shader_node.image.filepath,
+ result.shader_node.image.size[0],
+ result.shader_node.image.size[1],
+ channel,
+ pixels)
+
+ if image is None:
+ image = image_data
+ else:
+ image.add_to_image(channel, image_data)
+
+ return image
+ elif __is_slot(sockets_or_slots):
+ texture = __get_tex_from_slot(sockets_or_slots[0])
+ pixels = split_pixels_by_channels(texture.image, export_settings)
+
+ image_data = gltf2_io_image_data.ImageData(
+ texture.name,
+ texture.image.filepath,
+ texture.image.size[0],
+ texture.image.size[1],
+ 0,
+ pixels)
+ return image_data
+ else:
+ # Texture slots
+ raise NotImplementedError()
+
+
+def __get_tex_from_socket(blender_shader_socket: bpy.types.NodeSocket):
+ result = gltf2_blender_search_node_tree.from_socket(
+ blender_shader_socket,
+ gltf2_blender_search_node_tree.FilterByType(bpy.types.ShaderNodeTexImage))
+ if not result:
+ return None
+ return result[0]
+
+
+def __get_tex_from_slot(blender_texture_slot):
+ return blender_texture_slot.texture
+
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_joints.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_joints.py
new file mode 100755
index 00000000..38e47031
--- /dev/null
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_joints.py
@@ -0,0 +1,80 @@
+# Copyright 2018 The glTF-Blender-IO authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import mathutils
+
+from . import gltf2_blender_export_keys
+from io_scene_gltf2.blender.exp.gltf2_blender_gather_cache import cached
+from io_scene_gltf2.io.com import gltf2_io
+from io_scene_gltf2.io.com import gltf2_io_debug
+from io_scene_gltf2.blender.exp import gltf2_blender_extract
+from io_scene_gltf2.blender.com import gltf2_blender_math
+
+
+@cached
+def gather_joint(blender_bone, export_settings):
+ """
+ Generate a glTF2 node from a blender bone, as joints in glTF2 are simply nodes.
+
+ :param blender_bone: a blender PoseBone
+ :param export_settings: the settings for this export
+ :return: a glTF2 node (acting as a joint)
+ """
+ axis_basis_change = mathutils.Matrix.Identity(4)
+ if export_settings[gltf2_blender_export_keys.YUP]:
+ axis_basis_change = mathutils.Matrix(
+ ((1.0, 0.0, 0.0, 0.0), (0.0, 0.0, 1.0, 0.0), (0.0, -1.0, 0.0, 0.0), (0.0, 0.0, 0.0, 1.0)))
+
+ # extract bone transform
+ if blender_bone.parent is None:
+ correction_matrix_local = gltf2_blender_math.multiply(axis_basis_change, blender_bone.bone.matrix_local)
+ else:
+ correction_matrix_local = gltf2_blender_math.multiply(
+ blender_bone.parent.bone.matrix_local.inverted(), blender_bone.bone.matrix_local)
+ matrix_basis = blender_bone.matrix_basis
+ if export_settings[gltf2_blender_export_keys.BAKE_SKINS]:
+ gltf2_io_debug.print_console("WARNING", "glTF bake skins not supported")
+ # matrix_basis = blender_object.convert_space(blender_bone, blender_bone.matrix, from_space='POSE',
+ # to_space='LOCAL')
+ trans, rot, sca = gltf2_blender_extract.decompose_transition(
+ gltf2_blender_math.multiply(correction_matrix_local, matrix_basis), 'JOINT', export_settings)
+ translation, rotation, scale = (None, None, None)
+ if trans[0] != 0.0 or trans[1] != 0.0 or trans[2] != 0.0:
+ translation = [trans[0], trans[1], trans[2]]
+ if rot[0] != 0.0 or rot[1] != 0.0 or rot[2] != 0.0 or rot[3] != 1.0:
+ rotation = [rot[0], rot[1], rot[2], rot[3]]
+ if sca[0] != 1.0 or sca[1] != 1.0 or sca[2] != 1.0:
+ scale = [sca[0], sca[1], sca[2]]
+
+ # traverse into children
+ children = []
+ for bone in blender_bone.children:
+ children.append(gather_joint(bone, export_settings))
+
+ # finally add to the joints array containing all the joints in the hierarchy
+ return gltf2_io.Node(
+ camera=None,
+ children=children,
+ extensions=None,
+ extras=None,
+ matrix=None,
+ mesh=None,
+ name=blender_bone.name,
+ rotation=rotation,
+ scale=scale,
+ skin=None,
+ translation=translation,
+ weights=None
+ )
+
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_light_spots.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_light_spots.py
new file mode 100644
index 00000000..b2ab3953
--- /dev/null
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_light_spots.py
@@ -0,0 +1,45 @@
+# Copyright 2018 The glTF-Blender-IO authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from typing import Optional
+from io_scene_gltf2.io.com import gltf2_io_lights_punctual
+
+
+def gather_light_spot(blender_lamp, export_settings) -> Optional[gltf2_io_lights_punctual.LightSpot]:
+
+ if not __filter_light_spot(blender_lamp, export_settings):
+ return None
+
+ spot = gltf2_io_lights_punctual.LightSpot(
+ inner_cone_angle=__gather_inner_cone_angle(blender_lamp, export_settings),
+ outer_cone_angle=__gather_outer_cone_angle(blender_lamp, export_settings)
+ )
+ return spot
+
+
+def __filter_light_spot(blender_lamp, _) -> bool:
+ if blender_lamp.type != "SPOT":
+ return False
+
+ return True
+
+
+def __gather_inner_cone_angle(blender_lamp, _) -> Optional[float]:
+ angle = blender_lamp.spot_size * 0.5
+ return angle - angle * blender_lamp.spot_blend
+
+
+def __gather_outer_cone_angle(blender_lamp, _) -> Optional[float]:
+ return blender_lamp.spot_size * 0.5
+
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_lights.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_lights.py
new file mode 100644
index 00000000..108656ea
--- /dev/null
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_lights.py
@@ -0,0 +1,132 @@
+# Copyright 2018 The glTF-Blender-IO authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import bpy
+import math
+from typing import Optional, List, Dict, Any
+
+from io_scene_gltf2.blender.exp.gltf2_blender_gather_cache import cached
+
+from io_scene_gltf2.io.com import gltf2_io_lights_punctual
+from io_scene_gltf2.io.com import gltf2_io_debug
+
+from io_scene_gltf2.blender.exp import gltf2_blender_gather_light_spots
+from io_scene_gltf2.blender.exp import gltf2_blender_search_node_tree
+
+
+@cached
+def gather_lights_punctual(blender_lamp, export_settings) -> Optional[Dict[str, Any]]:
+ if not __filter_lights_punctual(blender_lamp, export_settings):
+ return None
+
+ light = gltf2_io_lights_punctual.Light(
+ color=__gather_color(blender_lamp, export_settings),
+ intensity=__gather_intensity(blender_lamp, export_settings),
+ spot=__gather_spot(blender_lamp, export_settings),
+ type=__gather_type(blender_lamp, export_settings),
+ range=__gather_range(blender_lamp, export_settings),
+ name=__gather_name(blender_lamp, export_settings),
+ extensions=__gather_extensions(blender_lamp, export_settings),
+ extras=__gather_extras(blender_lamp, export_settings)
+ )
+
+ return light.to_dict()
+
+
+def __filter_lights_punctual(blender_lamp, export_settings) -> bool:
+ if blender_lamp.type in ["HEMI", "AREA"]:
+ gltf2_io_debug.print_console("WARNING", "Unsupported light source {}".format(blender_lamp.type))
+ return False
+
+ return True
+
+
+def __gather_color(blender_lamp, export_settings) -> Optional[List[float]]:
+ emission_node = __get_cycles_emission_node(blender_lamp)
+ if emission_node is not None:
+ return emission_node.inputs["Color"].default_value
+
+ return list(blender_lamp.color)
+
+
+def __gather_intensity(blender_lamp, _) -> Optional[float]:
+ emission_node = __get_cycles_emission_node(blender_lamp)
+ if emission_node is not None:
+ if blender_lamp.type != 'SUN':
+ # When using cycles, the strength should be influenced by a LightFalloff node
+ result = gltf2_blender_search_node_tree.from_socket(
+ emission_node.get("Strength"),
+ gltf2_blender_search_node_tree.FilterByType(bpy.types.ShaderNodeLightFalloff)
+ )
+ if result:
+ quadratic_falloff_node = result[0].shader_node
+ emission_strength = quadratic_falloff_node.inputs["Strength"].default_value / (math.pi * 4.0)
+ else:
+ gltf2_io_debug.print_console('WARNING',
+ 'No quadratic light falloff node attached to emission strength property')
+ emission_strength = blender_lamp.energy
+ else:
+ emission_strength = emission_node.inputs["Strength"].default_value
+ return emission_strength
+
+ return blender_lamp.energy
+
+
+def __gather_spot(blender_lamp, export_settings) -> Optional[gltf2_io_lights_punctual.LightSpot]:
+ if blender_lamp.type == "SPOT":
+ return gltf2_blender_gather_light_spots.gather_light_spot(blender_lamp, export_settings)
+ return None
+
+
+def __gather_type(blender_lamp, _) -> str:
+ return {
+ "POINT": "point",
+ "SUN": "directional",
+ "SPOT": "spot"
+ }[blender_lamp.type]
+
+
+def __gather_range(blender_lamp, export_settings) -> Optional[float]:
+ # TODO: calculate range from
+ # https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_lights_punctual#range-property
+ return None
+
+
+def __gather_name(blender_lamp, export_settings) -> Optional[str]:
+ return blender_lamp.name
+
+
+def __gather_extensions(blender_lamp, export_settings) -> Optional[dict]:
+ return None
+
+
+def __gather_extras(blender_lamp, export_settings) -> Optional[Any]:
+ return None
+
+
+def __get_cycles_emission_node(blender_lamp) -> Optional[bpy.types.ShaderNodeEmission]:
+ if blender_lamp.use_nodes and blender_lamp.node_tree:
+ for currentNode in blender_lamp.node_tree.nodes:
+ if isinstance(currentNode, bpy.types.ShaderNodeOutputLamp):
+ if not currentNode.is_active_output:
+ continue
+ result = gltf2_blender_search_node_tree.from_socket(
+ currentNode.inputs.get("Surface"),
+ gltf2_blender_search_node_tree.FilterByType(bpy.types.ShaderNodeEmission)
+ )
+ if not result:
+ continue
+ return result[0].shader_node
+ return None
+
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_material_normal_texture_info_class.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_material_normal_texture_info_class.py
new file mode 100755
index 00000000..3d78a478
--- /dev/null
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_material_normal_texture_info_class.py
@@ -0,0 +1,131 @@
+# Copyright 2018 The glTF-Blender-IO authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import bpy
+import typing
+from io_scene_gltf2.blender.exp.gltf2_blender_gather_cache import cached
+from io_scene_gltf2.io.com import gltf2_io
+from io_scene_gltf2.blender.exp import gltf2_blender_gather_texture
+from io_scene_gltf2.blender.exp import gltf2_blender_search_node_tree
+from io_scene_gltf2.blender.exp import gltf2_blender_export_keys
+from io_scene_gltf2.blender.exp import gltf2_blender_get
+from io_scene_gltf2.io.com.gltf2_io_extensions import Extension
+
+
+@cached
+def gather_material_normal_texture_info_class(blender_shader_sockets_or_texture_slots: typing.Union[
+ typing.Tuple[bpy.types.NodeSocket], typing.Tuple[bpy.types.Texture]],
+ export_settings):
+ if not __filter_texture_info(blender_shader_sockets_or_texture_slots, export_settings):
+ return None
+
+ texture_info = gltf2_io.MaterialNormalTextureInfoClass(
+ extensions=__gather_extensions(blender_shader_sockets_or_texture_slots, export_settings),
+ extras=__gather_extras(blender_shader_sockets_or_texture_slots, export_settings),
+ scale=__gather_scale(blender_shader_sockets_or_texture_slots, export_settings),
+ index=__gather_index(blender_shader_sockets_or_texture_slots, export_settings),
+ tex_coord=__gather_tex_coord(blender_shader_sockets_or_texture_slots, export_settings)
+ )
+
+ return texture_info
+
+
+def __filter_texture_info(blender_shader_sockets_or_texture_slots, export_settings):
+ if not blender_shader_sockets_or_texture_slots:
+ return False
+ if not all([elem is not None for elem in blender_shader_sockets_or_texture_slots]):
+ return False
+ if isinstance(blender_shader_sockets_or_texture_slots[0], bpy.types.NodeSocket):
+ if any([__get_tex_from_socket(socket) is None for socket in blender_shader_sockets_or_texture_slots]):
+ # sockets do not lead to a texture --> discard
+ return False
+ return True
+
+
+def __gather_extensions(blender_shader_sockets_or_texture_slots, export_settings):
+ if not export_settings[gltf2_blender_export_keys.TEXTURE_TRANSFORM]:
+ return None
+
+ normal_map_node = blender_shader_sockets_or_texture_slots[0].links[0].from_node
+ if not isinstance(normal_map_node, bpy.types.ShaderNodeNormalMap):
+ return None
+
+ texture_socket = normal_map_node.inputs["Color"]
+ if len(texture_socket.links) == 0:
+ return None
+
+ texture_node = texture_socket.links[0].from_node
+ texture_transform = gltf2_blender_get.get_texture_transform_from_texture_node(texture_node)
+
+ extension = Extension("KHR_texture_transform", texture_transform)
+ return {"KHR_texture_transform": extension}
+
+
+def __gather_extras(blender_shader_sockets_or_texture_slots, export_settings):
+ return None
+
+
+def __gather_scale(blender_shader_sockets_or_texture_slots, export_settings):
+ return None
+
+
+def __gather_index(blender_shader_sockets_or_texture_slots, export_settings):
+ # We just put the actual shader into the 'index' member
+ return gltf2_blender_gather_texture.gather_texture(blender_shader_sockets_or_texture_slots, export_settings)
+
+
+def __gather_tex_coord(blender_shader_sockets_or_texture_slots, export_settings):
+ if __is_socket(blender_shader_sockets_or_texture_slots):
+ blender_shader_node = __get_tex_from_socket(blender_shader_sockets_or_texture_slots[0]).shader_node
+ if len(blender_shader_node.inputs['Vector'].links) == 0:
+ return 0
+
+ input_node = blender_shader_node.inputs['Vector'].links[0].from_node
+
+ if isinstance(input_node, bpy.types.ShaderNodeMapping):
+
+ if len(input_node.inputs['Vector'].links) == 0:
+ return 0
+
+ input_node = input_node.inputs['Vector'].links[0].from_node
+
+ if not isinstance(input_node, bpy.types.ShaderNodeUVMap):
+ return 0
+
+ if input_node.uv_map == '':
+ return 0
+
+ # Try to gather map index.
+ for blender_mesh in bpy.data.meshes:
+ texCoordIndex = blender_mesh.uv_layers.find(input_node.uv_map)
+ if texCoordIndex >= 0:
+ return texCoordIndex
+
+ return 0
+ else:
+ raise NotImplementedError()
+
+
+def __is_socket(sockets_or_slots):
+ return isinstance(sockets_or_slots[0], bpy.types.NodeSocket)
+
+
+def __get_tex_from_socket(socket):
+ result = gltf2_blender_search_node_tree.from_socket(
+ socket,
+ gltf2_blender_search_node_tree.FilterByType(bpy.types.ShaderNodeTexImage))
+ if not result:
+ return None
+ return result[0]
+
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_material_occlusion_texture_info_class.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_material_occlusion_texture_info_class.py
new file mode 100755
index 00000000..c9c70e42
--- /dev/null
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_material_occlusion_texture_info_class.py
@@ -0,0 +1,113 @@
+# Copyright 2018 The glTF-Blender-IO authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import bpy
+import typing
+from io_scene_gltf2.blender.exp.gltf2_blender_gather_cache import cached
+from io_scene_gltf2.io.com import gltf2_io
+from io_scene_gltf2.blender.exp import gltf2_blender_gather_texture
+from io_scene_gltf2.blender.exp import gltf2_blender_search_node_tree
+
+
+@cached
+def gather_material_occlusion_texture_info_class(blender_shader_sockets_or_texture_slots: typing.Union[
+ typing.Tuple[bpy.types.NodeSocket], typing.Tuple[bpy.types.Texture]],
+ export_settings):
+ if not __filter_texture_info(blender_shader_sockets_or_texture_slots, export_settings):
+ return None
+
+ texture_info = gltf2_io.MaterialOcclusionTextureInfoClass(
+ extensions=__gather_extensions(blender_shader_sockets_or_texture_slots, export_settings),
+ extras=__gather_extras(blender_shader_sockets_or_texture_slots, export_settings),
+ strength=__gather_scale(blender_shader_sockets_or_texture_slots, export_settings),
+ index=__gather_index(blender_shader_sockets_or_texture_slots, export_settings),
+ tex_coord=__gather_tex_coord(blender_shader_sockets_or_texture_slots, export_settings)
+ )
+
+ return texture_info
+
+
+def __filter_texture_info(blender_shader_sockets_or_texture_slots, export_settings):
+ if not blender_shader_sockets_or_texture_slots:
+ return False
+ if not all([elem is not None for elem in blender_shader_sockets_or_texture_slots]):
+ return False
+ if isinstance(blender_shader_sockets_or_texture_slots[0], bpy.types.NodeSocket):
+ if any([__get_tex_from_socket(socket) is None for socket in blender_shader_sockets_or_texture_slots]):
+ # sockets do not lead to a texture --> discard
+ return False
+ return True
+
+
+def __gather_extensions(blender_shader_sockets_or_texture_slots, export_settings):
+ return None
+
+
+def __gather_extras(blender_shader_sockets_or_texture_slots, export_settings):
+ return None
+
+
+def __gather_scale(blender_shader_sockets_or_texture_slots, export_settings):
+ return None
+
+
+def __gather_index(blender_shader_sockets_or_texture_slots, export_settings):
+ # We just put the actual shader into the 'index' member
+ return gltf2_blender_gather_texture.gather_texture(blender_shader_sockets_or_texture_slots, export_settings)
+
+
+def __gather_tex_coord(blender_shader_sockets_or_texture_slots, export_settings):
+ if __is_socket(blender_shader_sockets_or_texture_slots):
+ blender_shader_node = __get_tex_from_socket(blender_shader_sockets_or_texture_slots[0]).shader_node
+ if len(blender_shader_node.inputs['Vector'].links) == 0:
+ return 0
+
+ input_node = blender_shader_node.inputs['Vector'].links[0].from_node
+
+ if isinstance(input_node, bpy.types.ShaderNodeMapping):
+
+ if len(input_node.inputs['Vector'].links) == 0:
+ return 0
+
+ input_node = input_node.inputs['Vector'].links[0].from_node
+
+ if not isinstance(input_node, bpy.types.ShaderNodeUVMap):
+ return 0
+
+ if input_node.uv_map == '':
+ return 0
+
+ # Try to gather map index.
+ for blender_mesh in bpy.data.meshes:
+ texCoordIndex = blender_mesh.uv_layers.find(input_node.uv_map)
+ if texCoordIndex >= 0:
+ return texCoordIndex
+
+ return 0
+ else:
+ raise NotImplementedError()
+
+
+def __is_socket(sockets_or_slots):
+ return isinstance(sockets_or_slots[0], bpy.types.NodeSocket)
+
+
+def __get_tex_from_socket(socket):
+ result = gltf2_blender_search_node_tree.from_socket(
+ socket,
+ gltf2_blender_search_node_tree.FilterByType(bpy.types.ShaderNodeTexImage))
+ if not result:
+ return None
+ return result[0]
+
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials.py
new file mode 100755
index 00000000..427f07ce
--- /dev/null
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials.py
@@ -0,0 +1,154 @@
+# Copyright 2018 The glTF-Blender-IO authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import bpy
+
+from io_scene_gltf2.blender.exp.gltf2_blender_gather_cache import cached
+from io_scene_gltf2.io.com import gltf2_io
+from io_scene_gltf2.blender.exp import gltf2_blender_gather_texture_info
+from io_scene_gltf2.blender.exp import gltf2_blender_gather_material_normal_texture_info_class
+from io_scene_gltf2.blender.exp import gltf2_blender_gather_material_occlusion_texture_info_class
+
+from io_scene_gltf2.blender.exp import gltf2_blender_gather_materials_pbr_metallic_roughness
+from io_scene_gltf2.blender.exp import gltf2_blender_generate_extras
+from io_scene_gltf2.blender.exp import gltf2_blender_get
+
+
+@cached
+def gather_material(blender_material, export_settings):
+ """
+ Gather the material used by the blender primitive.
+
+ :param blender_material: the blender material used in the glTF primitive
+ :param export_settings:
+ :return: a glTF material
+ """
+ if not __filter_material(blender_material, export_settings):
+ return None
+
+ material = gltf2_io.Material(
+ alpha_cutoff=__gather_alpha_cutoff(blender_material, export_settings),
+ alpha_mode=__gather_alpha_mode(blender_material, export_settings),
+ double_sided=__gather_double_sided(blender_material, export_settings),
+ emissive_factor=__gather_emissive_factor(blender_material, export_settings),
+ emissive_texture=__gather_emissive_texture(blender_material, export_settings),
+ extensions=__gather_extensions(blender_material, export_settings),
+ extras=__gather_extras(blender_material, export_settings),
+ name=__gather_name(blender_material, export_settings),
+ normal_texture=__gather_normal_texture(blender_material, export_settings),
+ occlusion_texture=__gather_occlusion_texture(blender_material, export_settings),
+ pbr_metallic_roughness=__gather_pbr_metallic_roughness(blender_material, export_settings)
+ )
+
+ return material
+ # material = blender_primitive['material']
+ #
+ # if get_material_requires_texcoords(glTF, material) and not export_settings['gltf_texcoords']:
+ # material = -1
+ #
+ # if get_material_requires_normals(glTF, material) and not export_settings['gltf_normals']:
+ # material = -1
+ #
+ # # Meshes/primitives without material are allowed.
+ # if material >= 0:
+ # primitive.material = material
+ # else:
+ # print_console('WARNING', 'Material ' + internal_primitive[
+ # 'material'] + ' not found. Please assign glTF 2.0 material or enable Blinn-Phong material in export.')
+
+
+def __filter_material(blender_material, export_settings):
+ # if not blender_material.use_nodes:
+ # return False
+ # if not blender_material.node_tree:
+ # return False
+ return True
+
+
+def __gather_alpha_cutoff(blender_material, export_settings):
+ if blender_material.blend_method == 'CLIP':
+ return blender_material.alpha_threshold
+ return None
+
+
+def __gather_alpha_mode(blender_material, export_settings):
+ if blender_material.blend_method == 'CLIP':
+ return 'MASK'
+ elif blender_material.blend_method == 'BLEND':
+ return 'BLEND'
+ return None
+
+
+def __gather_double_sided(blender_material, export_settings):
+ return None
+
+
+def __gather_emissive_factor(blender_material, export_settings):
+ emissive_socket = gltf2_blender_get.get_socket_or_texture_slot(blender_material, "Emissive")
+ if emissive_socket is None:
+ emissive_socket = gltf2_blender_get.get_socket_or_texture_slot_old(blender_material, "EmissiveFactor")
+ if isinstance(emissive_socket, bpy.types.NodeSocket) and not emissive_socket.is_linked:
+ return list(emissive_socket.default_value)[0:3]
+ return None
+
+
+def __gather_emissive_texture(blender_material, export_settings):
+ emissive = gltf2_blender_get.get_socket_or_texture_slot(blender_material, "Emissive")
+ if emissive is None:
+ emissive = gltf2_blender_get.get_socket_or_texture_slot_old(blender_material, "Emissive")
+ return gltf2_blender_gather_texture_info.gather_texture_info((emissive,), export_settings)
+
+
+def __gather_extensions(blender_material, export_settings):
+ extensions = {}
+
+
+ # TODO specular glossiness extension
+
+ return extensions if extensions else None
+
+
+def __gather_extras(blender_material, export_settings):
+ if export_settings['gltf_extras']:
+ return gltf2_blender_generate_extras.generate_extras(blender_material)
+ return None
+
+
+def __gather_name(blender_material, export_settings):
+ return blender_material.name
+
+
+def __gather_normal_texture(blender_material, export_settings):
+ normal = gltf2_blender_get.get_socket_or_texture_slot(blender_material, "Normal")
+ if normal is None:
+ normal = gltf2_blender_get.get_socket_or_texture_slot_old(blender_material, "Normal")
+ return gltf2_blender_gather_material_normal_texture_info_class.gather_material_normal_texture_info_class(
+ (normal,),
+ export_settings)
+
+
+def __gather_occlusion_texture(blender_material, export_settings):
+ occlusion = gltf2_blender_get.get_socket_or_texture_slot(blender_material, "Occlusion")
+ if occlusion is None:
+ occlusion = gltf2_blender_get.get_socket_or_texture_slot_old(blender_material, "Occlusion")
+ return gltf2_blender_gather_material_occlusion_texture_info_class.gather_material_occlusion_texture_info_class(
+ (occlusion,),
+ export_settings)
+
+
+def __gather_pbr_metallic_roughness(blender_material, export_settings):
+ return gltf2_blender_gather_materials_pbr_metallic_roughness.gather_material_pbr_metallic_roughness(
+ blender_material,
+ export_settings)
+
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_pbr_metallic_roughness.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_pbr_metallic_roughness.py
new file mode 100755
index 00000000..2a6315bf
--- /dev/null
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_pbr_metallic_roughness.py
@@ -0,0 +1,103 @@
+# Copyright 2018 The glTF-Blender-IO authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import bpy
+
+from io_scene_gltf2.io.com import gltf2_io
+from io_scene_gltf2.blender.exp import gltf2_blender_gather_texture_info
+from io_scene_gltf2.blender.exp import gltf2_blender_get
+from io_scene_gltf2.blender.exp.gltf2_blender_gather_cache import cached
+
+
+@cached
+def gather_material_pbr_metallic_roughness(blender_material, export_settings):
+ if not __filter_pbr_material(blender_material, export_settings):
+ return None
+
+ material = gltf2_io.MaterialPBRMetallicRoughness(
+ base_color_factor=__gather_base_color_factor(blender_material, export_settings),
+ base_color_texture=__gather_base_color_texture(blender_material, export_settings),
+ extensions=__gather_extensions(blender_material, export_settings),
+ extras=__gather_extras(blender_material, export_settings),
+ metallic_factor=__gather_metallic_factor(blender_material, export_settings),
+ metallic_roughness_texture=__gather_metallic_roughness_texture(blender_material, export_settings),
+ roughness_factor=__gather_roughness_factor(blender_material, export_settings)
+ )
+
+ return material
+
+
+def __filter_pbr_material(blender_material, export_settings):
+ return True
+
+
+def __gather_base_color_factor(blender_material, export_settings):
+ base_color_socket = gltf2_blender_get.get_socket_or_texture_slot(blender_material, "Base Color")
+ if base_color_socket is None:
+ base_color_socket = gltf2_blender_get.get_socket_or_texture_slot(blender_material, "BaseColor")
+ if base_color_socket is None:
+ base_color_socket = gltf2_blender_get.get_socket_or_texture_slot_old(blender_material, "BaseColorFactor")
+ if isinstance(base_color_socket, bpy.types.NodeSocket) and not base_color_socket.is_linked:
+ return list(base_color_socket.default_value)
+ return None
+
+def __gather_base_color_texture(blender_material, export_settings):
+ base_color_socket = gltf2_blender_get.get_socket_or_texture_slot(blender_material, "Base Color")
+ if base_color_socket is None:
+ base_color_socket = gltf2_blender_get.get_socket_or_texture_slot(blender_material, "BaseColor")
+ if base_color_socket is None:
+ base_color_socket = gltf2_blender_get.get_socket_or_texture_slot_old(blender_material, "BaseColor")
+ return gltf2_blender_gather_texture_info.gather_texture_info((base_color_socket,), export_settings)
+
+
+def __gather_extensions(blender_material, export_settings):
+ return None
+
+
+def __gather_extras(blender_material, export_settings):
+ return None
+
+
+def __gather_metallic_factor(blender_material, export_settings):
+ metallic_socket = gltf2_blender_get.get_socket_or_texture_slot(blender_material, "Metallic")
+ if metallic_socket is None:
+ metallic_socket = gltf2_blender_get.get_socket_or_texture_slot_old(blender_material, "MetallicFactor")
+ if isinstance(metallic_socket, bpy.types.NodeSocket) and not metallic_socket.is_linked:
+ return metallic_socket.default_value
+ return None
+
+
+def __gather_metallic_roughness_texture(blender_material, export_settings):
+ metallic_socket = gltf2_blender_get.get_socket_or_texture_slot(blender_material, "Metallic")
+ roughness_socket = gltf2_blender_get.get_socket_or_texture_slot(blender_material, "Roughness")
+
+ if metallic_socket is None and roughness_socket is None:
+ metallic_roughness = gltf2_blender_get.get_socket_or_texture_slot(blender_material, "MetallicRoughness")
+ if metallic_roughness is None:
+ metallic_roughness = gltf2_blender_get.get_socket_or_texture_slot_old(blender_material, "MetallicRoughness")
+ texture_input = (metallic_roughness,)
+ else:
+ texture_input = (metallic_socket, roughness_socket)
+
+ return gltf2_blender_gather_texture_info.gather_texture_info(texture_input, export_settings)
+
+
+def __gather_roughness_factor(blender_material, export_settings):
+ roughness_socket = gltf2_blender_get.get_socket_or_texture_slot(blender_material, "Roughness")
+ if roughness_socket is None:
+ roughness_socket = gltf2_blender_get.get_socket_or_texture_slot_old(blender_material, "RoughnessFactor")
+ if isinstance(roughness_socket, bpy.types.NodeSocket) and not roughness_socket.is_linked:
+ return roughness_socket.default_value
+ return None
+
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_mesh.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_mesh.py
new file mode 100755
index 00000000..f32eb733
--- /dev/null
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_mesh.py
@@ -0,0 +1,94 @@
+# Copyright 2018 The glTF-Blender-IO authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import bpy
+from typing import Optional, Dict, List, Any
+from io_scene_gltf2.blender.exp.gltf2_blender_gather_cache import cached
+from io_scene_gltf2.io.com import gltf2_io
+from io_scene_gltf2.blender.exp import gltf2_blender_gather_primitives
+from io_scene_gltf2.blender.exp import gltf2_blender_generate_extras
+
+
+@cached
+def gather_mesh(blender_mesh: bpy.types.Mesh,
+ vertex_groups: Optional[bpy.types.VertexGroups],
+ modifiers: Optional[bpy.types.ObjectModifiers],
+ skip_filter: bool,
+ export_settings
+ ) -> Optional[gltf2_io.Mesh]:
+ if not skip_filter and not __filter_mesh(blender_mesh, vertex_groups, modifiers, export_settings):
+ return None
+
+ mesh = gltf2_io.Mesh(
+ extensions=__gather_extensions(blender_mesh, vertex_groups, modifiers, export_settings),
+ extras=__gather_extras(blender_mesh, vertex_groups, modifiers, export_settings),
+ name=__gather_name(blender_mesh, vertex_groups, modifiers, export_settings),
+ primitives=__gather_primitives(blender_mesh, vertex_groups, modifiers, export_settings),
+ weights=__gather_weights(blender_mesh, vertex_groups, modifiers, export_settings)
+ )
+
+ return mesh
+
+
+def __filter_mesh(blender_mesh: bpy.types.Mesh,
+ vertex_groups: Optional[bpy.types.VertexGroups],
+ modifiers: Optional[bpy.types.ObjectModifiers],
+ export_settings
+ ) -> bool:
+ if blender_mesh.users == 0:
+ return False
+ return True
+
+
+def __gather_extensions(blender_mesh: bpy.types.Mesh,
+ vertex_groups: Optional[bpy.types.VertexGroups],
+ modifiers: Optional[bpy.types.ObjectModifiers],
+ export_settings
+ ) -> Any:
+ return None
+
+
+def __gather_extras(blender_mesh: bpy.types.Mesh,
+ vertex_groups: Optional[bpy.types.VertexGroups],
+ modifiers: Optional[bpy.types.ObjectModifiers],
+ export_settings
+ ) -> Optional[Dict[Any, Any]]:
+ if export_settings['gltf_extras']:
+ return gltf2_blender_generate_extras.generate_extras(blender_mesh)
+ return None
+
+
+def __gather_name(blender_mesh: bpy.types.Mesh,
+ vertex_groups: Optional[bpy.types.VertexGroups],
+ modifiers: Optional[bpy.types.ObjectModifiers],
+ export_settings
+ ) -> str:
+ return blender_mesh.name
+
+
+def __gather_primitives(blender_mesh: bpy.types.Mesh,
+ vertex_groups: Optional[bpy.types.VertexGroups],
+ modifiers: Optional[bpy.types.ObjectModifiers],
+ export_settings
+ ) -> List[gltf2_io.MeshPrimitive]:
+ return gltf2_blender_gather_primitives.gather_primitives(blender_mesh, vertex_groups, modifiers, export_settings)
+
+
+def __gather_weights(blender_mesh: bpy.types.Mesh,
+ vertex_groups: Optional[bpy.types.VertexGroups],
+ modifiers: Optional[bpy.types.ObjectModifiers],
+ export_settings
+ ) -> Optional[List[float]]:
+ return None
+
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_nodes.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_nodes.py
new file mode 100755
index 00000000..f6533c14
--- /dev/null
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_nodes.py
@@ -0,0 +1,240 @@
+# Copyright 2018 The glTF-Blender-IO authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import math
+import bpy
+from mathutils import Quaternion
+
+from . import gltf2_blender_export_keys
+from io_scene_gltf2.blender.com import gltf2_blender_math
+from io_scene_gltf2.blender.exp.gltf2_blender_gather_cache import cached
+from io_scene_gltf2.blender.exp import gltf2_blender_gather_skins
+from io_scene_gltf2.blender.exp import gltf2_blender_gather_cameras
+from io_scene_gltf2.blender.exp import gltf2_blender_gather_mesh
+from io_scene_gltf2.blender.exp import gltf2_blender_gather_joints
+from io_scene_gltf2.blender.exp import gltf2_blender_extract
+from io_scene_gltf2.blender.exp import gltf2_blender_gather_lights
+from io_scene_gltf2.blender.exp import gltf2_blender_generate_extras
+from io_scene_gltf2.io.com import gltf2_io
+from io_scene_gltf2.io.com import gltf2_io_extensions
+
+
+@cached
+def gather_node(blender_object, export_settings):
+ if not __filter_node(blender_object, export_settings):
+ return None
+
+ node = gltf2_io.Node(
+ camera=__gather_camera(blender_object, export_settings),
+ children=__gather_children(blender_object, export_settings),
+ extensions=__gather_extensions(blender_object, export_settings),
+ extras=__gather_extras(blender_object, export_settings),
+ matrix=__gather_matrix(blender_object, export_settings),
+ mesh=__gather_mesh(blender_object, export_settings),
+ name=__gather_name(blender_object, export_settings),
+ rotation=None,
+ scale=None,
+ skin=__gather_skin(blender_object, export_settings),
+ translation=None,
+ weights=__gather_weights(blender_object, export_settings)
+ )
+ node.translation, node.rotation, node.scale = __gather_trans_rot_scale(blender_object, export_settings)
+
+ if export_settings[gltf2_blender_export_keys.YUP]:
+ if blender_object.type == 'LIGHT' and export_settings[gltf2_blender_export_keys.LIGHTS]:
+ correction_node = __get_correction_node(blender_object, export_settings)
+ correction_node.extensions = {"KHR_lights_punctual": node.extensions["KHR_lights_punctual"]}
+ del node.extensions["KHR_lights_punctual"]
+ node.children.append(correction_node)
+ if blender_object.type == 'CAMERA' and export_settings[gltf2_blender_export_keys.CAMERAS]:
+ correction_node = __get_correction_node(blender_object, export_settings)
+ correction_node.camera = node.camera
+ node.children.append(correction_node)
+ node.camera = None
+
+ return node
+
+
+def __filter_node(blender_object, export_settings):
+ if blender_object.users == 0:
+ return False
+ if export_settings[gltf2_blender_export_keys.SELECTED] and blender_object.select_get() is False:
+ return False
+ if not export_settings[gltf2_blender_export_keys.LAYERS] and not blender_object.layers[0]:
+ return False
+ if blender_object.instance_collection is not None and not blender_object.instance_collection.layers[0]:
+ return False
+
+ return True
+
+
+def __gather_camera(blender_object, export_settings):
+ return gltf2_blender_gather_cameras.gather_camera(blender_object, export_settings)
+
+
+def __gather_children(blender_object, export_settings):
+ children = []
+ # standard children
+ for child_object in blender_object.children:
+ node = gather_node(child_object, export_settings)
+ if node is not None:
+ children.append(node)
+ # blender dupli objects
+ if blender_object.instance_type == 'COLLECTION' and blender_object.instance_collection:
+ for dupli_object in blender_object.instance_collection.objects:
+ node = gather_node(dupli_object, export_settings)
+ if node is not None:
+ children.append(node)
+
+ # blender bones
+ if blender_object.type == "ARMATURE":
+ for blender_bone in blender_object.pose.bones:
+ if not blender_bone.parent:
+ children.append(gltf2_blender_gather_joints.gather_joint(blender_bone, export_settings))
+
+ return children
+
+
+def __gather_extensions(blender_object, export_settings):
+ extensions = {}
+
+ if export_settings["gltf_lights"] and (blender_object.type == "LAMP" or blender_object.type == "LIGHT"):
+ blender_lamp = blender_object.data
+ light = gltf2_blender_gather_lights.gather_lights_punctual(
+ blender_lamp,
+ export_settings
+ )
+ if light is not None:
+ light_extension = gltf2_io_extensions.ChildOfRootExtension(
+ name="KHR_lights_punctual",
+ path=["lights"],
+ extension=light
+ )
+ extensions["KHR_lights_punctual"] = gltf2_io_extensions.Extension(
+ name="KHR_lights_punctual",
+ extension={
+ "light": light_extension
+ }
+ )
+
+ return extensions if extensions else None
+
+
+def __gather_extras(blender_object, export_settings):
+ if export_settings['gltf_extras']:
+ return gltf2_blender_generate_extras.generate_extras(blender_object)
+ return None
+
+
+def __gather_matrix(blender_object, export_settings):
+ # return blender_object.matrix_local
+ return []
+
+
+def __gather_mesh(blender_object, export_settings):
+ if blender_object.type != "MESH":
+ return None
+
+ # If not using vertex group, they are irrelevant for caching --> ensure that they do not trigger a cache miss
+ vertex_groups = blender_object.vertex_groups
+ modifiers = blender_object.modifiers
+ if len(vertex_groups) == 0:
+ vertex_groups = None
+ if len(modifiers) == 0:
+ modifiers = None
+
+ if export_settings[gltf2_blender_export_keys.APPLY]:
+ auto_smooth = blender_object.data.use_auto_smooth
+ if auto_smooth:
+ blender_object = blender_object.copy()
+ edge_split = blender_object.modifiers.new('Temporary_Auto_Smooth', 'EDGE_SPLIT')
+ edge_split.split_angle = blender_object.data.auto_smooth_angle
+ edge_split.use_edge_angle = not blender_object.data.has_custom_normals
+
+ blender_mesh = blender_object.to_mesh(bpy.context.depsgraph, True)
+ skip_filter = True
+
+ if auto_smooth:
+ bpy.data.objects.remove(blender_object)
+ else:
+ blender_mesh = blender_object.data
+ skip_filter = False
+
+ result = gltf2_blender_gather_mesh.gather_mesh(blender_mesh, vertex_groups, modifiers, skip_filter, export_settings)
+
+ if export_settings[gltf2_blender_export_keys.APPLY]:
+ bpy.data.meshes.remove(blender_mesh)
+
+ return result
+
+
+def __gather_name(blender_object, export_settings):
+ if blender_object.instance_type == 'COLLECTION' and blender_object.instance_collection:
+ return "Duplication_Offset_" + blender_object.name
+ return blender_object.name
+
+
+def __gather_trans_rot_scale(blender_object, export_settings):
+ trans, rot, sca = gltf2_blender_extract.decompose_transition(blender_object.matrix_local, 'NODE', export_settings)
+ if blender_object.instance_type == 'COLLECTION' and blender_object.instance_collection:
+ trans = -gltf2_blender_extract.convert_swizzle_location(
+ blender_object.instance_collection.instance_offset, export_settings)
+ translation, rotation, scale = (None, None, None)
+ trans[0], trans[1], trans[2] = gltf2_blender_math.round_if_near(trans[0], 0.0), gltf2_blender_math.round_if_near(trans[1], 0.0), \
+ gltf2_blender_math.round_if_near(trans[2], 0.0)
+ rot[0], rot[1], rot[2], rot[3] = gltf2_blender_math.round_if_near(rot[0], 0.0), gltf2_blender_math.round_if_near(rot[1], 0.0), \
+ gltf2_blender_math.round_if_near(rot[2], 0.0), gltf2_blender_math.round_if_near(rot[3], 1.0)
+ sca[0], sca[1], sca[2] = gltf2_blender_math.round_if_near(sca[0], 1.0), gltf2_blender_math.round_if_near(sca[1], 1.0), \
+ gltf2_blender_math.round_if_near(sca[2], 1.0)
+ if trans[0] != 0.0 or trans[1] != 0.0 or trans[2] != 0.0:
+ translation = [trans[0], trans[1], trans[2]]
+ if rot[0] != 0.0 or rot[1] != 0.0 or rot[2] != 0.0 or rot[3] != 1.0:
+ rotation = [rot[0], rot[1], rot[2], rot[3]]
+ if sca[0] != 1.0 or sca[1] != 1.0 or sca[2] != 1.0:
+ scale = [sca[0], sca[1], sca[2]]
+ return translation, rotation, scale
+
+
+def __gather_skin(blender_object, export_settings):
+ modifiers = {m.type: m for m in blender_object.modifiers}
+
+ if "ARMATURE" in modifiers:
+ # Skins and meshes must be in the same glTF node, which is different from how blender handles armatures
+ return gltf2_blender_gather_skins.gather_skin(modifiers["ARMATURE"].object, export_settings)
+
+
+def __gather_weights(blender_object, export_settings):
+ return None
+
+
+def __get_correction_node(blender_object, export_settings):
+ correction_quaternion = gltf2_blender_extract.convert_swizzle_rotation(
+ Quaternion((1.0, 0.0, 0.0), math.radians(-90.0)), export_settings)
+ correction_quaternion = [correction_quaternion[1], correction_quaternion[2],
+ correction_quaternion[3], correction_quaternion[0]]
+ return gltf2_io.Node(
+ camera=None,
+ children=None,
+ extensions=None,
+ extras=None,
+ matrix=None,
+ mesh=None,
+ name=blender_object.name + '_Orientation',
+ rotation=correction_quaternion,
+ scale=None,
+ skin=None,
+ translation=None,
+ weights=None
+ )
+
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_primitive_attributes.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_primitive_attributes.py
new file mode 100755
index 00000000..76a1f0e4
--- /dev/null
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_primitive_attributes.py
@@ -0,0 +1,217 @@
+# Copyright 2018 The glTF-Blender-IO authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from . import gltf2_blender_export_keys
+from io_scene_gltf2.io.com import gltf2_io
+from io_scene_gltf2.io.com import gltf2_io_constants
+from io_scene_gltf2.io.com import gltf2_io_debug
+from io_scene_gltf2.io.exp import gltf2_io_binary_data
+from io_scene_gltf2.blender.exp import gltf2_blender_utils
+
+
+def gather_primitive_attributes(blender_primitive, export_settings):
+ """
+ Gathers the attributes, such as POSITION, NORMAL, TANGENT from a blender primitive.
+
+ :return: a dictionary of attributes
+ """
+ attributes = {}
+ attributes.update(__gather_position(blender_primitive, export_settings))
+ attributes.update(__gather_normal(blender_primitive, export_settings))
+ attributes.update(__gather_tangent(blender_primitive, export_settings))
+ attributes.update(__gather_texcoord(blender_primitive, export_settings))
+ attributes.update(__gather_colors(blender_primitive, export_settings))
+ attributes.update(__gather_skins(blender_primitive, export_settings))
+ return attributes
+
+
+def __gather_position(blender_primitive, export_settings):
+ position = blender_primitive["attributes"]["POSITION"]
+ componentType = gltf2_io_constants.ComponentType.Float
+ return {
+ "POSITION": gltf2_io.Accessor(
+ buffer_view=gltf2_io_binary_data.BinaryData.from_list(position, componentType),
+ byte_offset=None,
+ component_type=componentType,
+ count=len(position) // gltf2_io_constants.DataType.num_elements(gltf2_io_constants.DataType.Vec3),
+ extensions=None,
+ extras=None,
+ max=gltf2_blender_utils.max_components(position, gltf2_io_constants.DataType.Vec3),
+ min=gltf2_blender_utils.min_components(position, gltf2_io_constants.DataType.Vec3),
+ name=None,
+ normalized=None,
+ sparse=None,
+ type=gltf2_io_constants.DataType.Vec3
+ )
+ }
+
+
+def __gather_normal(blender_primitive, export_settings):
+ if export_settings[gltf2_blender_export_keys.NORMALS]:
+ normal = blender_primitive["attributes"]['NORMAL']
+ return {
+ "NORMAL": gltf2_io.Accessor(
+ buffer_view=gltf2_io_binary_data.BinaryData.from_list(normal, gltf2_io_constants.ComponentType.Float),
+ byte_offset=None,
+ component_type=gltf2_io_constants.ComponentType.Float,
+ count=len(normal) // gltf2_io_constants.DataType.num_elements(gltf2_io_constants.DataType.Vec3),
+ extensions=None,
+ extras=None,
+ max=None,
+ min=None,
+ name=None,
+ normalized=None,
+ sparse=None,
+ type=gltf2_io_constants.DataType.Vec3
+ )
+ }
+ return {}
+
+
+def __gather_tangent(blender_primitive, export_settings):
+ if export_settings[gltf2_blender_export_keys.TANGENTS]:
+ if blender_primitive["attributes"].get('TANGENT') is not None:
+ tangent = blender_primitive["attributes"]['TANGENT']
+ return {
+ "TANGENT": gltf2_io.Accessor(
+ buffer_view=gltf2_io_binary_data.BinaryData.from_list(
+ tangent, gltf2_io_constants.ComponentType.Float),
+ byte_offset=None,
+ component_type=gltf2_io_constants.ComponentType.Float,
+ count=len(tangent) // gltf2_io_constants.DataType.num_elements(gltf2_io_constants.DataType.Vec4),
+ extensions=None,
+ extras=None,
+ max=None,
+ min=None,
+ name=None,
+ normalized=None,
+ sparse=None,
+ type=gltf2_io_constants.DataType.Vec4
+ )
+ }
+
+ return {}
+
+
+def __gather_texcoord(blender_primitive, export_settings):
+ attributes = {}
+ if export_settings[gltf2_blender_export_keys.TEX_COORDS]:
+ tex_coord_index = 0
+ tex_coord_id = 'TEXCOORD_' + str(tex_coord_index)
+ while blender_primitive["attributes"].get(tex_coord_id) is not None:
+ tex_coord = blender_primitive["attributes"][tex_coord_id]
+ attributes[tex_coord_id] = gltf2_io.Accessor(
+ buffer_view=gltf2_io_binary_data.BinaryData.from_list(
+ tex_coord, gltf2_io_constants.ComponentType.Float),
+ byte_offset=None,
+ component_type=gltf2_io_constants.ComponentType.Float,
+ count=len(tex_coord) // gltf2_io_constants.DataType.num_elements(gltf2_io_constants.DataType.Vec2),
+ extensions=None,
+ extras=None,
+ max=None,
+ min=None,
+ name=None,
+ normalized=None,
+ sparse=None,
+ type=gltf2_io_constants.DataType.Vec2
+ )
+ tex_coord_index += 1
+ tex_coord_id = 'TEXCOORD_' + str(tex_coord_index)
+ return attributes
+
+
+def __gather_colors(blender_primitive, export_settings):
+ attributes = {}
+ if export_settings[gltf2_blender_export_keys.COLORS]:
+ color_index = 0
+ color_id = 'COLOR_' + str(color_index)
+ while blender_primitive["attributes"].get(color_id) is not None:
+ internal_color = blender_primitive["attributes"][color_id]
+ attributes[color_id] = gltf2_io.Accessor(
+ buffer_view=gltf2_io_binary_data.BinaryData.from_list(
+ internal_color, gltf2_io_constants.ComponentType.Float),
+ byte_offset=None,
+ component_type=gltf2_io_constants.ComponentType.Float,
+ count=len(internal_color) // gltf2_io_constants.DataType.num_elements(gltf2_io_constants.DataType.Vec4),
+ extensions=None,
+ extras=None,
+ max=None,
+ min=None,
+ name=None,
+ normalized=None,
+ sparse=None,
+ type=gltf2_io_constants.DataType.Vec4
+ )
+ color_index += 1
+ color_id = 'COLOR_' + str(color_index)
+ return attributes
+
+
+def __gather_skins(blender_primitive, export_settings):
+ attributes = {}
+ if export_settings[gltf2_blender_export_keys.SKINS]:
+ bone_index = 0
+ joint_id = 'JOINTS_' + str(bone_index)
+ weight_id = 'WEIGHTS_' + str(bone_index)
+ while blender_primitive["attributes"].get(joint_id) and blender_primitive["attributes"].get(weight_id):
+ if bone_index >= 4:
+ gltf2_io_debug.print_console("WARNING", "There are more than 4 joint vertex influences."
+ "Consider to apply blenders Limit Total function.")
+ if not export_settings['gltf_all_vertex_influences']:
+ break
+
+ # joints
+ internal_joint = blender_primitive["attributes"][joint_id]
+ joint = gltf2_io.Accessor(
+ buffer_view=gltf2_io_binary_data.BinaryData.from_list(
+ internal_joint, gltf2_io_constants.ComponentType.UnsignedShort),
+ byte_offset=None,
+ component_type=gltf2_io_constants.ComponentType.UnsignedShort,
+ count=len(internal_joint) // gltf2_io_constants.DataType.num_elements(gltf2_io_constants.DataType.Vec4),
+ extensions=None,
+ extras=None,
+ max=None,
+ min=None,
+ name=None,
+ normalized=None,
+ sparse=None,
+ type=gltf2_io_constants.DataType.Vec4
+ )
+ attributes[joint_id] = joint
+
+ # weights
+ internal_weight = blender_primitive["attributes"][weight_id]
+ weight = gltf2_io.Accessor(
+ buffer_view=gltf2_io_binary_data.BinaryData.from_list(
+ internal_weight, gltf2_io_constants.ComponentType.Float),
+ byte_offset=None,
+ component_type=gltf2_io_constants.ComponentType.Float,
+ count=len(internal_weight) // gltf2_io_constants.DataType.num_elements(
+ gltf2_io_constants.DataType.Vec4),
+ extensions=None,
+ extras=None,
+ max=None,
+ min=None,
+ name=None,
+ normalized=None,
+ sparse=None,
+ type=gltf2_io_constants.DataType.Vec4
+ )
+ attributes[weight_id] = weight
+
+ bone_index += 1
+ joint_id = 'JOINTS_' + str(bone_index)
+ weight_id = 'WEIGHTS_' + str(bone_index)
+ return attributes
+
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_primitives.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_primitives.py
new file mode 100755
index 00000000..4fa025b2
--- /dev/null
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_primitives.py
@@ -0,0 +1,197 @@
+# Copyright 2018 The glTF-Blender-IO authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import bpy
+from typing import List, Optional
+
+from .gltf2_blender_export_keys import NORMALS, MORPH_NORMAL, TANGENTS, MORPH_TANGENT, MORPH
+
+from io_scene_gltf2.blender.exp.gltf2_blender_gather_cache import cached
+from io_scene_gltf2.blender.exp import gltf2_blender_extract
+from io_scene_gltf2.blender.exp import gltf2_blender_gather_primitive_attributes
+from io_scene_gltf2.blender.exp import gltf2_blender_utils
+from io_scene_gltf2.blender.exp import gltf2_blender_gather_materials
+
+from io_scene_gltf2.io.com import gltf2_io
+from io_scene_gltf2.io.exp import gltf2_io_binary_data
+from io_scene_gltf2.io.com import gltf2_io_constants
+from io_scene_gltf2.io.com.gltf2_io_debug import print_console
+
+
+@cached
+def gather_primitives(
+ blender_mesh: bpy.types.Mesh,
+ vertex_groups: Optional[bpy.types.VertexGroups],
+ modifiers: Optional[bpy.types.ObjectModifiers],
+ export_settings
+ ) -> List[gltf2_io.MeshPrimitive]:
+ """
+ Extract the mesh primitives from a blender object
+
+ :return: a list of glTF2 primitives
+ """
+ primitives = []
+ blender_primitives = gltf2_blender_extract.extract_primitives(
+ None, blender_mesh, vertex_groups, modifiers, export_settings)
+
+ for internal_primitive in blender_primitives:
+
+ primitive = gltf2_io.MeshPrimitive(
+ attributes=__gather_attributes(internal_primitive, blender_mesh, modifiers, export_settings),
+ extensions=None,
+ extras=None,
+ indices=__gather_indices(internal_primitive, blender_mesh, modifiers, export_settings),
+ material=__gather_materials(internal_primitive, blender_mesh, modifiers, export_settings),
+ mode=None,
+ targets=__gather_targets(internal_primitive, blender_mesh, modifiers, export_settings)
+ )
+ primitives.append(primitive)
+
+ return primitives
+
+
+def __gather_materials(blender_primitive, blender_mesh, modifiers, export_settings):
+ if not blender_primitive['material']:
+ # TODO: fix 'extract_promitives' so that the value of 'material' is None and not empty string
+ return None
+ material = bpy.data.materials[blender_primitive['material']]
+ return gltf2_blender_gather_materials.gather_material(material, export_settings)
+
+
+def __gather_indices(blender_primitive, blender_mesh, modifiers, export_settings):
+ indices = blender_primitive['indices']
+
+ max_index = max(indices)
+ if max_index < (1 << 8):
+ component_type = gltf2_io_constants.ComponentType.UnsignedByte
+ elif max_index < (1 << 16):
+ component_type = gltf2_io_constants.ComponentType.UnsignedShort
+ elif max_index < (1 << 32):
+ component_type = gltf2_io_constants.ComponentType.UnsignedInt
+ else:
+ print_console('ERROR', 'Invalid max_index: ' + str(max_index))
+ return None
+
+ element_type = gltf2_io_constants.DataType.Scalar
+ binary_data = gltf2_io_binary_data.BinaryData.from_list(indices, component_type)
+ return gltf2_io.Accessor(
+ buffer_view=binary_data,
+ byte_offset=None,
+ component_type=component_type,
+ count=len(indices) // gltf2_io_constants.DataType.num_elements(element_type),
+ extensions=None,
+ extras=None,
+ max=None,
+ min=None,
+ name=None,
+ normalized=None,
+ sparse=None,
+ type=element_type
+ )
+
+
+def __gather_attributes(blender_primitive, blender_mesh, modifiers, export_settings):
+ return gltf2_blender_gather_primitive_attributes.gather_primitive_attributes(blender_primitive, export_settings)
+
+
+def __gather_targets(blender_primitive, blender_mesh, modifiers, export_settings):
+ if export_settings[MORPH]:
+ targets = []
+ if blender_mesh.shape_keys is not None:
+ morph_index = 0
+ for blender_shape_key in blender_mesh.shape_keys.key_blocks:
+ if blender_shape_key != blender_shape_key.relative_key:
+
+ target_position_id = 'MORPH_POSITION_' + str(morph_index)
+ target_normal_id = 'MORPH_NORMAL_' + str(morph_index)
+ target_tangent_id = 'MORPH_TANGENT_' + str(morph_index)
+
+ if blender_primitive["attributes"].get(target_position_id):
+ target = {}
+ internal_target_position = blender_primitive["attributes"][target_position_id]
+ binary_data = gltf2_io_binary_data.BinaryData.from_list(
+ internal_target_position,
+ gltf2_io_constants.ComponentType.Float
+ )
+ target["POSITION"] = gltf2_io.Accessor(
+ buffer_view=binary_data,
+ byte_offset=None,
+ component_type=gltf2_io_constants.ComponentType.Float,
+ count=len(internal_target_position) // gltf2_io_constants.DataType.num_elements(
+ gltf2_io_constants.DataType.Vec3),
+ extensions=None,
+ extras=None,
+ max=gltf2_blender_utils.max_components(
+ internal_target_position, gltf2_io_constants.DataType.Vec3),
+ min=gltf2_blender_utils.min_components(
+ internal_target_position, gltf2_io_constants.DataType.Vec3),
+ name=None,
+ normalized=None,
+ sparse=None,
+ type=gltf2_io_constants.DataType.Vec3
+ )
+
+ if export_settings[NORMALS] \
+ and export_settings[MORPH_NORMAL] \
+ and blender_primitive["attributes"].get(target_normal_id):
+
+ internal_target_normal = blender_primitive["attributes"][target_normal_id]
+ binary_data = gltf2_io_binary_data.BinaryData.from_list(
+ internal_target_normal,
+ gltf2_io_constants.ComponentType.Float,
+ )
+ target['NORMAL'] = gltf2_io.Accessor(
+ buffer_view=binary_data,
+ byte_offset=None,
+ component_type=gltf2_io_constants.ComponentType.Float,
+ count=len(internal_target_normal) // gltf2_io_constants.DataType.num_elements(
+ gltf2_io_constants.DataType.Vec3),
+ extensions=None,
+ extras=None,
+ max=None,
+ min=None,
+ name=None,
+ normalized=None,
+ sparse=None,
+ type=gltf2_io_constants.DataType.Vec3
+ )
+
+ if export_settings[TANGENTS] \
+ and export_settings[MORPH_TANGENT] \
+ and blender_primitive["attributes"].get(target_tangent_id):
+ internal_target_tangent = blender_primitive["attributes"][target_tangent_id]
+ binary_data = gltf2_io_binary_data.BinaryData.from_list(
+ internal_target_tangent,
+ gltf2_io_constants.ComponentType.Float,
+ )
+ target['TANGENT'] = gltf2_io.Accessor(
+ buffer_view=binary_data,
+ byte_offset=None,
+ component_type=gltf2_io_constants.ComponentType.Float,
+ count=len(internal_target_tangent) // gltf2_io_constants.DataType.num_elements(
+ gltf2_io_constants.DataType.Vec3),
+ extensions=None,
+ extras=None,
+ max=None,
+ min=None,
+ name=None,
+ normalized=None,
+ sparse=None,
+ type=gltf2_io_constants.DataType.Vec3
+ )
+ targets.append(target)
+ morph_index += 1
+ return targets
+ return None
+
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_sampler.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_sampler.py
new file mode 100755
index 00000000..840c98f4
--- /dev/null
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_sampler.py
@@ -0,0 +1,98 @@
+# Copyright 2018 The glTF-Blender-IO authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import bpy
+from io_scene_gltf2.io.com import gltf2_io
+from io_scene_gltf2.blender.exp.gltf2_blender_gather_cache import cached
+
+
+@cached
+def gather_sampler(blender_shader_node: bpy.types.Node, export_settings):
+ if not __filter_sampler(blender_shader_node, export_settings):
+ return None
+
+ return gltf2_io.Sampler(
+ extensions=__gather_extensions(blender_shader_node, export_settings),
+ extras=__gather_extras(blender_shader_node, export_settings),
+ mag_filter=__gather_mag_filter(blender_shader_node, export_settings),
+ min_filter=__gather_min_filter(blender_shader_node, export_settings),
+ name=__gather_name(blender_shader_node, export_settings),
+ wrap_s=__gather_wrap_s(blender_shader_node, export_settings),
+ wrap_t=__gather_wrap_t(blender_shader_node, export_settings)
+ )
+
+
+def __filter_sampler(blender_shader_node, export_settings):
+ if not blender_shader_node.interpolation == 'Closest' and not blender_shader_node.extension == 'CLIP':
+ return False
+ return True
+
+
+def __gather_extensions(blender_shader_node, export_settings):
+ return None
+
+
+def __gather_extras(blender_shader_node, export_settings):
+ return None
+
+
+def __gather_mag_filter(blender_shader_node, export_settings):
+ if blender_shader_node.interpolation == 'Closest':
+ return 9728 # NEAREST
+ return 9729 # LINEAR
+
+
+def __gather_min_filter(blender_shader_node, export_settings):
+ if blender_shader_node.interpolation == 'Closest':
+ return 9984 # NEAREST_MIPMAP_NEAREST
+ return 9986 # NEAREST_MIPMAP_LINEAR
+
+
+def __gather_name(blender_shader_node, export_settings):
+ return None
+
+
+def __gather_wrap_s(blender_shader_node, export_settings):
+ if blender_shader_node.extension == 'CLIP':
+ return 33071
+ return None
+
+
+def __gather_wrap_t(blender_shader_node, export_settings):
+ if blender_shader_node.extension == 'CLIP':
+ return 33071
+ return None
+
+
+@cached
+def gather_sampler_from_texture_slot(blender_texture: bpy.types.TextureSlot, export_settings):
+ magFilter = 9729
+ wrap = 10497
+ if blender_texture.texture.extension == 'CLIP':
+ wrap = 33071
+
+ minFilter = 9986
+ if magFilter == 9728:
+ minFilter = 9984
+
+ return gltf2_io.Sampler(
+ extensions=None,
+ extras=None,
+ mag_filter=magFilter,
+ min_filter=minFilter,
+ name=None,
+ wrap_s=wrap,
+ wrap_t=wrap
+ )
+
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_skins.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_skins.py
new file mode 100755
index 00000000..84703414
--- /dev/null
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_skins.py
@@ -0,0 +1,150 @@
+# Copyright 2018 The glTF-Blender-IO authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import mathutils
+from . import gltf2_blender_export_keys
+from io_scene_gltf2.blender.exp.gltf2_blender_gather_cache import cached
+from io_scene_gltf2.io.com import gltf2_io
+from io_scene_gltf2.io.exp import gltf2_io_binary_data
+from io_scene_gltf2.io.com import gltf2_io_constants
+from io_scene_gltf2.blender.exp import gltf2_blender_gather_joints
+from io_scene_gltf2.blender.com import gltf2_blender_math
+
+
+@cached
+def gather_skin(blender_object, export_settings):
+ """
+ Gather armatures, bones etc into a glTF2 skin object.
+
+ :param blender_object: the object which may contain a skin
+ :param export_settings:
+ :return: a glTF2 skin object
+ """
+ if not __filter_skin(blender_object, export_settings):
+ return None
+
+ return gltf2_io.Skin(
+ extensions=__gather_extensions(blender_object, export_settings),
+ extras=__gather_extras(blender_object, export_settings),
+ inverse_bind_matrices=__gather_inverse_bind_matrices(blender_object, export_settings),
+ joints=__gather_joints(blender_object, export_settings),
+ name=__gather_name(blender_object, export_settings),
+ skeleton=__gather_skeleton(blender_object, export_settings)
+ )
+
+
+def __filter_skin(blender_object, export_settings):
+ if not export_settings[gltf2_blender_export_keys.SKINS]:
+ return False
+ if blender_object.type != 'ARMATURE' or len(blender_object.pose.bones) == 0:
+ return False
+
+ return True
+
+
+def __gather_extensions(blender_object, export_settings):
+ return None
+
+
+def __gather_extras(blender_object, export_settings):
+ return None
+
+
+def __gather_inverse_bind_matrices(blender_object, export_settings):
+ inverse_matrices = []
+
+ axis_basis_change = mathutils.Matrix.Identity(4)
+ if export_settings[gltf2_blender_export_keys.YUP]:
+ axis_basis_change = mathutils.Matrix(
+ ((1.0, 0.0, 0.0, 0.0), (0.0, 0.0, 1.0, 0.0), (0.0, -1.0, 0.0, 0.0), (0.0, 0.0, 0.0, 1.0)))
+
+ # # artificial torso, as needed by glTF
+ # inverse_bind_matrix = blender_object.matrix_world.inverted() * axis_basis_change.inverted()
+ # for column in range(0, 4):
+ # for row in range(0, 4):
+ # inverse_matrices.append(inverse_bind_matrix[row][column])
+
+ #
+ for blender_bone in blender_object.pose.bones:
+ inverse_bind_matrix = gltf2_blender_math.multiply(axis_basis_change, blender_bone.bone.matrix_local)
+ bind_shape_matrix = gltf2_blender_math.multiply(gltf2_blender_math.multiply(
+ axis_basis_change, blender_object.matrix_world.inverted()), axis_basis_change.inverted())
+
+ inverse_bind_matrix = gltf2_blender_math.multiply(inverse_bind_matrix.inverted(), bind_shape_matrix)
+ for column in range(0, 4):
+ for row in range(0, 4):
+ inverse_matrices.append(inverse_bind_matrix[row][column])
+
+ binary_data = gltf2_io_binary_data.BinaryData.from_list(inverse_matrices, gltf2_io_constants.ComponentType.Float)
+ return gltf2_io.Accessor(
+ buffer_view=binary_data,
+ byte_offset=None,
+ component_type=gltf2_io_constants.ComponentType.Float,
+ count=len(inverse_matrices) // gltf2_io_constants.DataType.num_elements(gltf2_io_constants.DataType.Mat4),
+ extensions=None,
+ extras=None,
+ max=None,
+ min=None,
+ name=None,
+ normalized=None,
+ sparse=None,
+ type=gltf2_io_constants.DataType.Mat4
+ )
+
+
+def __gather_joints(blender_object, export_settings):
+ # # the skeletal hierarchy groups below a 'root' joint
+ # # TODO: add transform?
+ # torso = gltf2_io.Node(
+ # camera=None,
+ # children=[],
+ # extensions={},
+ # extras=None,
+ # matrix=[],
+ # mesh=None,
+ # name="Skeleton_" + blender_object.name,
+ # rotation=None,
+ # scale=None,
+ # skin=None,
+ # translation=None,
+ # weights=None
+ # )
+
+ root_joints = []
+ # build the hierarchy of nodes out of the bones
+ for blender_bone in blender_object.pose.bones:
+ if not blender_bone.parent:
+ root_joints.append(gltf2_blender_gather_joints.gather_joint(blender_bone, export_settings))
+
+ # joints is a flat list containing all nodes belonging to the skin
+ joints = []
+
+ def __collect_joints(node):
+ joints.append(node)
+ for child in node.children:
+ __collect_joints(child)
+ for joint in root_joints:
+ __collect_joints(joint)
+
+ return joints
+
+
+def __gather_name(blender_object, export_settings):
+ return blender_object.name
+
+
+def __gather_skeleton(blender_object, export_settings):
+ # In the future support the result of https://github.com/KhronosGroup/glTF/pull/1195
+ return None # gltf2_blender_gather_nodes.gather_node(blender_object, export_settings)
+
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_texture.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_texture.py
new file mode 100755
index 00000000..93db33f9
--- /dev/null
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_texture.py
@@ -0,0 +1,100 @@
+# Copyright 2018 The glTF-Blender-IO authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import typing
+import bpy
+from io_scene_gltf2.blender.exp.gltf2_blender_gather_cache import cached
+
+from io_scene_gltf2.io.com import gltf2_io
+from io_scene_gltf2.blender.exp import gltf2_blender_gather_sampler
+from io_scene_gltf2.blender.exp import gltf2_blender_search_node_tree
+from io_scene_gltf2.blender.exp import gltf2_blender_gather_image
+from io_scene_gltf2.io.com import gltf2_io_debug
+
+
+@cached
+def gather_texture(
+ blender_shader_sockets_or_texture_slots: typing.Union[
+ typing.Tuple[bpy.types.NodeSocket], typing.Tuple[typing.Any]],
+ export_settings):
+ """
+ Gather texture sampling information and image channels from a blender shader textu re attached to a shader socket.
+
+ :param blender_shader_sockets: The sockets of the material which should contribute to the texture
+ :param export_settings: configuration of the export
+ :return: a glTF 2.0 texture with sampler and source embedded (will be converted to references by the exporter)
+ """
+ # TODO: extend to texture slots
+ if not __filter_texture(blender_shader_sockets_or_texture_slots, export_settings):
+ return None
+
+ return gltf2_io.Texture(
+ extensions=__gather_extensions(blender_shader_sockets_or_texture_slots, export_settings),
+ extras=__gather_extras(blender_shader_sockets_or_texture_slots, export_settings),
+ name=__gather_name(blender_shader_sockets_or_texture_slots, export_settings),
+ sampler=__gather_sampler(blender_shader_sockets_or_texture_slots, export_settings),
+ source=__gather_source(blender_shader_sockets_or_texture_slots, export_settings)
+ )
+
+
+def __filter_texture(blender_shader_sockets_or_texture_slots, export_settings):
+ return True
+
+
+def __gather_extensions(blender_shader_sockets, export_settings):
+ return None
+
+
+def __gather_extras(blender_shader_sockets, export_settings):
+ return None
+
+
+def __gather_name(blender_shader_sockets, export_settings):
+ return None
+
+
+def __gather_sampler(blender_shader_sockets_or_texture_slots, export_settings):
+ if isinstance(blender_shader_sockets_or_texture_slots[0], bpy.types.NodeSocket):
+ shader_nodes = [__get_tex_from_socket(socket).shader_node for socket in blender_shader_sockets_or_texture_slots]
+ if len(shader_nodes) > 1:
+ gltf2_io_debug.print_console("WARNING",
+ "More than one shader node tex image used for a texture. "
+ "The resulting glTF sampler will behave like the first shader node tex image.")
+ return gltf2_blender_gather_sampler.gather_sampler(
+ shader_nodes[0],
+ export_settings)
+ elif isinstance(blender_shader_sockets_or_texture_slots[0], bpy.types.MaterialTextureSlot):
+ return gltf2_blender_gather_sampler.gather_sampler_from_texture_slot(
+ blender_shader_sockets_or_texture_slots[0],
+ export_settings
+ )
+ else:
+ # TODO: implement texture slot sampler
+ raise NotImplementedError()
+
+
+def __gather_source(blender_shader_sockets_or_texture_slots, export_settings):
+ return gltf2_blender_gather_image.gather_image(blender_shader_sockets_or_texture_slots, export_settings)
+
+# Helpers
+
+
+def __get_tex_from_socket(socket):
+ result = gltf2_blender_search_node_tree.from_socket(
+ socket,
+ gltf2_blender_search_node_tree.FilterByType(bpy.types.ShaderNodeTexImage))
+ if not result:
+ return None
+ return result[0]
+
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_texture_info.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_texture_info.py
new file mode 100755
index 00000000..600bf81e
--- /dev/null
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_texture_info.py
@@ -0,0 +1,119 @@
+# Copyright 2018 The glTF-Blender-IO authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import bpy
+import typing
+from io_scene_gltf2.blender.exp.gltf2_blender_gather_cache import cached
+from io_scene_gltf2.io.com import gltf2_io
+from io_scene_gltf2.blender.exp import gltf2_blender_gather_texture
+from io_scene_gltf2.blender.exp import gltf2_blender_search_node_tree
+from io_scene_gltf2.blender.exp import gltf2_blender_export_keys
+from io_scene_gltf2.blender.exp import gltf2_blender_get
+from io_scene_gltf2.io.com.gltf2_io_extensions import Extension
+
+
+@cached
+def gather_texture_info(blender_shader_sockets_or_texture_slots: typing.Union[
+ typing.Tuple[bpy.types.NodeSocket], typing.Tuple[bpy.types.Texture]],
+ export_settings):
+ if not __filter_texture_info(blender_shader_sockets_or_texture_slots, export_settings):
+ return None
+
+ texture_info = gltf2_io.TextureInfo(
+ extensions=__gather_extensions(blender_shader_sockets_or_texture_slots, export_settings),
+ extras=__gather_extras(blender_shader_sockets_or_texture_slots, export_settings),
+ index=__gather_index(blender_shader_sockets_or_texture_slots, export_settings),
+ tex_coord=__gather_tex_coord(blender_shader_sockets_or_texture_slots, export_settings)
+ )
+
+ return texture_info
+
+
+def __filter_texture_info(blender_shader_sockets_or_texture_slots, export_settings):
+ if not blender_shader_sockets_or_texture_slots:
+ return False
+ if not all([elem is not None for elem in blender_shader_sockets_or_texture_slots]):
+ return False
+ if isinstance(blender_shader_sockets_or_texture_slots[0], bpy.types.NodeSocket):
+ if any([__get_tex_from_socket(socket) is None for socket in blender_shader_sockets_or_texture_slots]):
+ # sockets do not lead to a texture --> discard
+ return False
+ return True
+
+
+def __gather_extensions(blender_shader_sockets_or_texture_slots, export_settings):
+ if not export_settings[gltf2_blender_export_keys.TEXTURE_TRANSFORM]:
+ return None
+
+ texture_node = blender_shader_sockets_or_texture_slots[0].links[0].from_node
+ texture_transform = gltf2_blender_get.get_texture_transform_from_texture_node(texture_node)
+ if texture_transform is None:
+ return None
+
+ extension = Extension("KHR_texture_transform", texture_transform)
+ return {"KHR_texture_transform": extension}
+
+
+def __gather_extras(blender_shader_sockets_or_texture_slots, export_settings):
+ return None
+
+
+def __gather_index(blender_shader_sockets_or_texture_slots, export_settings):
+ # We just put the actual shader into the 'index' member
+ return gltf2_blender_gather_texture.gather_texture(blender_shader_sockets_or_texture_slots, export_settings)
+
+
+def __gather_tex_coord(blender_shader_sockets_or_texture_slots, export_settings):
+ if isinstance(blender_shader_sockets_or_texture_slots[0], bpy.types.NodeSocket):
+ blender_shader_node = __get_tex_from_socket(blender_shader_sockets_or_texture_slots[0]).shader_node
+ if len(blender_shader_node.inputs['Vector'].links) == 0:
+ return 0
+
+ input_node = blender_shader_node.inputs['Vector'].links[0].from_node
+
+ if isinstance(input_node, bpy.types.ShaderNodeMapping):
+
+ if len(input_node.inputs['Vector'].links) == 0:
+ return 0
+
+ input_node = input_node.inputs['Vector'].links[0].from_node
+
+ if not isinstance(input_node, bpy.types.ShaderNodeUVMap):
+ return 0
+
+ if input_node.uv_map == '':
+ return 0
+
+ # Try to gather map index.
+ for blender_mesh in bpy.data.meshes:
+ texCoordIndex = blender_mesh.uv_layers.find(input_node.uv_map)
+ if texCoordIndex >= 0:
+ return texCoordIndex
+
+ return 0
+ elif isinstance(blender_shader_sockets_or_texture_slots[0], bpy.types.MaterialTextureSlot):
+ # TODO: implement for texture slots
+ return 0
+ else:
+ raise NotImplementedError()
+
+
+def __get_tex_from_socket(socket):
+ result = gltf2_blender_search_node_tree.from_socket(
+ socket,
+ gltf2_blender_search_node_tree.FilterByType(bpy.types.ShaderNodeTexImage))
+ if not result:
+ return None
+ return result[0]
+
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_generate_extras.py b/io_scene_gltf2/blender/exp/gltf2_blender_generate_extras.py
new file mode 100755
index 00000000..c26c494a
--- /dev/null
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_generate_extras.py
@@ -0,0 +1,64 @@
+# Copyright 2018 The glTF-Blender-IO authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+import bpy
+from io_scene_gltf2.blender.com import gltf2_blender_json
+
+
+def generate_extras(blender_element):
+ """Filter and create a custom property, which is stored in the glTF extra field."""
+ if not blender_element:
+ return None
+
+ extras = {}
+
+ # Custom properties, which are in most cases present and should not be exported.
+ black_list = ['cycles', 'cycles_visibility', 'cycles_curves', '_RNA_UI']
+
+ count = 0
+ for custom_property in blender_element.keys():
+ if custom_property in black_list:
+ continue
+
+ value = blender_element[custom_property]
+
+ add_value = False
+
+ if isinstance(value, bpy.types.ID):
+ add_value = True
+
+ if isinstance(value, str):
+ add_value = True
+
+ if isinstance(value, (int, float)):
+ add_value = True
+
+ if hasattr(value, "to_list"):
+ value = value.to_list()
+ add_value = True
+
+ if hasattr(value, "to_dict"):
+ value = value.to_dict()
+ add_value = gltf2_blender_json.is_json_convertible(value)
+
+ if add_value:
+ extras[custom_property] = value
+ count += 1
+
+ if count == 0:
+ return None
+
+ return extras
+
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_get.py b/io_scene_gltf2/blender/exp/gltf2_blender_get.py
new file mode 100755
index 00000000..7801190b
--- /dev/null
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_get.py
@@ -0,0 +1,423 @@
+# Copyright 2018 The glTF-Blender-IO authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+#
+# Imports
+#
+
+import bpy
+
+from . import gltf2_blender_export_keys
+from ...io.exp import gltf2_io_get
+from io_scene_gltf2.io.com import gltf2_io_debug
+#
+# Globals
+#
+
+#
+# Functions
+#
+
+
+def get_animation_target(action_group: bpy.types.ActionGroup):
+ return action_group.channels[0].data_path.split('.')[-1]
+
+
+def get_socket_or_texture_slot(blender_material: bpy.types.Material, name: str):
+ """
+ For a given material input name, retrieve the corresponding node tree socket or blender render texture slot.
+
+ :param blender_material: a blender material for which to get the socket/slot
+ :param name: the name of the socket/slot
+ :return: either a blender NodeSocket, if the material is a node tree or a blender Texture otherwise
+ """
+ if blender_material.node_tree and blender_material.use_nodes:
+ #i = [input for input in blender_material.node_tree.inputs]
+ #o = [output for output in blender_material.node_tree.outputs]
+ if name == "Emissive":
+ type = bpy.types.ShaderNodeEmission
+ name = "Color"
+ else:
+ type = bpy.types.ShaderNodeBsdfPrincipled
+ nodes = [n for n in blender_material.node_tree.nodes if isinstance(n, type)]
+ inputs = sum([[input for input in node.inputs if input.name == name] for node in nodes], [])
+ if inputs:
+ return inputs[0]
+
+
+
+ return None
+
+
+def get_socket_or_texture_slot_old(blender_material: bpy.types.Material, name: str):
+ """
+ For a given material input name, retrieve the corresponding node tree socket in the special glTF Metallic Roughness nodes (which might be deprecated?).
+
+ :param blender_material: a blender material for which to get the socket/slot
+ :param name: the name of the socket/slot
+ :return: either a blender NodeSocket, if the material is a node tree or a blender Texture otherwise
+ """
+ if blender_material.node_tree and blender_material.use_nodes:
+ nodes = [n for n in blender_material.node_tree.nodes if \
+ isinstance(n, bpy.types.ShaderNodeGroup) and \
+ n.node_tree.name.startswith('glTF Metallic Roughness')]
+ inputs = sum([[input for input in node.inputs if input.name == name] for node in nodes], [])
+ if inputs:
+ return inputs[0]
+
+ return None
+
+
+def find_shader_image_from_shader_socket(shader_socket, max_hops=10):
+ """Find any ShaderNodeTexImage in the path from the socket."""
+ if shader_socket is None:
+ return None
+
+ if max_hops <= 0:
+ return None
+
+ for link in shader_socket.links:
+ if isinstance(link.from_node, bpy.types.ShaderNodeTexImage):
+ return link.from_node
+
+ for socket in link.from_node.inputs.values():
+ image = find_shader_image_from_shader_socket(shader_socket=socket, max_hops=max_hops - 1)
+ if image is not None:
+ return image
+
+ return None
+
+
+def get_shader_add_to_shader_node(shader_node):
+
+ if shader_node is None:
+ return None
+
+ if len(shader_node.outputs['BSDF'].links) == 0:
+ return None
+
+ to_node = shader_node.outputs['BSDF'].links[0].to_node
+
+ if not isinstance(to_node, bpy.types.ShaderNodeAddShader):
+ return None
+
+ return to_node
+
+#
+
+
+def get_shader_emission_from_shader_add(shader_add):
+
+ if shader_add is None:
+ return None
+
+ if not isinstance(shader_add, bpy.types.ShaderNodeAddShader):
+ return None
+
+ from_node = None
+
+ for input in shader_add.inputs:
+
+ if len(input.links) == 0:
+ continue
+
+ from_node = input.links[0].from_node
+
+ if isinstance(from_node, bpy.types.ShaderNodeEmission):
+ break
+
+ return from_node
+
+
+def get_shader_mapping_from_shader_image(shader_image):
+
+ if shader_image is None:
+ return None
+
+ if not isinstance(shader_image, bpy.types.ShaderNodeTexImage):
+ return None
+
+ if shader_image.inputs.get('Vector') is None:
+ return None
+
+ if len(shader_image.inputs['Vector'].links) == 0:
+ return None
+
+ from_node = shader_image.inputs['Vector'].links[0].from_node
+
+ #
+
+ if not isinstance(from_node, bpy.types.ShaderNodeMapping):
+ return None
+
+ return from_node
+
+
+def get_image_material_usage_to_socket(shader_image, socket_name):
+ if shader_image is None:
+ return -1
+
+ if not isinstance(shader_image, bpy.types.ShaderNodeTexImage):
+ return -2
+
+ if shader_image.outputs.get('Color') is None:
+ return -3
+
+ if len(shader_image.outputs.get('Color').links) == 0:
+ return -4
+
+ for img_link in shader_image.outputs.get('Color').links:
+ separate_rgb = img_link.to_node
+
+ if not isinstance(separate_rgb, bpy.types.ShaderNodeSeparateRGB):
+ continue
+
+ for i, channel in enumerate("RGB"):
+ if separate_rgb.outputs.get(channel) is None:
+ continue
+ for link in separate_rgb.outputs.get(channel).links:
+ if socket_name == link.to_socket.name:
+ return i
+
+ return -6
+
+
+def get_emission_node_from_lamp_output_node(lamp_node):
+ if lamp_node is None:
+ return None
+
+ if not isinstance(lamp_node, bpy.types.ShaderNodeOutputLamp):
+ return None
+
+ if lamp_node.inputs.get('Surface') is None:
+ return None
+
+ if len(lamp_node.inputs.get('Surface').links) == 0:
+ return None
+
+ from_node = lamp_node.inputs.get('Surface').links[0].from_node
+ if isinstance(from_node, bpy.types.ShaderNodeEmission):
+ return from_node
+
+ return None
+
+
+def get_ligth_falloff_node_from_emission_node(emission_node, type):
+ if emission_node is None:
+ return None
+
+ if not isinstance(emission_node, bpy.types.ShaderNodeEmission):
+ return None
+
+ if emission_node.inputs.get('Strength') is None:
+ return None
+
+ if len(emission_node.inputs.get('Strength').links) == 0:
+ return None
+
+ from_node = emission_node.inputs.get('Strength').links[0].from_node
+ if not isinstance(from_node, bpy.types.ShaderNodeLightFalloff):
+ return None
+
+ if from_node.outputs.get(type) is None:
+ return None
+
+ if len(from_node.outputs.get(type).links) == 0:
+ return None
+
+ if emission_node != from_node.outputs.get(type).links[0].to_node:
+ return None
+
+ return from_node
+
+
+def get_shader_image_from_shader_node(name, shader_node):
+
+ if shader_node is None:
+ return None
+
+ if not isinstance(shader_node, bpy.types.ShaderNodeGroup) and \
+ not isinstance(shader_node, bpy.types.ShaderNodeBsdfPrincipled) and \
+ not isinstance(shader_node, bpy.types.ShaderNodeEmission):
+ return None
+
+ if shader_node.inputs.get(name) is None:
+ return None
+
+ if len(shader_node.inputs[name].links) == 0:
+ return None
+
+ from_node = shader_node.inputs[name].links[0].from_node
+
+ #
+
+ if isinstance(from_node, bpy.types.ShaderNodeNormalMap):
+
+ name = 'Color'
+
+ if len(from_node.inputs[name].links) == 0:
+ return None
+
+ from_node = from_node.inputs[name].links[0].from_node
+
+ #
+
+ if not isinstance(from_node, bpy.types.ShaderNodeTexImage):
+ return None
+
+ return from_node
+
+
+def get_texture_index_from_shader_node(export_settings, glTF, name, shader_node):
+ """Return the texture index in the glTF array."""
+ from_node = get_shader_image_from_shader_node(name, shader_node)
+
+ if from_node is None:
+ return -1
+
+ #
+
+ if from_node.image is None or from_node.image.size[0] == 0 or from_node.image.size[1] == 0:
+ return -1
+
+ return gltf2_io_get.get_texture_index(glTF, from_node.image.name)
+
+
+def get_texture_index_from_export_settings(export_settings, name):
+ """Return the texture index in the glTF array."""
+
+
+def get_texcoord_index_from_shader_node(glTF, name, shader_node):
+ """Return the texture coordinate index, if assigned and used."""
+ from_node = get_shader_image_from_shader_node(name, shader_node)
+
+ if from_node is None:
+ return 0
+
+ #
+
+ if len(from_node.inputs['Vector'].links) == 0:
+ return 0
+
+ input_node = from_node.inputs['Vector'].links[0].from_node
+
+ #
+
+ if isinstance(input_node, bpy.types.ShaderNodeMapping):
+
+ if len(input_node.inputs['Vector'].links) == 0:
+ return 0
+
+ input_node = input_node.inputs['Vector'].links[0].from_node
+
+ #
+
+ if not isinstance(input_node, bpy.types.ShaderNodeUVMap):
+ return 0
+
+ if input_node.uv_map == '':
+ return 0
+
+ #
+
+ # Try to gather map index.
+ for blender_mesh in bpy.data.meshes:
+ texCoordIndex = blender_mesh.uv_layers.find(input_node.uv_map)
+ if texCoordIndex >= 0:
+ return texCoordIndex
+
+ return 0
+
+
+def get_texture_transform_from_texture_node(texture_node):
+ if not isinstance(texture_node, bpy.types.ShaderNodeTexImage):
+ return None
+
+ mapping_socket = texture_node.inputs["Vector"]
+ if len(mapping_socket.links) == 0:
+ return None
+
+ mapping_node = mapping_socket.links[0].from_node
+ if not isinstance(mapping_node, bpy.types.ShaderNodeMapping):
+ return None
+
+ texture_transform = {}
+ if mapping_node.vector_type == 'TEXTURE':
+ texture_transform["offset"] = [-mapping_node.translation[0], -mapping_node.translation[1]]
+ texture_transform["rotation"] = -mapping_node.rotation[2]
+ texture_transform["scale"] = [1.0 / mapping_node.scale[0], 1.0 / mapping_node.scale[1]]
+ elif mapping_node.vector_type == 'POINT':
+ texture_transform["offset"] = [mapping_node.translation[0], mapping_node.translation[1]]
+ texture_transform["rotation"] = mapping_node.rotation[2]
+ texture_transform["scale"] = [mapping_node.scale[0], mapping_node.scale[1]]
+
+ if all([component == 0 for component in texture_transform["offset"]]):
+ del(texture_transform["offset"])
+ if all([component == 1 for component in texture_transform["scale"]]):
+ del(texture_transform["scale"])
+ if texture_transform["rotation"] == 0:
+ del(texture_transform["rotation"])
+
+ return texture_transform
+
+
+def get_image_uri(export_settings, blender_image):
+ """Return the final URI depending on a file path."""
+ file_format = get_image_format(export_settings, blender_image)
+ extension = '.jpg' if file_format == 'JPEG' else '.png'
+
+ return gltf2_io_get.get_image_name(blender_image.name) + extension
+
+
+def get_image_format(export_settings, blender_image):
+ """
+ Return the final output format of the given image.
+
+ Only PNG and JPEG are supported as outputs - all other formats must be converted.
+ """
+ if blender_image.file_format in ['PNG', 'JPEG']:
+ return blender_image.file_format
+
+ use_alpha = export_settings[gltf2_blender_export_keys.FILTERED_IMAGES_USE_ALPHA].get(blender_image.name)
+
+ return 'PNG' if use_alpha else 'JPEG'
+
+
+def get_node(data_path):
+ """Return Blender node on a given Blender data path."""
+ if data_path is None:
+ return None
+
+ index = data_path.find("[\"")
+ if (index == -1):
+ return None
+
+ node_name = data_path[(index + 2):]
+
+ index = node_name.find("\"")
+ if (index == -1):
+ return None
+
+ return node_name[:(index)]
+
+
+def get_data_path(data_path):
+ """Return Blender data path."""
+ index = data_path.rfind('.')
+
+ if index == -1:
+ return data_path
+
+ return data_path[(index + 1):]
+
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gltf2_exporter.py b/io_scene_gltf2/blender/exp/gltf2_blender_gltf2_exporter.py
new file mode 100755
index 00000000..65efafef
--- /dev/null
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gltf2_exporter.py
@@ -0,0 +1,301 @@
+# Copyright 2018 The glTF-Blender-IO authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from typing import Optional, List, Dict
+
+from io_scene_gltf2.io.com import gltf2_io
+from io_scene_gltf2.io.com import gltf2_io_extensions
+from io_scene_gltf2.io.exp import gltf2_io_binary_data
+from io_scene_gltf2.io.exp import gltf2_io_image_data
+from io_scene_gltf2.io.exp import gltf2_io_buffer
+
+import bpy
+import os
+from shutil import copyfile
+
+class GlTF2Exporter:
+ """
+ The glTF exporter flattens a scene graph to a glTF serializable format.
+
+ Any child properties are replaced with references where necessary
+ """
+
+ def __init__(self, copyright=None):
+ self.__finalized = False
+
+ asset = gltf2_io.Asset(
+ copyright=copyright,
+ extensions=None,
+ extras=None,
+ generator='Khronos Blender glTF 2.0 I/O',
+ min_version=None,
+ version='2.0')
+
+ self.__gltf = gltf2_io.Gltf(
+ accessors=[],
+ animations=[],
+ asset=asset,
+ buffers=[],
+ buffer_views=[],
+ cameras=[],
+ extensions={},
+ extensions_required=[],
+ extensions_used=[],
+ extras=None,
+ images=[],
+ materials=[],
+ meshes=[],
+ nodes=[],
+ samplers=[],
+ scene=-1,
+ scenes=[],
+ skins=[],
+ textures=[]
+ )
+
+ self.__buffer = gltf2_io_buffer.Buffer()
+ self.__images = []
+
+ # mapping of all glTFChildOfRootProperty types to their corresponding root level arrays
+ self.__childOfRootPropertyTypeLookup = {
+ gltf2_io.Accessor: self.__gltf.accessors,
+ gltf2_io.Animation: self.__gltf.animations,
+ gltf2_io.Buffer: self.__gltf.buffers,
+ gltf2_io.BufferView: self.__gltf.buffer_views,
+ gltf2_io.Camera: self.__gltf.cameras,
+ gltf2_io.Image: self.__gltf.images,
+ gltf2_io.Material: self.__gltf.materials,
+ gltf2_io.Mesh: self.__gltf.meshes,
+ gltf2_io.Node: self.__gltf.nodes,
+ gltf2_io.Sampler: self.__gltf.samplers,
+ gltf2_io.Scene: self.__gltf.scenes,
+ gltf2_io.Skin: self.__gltf.skins,
+ gltf2_io.Texture: self.__gltf.textures
+ }
+
+ self.__propertyTypeLookup = [
+ gltf2_io.AccessorSparseIndices,
+ gltf2_io.AccessorSparse,
+ gltf2_io.AccessorSparseValues,
+ gltf2_io.AnimationChannel,
+ gltf2_io.AnimationChannelTarget,
+ gltf2_io.AnimationSampler,
+ gltf2_io.Asset,
+ gltf2_io.CameraOrthographic,
+ gltf2_io.CameraPerspective,
+ gltf2_io.MeshPrimitive,
+ gltf2_io.TextureInfo,
+ gltf2_io.MaterialPBRMetallicRoughness,
+ gltf2_io.MaterialNormalTextureInfoClass,
+ gltf2_io.MaterialOcclusionTextureInfoClass
+ ]
+
+ @property
+ def glTF(self):
+ if not self.__finalized:
+ raise RuntimeError("glTF requested, but buffers are not finalized yet")
+ return self.__gltf
+
+ def finalize_buffer(self, output_path=None, buffer_name=None, is_glb=False):
+ """Finalize the glTF and write buffers."""
+ if self.__finalized:
+ raise RuntimeError("Tried to finalize buffers for finalized glTF file")
+
+ if is_glb:
+ uri = None
+ elif output_path and buffer_name:
+ with open(output_path + buffer_name, 'wb') as f:
+ f.write(self.__buffer.to_bytes())
+ uri = buffer_name
+ else:
+ uri = self.__buffer.to_embed_string()
+
+ buffer = gltf2_io.Buffer(
+ byte_length=self.__buffer.byte_length,
+ extensions=None,
+ extras=None,
+ name=None,
+ uri=uri
+ )
+ self.__gltf.buffers.append(buffer)
+
+ self.__finalized = True
+
+ if is_glb:
+ return self.__buffer.to_bytes()
+
+ def finalize_images(self, output_path):
+ """
+ Write all images.
+
+ Due to a current limitation the output_path must be the same as that of the glTF file
+ :param output_path:
+ :return:
+ """
+ for image in self.__images:
+ dst_path = output_path + image.name + image.get_extension()
+ src_path = bpy.path.abspath(image.filepath)
+ if os.path.isfile(src_path):
+ # Source file exists.
+ if os.path.abspath(dst_path) != os.path.abspath(src_path):
+ # Only copy, if source and destination are not the same.
+ copyfile(src_path, dst_path)
+ else:
+ # Source file does not exist e.g. it is packed or has been generated.
+ with open(dst_path, 'wb') as f:
+ f.write(image.to_png_data())
+
+ def add_scene(self, scene: gltf2_io.Scene, active: bool = True):
+ """
+ Add a scene to the glTF.
+
+ The scene should be built up with the generated glTF classes
+ :param scene: gltf2_io.Scene type. Root node of the scene graph
+ :param active: If true, sets the glTD.scene index to the added scene
+ :return: nothing
+ """
+ if self.__finalized:
+ raise RuntimeError("Tried to add scene to finalized glTF file")
+
+ # for node in scene.nodes:
+ # self.__traverse(node)
+ scene_num = self.__traverse(scene)
+ if active:
+ self.__gltf.scene = scene_num
+
+ def add_animation(self, animation: gltf2_io.Animation):
+ """
+ Add an animation to the glTF.
+
+ :param animation: glTF animation, with python style references (names)
+ :return: nothing
+ """
+ if self.__finalized:
+ raise RuntimeError("Tried to add animation to finalized glTF file")
+
+ self.__traverse(animation)
+
+ def __to_reference(self, property):
+ """
+ Append a child of root property to its respective list and return a reference into said list.
+
+ If the property is not child of root, the property itself is returned.
+ :param property: A property type object that should be converted to a reference
+ :return: a reference or the object itself if it is not child or root
+ """
+ gltf_list = self.__childOfRootPropertyTypeLookup.get(type(property), None)
+ if gltf_list is None:
+ # The object is not of a child of root --> don't convert to reference
+ return property
+
+ return self.__append_unique_and_get_index(gltf_list, property)
+
+ @staticmethod
+ def __append_unique_and_get_index(target: list, obj):
+ if obj in target:
+ return target.index(obj)
+ else:
+ index = len(target)
+ target.append(obj)
+ return index
+
+ def __add_image(self, image: gltf2_io_image_data.ImageData):
+ self.__images.append(image)
+ # TODO: we need to know the image url at this point already --> maybe add all options to the constructor of the
+ # exporter
+ # TODO: allow embedding of images (base64)
+ return image.name + ".png"
+
+ @classmethod
+ def __get_key_path(cls, d: dict, keypath: List[str], default=[]):
+ """Create if necessary and get the element at key path from a dict"""
+ key = keypath.pop(0)
+
+ if len(keypath) == 0:
+ v = d.get(key, default)
+ d[key] = v
+ return v
+
+ d_key = d.get(key, {})
+ d[key] = d_key
+ return cls.__get_key_path(d[key], keypath, default)
+
+ def __traverse(self, node):
+ """
+ Recursively traverse a scene graph consisting of gltf compatible elements.
+
+ The tree is traversed downwards until a primitive is reached. Then any ChildOfRoot property
+ is stored in the according list in the glTF and replaced with a index reference in the upper level.
+ """
+ def __traverse_property(node):
+ for member_name in [a for a in dir(node) if not a.startswith('__') and not callable(getattr(node, a))]:
+ new_value = self.__traverse(getattr(node, member_name))
+ setattr(node, member_name, new_value) # usually this is the same as before
+
+ # # TODO: maybe with extensions hooks we can find a more elegant solution
+ # if member_name == "extensions" and new_value is not None:
+ # for extension_name in new_value.keys():
+ # self.__append_unique_and_get_index(self.__gltf.extensions_used, extension_name)
+ # self.__append_unique_and_get_index(self.__gltf.extensions_required, extension_name)
+ return node
+
+ # traverse nodes of a child of root property type and add them to the glTF root
+ if type(node) in self.__childOfRootPropertyTypeLookup:
+ node = __traverse_property(node)
+ idx = self.__to_reference(node)
+ # child of root properties are only present at root level --> replace with index in upper level
+ return idx
+
+ # traverse lists, such as children and replace them with indices
+ if isinstance(node, list):
+ for i in range(len(node)):
+ node[i] = self.__traverse(node[i])
+ return node
+
+ if isinstance(node, dict):
+ for key in node.keys():
+ node[key] = self.__traverse(node[key])
+ return node
+
+ # traverse into any other property
+ if type(node) in self.__propertyTypeLookup:
+ return __traverse_property(node)
+
+ # binary data needs to be moved to a buffer and referenced with a buffer view
+ if isinstance(node, gltf2_io_binary_data.BinaryData):
+ buffer_view = self.__buffer.add_and_get_view(node)
+ return self.__to_reference(buffer_view)
+
+ # image data needs to be saved to file
+ if isinstance(node, gltf2_io_image_data.ImageData):
+ return self.__add_image(node)
+
+ # extensions
+ if isinstance(node, gltf2_io_extensions.Extension):
+ extension = self.__traverse(node.extension)
+ self.__append_unique_and_get_index(self.__gltf.extensions_used, node.name)
+ self.__append_unique_and_get_index(self.__gltf.extensions_required, node.name)
+
+ # extensions that lie in the root of the glTF.
+ # They need to be converted to a reference at place of occurrence
+ if isinstance(node, gltf2_io_extensions.ChildOfRootExtension):
+ root_extension_list = self.__get_key_path(self.__gltf.extensions, [node.name] + node.path)
+ idx = self.__append_unique_and_get_index(root_extension_list, extension)
+ return idx
+
+ return extension
+
+ # do nothing for any type that does not match a glTF schema (primitives)
+ return node
+
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_search_node_tree.py b/io_scene_gltf2/blender/exp/gltf2_blender_search_node_tree.py
new file mode 100755
index 00000000..92b63c7d
--- /dev/null
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_search_node_tree.py
@@ -0,0 +1,98 @@
+# Copyright 2018 The glTF-Blender-IO authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+#
+# Imports
+#
+
+import bpy
+import typing
+
+
+class Filter:
+ """Base class for all node tree filter operations."""
+
+ def __init__(self):
+ pass
+
+ def __call__(self, shader_node):
+ return True
+
+
+class FilterByName(Filter):
+ """
+ Filter the material node tree by name.
+
+ example usage:
+ find_from_socket(start_socket, ShaderNodeFilterByName("Normal"))
+ """
+
+ def __init__(self, name):
+ self.name = name
+ super(FilterByName, self).__init__()
+
+ def __call__(self, shader_node):
+ return shader_node.name == self.name
+
+
+class FilterByType(Filter):
+ """Filter the material node tree by type."""
+
+ def __init__(self, type):
+ self.type = type
+ super(FilterByType, self).__init__()
+
+ def __call__(self, shader_node):
+ return isinstance(shader_node, self.type)
+
+
+class NodeTreeSearchResult:
+ def __init__(self, shader_node: bpy.types.Node, path: typing.List[bpy.types.NodeLink]):
+ self.shader_node = shader_node
+ self.path = path
+
+
+# TODO: cache these searches
+def from_socket(start_socket: bpy.types.NodeSocket,
+ shader_node_filter: typing.Union[Filter, typing.Callable]) -> typing.List[NodeTreeSearchResult]:
+ """
+ Find shader nodes where the filter expression is true.
+
+ :param start_socket: the beginning of the traversal
+ :param shader_node_filter: should be a function(x: shader_node) -> bool
+ :return: a list of shader nodes for which filter is true
+ """
+ # hide implementation (especially the search path
+ def __search_from_socket(start_socket: bpy.types.NodeSocket,
+ shader_node_filter: typing.Union[Filter, typing.Callable],
+ search_path: typing.List[bpy.types.NodeLink]) -> typing.List[NodeTreeSearchResult]:
+ results = []
+
+ for link in start_socket.links:
+ # follow the link to a shader node
+ linked_node = link.from_node
+ # add the link to the current path
+ search_path.append(link)
+ # check if the node matches the filter
+ if shader_node_filter(linked_node):
+ results.append(NodeTreeSearchResult(linked_node, search_path))
+ # traverse into inputs of the node
+ for input_socket in linked_node.inputs:
+ results += __search_from_socket(input_socket, shader_node_filter, search_path)
+
+ return results
+
+ return __search_from_socket(start_socket, shader_node_filter, [])
+
+
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_search_scene.py b/io_scene_gltf2/blender/exp/gltf2_blender_search_scene.py
new file mode 100755
index 00000000..0fa7db6e
--- /dev/null
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_search_scene.py
@@ -0,0 +1,89 @@
+# Copyright 2018 The glTF-Blender-IO authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import bpy
+import typing
+
+
+class Filter:
+ """Base class for all node tree filter operations."""
+
+ def __call__(self, obj: bpy.types.Object):
+ return True
+
+
+class ByName(Filter):
+ """
+ Filter the objects by name.
+
+ example usage:
+ find_objects(FilterByName("Cube"))
+ """
+
+ def __init__(self, name):
+ self.name = name
+
+ def __call__(self, obj: bpy.types.Object):
+ return obj.name == self.name
+
+
+class ByDataType(Filter):
+ """Filter the scene objects by their data type."""
+
+ def __init__(self, data_type: str):
+ self.type = data_type
+
+ def __call__(self, obj: bpy.types.Object):
+ return obj.type == self.type
+
+
+class ByDataInstance(Filter):
+ """Filter the scene objects by a specific ID instance."""
+
+ def __init__(self, data_instance: bpy.types.ID):
+ self.data = data_instance
+
+ def __call__(self, obj: bpy.types.Object):
+ return self.data == obj.data
+
+
+def find_objects(object_filter: typing.Union[Filter, typing.Callable]):
+ """
+ Find objects in the scene where the filter expression is true.
+
+ :param object_filter: should be a function(x: object) -> bool
+ :return: a list of shader nodes for which filter is true
+ """
+ results = []
+ for obj in bpy.context.scene.objects:
+ if object_filter(obj):
+ results.append(obj)
+ return results
+
+
+def find_objects_from(obj: bpy.types.Object, object_filter: typing.Union[Filter, typing.Callable]):
+ """
+ Search for objects matching a filter function below a specified object.
+
+ :param obj: the starting point of the search
+ :param object_filter: a function(x: object) -> bool
+ :return: a list of objects which passed the filter
+ """
+ results = []
+ if object_filter(obj):
+ results.append(obj)
+ for child in obj.children:
+ results += find_objects_from(child, object_filter)
+ return results
+
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_utils.py b/io_scene_gltf2/blender/exp/gltf2_blender_utils.py
new file mode 100755
index 00000000..c3e0d6ee
--- /dev/null
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_utils.py
@@ -0,0 +1,68 @@
+# Copyright 2018 The glTF-Blender-IO authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import math
+from io_scene_gltf2.io.com import gltf2_io_constants
+
+
+# TODO: we could apply functional programming to these problems (currently we only have a single use case)
+
+def split_list_by_data_type(l: list, data_type: gltf2_io_constants.DataType):
+ """
+ Split a flat list of components by their data type.
+
+ E.g.: A list [0,1,2,3,4,5] of data type Vec3 would be split to [[0,1,2], [3,4,5]]
+ :param l: the flat list
+ :param data_type: the data type of the list
+ :return: a list of lists, where each element list contains the components of the data type
+ """
+ if not (len(l) % gltf2_io_constants.DataType.num_elements(data_type) == 0):
+ raise ValueError("List length does not match specified data type")
+ num_elements = gltf2_io_constants.DataType.num_elements(data_type)
+ return [l[i:i + num_elements] for i in range(0, len(l), num_elements)]
+
+
+def max_components(l: list, data_type: gltf2_io_constants.DataType) -> list:
+ """
+ Find the maximum components in a flat list.
+
+ This is required, for example, for the glTF2.0 accessor min and max properties
+ :param l: the flat list of components
+ :param data_type: the data type of the list (determines the length of the result)
+ :return: a list with length num_elements(data_type) containing the maximum per component along the list
+ """
+ components_lists = split_list_by_data_type(l, data_type)
+ result = [-math.inf] * gltf2_io_constants.DataType.num_elements(data_type)
+ for components in components_lists:
+ for i, c in enumerate(components):
+ result[i] = max(result[i], c)
+ return result
+
+
+def min_components(l: list, data_type: gltf2_io_constants.DataType) -> list:
+ """
+ Find the minimum components in a flat list.
+
+ This is required, for example, for the glTF2.0 accessor min and max properties
+ :param l: the flat list of components
+ :param data_type: the data type of the list (determines the length of the result)
+ :return: a list with length num_elements(data_type) containing the minimum per component along the list
+ """
+ components_lists = split_list_by_data_type(l, data_type)
+ result = [math.inf] * gltf2_io_constants.DataType.num_elements(data_type)
+ for components in components_lists:
+ for i, c in enumerate(components):
+ result[i] = min(result[i], c)
+ return result
+
diff --git a/io_scene_gltf2/blender/imp/gltf2_blender_KHR_materials_pbrSpecularGlossiness.py b/io_scene_gltf2/blender/imp/gltf2_blender_KHR_materials_pbrSpecularGlossiness.py
new file mode 100755
index 00000000..1854f45d
--- /dev/null
+++ b/io_scene_gltf2/blender/imp/gltf2_blender_KHR_materials_pbrSpecularGlossiness.py
@@ -0,0 +1,327 @@
+# Copyright 2018 The glTF-Blender-IO authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import bpy
+from .gltf2_blender_texture import BlenderTextureInfo
+
+
+class BlenderKHR_materials_pbrSpecularGlossiness():
+ """Blender KHR_materials_pbrSpecularGlossiness extension."""
+ def __new__(cls, *args, **kwargs):
+ raise RuntimeError("%s should not be instantiated" % cls)
+
+ @staticmethod
+ def create(gltf, pbrSG, mat_name, vertex_color):
+ """KHR_materials_pbrSpecularGlossiness creation."""
+ engine = bpy.context.scene.render.engine
+ if engine in ['CYCLES', 'BLENDER_EEVEE']:
+ BlenderKHR_materials_pbrSpecularGlossiness.create_nodetree(gltf, pbrSG, mat_name, vertex_color)
+
+ @staticmethod
+ def create_nodetree(gltf, pbrSG, mat_name, vertex_color):
+ """Node tree creation."""
+ material = bpy.data.materials[mat_name]
+ material.use_nodes = True
+ node_tree = material.node_tree
+
+ # delete all nodes except output
+ for node in list(node_tree.nodes):
+ if not node.type == 'OUTPUT_MATERIAL':
+ node_tree.nodes.remove(node)
+
+ output_node = node_tree.nodes[0]
+ output_node.location = 1000, 0
+
+ # create PBR node
+ diffuse = node_tree.nodes.new('ShaderNodeBsdfDiffuse')
+ diffuse.location = 0, 0
+ glossy = node_tree.nodes.new('ShaderNodeBsdfGlossy')
+ glossy.location = 0, 100
+ mix = node_tree.nodes.new('ShaderNodeMixShader')
+ mix.location = 500, 0
+
+ glossy.inputs[1].default_value = 1 - pbrSG['glossinessFactor']
+
+ if pbrSG['diffuse_type'] == gltf.SIMPLE:
+ if not vertex_color:
+ # change input values
+ diffuse.inputs[0].default_value = pbrSG['diffuseFactor']
+
+ else:
+ # Create attribute node to get COLOR_0 data
+ attribute_node = node_tree.nodes.new('ShaderNodeAttribute')
+ attribute_node.attribute_name = 'COLOR_0'
+ attribute_node.location = -500, 0
+
+ # links
+ node_tree.links.new(diffuse.inputs[0], attribute_node.outputs[0])
+
+ elif pbrSG['diffuse_type'] == gltf.TEXTURE_FACTOR:
+
+ # TODO alpha ?
+ if vertex_color:
+ # TODO tree locations
+ # Create attribute / separate / math nodes
+ attribute_node = node_tree.nodes.new('ShaderNodeAttribute')
+ attribute_node.attribute_name = 'COLOR_0'
+
+ separate_vertex_color = node_tree.nodes.new('ShaderNodeSeparateRGB')
+ math_vc_R = node_tree.nodes.new('ShaderNodeMath')
+ math_vc_R.operation = 'MULTIPLY'
+
+ math_vc_G = node_tree.nodes.new('ShaderNodeMath')
+ math_vc_G.operation = 'MULTIPLY'
+
+ math_vc_B = node_tree.nodes.new('ShaderNodeMath')
+ math_vc_B.operation = 'MULTIPLY'
+
+ BlenderTextureInfo.create(gltf, pbrSG['diffuseTexture']['index'])
+
+ # create UV Map / Mapping / Texture nodes / separate & math and combine
+ text_node = node_tree.nodes.new('ShaderNodeTexImage')
+ text_node.image = \
+ bpy.data.images[
+ gltf.data.images[gltf.data.textures[pbrSG['diffuseTexture']['index']].source].blender_image_name
+ ]
+ text_node.location = -1000, 500
+
+ combine = node_tree.nodes.new('ShaderNodeCombineRGB')
+ combine.location = -250, 500
+
+ math_R = node_tree.nodes.new('ShaderNodeMath')
+ math_R.location = -500, 750
+ math_R.operation = 'MULTIPLY'
+ math_R.inputs[1].default_value = pbrSG['diffuseFactor'][0]
+
+ math_G = node_tree.nodes.new('ShaderNodeMath')
+ math_G.location = -500, 500
+ math_G.operation = 'MULTIPLY'
+ math_G.inputs[1].default_value = pbrSG['diffuseFactor'][1]
+
+ math_B = node_tree.nodes.new('ShaderNodeMath')
+ math_B.location = -500, 250
+ math_B.operation = 'MULTIPLY'
+ math_B.inputs[1].default_value = pbrSG['diffuseFactor'][2]
+
+ separate = node_tree.nodes.new('ShaderNodeSeparateRGB')
+ separate.location = -750, 500
+
+ mapping = node_tree.nodes.new('ShaderNodeMapping')
+ mapping.location = -1500, 500
+
+ uvmap = node_tree.nodes.new('ShaderNodeUVMap')
+ uvmap.location = -2000, 500
+ if 'texCoord' in pbrSG['diffuseTexture'].keys():
+ uvmap["gltf2_texcoord"] = pbrSG['diffuseTexture']['texCoord'] # Set custom flag to retrieve TexCoord
+ else:
+ uvmap["gltf2_texcoord"] = 0 # TODO: set in precompute instead of here?
+ # UV Map will be set after object/UVMap creation
+
+ # Create links
+ if vertex_color:
+ node_tree.links.new(separate_vertex_color.inputs[0], attribute_node.outputs[0])
+ node_tree.links.new(math_vc_R.inputs[1], separate_vertex_color.outputs[0])
+ node_tree.links.new(math_vc_G.inputs[1], separate_vertex_color.outputs[1])
+ node_tree.links.new(math_vc_B.inputs[1], separate_vertex_color.outputs[2])
+ node_tree.links.new(math_vc_R.inputs[0], math_R.outputs[0])
+ node_tree.links.new(math_vc_G.inputs[0], math_G.outputs[0])
+ node_tree.links.new(math_vc_B.inputs[0], math_B.outputs[0])
+ node_tree.links.new(combine.inputs[0], math_vc_R.outputs[0])
+ node_tree.links.new(combine.inputs[1], math_vc_G.outputs[0])
+ node_tree.links.new(combine.inputs[2], math_vc_B.outputs[0])
+
+ else:
+ node_tree.links.new(combine.inputs[0], math_R.outputs[0])
+ node_tree.links.new(combine.inputs[1], math_G.outputs[0])
+ node_tree.links.new(combine.inputs[2], math_B.outputs[0])
+
+ # Common for both mode (non vertex color / vertex color)
+ node_tree.links.new(math_R.inputs[0], separate.outputs[0])
+ node_tree.links.new(math_G.inputs[0], separate.outputs[1])
+ node_tree.links.new(math_B.inputs[0], separate.outputs[2])
+
+ node_tree.links.new(mapping.inputs[0], uvmap.outputs[0])
+ node_tree.links.new(text_node.inputs[0], mapping.outputs[0])
+ node_tree.links.new(separate.inputs[0], text_node.outputs[0])
+
+ node_tree.links.new(diffuse.inputs[0], combine.outputs[0])
+
+ elif pbrSG['diffuse_type'] == gltf.TEXTURE:
+
+ BlenderTextureInfo.create(gltf, pbrSG['diffuseTexture']['index'])
+
+ # TODO alpha ?
+ if vertex_color:
+ # Create attribute / separate / math nodes
+ attribute_node = node_tree.nodes.new('ShaderNodeAttribute')
+ attribute_node.attribute_name = 'COLOR_0'
+ attribute_node.location = -2000, 250
+
+ separate_vertex_color = node_tree.nodes.new('ShaderNodeSeparateRGB')
+ separate_vertex_color.location = -1500, 250
+
+ math_vc_R = node_tree.nodes.new('ShaderNodeMath')
+ math_vc_R.operation = 'MULTIPLY'
+ math_vc_R.location = -1000, 750
+
+ math_vc_G = node_tree.nodes.new('ShaderNodeMath')
+ math_vc_G.operation = 'MULTIPLY'
+ math_vc_G.location = -1000, 500
+
+ math_vc_B = node_tree.nodes.new('ShaderNodeMath')
+ math_vc_B.operation = 'MULTIPLY'
+ math_vc_B.location = -1000, 250
+
+ combine = node_tree.nodes.new('ShaderNodeCombineRGB')
+ combine.location = -500, 500
+
+ separate = node_tree.nodes.new('ShaderNodeSeparateRGB')
+ separate.location = -1500, 500
+
+ # create UV Map / Mapping / Texture nodes / separate & math and combine
+ text_node = node_tree.nodes.new('ShaderNodeTexImage')
+ text_node.image = bpy.data.images[
+ gltf.data.images[gltf.data.textures[pbrSG['diffuseTexture']['index']].source].blender_image_name
+ ]
+ if vertex_color:
+ text_node.location = -2000, 500
+ else:
+ text_node.location = -500, 500
+
+ mapping = node_tree.nodes.new('ShaderNodeMapping')
+ if vertex_color:
+ mapping.location = -2500, 500
+ else:
+ mapping.location = -1500, 500
+
+ uvmap = node_tree.nodes.new('ShaderNodeUVMap')
+ if vertex_color:
+ uvmap.location = -3000, 500
+ else:
+ uvmap.location = -2000, 500
+ if 'texCoord' in pbrSG['diffuseTexture'].keys():
+ uvmap["gltf2_texcoord"] = pbrSG['diffuseTexture']['texCoord'] # Set custom flag to retrieve TexCoord
+ else:
+ uvmap["gltf2_texcoord"] = 0 # TODO: set in precompute instead of here?
+ # UV Map will be set after object/UVMap creation
+
+ # Create links
+ if vertex_color:
+ node_tree.links.new(separate_vertex_color.inputs[0], attribute_node.outputs[0])
+
+ node_tree.links.new(math_vc_R.inputs[1], separate_vertex_color.outputs[0])
+ node_tree.links.new(math_vc_G.inputs[1], separate_vertex_color.outputs[1])
+ node_tree.links.new(math_vc_B.inputs[1], separate_vertex_color.outputs[2])
+
+ node_tree.links.new(combine.inputs[0], math_vc_R.outputs[0])
+ node_tree.links.new(combine.inputs[1], math_vc_G.outputs[0])
+ node_tree.links.new(combine.inputs[2], math_vc_B.outputs[0])
+
+ node_tree.links.new(separate.inputs[0], text_node.outputs[0])
+
+ node_tree.links.new(diffuse.inputs[0], combine.outputs[0])
+
+ node_tree.links.new(math_vc_R.inputs[0], separate.outputs[0])
+ node_tree.links.new(math_vc_G.inputs[0], separate.outputs[1])
+ node_tree.links.new(math_vc_B.inputs[0], separate.outputs[2])
+
+ else:
+ node_tree.links.new(diffuse.inputs[0], text_node.outputs[0])
+
+ # Common for both mode (non vertex color / vertex color)
+
+ node_tree.links.new(mapping.inputs[0], uvmap.outputs[0])
+ node_tree.links.new(text_node.inputs[0], mapping.outputs[0])
+
+ if pbrSG['specgloss_type'] == gltf.SIMPLE:
+
+ combine = node_tree.nodes.new('ShaderNodeCombineRGB')
+ combine.inputs[0].default_value = pbrSG['specularFactor'][0]
+ combine.inputs[1].default_value = pbrSG['specularFactor'][1]
+ combine.inputs[2].default_value = pbrSG['specularFactor'][2]
+
+ # links
+ node_tree.links.new(glossy.inputs[0], combine.outputs[0])
+
+ elif pbrSG['specgloss_type'] == gltf.TEXTURE:
+ BlenderTextureInfo.create(gltf, pbrSG['specularGlossinessTexture']['index'])
+ spec_text = node_tree.nodes.new('ShaderNodeTexImage')
+ spec_text.image = bpy.data.images[
+ gltf.data.images[
+ gltf.data.textures[pbrSG['specularGlossinessTexture']['index']].source
+ ].blender_image_name
+ ]
+ spec_text.color_space = 'NONE'
+ spec_text.location = -500, 0
+
+ spec_mapping = node_tree.nodes.new('ShaderNodeMapping')
+ spec_mapping.location = -1000, 0
+
+ spec_uvmap = node_tree.nodes.new('ShaderNodeUVMap')
+ spec_uvmap.location = -1500, 0
+ if 'texCoord' in pbrSG['specularGlossinessTexture'].keys():
+ # Set custom flag to retrieve TexCoord
+ spec_uvmap["gltf2_texcoord"] = pbrSG['specularGlossinessTexture']['texCoord']
+ else:
+ spec_uvmap["gltf2_texcoord"] = 0 # TODO: set in precompute instead of here?
+
+ # links
+ node_tree.links.new(glossy.inputs[0], spec_text.outputs[0])
+ node_tree.links.new(mix.inputs[0], spec_text.outputs[1])
+
+ node_tree.links.new(spec_mapping.inputs[0], spec_uvmap.outputs[0])
+ node_tree.links.new(spec_text.inputs[0], spec_mapping.outputs[0])
+
+ elif pbrSG['specgloss_type'] == gltf.TEXTURE_FACTOR:
+
+ BlenderTextureInfo.create(gltf, pbrSG['specularGlossinessTexture']['index'])
+
+ spec_text = node_tree.nodes.new('ShaderNodeTexImage')
+ spec_text.image = bpy.data.images[gltf.data.images[
+ gltf.data.textures[pbrSG['specularGlossinessTexture']['index']].source
+ ].blender_image_name]
+ spec_text.color_space = 'NONE'
+ spec_text.location = -1000, 0
+
+ spec_math = node_tree.nodes.new('ShaderNodeMath')
+ spec_math.operation = 'MULTIPLY'
+ spec_math.inputs[0].default_value = pbrSG['glossinessFactor']
+ spec_math.location = -250, 100
+
+ spec_mapping = node_tree.nodes.new('ShaderNodeMapping')
+ spec_mapping.location = -1000, 0
+
+ spec_uvmap = node_tree.nodes.new('ShaderNodeUVMap')
+ spec_uvmap.location = -1500, 0
+ if 'texCoord' in pbrSG['specularGlossinessTexture'].keys():
+ # Set custom flag to retrieve TexCoord
+ spec_uvmap["gltf2_texcoord"] = pbrSG['specularGlossinessTexture']['texCoord']
+ else:
+ spec_uvmap["gltf2_texcoord"] = 0 # TODO: set in precompute instead of here?
+
+ # links
+
+ node_tree.links.new(spec_math.inputs[1], spec_text.outputs[0])
+ node_tree.links.new(mix.inputs[0], spec_text.outputs[1])
+ node_tree.links.new(glossy.inputs[1], spec_math.outputs[0])
+ node_tree.links.new(glossy.inputs[0], spec_text.outputs[0])
+
+ node_tree.links.new(spec_mapping.inputs[0], spec_uvmap.outputs[0])
+ node_tree.links.new(spec_text.inputs[0], spec_mapping.outputs[0])
+
+ # link node to output
+ node_tree.links.new(mix.inputs[2], diffuse.outputs[0])
+ node_tree.links.new(mix.inputs[1], glossy.outputs[0])
+ node_tree.links.new(output_node.inputs[0], mix.outputs[0])
+
diff --git a/io_scene_gltf2/blender/imp/gltf2_blender_animation.py b/io_scene_gltf2/blender/imp/gltf2_blender_animation.py
new file mode 100755
index 00000000..4180672a
--- /dev/null
+++ b/io_scene_gltf2/blender/imp/gltf2_blender_animation.py
@@ -0,0 +1,35 @@
+# Copyright 2018 The glTF-Blender-IO authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from .gltf2_blender_animation_bone import BlenderBoneAnim
+from .gltf2_blender_animation_node import BlenderNodeAnim
+
+
+class BlenderAnimation():
+ """Dispatch Animation to bone or object animation."""
+ def __new__(cls, *args, **kwargs):
+ raise RuntimeError("%s should not be instantiated" % cls)
+
+ @staticmethod
+ def anim(gltf, anim_idx, node_idx):
+ """Dispatch Animation to bone or object."""
+ if gltf.data.nodes[node_idx].is_joint:
+ BlenderBoneAnim.anim(gltf, anim_idx, node_idx)
+ else:
+ BlenderNodeAnim.anim(gltf, anim_idx, node_idx)
+
+ if gltf.data.nodes[node_idx].children:
+ for child in gltf.data.nodes[node_idx].children:
+ BlenderAnimation.anim(gltf, anim_idx, child)
+
diff --git a/io_scene_gltf2/blender/imp/gltf2_blender_animation_bone.py b/io_scene_gltf2/blender/imp/gltf2_blender_animation_bone.py
new file mode 100755
index 00000000..c0884966
--- /dev/null
+++ b/io_scene_gltf2/blender/imp/gltf2_blender_animation_bone.py
@@ -0,0 +1,194 @@
+# Copyright 2018 The glTF-Blender-IO authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import bpy
+from mathutils import Matrix
+
+from ..com.gltf2_blender_conversion import loc_gltf_to_blender, quaternion_gltf_to_blender, scale_to_matrix
+from ...io.imp.gltf2_io_binary import BinaryData
+
+
+class BlenderBoneAnim():
+ """Blender Bone Animation."""
+ def __new__(cls, *args, **kwargs):
+ raise RuntimeError("%s should not be instantiated" % cls)
+
+ @staticmethod
+ def set_interpolation(interpolation, kf):
+ """Set interpolation."""
+ if interpolation == "LINEAR":
+ kf.interpolation = 'LINEAR'
+ elif interpolation == "STEP":
+ kf.interpolation = 'CONSTANT'
+ elif interpolation == "CUBICSPLINE":
+ kf.interpolation = 'BEZIER'
+ else:
+ kf.interpolation = 'BEZIER'
+
+ @staticmethod
+ def parse_translation_channel(gltf, node, obj, bone, channel, animation):
+ """Manage Location animation."""
+ fps = bpy.context.scene.render.fps
+ blender_path = "location"
+
+ keys = BinaryData.get_data_from_accessor(gltf, animation.samplers[channel.sampler].input)
+ values = BinaryData.get_data_from_accessor(gltf, animation.samplers[channel.sampler].output)
+ inv_bind_matrix = node.blender_bone_matrix.to_quaternion().to_matrix().to_4x4().inverted() \
+ @ Matrix.Translation(node.blender_bone_matrix.to_translation()).inverted()
+
+ for idx, key in enumerate(keys):
+ if animation.samplers[channel.sampler].interpolation == "CUBICSPLINE":
+ # TODO manage tangent?
+ translation_keyframe = loc_gltf_to_blender(values[idx * 3 + 1])
+ else:
+ translation_keyframe = loc_gltf_to_blender(values[idx])
+ if not node.parent:
+ parent_mat = Matrix()
+ else:
+ if not gltf.data.nodes[node.parent].is_joint:
+ parent_mat = Matrix()
+ else:
+ parent_mat = gltf.data.nodes[node.parent].blender_bone_matrix
+
+ # Pose is in object (armature) space and it's value if the offset from the bind pose
+ # (which is also in object space)
+ # Scale is not taken into account
+ final_trans = (parent_mat @ Matrix.Translation(translation_keyframe)).to_translation()
+ bone.location = inv_bind_matrix @ final_trans
+ bone.keyframe_insert(blender_path, frame=key[0] * fps, group="location")
+
+ for fcurve in [curve for curve in obj.animation_data.action.fcurves if curve.group.name == "location"]:
+ for kf in fcurve.keyframe_points:
+ BlenderBoneAnim.set_interpolation(animation.samplers[channel.sampler].interpolation, kf)
+
+ @staticmethod
+ def parse_rotation_channel(gltf, node, obj, bone, channel, animation):
+ """Manage rotation animation."""
+ # Note: some operations lead to issue with quaternions. Converting to matrix and then back to quaternions breaks
+ # quaternion continuity
+ # (see antipodal quaternions). Blender interpolates between two antipodal quaternions, which causes glitches in
+ # animation.
+ # Converting to euler and then back to quaternion is a dirty fix preventing this issue in animation, until a
+ # better solution is found
+ # This fix is skipped when parent matrix is identity
+ fps = bpy.context.scene.render.fps
+ blender_path = "rotation_quaternion"
+
+ keys = BinaryData.get_data_from_accessor(gltf, animation.samplers[channel.sampler].input)
+ values = BinaryData.get_data_from_accessor(gltf, animation.samplers[channel.sampler].output)
+ bind_rotation = node.blender_bone_matrix.to_quaternion()
+
+ for idx, key in enumerate(keys):
+ if animation.samplers[channel.sampler].interpolation == "CUBICSPLINE":
+ # TODO manage tangent?
+ quat_keyframe = quaternion_gltf_to_blender(values[idx * 3 + 1])
+ else:
+ quat_keyframe = quaternion_gltf_to_blender(values[idx])
+ if not node.parent:
+ bone.rotation_quaternion = bind_rotation.inverted() @ quat_keyframe
+ else:
+ if not gltf.data.nodes[node.parent].is_joint:
+ parent_mat = Matrix()
+ else:
+ parent_mat = gltf.data.nodes[node.parent].blender_bone_matrix
+
+ if parent_mat != parent_mat.inverted():
+ final_rot = (parent_mat @ quat_keyframe.to_matrix().to_4x4()).to_quaternion()
+ bone.rotation_quaternion = bind_rotation.rotation_difference(final_rot).to_euler().to_quaternion()
+ else:
+ bone.rotation_quaternion = \
+ bind_rotation.rotation_difference(quat_keyframe).to_euler().to_quaternion()
+
+ bone.keyframe_insert(blender_path, frame=key[0] * fps, group='rotation')
+
+ for fcurve in [curve for curve in obj.animation_data.action.fcurves if curve.group.name == "rotation"]:
+ for kf in fcurve.keyframe_points:
+ BlenderBoneAnim.set_interpolation(animation.samplers[channel.sampler].interpolation, kf)
+
+ @staticmethod
+ def parse_scale_channel(gltf, node, obj, bone, channel, animation):
+ """Manage scaling animation."""
+ fps = bpy.context.scene.render.fps
+ blender_path = "scale"
+
+ keys = BinaryData.get_data_from_accessor(gltf, animation.samplers[channel.sampler].input)
+ values = BinaryData.get_data_from_accessor(gltf, animation.samplers[channel.sampler].output)
+ bind_scale = scale_to_matrix(node.blender_bone_matrix.to_scale())
+
+ for idx, key in enumerate(keys):
+ if animation.samplers[channel.sampler].interpolation == "CUBICSPLINE":
+ # TODO manage tangent?
+ scale_mat = scale_to_matrix(loc_gltf_to_blender(values[idx * 3 + 1]))
+ else:
+ scale_mat = scale_to_matrix(loc_gltf_to_blender(values[idx]))
+ if not node.parent:
+ bone.scale = (bind_scale.inverted() @ scale_mat).to_scale()
+ else:
+ if not gltf.data.nodes[node.parent].is_joint:
+ parent_mat = Matrix()
+ else:
+ parent_mat = gltf.data.nodes[node.parent].blender_bone_matrix
+
+ bone.scale = (
+ bind_scale.inverted() @ scale_to_matrix(parent_mat.to_scale()) @ scale_mat
+ ).to_scale()
+
+ bone.keyframe_insert(blender_path, frame=key[0] * fps, group='scale')
+
+ for fcurve in [curve for curve in obj.animation_data.action.fcurves if curve.group.name == "scale"]:
+ for kf in fcurve.keyframe_points:
+ BlenderBoneAnim.set_interpolation(animation.samplers[channel.sampler].interpolation, kf)
+
+ @staticmethod
+ def anim(gltf, anim_idx, node_idx):
+ """Manage animation."""
+ node = gltf.data.nodes[node_idx]
+ obj = bpy.data.objects[gltf.data.skins[node.skin_id].blender_armature_name]
+ bone = obj.pose.bones[node.blender_bone_name]
+
+ if anim_idx not in node.animations.keys():
+ return
+
+ animation = gltf.data.animations[anim_idx]
+
+ if animation.name:
+ name = animation.name + "_" + obj.name
+ else:
+ name = "Animation_" + str(anim_idx) + "_" + obj.name
+ if name not in bpy.data.actions:
+ action = bpy.data.actions.new(name)
+ else:
+ action = bpy.data.actions[name]
+ # Check if this action has some users.
+ # If no user (only 1 indeed), that means that this action must be deleted
+ # (is an action from a deleted object)
+ if action.users == 1:
+ bpy.data.actions.remove(action)
+ action = bpy.data.actions.new(name)
+ if not obj.animation_data:
+ obj.animation_data_create()
+ obj.animation_data.action = bpy.data.actions[action.name]
+
+ for channel_idx in node.animations[anim_idx]:
+ channel = animation.channels[channel_idx]
+
+ if channel.target.path == "translation":
+ BlenderBoneAnim.parse_translation_channel(gltf, node, obj, bone, channel, animation)
+
+ elif channel.target.path == "rotation":
+ BlenderBoneAnim.parse_rotation_channel(gltf, node, obj, bone, channel, animation)
+
+ elif channel.target.path == "scale":
+ BlenderBoneAnim.parse_scale_channel(gltf, node, obj, bone, channel, animation)
+
diff --git a/io_scene_gltf2/blender/imp/gltf2_blender_animation_node.py b/io_scene_gltf2/blender/imp/gltf2_blender_animation_node.py
new file mode 100755
index 00000000..c053f84a
--- /dev/null
+++ b/io_scene_gltf2/blender/imp/gltf2_blender_animation_node.py
@@ -0,0 +1,142 @@
+# Copyright 2018 The glTF-Blender-IO authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import bpy
+from mathutils import Vector
+
+from ..com.gltf2_blender_conversion import loc_gltf_to_blender, quaternion_gltf_to_blender, scale_gltf_to_blender
+from ...io.imp.gltf2_io_binary import BinaryData
+
+
+class BlenderNodeAnim():
+ """Blender Object Animation."""
+ def __new__(cls, *args, **kwargs):
+ raise RuntimeError("%s should not be instantiated" % cls)
+
+ @staticmethod
+ def set_interpolation(interpolation, kf):
+ """Manage interpolation."""
+ if interpolation == "LINEAR":
+ kf.interpolation = 'LINEAR'
+ elif interpolation == "STEP":
+ kf.interpolation = 'CONSTANT'
+ elif interpolation == "CUBICSPLINE":
+ kf.interpolation = 'BEZIER'
+ else:
+ kf.interpolation = 'BEZIER'
+
+ @staticmethod
+ def anim(gltf, anim_idx, node_idx):
+ """Manage animation."""
+ node = gltf.data.nodes[node_idx]
+ obj = bpy.data.objects[node.blender_object]
+ fps = bpy.context.scene.render.fps
+
+ if anim_idx not in node.animations.keys():
+ return
+
+ animation = gltf.data.animations[anim_idx]
+
+ if animation.name:
+ name = animation.name + "_" + obj.name
+ else:
+ name = "Animation_" + str(anim_idx) + "_" + obj.name
+ action = bpy.data.actions.new(name)
+ # Check if this action has some users.
+ # If no user (only 1 indeed), that means that this action must be deleted
+ # (is an action from a deleted object)
+ if action.users == 1:
+ bpy.data.actions.remove(action)
+ action = bpy.data.actions.new(name)
+ if not obj.animation_data:
+ obj.animation_data_create()
+ obj.animation_data.action = bpy.data.actions[action.name]
+
+ for channel_idx in node.animations[anim_idx]:
+ channel = animation.channels[channel_idx]
+
+ keys = BinaryData.get_data_from_accessor(gltf, animation.samplers[channel.sampler].input)
+ values = BinaryData.get_data_from_accessor(gltf, animation.samplers[channel.sampler].output)
+
+ if channel.target.path in ['translation', 'rotation', 'scale']:
+
+ # There is an animation on object
+ # We can't remove Yup2Zup oject
+ gltf.animation_object = True
+
+ if channel.target.path == "translation":
+ blender_path = "location"
+ for idx, key in enumerate(keys):
+ if animation.samplers[channel.sampler].interpolation == "CUBICSPLINE":
+ # TODO manage tangent?
+ obj.location = Vector(loc_gltf_to_blender(list(values[idx * 3 + 1])))
+ else:
+ obj.location = Vector(loc_gltf_to_blender(list(values[idx])))
+ obj.keyframe_insert(blender_path, frame=key[0] * fps, group='location')
+
+ # Setting interpolation
+ for fcurve in [curve for curve in obj.animation_data.action.fcurves
+ if curve.group.name == "location"]:
+ for kf in fcurve.keyframe_points:
+ BlenderNodeAnim.set_interpolation(animation.samplers[channel.sampler].interpolation, kf)
+
+ elif channel.target.path == "rotation":
+ blender_path = "rotation_quaternion"
+ for idx, key in enumerate(keys):
+ if animation.samplers[channel.sampler].interpolation == "CUBICSPLINE":
+ # TODO manage tangent?
+ obj.rotation_quaternion = quaternion_gltf_to_blender(values[idx * 3 + 1])
+ else:
+ obj.rotation_quaternion = quaternion_gltf_to_blender(values[idx])
+ obj.keyframe_insert(blender_path, frame=key[0] * fps, group='rotation')
+
+ # Setting interpolation
+ for fcurve in [curve for curve in obj.animation_data.action.fcurves
+ if curve.group.name == "rotation"]:
+ for kf in fcurve.keyframe_points:
+ BlenderNodeAnim.set_interpolation(animation.samplers[channel.sampler].interpolation, kf)
+
+ elif channel.target.path == "scale":
+ blender_path = "scale"
+ for idx, key in enumerate(keys):
+ # TODO manage tangent?
+ if animation.samplers[channel.sampler].interpolation == "CUBICSPLINE":
+ obj.scale = Vector(scale_gltf_to_blender(list(values[idx * 3 + 1])))
+ else:
+ obj.scale = Vector(scale_gltf_to_blender(list(values[idx])))
+ obj.keyframe_insert(blender_path, frame=key[0] * fps, group='scale')
+
+ # Setting interpolation
+ for fcurve in [curve for curve in obj.animation_data.action.fcurves if curve.group.name == "scale"]:
+ for kf in fcurve.keyframe_points:
+ BlenderNodeAnim.set_interpolation(animation.samplers[channel.sampler].interpolation, kf)
+
+ elif channel.target.path == 'weights':
+
+ # retrieve number of targets
+ nb_targets = 0
+ for prim in gltf.data.meshes[gltf.data.nodes[node_idx].mesh].primitives:
+ if prim.targets:
+ if len(prim.targets) > nb_targets:
+ nb_targets = len(prim.targets)
+
+ for idx, key in enumerate(keys):
+ for sk in range(nb_targets):
+ obj.data.shape_keys.key_blocks[sk + 1].value = values[idx * nb_targets + sk][0]
+ obj.data.shape_keys.key_blocks[sk + 1].keyframe_insert(
+ "value",
+ frame=key[0] * fps,
+ group='ShapeKeys'
+ )
+
diff --git a/io_scene_gltf2/blender/imp/gltf2_blender_camera.py b/io_scene_gltf2/blender/imp/gltf2_blender_camera.py
new file mode 100755
index 00000000..b5a10ac5
--- /dev/null
+++ b/io_scene_gltf2/blender/imp/gltf2_blender_camera.py
@@ -0,0 +1,47 @@
+# Copyright 2018 The glTF-Blender-IO authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import bpy
+
+
+class BlenderCamera():
+ """Blender Camera."""
+ def __new__(cls, *args, **kwargs):
+ raise RuntimeError("%s should not be instantiated" % cls)
+
+ @staticmethod
+ def create(gltf, camera_id):
+ """Camera creation."""
+ pycamera = gltf.data.cameras[camera_id]
+
+ if not pycamera.name:
+ pycamera.name = "Camera"
+
+ cam = bpy.data.cameras.new(pycamera.name)
+
+ # Blender create a perspective camera by default
+ if pycamera.type == "orthographic":
+ cam.type = "ORTHO"
+
+ # TODO: lot's of work for camera here...
+ if hasattr(pycamera, "znear"):
+ cam.clip_start = pycamera.znear
+
+ if hasattr(pycamera, "zfar"):
+ cam.clip_end = pycamera.zfar
+
+ obj = bpy.data.objects.new(pycamera.name, cam)
+ bpy.data.scenes[gltf.blender_scene].collection.objects.link(obj)
+ return obj
+
diff --git a/io_scene_gltf2/blender/imp/gltf2_blender_gltf.py b/io_scene_gltf2/blender/imp/gltf2_blender_gltf.py
new file mode 100755
index 00000000..1f708b40
--- /dev/null
+++ b/io_scene_gltf2/blender/imp/gltf2_blender_gltf.py
@@ -0,0 +1,225 @@
+# Copyright 2018 The glTF-Blender-IO authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import bpy
+from .gltf2_blender_scene import BlenderScene
+from ...io.com.gltf2_io_trs import TRS
+
+
+class BlenderGlTF():
+ """Main glTF import class."""
+ def __new__(cls, *args, **kwargs):
+ raise RuntimeError("%s should not be instantiated" % cls)
+
+ @staticmethod
+ def create(gltf):
+ """Create glTF main method."""
+ bpy.context.scene.render.engine = 'BLENDER_EEVEE'
+ BlenderGlTF.pre_compute(gltf)
+
+ for scene_idx, scene in enumerate(gltf.data.scenes):
+ BlenderScene.create(gltf, scene_idx)
+
+ # Armature correction
+ # Try to detect bone chains, and set bone lengths
+ # To detect if a bone is in a chain, we try to detect if a bone head is aligned
+ # with parent_bone :
+ # Parent bone defined a line (between head & tail)
+ # Bone head defined a point
+ # Calcul of distance between point and line
+ # If < threshold --> In a chain
+ # Based on an idea of @Menithal, but added alignement detection to avoid some bad cases
+
+ threshold = 0.001
+ for armobj in [obj for obj in bpy.data.objects if obj.type == "ARMATURE"]:
+ bpy.context.view_layer.objects.active = armobj
+ armature = armobj.data
+ bpy.ops.object.mode_set(mode="EDIT")
+ for bone in armature.edit_bones:
+ if bone.parent is None:
+ continue
+
+ parent = bone.parent
+
+ # case where 2 bones are aligned (not in chain, same head)
+ if (bone.head - parent.head).length < threshold:
+ continue
+
+ u = (parent.tail - parent.head).normalized()
+ point = bone.head
+ distance = ((point - parent.head).cross(u)).length / u.length
+ if distance < threshold:
+ save_parent_direction = (parent.tail - parent.head).normalized().copy()
+ save_parent_tail = parent.tail.copy()
+ parent.tail = bone.head
+
+ # case where 2 bones are aligned (not in chain, same head)
+ # bone is no more is same direction
+ if (parent.tail - parent.head).normalized().dot(save_parent_direction) < 0.9:
+ parent.tail = save_parent_tail
+
+ bpy.ops.object.mode_set(mode="OBJECT")
+
+ @staticmethod
+ def pre_compute(gltf):
+ """Pre compute, just before creation."""
+ # default scene used
+ gltf.blender_scene = None
+
+ # Check if there is animation on object
+ # Init is to False, and will be set to True during creation
+ gltf.animation_object = False
+
+ # Blender material
+ if gltf.data.materials:
+ for material in gltf.data.materials:
+ material.blender_material = {}
+
+ if material.pbr_metallic_roughness:
+ # Init
+ material.pbr_metallic_roughness.color_type = gltf.SIMPLE
+ material.pbr_metallic_roughness.vertex_color = False
+ material.pbr_metallic_roughness.metallic_type = gltf.SIMPLE
+
+ if material.pbr_metallic_roughness.base_color_texture:
+ material.pbr_metallic_roughness.color_type = gltf.TEXTURE
+
+ if material.pbr_metallic_roughness.metallic_roughness_texture:
+ material.pbr_metallic_roughness.metallic_type = gltf.TEXTURE
+
+ if material.pbr_metallic_roughness.base_color_factor:
+ if material.pbr_metallic_roughness.color_type == gltf.TEXTURE and \
+ material.pbr_metallic_roughness.base_color_factor != [1.0, 1.0, 1.0, 1.0]:
+ material.pbr_metallic_roughness.color_type = gltf.TEXTURE_FACTOR
+ else:
+ material.pbr_metallic_roughness.base_color_factor = [1.0, 1.0, 1.0, 1.0]
+
+ if material.pbr_metallic_roughness.metallic_factor is not None:
+ if material.pbr_metallic_roughness.metallic_type == gltf.TEXTURE \
+ and material.pbr_metallic_roughness.metallic_factor != 1.0:
+ material.pbr_metallic_roughness.metallic_type = gltf.TEXTURE_FACTOR
+ else:
+ material.pbr_metallic_roughness.metallic_factor = 1.0
+
+ if material.pbr_metallic_roughness.roughness_factor is not None:
+ if material.pbr_metallic_roughness.metallic_type == gltf.TEXTURE \
+ and material.pbr_metallic_roughness.roughness_factor != 1.0:
+ material.pbr_metallic_roughness.metallic_type = gltf.TEXTURE_FACTOR
+ else:
+ material.pbr_metallic_roughness.roughness_factor = 1.0
+
+ # pre compute material for KHR_materials_pbrSpecularGlossiness
+ if material.extensions is not None \
+ and 'KHR_materials_pbrSpecularGlossiness' in material.extensions.keys():
+ # Init
+ material.extensions['KHR_materials_pbrSpecularGlossiness']['diffuse_type'] = gltf.SIMPLE
+ material.extensions['KHR_materials_pbrSpecularGlossiness']['vertex_color'] = False
+ material.extensions['KHR_materials_pbrSpecularGlossiness']['specgloss_type'] = gltf.SIMPLE
+
+ if 'diffuseTexture' in material.extensions['KHR_materials_pbrSpecularGlossiness'].keys():
+ material.extensions['KHR_materials_pbrSpecularGlossiness']['diffuse_type'] = gltf.TEXTURE
+
+ if 'diffuseFactor' in material.extensions['KHR_materials_pbrSpecularGlossiness'].keys():
+ if material.extensions['KHR_materials_pbrSpecularGlossiness']['diffuse_type'] == gltf.TEXTURE \
+ and material.extensions['KHR_materials_pbrSpecularGlossiness']['diffuseFactor'] != \
+ [1.0, 1.0, 1.0, 1.0]:
+ material.extensions['KHR_materials_pbrSpecularGlossiness']['diffuse_type'] = \
+ gltf.TEXTURE_FACTOR
+ else:
+ material.extensions['KHR_materials_pbrSpecularGlossiness']['diffuseFactor'] = \
+ [1.0, 1.0, 1.0, 1.0]
+
+ if 'specularGlossinessTexture' in material.extensions['KHR_materials_pbrSpecularGlossiness'].keys():
+ material.extensions['KHR_materials_pbrSpecularGlossiness']['specgloss_type'] = gltf.TEXTURE
+
+ if 'specularFactor' in material.extensions['KHR_materials_pbrSpecularGlossiness'].keys():
+ if material.extensions['KHR_materials_pbrSpecularGlossiness']['specgloss_type'] == \
+ gltf.TEXTURE \
+ and material.extensions['KHR_materials_pbrSpecularGlossiness']['specularFactor'] != \
+ [1.0, 1.0, 1.0]:
+ material.extensions['KHR_materials_pbrSpecularGlossiness']['specgloss_type'] = \
+ gltf.TEXTURE_FACTOR
+ else:
+ material.extensions['KHR_materials_pbrSpecularGlossiness']['specularFactor'] = [1.0, 1.0, 1.0]
+
+ if 'glossinessFactor' not in material.extensions['KHR_materials_pbrSpecularGlossiness'].keys():
+ material.extensions['KHR_materials_pbrSpecularGlossiness']['glossinessFactor'] = 1.0
+
+ if gltf.data.nodes is None:
+ # Something is wrong in file, there is no nodes
+ return
+
+ for node_idx, node in enumerate(gltf.data.nodes):
+
+ # skin management
+ if node.skin is not None and node.mesh is not None:
+ if not hasattr(gltf.data.skins[node.skin], "node_ids"):
+ gltf.data.skins[node.skin].node_ids = []
+
+ gltf.data.skins[node.skin].node_ids.append(node_idx)
+
+ # transform management
+ if node.matrix:
+ node.transform = node.matrix
+ continue
+
+ # No matrix, but TRS
+ mat = [1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0] # init
+
+ if node.scale:
+ mat = TRS.scale_to_matrix(node.scale)
+
+ if node.rotation:
+ q_mat = TRS.quaternion_to_matrix(node.rotation)
+ mat = TRS.matrix_multiply(q_mat, mat)
+
+ if node.translation:
+ loc_mat = TRS.translation_to_matrix(node.translation)
+ mat = TRS.matrix_multiply(loc_mat, mat)
+
+ node.transform = mat
+
+ # joint management
+ for node_idx, node in enumerate(gltf.data.nodes):
+ is_joint, skin_idx = gltf.is_node_joint(node_idx)
+ if is_joint:
+ node.is_joint = True
+ node.skin_id = skin_idx
+ else:
+ node.is_joint = False
+
+ if gltf.data.skins:
+ for skin_id, skin in enumerate(gltf.data.skins):
+ # init blender values
+ skin.blender_armature_name = None
+ # if skin.skeleton and skin.skeleton not in skin.joints:
+ # gltf.data.nodes[skin.skeleton].is_joint = True
+ # gltf.data.nodes[skin.skeleton].skin_id = skin_id
+
+ # Dispatch animation
+ if gltf.data.animations:
+ for node_idx, node in enumerate(gltf.data.nodes):
+ node.animations = {}
+
+ for anim_idx, anim in enumerate(gltf.data.animations):
+ for channel_idx, channel in enumerate(anim.channels):
+ if anim_idx not in gltf.data.nodes[channel.target.node].animations.keys():
+ gltf.data.nodes[channel.target.node].animations[anim_idx] = []
+ gltf.data.nodes[channel.target.node].animations[anim_idx].append(channel_idx)
+
+ # Meshes
+ if gltf.data.meshes:
+ for mesh in gltf.data.meshes:
+ mesh.blender_name = None
+
diff --git a/io_scene_gltf2/blender/imp/gltf2_blender_image.py b/io_scene_gltf2/blender/imp/gltf2_blender_image.py
new file mode 100755
index 00000000..ca1eb626
--- /dev/null
+++ b/io_scene_gltf2/blender/imp/gltf2_blender_image.py
@@ -0,0 +1,101 @@
+# Copyright 2018 The glTF-Blender-IO authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import bpy
+import os
+import tempfile
+from os.path import dirname, join, isfile, basename
+
+from ...io.imp.gltf2_io_binary import BinaryData
+
+
+# Note that Image is not a glTF2.0 object
+class BlenderImage():
+ """Manage Image."""
+ def __new__(cls, *args, **kwargs):
+ raise RuntimeError("%s should not be instantiated" % cls)
+
+ @staticmethod
+ def get_image_path(gltf, img_idx):
+ """Return image path."""
+ pyimage = gltf.data.images[img_idx]
+
+ image_name = "Image_" + str(img_idx)
+
+ if pyimage.uri:
+ sep = ';base64,'
+ if pyimage.uri[:5] == 'data:':
+ idx = pyimage.uri.find(sep)
+ if idx != -1:
+ return False, None, None
+
+ if isfile(join(dirname(gltf.filename), pyimage.uri)):
+ return True, join(dirname(gltf.filename), pyimage.uri), basename(join(dirname(gltf.filename), pyimage.uri))
+ else:
+ pyimage.gltf.log.error("Missing file (index " + str(img_idx) + "): " + pyimage.uri)
+ return False, None, None
+
+ if pyimage.buffer_view is None:
+ return False, None, None
+
+ return False, None, None
+
+ @staticmethod
+ def create(gltf, img_idx):
+ """Image creation."""
+ img = gltf.data.images[img_idx]
+
+ img.blender_image_name = None
+
+ if gltf.import_settings['import_pack_images'] is False:
+
+ # Images are not packed (if image is a real file)
+ real, path, img_name = BlenderImage.get_image_path(gltf, img_idx)
+
+ if real is True:
+
+ # Check if image is already loaded
+ for img_ in bpy.data.images:
+ if img_.filepath == path:
+ # Already loaded, not needed to reload it
+ img.blender_image_name = img_.name
+ return
+
+ blender_image = bpy.data.images.load(path)
+ blender_image.name = img_name
+ img.blender_image_name = blender_image.name
+ return
+
+ # Check if the file is already loaded (packed file)
+ file_creation_needed = True
+ for img_ in bpy.data.images:
+ if hasattr(img_, "gltf_index") and img_['gltf_index'] == img_idx:
+ file_creation_needed = False
+ img.blender_image_name = img_.name
+ break
+
+ if file_creation_needed is True:
+ # Create a temp image, pack, and delete image
+ tmp_image = tempfile.NamedTemporaryFile(delete=False)
+ img_data, img_name = BinaryData.get_image_data(gltf, img_idx)
+ tmp_image.write(img_data)
+ tmp_image.close()
+
+ blender_image = bpy.data.images.load(tmp_image.name)
+ blender_image.pack()
+ blender_image.name = img_name
+ img.blender_image_name = blender_image.name
+ blender_image['gltf_index'] = img_idx
+ os.remove(tmp_image.name)
+
diff --git a/io_scene_gltf2/blender/imp/gltf2_blender_map_emissive.py b/io_scene_gltf2/blender/imp/gltf2_blender_map_emissive.py
new file mode 100755
index 00000000..c3d0cb22
--- /dev/null
+++ b/io_scene_gltf2/blender/imp/gltf2_blender_map_emissive.py
@@ -0,0 +1,110 @@
+# Copyright 2018 The glTF-Blender-IO authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import bpy
+from .gltf2_blender_texture import BlenderTextureInfo
+from ..com.gltf2_blender_material_helpers import get_preoutput_node_output
+
+
+class BlenderEmissiveMap():
+ """Blender Emissive Map."""
+ def __new__(cls, *args, **kwargs):
+ raise RuntimeError("%s should not be instantiated" % cls)
+
+ @staticmethod
+ def create(gltf, material_idx, vertex_color):
+ """Create emissive map."""
+ engine = bpy.context.scene.render.engine
+ if engine in ['CYCLES', 'BLENDER_EEVEE']:
+ BlenderEmissiveMap.create_nodetree(gltf, material_idx, vertex_color)
+
+ def create_nodetree(gltf, material_idx, vertex_color):
+ """Create node tree."""
+ pymaterial = gltf.data.materials[material_idx]
+
+ material = bpy.data.materials[pymaterial.blender_material[vertex_color]]
+ node_tree = material.node_tree
+
+ BlenderTextureInfo.create(gltf, pymaterial.emissive_texture.index)
+
+ # check if there is some emssive_factor on material
+ if pymaterial.emissive_factor is None:
+ pymaterial.emissive_factor = [1.0, 1.0, 1.0]
+
+ # retrieve principled node and output node
+ principled = get_preoutput_node_output(node_tree)
+ output = [node for node in node_tree.nodes if node.type == 'OUTPUT_MATERIAL'][0]
+
+ # add nodes
+ emit = node_tree.nodes.new('ShaderNodeEmission')
+ emit.location = 0, 1000
+ if pymaterial.emissive_factor != [1.0, 1.0, 1.0]:
+ separate = node_tree.nodes.new('ShaderNodeSeparateRGB')
+ separate.location = -750, 1000
+ combine = node_tree.nodes.new('ShaderNodeCombineRGB')
+ combine.location = -250, 1000
+ mapping = node_tree.nodes.new('ShaderNodeMapping')
+ mapping.location = -1500, 1000
+ uvmap = node_tree.nodes.new('ShaderNodeUVMap')
+ uvmap.location = -2000, 1000
+ if pymaterial.emissive_texture.tex_coord is not None:
+ uvmap["gltf2_texcoord"] = pymaterial.emissive_texture.tex_coord # Set custom flag to retrieve TexCoord
+ else:
+ uvmap["gltf2_texcoord"] = 0 # TODO: set in precompute instead of here?
+
+ text = node_tree.nodes.new('ShaderNodeTexImage')
+ text.image = bpy.data.images[gltf.data.images[
+ gltf.data.textures[pymaterial.emissive_texture.index].source
+ ].blender_image_name]
+ text.label = 'EMISSIVE'
+ text.location = -1000, 1000
+ add = node_tree.nodes.new('ShaderNodeAddShader')
+ add.location = 500, 500
+
+ if pymaterial.emissive_factor != [1.0, 1.0, 1.0]:
+ math_R = node_tree.nodes.new('ShaderNodeMath')
+ math_R.location = -500, 1500
+ math_R.operation = 'MULTIPLY'
+ math_R.inputs[1].default_value = pymaterial.emissive_factor[0]
+
+ math_G = node_tree.nodes.new('ShaderNodeMath')
+ math_G.location = -500, 1250
+ math_G.operation = 'MULTIPLY'
+ math_G.inputs[1].default_value = pymaterial.emissive_factor[1]
+
+ math_B = node_tree.nodes.new('ShaderNodeMath')
+ math_B.location = -500, 1000
+ math_B.operation = 'MULTIPLY'
+ math_B.inputs[1].default_value = pymaterial.emissive_factor[2]
+
+ # create links
+ node_tree.links.new(mapping.inputs[0], uvmap.outputs[0])
+ node_tree.links.new(text.inputs[0], mapping.outputs[0])
+ if pymaterial.emissive_factor != [1.0, 1.0, 1.0]:
+ node_tree.links.new(separate.inputs[0], text.outputs[0])
+ node_tree.links.new(math_R.inputs[0], separate.outputs[0])
+ node_tree.links.new(math_G.inputs[0], separate.outputs[1])
+ node_tree.links.new(math_B.inputs[0], separate.outputs[2])
+ node_tree.links.new(combine.inputs[0], math_R.outputs[0])
+ node_tree.links.new(combine.inputs[1], math_G.outputs[0])
+ node_tree.links.new(combine.inputs[2], math_B.outputs[0])
+ node_tree.links.new(emit.inputs[0], combine.outputs[0])
+ else:
+ node_tree.links.new(emit.inputs[0], text.outputs[0])
+
+ # following links will modify PBR node tree
+ node_tree.links.new(add.inputs[0], emit.outputs[0])
+ node_tree.links.new(add.inputs[1], principled)
+ node_tree.links.new(output.inputs[0], add.outputs[0])
+
diff --git a/io_scene_gltf2/blender/imp/gltf2_blender_map_normal.py b/io_scene_gltf2/blender/imp/gltf2_blender_map_normal.py
new file mode 100755
index 00000000..0f7bab81
--- /dev/null
+++ b/io_scene_gltf2/blender/imp/gltf2_blender_map_normal.py
@@ -0,0 +1,89 @@
+# Copyright 2018 The glTF-Blender-IO authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import bpy
+from .gltf2_blender_texture import BlenderTextureInfo
+
+
+class BlenderNormalMap():
+ """Blender Normal map."""
+ def __new__(cls, *args, **kwargs):
+ raise RuntimeError("%s should not be instantiated" % cls)
+
+ @staticmethod
+ def create(gltf, material_idx, vertex_color):
+ """Creation of Normal map."""
+ engine = bpy.context.scene.render.engine
+ if engine in ['CYCLES', 'BLENDER_EEVEE']:
+ BlenderNormalMap.create_nodetree(gltf, material_idx, vertex_color)
+
+ def create_nodetree(gltf, material_idx, vertex_color):
+ """Creation of Nodetree."""
+ pymaterial = gltf.data.materials[material_idx]
+
+ material = bpy.data.materials[pymaterial.blender_material[vertex_color]]
+ node_tree = material.node_tree
+
+ BlenderTextureInfo.create(gltf, pymaterial.normal_texture.index)
+
+ # retrieve principled node and output node
+ principled = None
+ diffuse = None
+ glossy = None
+ if len([node for node in node_tree.nodes if node.type == "BSDF_PRINCIPLED"]) != 0:
+ principled = [node for node in node_tree.nodes if node.type == "BSDF_PRINCIPLED"][0]
+ else:
+ # No principled, we are probably coming from extension
+ diffuse = [node for node in node_tree.nodes if node.type == "BSDF_DIFFUSE"][0]
+ glossy = [node for node in node_tree.nodes if node.type == "BSDF_GLOSSY"][0]
+
+ # add nodes
+ mapping = node_tree.nodes.new('ShaderNodeMapping')
+ mapping.location = -1000, -500
+ uvmap = node_tree.nodes.new('ShaderNodeUVMap')
+ uvmap.location = -1500, -500
+ if pymaterial.normal_texture.tex_coord is not None:
+ uvmap["gltf2_texcoord"] = pymaterial.normal_texture.tex_coord # Set custom flag to retrieve TexCoord
+ else:
+ uvmap["gltf2_texcoord"] = 0 # TODO set in pre_compute instead of here
+
+ text = node_tree.nodes.new('ShaderNodeTexImage')
+ text.image = bpy.data.images[gltf.data.images[
+ gltf.data.textures[pymaterial.normal_texture.index].source
+ ].blender_image_name]
+ text.label = 'NORMALMAP'
+ text.color_space = 'NONE'
+ text.location = -500, -500
+
+ normalmap_node = node_tree.nodes.new('ShaderNodeNormalMap')
+ normalmap_node.location = -250, -500
+ if pymaterial.normal_texture.tex_coord is not None:
+ # Set custom flag to retrieve TexCoord
+ normalmap_node["gltf2_texcoord"] = pymaterial.normal_texture.tex_coord
+ else:
+ normalmap_node["gltf2_texcoord"] = 0 # TODO set in pre_compute instead of here
+
+ # create links
+ node_tree.links.new(mapping.inputs[0], uvmap.outputs[0])
+ node_tree.links.new(text.inputs[0], mapping.outputs[0])
+ node_tree.links.new(normalmap_node.inputs[1], text.outputs[0])
+
+ # following links will modify PBR node tree
+ if principled:
+ node_tree.links.new(principled.inputs[17], normalmap_node.outputs[0])
+ if diffuse:
+ node_tree.links.new(diffuse.inputs[2], normalmap_node.outputs[0])
+ if glossy:
+ node_tree.links.new(glossy.inputs[2], normalmap_node.outputs[0])
+
diff --git a/io_scene_gltf2/blender/imp/gltf2_blender_map_occlusion.py b/io_scene_gltf2/blender/imp/gltf2_blender_map_occlusion.py
new file mode 100755
index 00000000..70e1e54a
--- /dev/null
+++ b/io_scene_gltf2/blender/imp/gltf2_blender_map_occlusion.py
@@ -0,0 +1,41 @@
+# Copyright 2018 The glTF-Blender-IO authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import bpy
+from .gltf2_blender_texture import BlenderTextureInfo
+
+
+class BlenderOcclusionMap():
+ """Blender Occlusion map."""
+ def __new__(cls, *args, **kwargs):
+ raise RuntimeError("%s should not be instantiated" % cls)
+
+ @staticmethod
+ def create(gltf, material_idx, vertex_color):
+ """Occlusion map creation."""
+ engine = bpy.context.scene.render.engine
+ if engine in ['CYCLES', 'BLENDER_EEVEE']:
+ BlenderOcclusionMap.create_nodetree(gltf, material_idx, vertex_color)
+
+ def create_nodetree(gltf, material_idx, vertex_color):
+ """Nodetree creation."""
+ pymaterial = gltf.data.materials[material_idx]
+
+ BlenderTextureInfo.create(gltf, pymaterial.occlusion_texture.index)
+
+ # Pack texture, but doesn't use it for now. Occlusion is calculated from Cycles.
+ bpy.data.images[gltf.data.images[gltf.data.textures[
+ pymaterial.occlusion_texture.index
+ ].source].blender_image_name].use_fake_user = True
+
diff --git a/io_scene_gltf2/blender/imp/gltf2_blender_material.py b/io_scene_gltf2/blender/imp/gltf2_blender_material.py
new file mode 100755
index 00000000..c910b7f8
--- /dev/null
+++ b/io_scene_gltf2/blender/imp/gltf2_blender_material.py
@@ -0,0 +1,156 @@
+# Copyright 2018 The glTF-Blender-IO authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import bpy
+from .gltf2_blender_pbrMetallicRoughness import BlenderPbr
+from .gltf2_blender_KHR_materials_pbrSpecularGlossiness import BlenderKHR_materials_pbrSpecularGlossiness
+from .gltf2_blender_map_emissive import BlenderEmissiveMap
+from .gltf2_blender_map_normal import BlenderNormalMap
+from .gltf2_blender_map_occlusion import BlenderOcclusionMap
+from ..com.gltf2_blender_material_helpers import get_output_surface_input
+from ..com.gltf2_blender_material_helpers import get_preoutput_node_output
+from ..com.gltf2_blender_material_helpers import get_base_color_node
+from ...io.com.gltf2_io import MaterialPBRMetallicRoughness
+
+
+class BlenderMaterial():
+ """Blender Material."""
+ def __new__(cls, *args, **kwargs):
+ raise RuntimeError("%s should not be instantiated" % cls)
+
+ @staticmethod
+ def create(gltf, material_idx, vertex_color):
+ """Material creation."""
+ pymaterial = gltf.data.materials[material_idx]
+
+ if vertex_color is None:
+ if pymaterial.name is not None:
+ name = pymaterial.name
+ else:
+ name = "Material_" + str(material_idx)
+ else:
+ if pymaterial.name is not None:
+ name = pymaterial.name + "_" + vertex_color
+ else:
+ name = "Material_" + str(material_idx) + "_" + vertex_color
+
+ mat = bpy.data.materials.new(name)
+ pymaterial.blender_material[vertex_color] = mat.name
+
+ if pymaterial.extensions is not None and 'KHR_materials_pbrSpecularGlossiness' in pymaterial.extensions.keys():
+ BlenderKHR_materials_pbrSpecularGlossiness.create(
+ gltf, pymaterial.extensions['KHR_materials_pbrSpecularGlossiness'], mat.name, vertex_color
+ )
+ else:
+ # create pbr material
+ if pymaterial.pbr_metallic_roughness is None:
+ # If no pbr material is set, we need to apply all default of pbr
+ pbr = {}
+ pbr["baseColorFactor"] = [1.0, 1.0, 1.0, 1.0]
+ pbr["metallicFactor"] = 1.0
+ pbr["roughnessFactor"] = 1.0
+ pymaterial.pbr_metallic_roughness = MaterialPBRMetallicRoughness.from_dict(pbr)
+ pymaterial.pbr_metallic_roughness.color_type = gltf.SIMPLE
+ pymaterial.pbr_metallic_roughness.metallic_type = gltf.SIMPLE
+
+ BlenderPbr.create(gltf, pymaterial.pbr_metallic_roughness, mat.name, vertex_color)
+
+ # add emission map if needed
+ if pymaterial.emissive_texture is not None:
+ BlenderEmissiveMap.create(gltf, material_idx, vertex_color)
+
+ # add normal map if needed
+ if pymaterial.normal_texture is not None:
+ BlenderNormalMap.create(gltf, material_idx, vertex_color)
+
+ # add occlusion map if needed
+ # will be pack, but not used
+ if pymaterial.occlusion_texture is not None:
+ BlenderOcclusionMap.create(gltf, material_idx, vertex_color)
+
+ if pymaterial.alpha_mode is not None and pymaterial.alpha_mode != 'OPAQUE':
+ BlenderMaterial.blender_alpha(gltf, material_idx, vertex_color)
+
+ @staticmethod
+ def set_uvmap(gltf, material_idx, prim, obj, vertex_color):
+ """Set UV Map."""
+ pymaterial = gltf.data.materials[material_idx]
+
+ node_tree = bpy.data.materials[pymaterial.blender_material[vertex_color]].node_tree
+ uvmap_nodes = [node for node in node_tree.nodes if node.type in ['UVMAP', 'NORMAL_MAP']]
+ for uvmap_node in uvmap_nodes:
+ if uvmap_node["gltf2_texcoord"] in prim.blender_texcoord.keys():
+ uvmap_node.uv_map = prim.blender_texcoord[uvmap_node["gltf2_texcoord"]]
+
+ @staticmethod
+ def blender_alpha(gltf, material_idx, vertex_color):
+ """Set alpha."""
+ pymaterial = gltf.data.materials[material_idx]
+ material = bpy.data.materials[pymaterial.blender_material[vertex_color]]
+
+ node_tree = material.node_tree
+ # Add nodes for basic transparency
+ # Add mix shader between output and Principled BSDF
+ trans = node_tree.nodes.new('ShaderNodeBsdfTransparent')
+ trans.location = 750, -500
+ mix = node_tree.nodes.new('ShaderNodeMixShader')
+ mix.location = 1000, 0
+
+ output_surface_input = get_output_surface_input(node_tree)
+ preoutput_node_output = get_preoutput_node_output(node_tree)
+
+ link = output_surface_input.links[0]
+ node_tree.links.remove(link)
+
+ # PBR => Mix input 1
+ node_tree.links.new(preoutput_node_output, mix.inputs[1])
+
+ # Trans => Mix input 2
+ node_tree.links.new(trans.outputs['BSDF'], mix.inputs[2])
+
+ # Mix => Output
+ node_tree.links.new(mix.outputs['Shader'], output_surface_input)
+
+ # alpha blend factor
+ add = node_tree.nodes.new('ShaderNodeMath')
+ add.operation = 'ADD'
+ add.location = 750, -250
+
+ diffuse_factor = 1.0
+ if pymaterial.extensions is not None and 'KHR_materials_pbrSpecularGlossiness' in pymaterial.extensions:
+ diffuse_factor = pymaterial.extensions['KHR_materials_pbrSpecularGlossiness']['diffuseFactor'][3]
+ elif pymaterial.pbr_metallic_roughness:
+ diffuse_factor = pymaterial.pbr_metallic_roughness.base_color_factor[3]
+
+ add.inputs[0].default_value = abs(1.0 - diffuse_factor)
+ add.inputs[1].default_value = 0.0
+ node_tree.links.new(add.outputs['Value'], mix.inputs[0])
+
+ # Take diffuse texture alpha into account if any
+ diffuse_texture = get_base_color_node(node_tree)
+ if diffuse_texture:
+ inverter = node_tree.nodes.new('ShaderNodeInvert')
+ inverter.location = 250, -250
+ inverter.inputs[1].default_value = (1.0, 1.0, 1.0, 1.0)
+ node_tree.links.new(diffuse_texture.outputs['Alpha'], inverter.inputs[0])
+
+ mult = node_tree.nodes.new('ShaderNodeMath')
+ mult.operation = 'MULTIPLY' if pymaterial.alpha_mode == 'BLEND' else 'GREATER_THAN'
+ mult.location = 500, -250
+ alpha_cutoff = 1.0 if pymaterial.alpha_mode == 'BLEND' else \
+ 1.0 - pymaterial.alpha_cutoff if pymaterial.alpha_cutoff is not None else 0.5
+ mult.inputs[1].default_value = alpha_cutoff
+ node_tree.links.new(inverter.outputs['Color'], mult.inputs[0])
+ node_tree.links.new(mult.outputs['Value'], add.inputs[0])
+
diff --git a/io_scene_gltf2/blender/imp/gltf2_blender_mesh.py b/io_scene_gltf2/blender/imp/gltf2_blender_mesh.py
new file mode 100755
index 00000000..2d346638
--- /dev/null
+++ b/io_scene_gltf2/blender/imp/gltf2_blender_mesh.py
@@ -0,0 +1,175 @@
+# Copyright 2018 The glTF-Blender-IO authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import bpy
+import bmesh
+
+from .gltf2_blender_primitive import BlenderPrimitive
+from ...io.imp.gltf2_io_binary import BinaryData
+from ..com.gltf2_blender_conversion import loc_gltf_to_blender
+
+
+class BlenderMesh():
+ """Blender Mesh."""
+ def __new__(cls, *args, **kwargs):
+ raise RuntimeError("%s should not be instantiated" % cls)
+
+ @staticmethod
+ def create(gltf, mesh_idx, node_idx, parent):
+ """Mesh creation."""
+ pymesh = gltf.data.meshes[mesh_idx]
+
+ # Geometry
+ if pymesh.name:
+ mesh_name = pymesh.name
+ else:
+ mesh_name = "Mesh_" + str(mesh_idx)
+
+ mesh = bpy.data.meshes.new(mesh_name)
+ verts = []
+ edges = []
+ faces = []
+ for prim in pymesh.primitives:
+ verts, edges, faces = BlenderPrimitive.create(gltf, prim, verts, edges, faces)
+
+ mesh.from_pydata(verts, edges, faces)
+ mesh.validate()
+
+ pymesh.blender_name = mesh.name
+
+ return mesh
+
+ @staticmethod
+ def set_mesh(gltf, pymesh, mesh, obj):
+ """Set all data after mesh creation."""
+ # Normals
+ offset = 0
+ custom_normals = [[0.0, 0.0, 0.0]] * len(mesh.vertices)
+
+ if gltf.import_settings['import_shading'] == "NORMALS":
+ mesh.create_normals_split()
+
+ for prim in pymesh.primitives:
+ offset = BlenderPrimitive.set_normals(gltf, prim, mesh, offset, custom_normals)
+
+ mesh.update()
+
+ # manage UV
+ offset = 0
+ for prim in pymesh.primitives:
+ offset = BlenderPrimitive.set_UV(gltf, prim, obj, mesh, offset)
+
+ mesh.update()
+
+ # Normals, now that every update is done
+ if gltf.import_settings['import_shading'] == "NORMALS":
+ mesh.normals_split_custom_set_from_vertices(custom_normals)
+ mesh.use_auto_smooth = True
+
+ # Object and UV are now created, we can set UVMap into material
+ for prim in pymesh.primitives:
+ vertex_color = None
+ if 'COLOR_0' in prim.attributes.keys():
+ vertex_color = 'COLOR_0'
+ BlenderPrimitive.set_UV_in_mat(gltf, prim, obj, vertex_color)
+
+ # Assign materials to mesh
+ offset = 0
+ cpt_index_mat = 0
+ bm = bmesh.new()
+ bm.from_mesh(obj.data)
+ bm.faces.ensure_lookup_table()
+ for prim in pymesh.primitives:
+ offset, cpt_index_mat = BlenderPrimitive.assign_material(gltf, prim, obj, bm, offset, cpt_index_mat)
+
+ bm.to_mesh(obj.data)
+ bm.free()
+
+ # Create shapekeys if needed
+ max_shape_to_create = 0
+ for prim in pymesh.primitives:
+ if prim.targets:
+ if len(prim.targets) > max_shape_to_create:
+ max_shape_to_create = len(prim.targets)
+
+ # Create basis shape key
+ if max_shape_to_create > 0:
+ obj.shape_key_add(name="Basis")
+
+ for i in range(max_shape_to_create):
+
+ obj.shape_key_add(name="target_" + str(i))
+
+ offset_idx = 0
+ for prim in pymesh.primitives:
+ if prim.targets is None:
+ continue
+ if i >= len(prim.targets):
+ continue
+
+ bm = bmesh.new()
+ bm.from_mesh(mesh)
+
+ shape_layer = bm.verts.layers.shape[i + 1]
+
+ pos = BinaryData.get_data_from_accessor(gltf, prim.targets[i]['POSITION'])
+
+ for vert in bm.verts:
+ if vert.index not in range(offset_idx, offset_idx + prim.vertices_length):
+ continue
+
+ shape = vert[shape_layer]
+
+ co = loc_gltf_to_blender(list(pos[vert.index - offset_idx]))
+ shape.x = obj.data.vertices[vert.index].co.x + co[0]
+ shape.y = obj.data.vertices[vert.index].co.y + co[1]
+ shape.z = obj.data.vertices[vert.index].co.z + co[2]
+
+ bm.to_mesh(obj.data)
+ bm.free()
+ offset_idx += prim.vertices_length
+
+ # set default weights for shape keys, and names
+ if pymesh.weights is not None:
+ for i in range(max_shape_to_create):
+ if i < len(pymesh.weights):
+ obj.data.shape_keys.key_blocks[i + 1].value = pymesh.weights[i]
+ if gltf.data.accessors[pymesh.primitives[0].targets[i]['POSITION']].name is not None:
+ obj.data.shape_keys.key_blocks[i + 1].name = \
+ gltf.data.accessors[pymesh.primitives[0].targets[i]['POSITION']].name
+
+ # Apply vertex color.
+ vertex_color = None
+ offset = 0
+ for prim in pymesh.primitives:
+ if 'COLOR_0' in prim.attributes.keys():
+ # Create vertex color, once only per object
+ if vertex_color is None:
+ vertex_color = obj.data.vertex_colors.new(name="COLOR_0")
+
+ color_data = BinaryData.get_data_from_accessor(gltf, prim.attributes['COLOR_0'])
+
+ for poly in mesh.polygons:
+ for loop_idx in range(poly.loop_start, poly.loop_start + poly.loop_total):
+ vert_idx = mesh.loops[loop_idx].vertex_index
+ if vert_idx in range(offset, offset + prim.vertices_length):
+ cpt_idx = vert_idx - offset
+ # check dimension, and add alpha if needed
+ if len(color_data[cpt_idx]) == 3:
+ vertex_color_data = color_data[cpt_idx] + (1.0,)
+ else:
+ vertex_color_data = color_data[cpt_idx]
+ vertex_color.data[loop_idx].color = vertex_color_data
+ offset = offset + prim.vertices_length
+
diff --git a/io_scene_gltf2/blender/imp/gltf2_blender_node.py b/io_scene_gltf2/blender/imp/gltf2_blender_node.py
new file mode 100755
index 00000000..a82f1db7
--- /dev/null
+++ b/io_scene_gltf2/blender/imp/gltf2_blender_node.py
@@ -0,0 +1,184 @@
+# Copyright 2018 The glTF-Blender-IO authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import bpy
+from .gltf2_blender_mesh import BlenderMesh
+from .gltf2_blender_camera import BlenderCamera
+from .gltf2_blender_skin import BlenderSkin
+from ..com.gltf2_blender_conversion import scale_to_matrix, matrix_gltf_to_blender
+
+
+class BlenderNode():
+ """Blender Node."""
+ def __new__(cls, *args, **kwargs):
+ raise RuntimeError("%s should not be instantiated" % cls)
+
+ @staticmethod
+ def create(gltf, node_idx, parent):
+ """Node creation."""
+ pynode = gltf.data.nodes[node_idx]
+
+ # Blender attributes initialization
+ pynode.blender_object = ""
+ pynode.parent = parent
+
+ if pynode.mesh is not None:
+
+ if gltf.data.meshes[pynode.mesh].blender_name is not None:
+ # Mesh is already created, only create instance
+ mesh = bpy.data.meshes[gltf.data.meshes[pynode.mesh].blender_name]
+ else:
+ if pynode.name:
+ gltf.log.info("Blender create Mesh node " + pynode.name)
+ else:
+ gltf.log.info("Blender create Mesh node")
+
+ mesh = BlenderMesh.create(gltf, pynode.mesh, node_idx, parent)
+
+ if pynode.name:
+ name = pynode.name
+ else:
+ # Take mesh name if exist
+ if gltf.data.meshes[pynode.mesh].name:
+ name = gltf.data.meshes[pynode.mesh].name
+ else:
+ name = "Object_" + str(node_idx)
+
+ obj = bpy.data.objects.new(name, mesh)
+ obj.rotation_mode = 'QUATERNION'
+ bpy.data.scenes[gltf.blender_scene].collection.objects.link(obj)
+
+ # Transforms apply only if this mesh is not skinned
+ # See implementation node of gltf2 specification
+ if not (pynode.mesh and pynode.skin is not None):
+ BlenderNode.set_transforms(gltf, node_idx, pynode, obj, parent)
+ pynode.blender_object = obj.name
+ BlenderNode.set_parent(gltf, pynode, obj, parent)
+
+ BlenderMesh.set_mesh(gltf, gltf.data.meshes[pynode.mesh], mesh, obj)
+
+ if pynode.children:
+ for child_idx in pynode.children:
+ BlenderNode.create(gltf, child_idx, node_idx)
+
+ return
+
+ if pynode.camera is not None:
+ if pynode.name:
+ gltf.log.info("Blender create Camera node " + pynode.name)
+ else:
+ gltf.log.info("Blender create Camera node")
+ obj = BlenderCamera.create(gltf, pynode.camera)
+ BlenderNode.set_transforms(gltf, node_idx, pynode, obj, parent) # TODO default rotation of cameras ?
+ pynode.blender_object = obj.name
+ BlenderNode.set_parent(gltf, pynode, obj, parent)
+
+ return
+
+ if pynode.is_joint:
+ if pynode.name:
+ gltf.log.info("Blender create Bone node " + pynode.name)
+ else:
+ gltf.log.info("Blender create Bone node")
+ # Check if corresponding armature is already created, create it if needed
+ if gltf.data.skins[pynode.skin_id].blender_armature_name is None:
+ BlenderSkin.create_armature(gltf, pynode.skin_id, parent)
+
+ BlenderSkin.create_bone(gltf, pynode.skin_id, node_idx, parent)
+
+ if pynode.children:
+ for child_idx in pynode.children:
+ BlenderNode.create(gltf, child_idx, node_idx)
+
+ return
+
+ # No mesh, no camera. For now, create empty #TODO
+
+ if pynode.name:
+ gltf.log.info("Blender create Empty node " + pynode.name)
+ obj = bpy.data.objects.new(pynode.name, None)
+ else:
+ gltf.log.info("Blender create Empty node")
+ obj = bpy.data.objects.new("Node", None)
+ obj.rotation_mode = 'QUATERNION'
+ bpy.data.scenes[gltf.blender_scene].collection.objects.link(obj)
+ BlenderNode.set_transforms(gltf, node_idx, pynode, obj, parent)
+ pynode.blender_object = obj.name
+ BlenderNode.set_parent(gltf, pynode, obj, parent)
+
+ if pynode.children:
+ for child_idx in pynode.children:
+ BlenderNode.create(gltf, child_idx, node_idx)
+
+ @staticmethod
+ def set_parent(gltf, pynode, obj, parent):
+ """Set parent."""
+ if parent is None:
+ return
+
+ for node_idx, node in enumerate(gltf.data.nodes):
+ if node_idx == parent:
+ if node.is_joint is True:
+ bpy.ops.object.select_all(action='DESELECT')
+ bpy.data.objects[node.blender_armature_name].select_set(True)
+ bpy.context.view_layer.objects.active = bpy.data.objects[node.blender_armature_name]
+
+ bpy.ops.object.mode_set(mode='EDIT')
+ bpy.data.objects[node.blender_armature_name].data.edit_bones.active = \
+ bpy.data.objects[node.blender_armature_name].data.edit_bones[node.blender_bone_name]
+ bpy.ops.object.mode_set(mode='OBJECT')
+ bpy.ops.object.select_all(action='DESELECT')
+ obj.select_set(True)
+ bpy.data.objects[node.blender_armature_name].select_set(True)
+ bpy.context.view_layer.objects.active = bpy.data.objects[node.blender_armature_name]
+ bpy.context.scene.update()
+ bpy.ops.object.parent_set(type='BONE_RELATIVE', keep_transform=True)
+ # From world transform to local (-armature transform -bone transform)
+ bone_trans = bpy.data.objects[node.blender_armature_name] \
+ .pose.bones[node.blender_bone_name].matrix.to_translation().copy()
+ bone_rot = bpy.data.objects[node.blender_armature_name] \
+ .pose.bones[node.blender_bone_name].matrix.to_quaternion().copy()
+ bone_scale_mat = scale_to_matrix(node.blender_bone_matrix.to_scale())
+ obj.location = bone_scale_mat @ obj.location
+ obj.location = bone_rot @ obj.location
+ obj.location += bone_trans
+ obj.location = bpy.data.objects[node.blender_armature_name].matrix_world.to_quaternion() \
+ @ obj.location
+ obj.rotation_quaternion = obj.rotation_quaternion \
+ @ bpy.data.objects[node.blender_armature_name].matrix_world.to_quaternion()
+ obj.scale = bone_scale_mat @ obj.scale
+
+ return
+ if node.blender_object:
+ obj.parent = bpy.data.objects[node.blender_object]
+ return
+
+ gltf.log.error("ERROR, parent not found")
+
+ @staticmethod
+ def set_transforms(gltf, node_idx, pynode, obj, parent):
+ """Set transforms."""
+ if parent is None:
+ obj.matrix_world = matrix_gltf_to_blender(pynode.transform)
+ return
+
+ for idx, node in enumerate(gltf.data.nodes):
+ if idx == parent:
+ if node.is_joint is True:
+ obj.matrix_world = matrix_gltf_to_blender(pynode.transform)
+ return
+ else:
+ obj.matrix_world = matrix_gltf_to_blender(pynode.transform)
+ return
+
diff --git a/io_scene_gltf2/blender/imp/gltf2_blender_pbrMetallicRoughness.py b/io_scene_gltf2/blender/imp/gltf2_blender_pbrMetallicRoughness.py
new file mode 100755
index 00000000..a86923ff
--- /dev/null
+++ b/io_scene_gltf2/blender/imp/gltf2_blender_pbrMetallicRoughness.py
@@ -0,0 +1,284 @@
+# Copyright 2018 The glTF-Blender-IO authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import bpy
+from .gltf2_blender_texture import BlenderTextureInfo
+
+
+class BlenderPbr():
+ """Blender Pbr."""
+ def __new__(cls, *args, **kwargs):
+ raise RuntimeError("%s should not be instantiated" % cls)
+
+ def create(gltf, pypbr, mat_name, vertex_color):
+ """Pbr creation."""
+ engine = bpy.context.scene.render.engine
+ if engine in ['CYCLES', 'BLENDER_EEVEE']:
+ BlenderPbr.create_nodetree(gltf, pypbr, mat_name, vertex_color)
+
+ def create_nodetree(gltf, pypbr, mat_name, vertex_color):
+ """Nodetree creation."""
+ material = bpy.data.materials[mat_name]
+ material.use_nodes = True
+ node_tree = material.node_tree
+
+ # If there is no diffuse texture, but only a color, wihtout
+ # vertex_color, we set this color in viewport color
+ if pypbr.color_type == gltf.SIMPLE and not vertex_color:
+ material.diffuse_color = pypbr.base_color_factor[:3]
+
+ # delete all nodes except output
+ for node in list(node_tree.nodes):
+ if not node.type == 'OUTPUT_MATERIAL':
+ node_tree.nodes.remove(node)
+
+ output_node = node_tree.nodes[0]
+ output_node.location = 1250, 0
+
+ # create PBR node
+ principled = node_tree.nodes.new('ShaderNodeBsdfPrincipled')
+ principled.location = 0, 0
+
+ if pypbr.color_type == gltf.SIMPLE:
+
+ if not vertex_color:
+
+ # change input values
+ principled.inputs[0].default_value = pypbr.base_color_factor
+ # TODO : currently set metallic & specular in same way
+ principled.inputs[5].default_value = pypbr.metallic_factor
+ principled.inputs[7].default_value = pypbr.roughness_factor
+
+ else:
+ # Create attribute node to get COLOR_0 data
+ attribute_node = node_tree.nodes.new('ShaderNodeAttribute')
+ attribute_node.attribute_name = 'COLOR_0'
+ attribute_node.location = -500, 0
+
+ # TODO : currently set metallic & specular in same way
+ principled.inputs[5].default_value = pypbr.metallic_factor
+ principled.inputs[7].default_value = pypbr.roughness_factor
+
+ # links
+ rgb_node = node_tree.nodes.new('ShaderNodeMixRGB')
+ rgb_node.blend_type = 'MULTIPLY'
+ rgb_node.inputs['Fac'].default_value = 1.0
+ rgb_node.inputs['Color1'].default_value = pypbr.base_color_factor
+ node_tree.links.new(rgb_node.inputs['Color2'], attribute_node.outputs[0])
+ node_tree.links.new(principled.inputs[0], rgb_node.outputs[0])
+
+ elif pypbr.color_type == gltf.TEXTURE_FACTOR:
+
+ # TODO alpha ?
+ if vertex_color:
+ # TODO tree locations
+ # Create attribute / separate / math nodes
+ attribute_node = node_tree.nodes.new('ShaderNodeAttribute')
+ attribute_node.attribute_name = 'COLOR_0'
+
+ vc_mult_node = node_tree.nodes.new('ShaderNodeMixRGB')
+ vc_mult_node.blend_type = 'MULTIPLY'
+ vc_mult_node.inputs['Fac'].default_value = 0.5
+
+ BlenderTextureInfo.create(gltf, pypbr.base_color_texture.index)
+
+ # create UV Map / Mapping / Texture nodes / separate & math and combine
+ text_node = node_tree.nodes.new('ShaderNodeTexImage')
+ text_node.image = bpy.data.images[gltf.data.images[
+ gltf.data.textures[pypbr.base_color_texture.index].source
+ ].blender_image_name]
+ text_node.label = 'BASE COLOR'
+ text_node.location = -1000, 500
+
+ mult_node = node_tree.nodes.new('ShaderNodeMixRGB')
+ mult_node.blend_type = 'MULTIPLY'
+ mult_node.inputs['Fac'].default_value = 0.5
+ mult_node.inputs['Color2'].default_value = [
+ pypbr.base_color_factor[0],
+ pypbr.base_color_factor[1],
+ pypbr.base_color_factor[2],
+ pypbr.base_color_factor[3],
+ ]
+
+ mapping = node_tree.nodes.new('ShaderNodeMapping')
+ mapping.location = -1500, 500
+
+ uvmap = node_tree.nodes.new('ShaderNodeUVMap')
+ uvmap.location = -2000, 500
+ if pypbr.base_color_texture.tex_coord is not None:
+ uvmap["gltf2_texcoord"] = pypbr.base_color_texture.tex_coord # Set custom flag to retrieve TexCoord
+ else:
+ uvmap["gltf2_texcoord"] = 0 # TODO set in pre_compute instead of here
+ # UV Map will be set after object/UVMap creation
+
+ # Create links
+ if vertex_color:
+ node_tree.links.new(vc_mult_node.inputs[2], attribute_node.outputs[0])
+ node_tree.links.new(vc_mult_node.inputs[1], mult_node.outputs[0])
+ node_tree.links.new(principled.inputs[0], vc_mult_node.outputs[0])
+
+ else:
+ node_tree.links.new(principled.inputs[0], mult_node.outputs[0])
+
+ # Common for both mode (non vertex color / vertex color)
+ node_tree.links.new(mapping.inputs[0], uvmap.outputs[0])
+ node_tree.links.new(text_node.inputs[0], mapping.outputs[0])
+ node_tree.links.new(mult_node.inputs[1], text_node.outputs[0])
+
+ elif pypbr.color_type == gltf.TEXTURE:
+
+ BlenderTextureInfo.create(gltf, pypbr.base_color_texture.index)
+
+ # TODO alpha ?
+ if vertex_color:
+ # Create attribute / separate / math nodes
+ attribute_node = node_tree.nodes.new('ShaderNodeAttribute')
+ attribute_node.attribute_name = 'COLOR_0'
+ attribute_node.location = -2000, 250
+
+ vc_mult_node = node_tree.nodes.new('ShaderNodeMixRGB')
+ vc_mult_node.blend_type = 'MULTIPLY'
+ vc_mult_node.inputs['Fac'].default_value = 0.5
+
+ # create UV Map / Mapping / Texture nodes / separate & math and combine
+ text_node = node_tree.nodes.new('ShaderNodeTexImage')
+ text_node.image = bpy.data.images[gltf.data.images[
+ gltf.data.textures[pypbr.base_color_texture.index].source
+ ].blender_image_name]
+ text_node.label = 'BASE COLOR'
+ if vertex_color:
+ text_node.location = -2000, 500
+ else:
+ text_node.location = -500, 500
+
+ mapping = node_tree.nodes.new('ShaderNodeMapping')
+ if vertex_color:
+ mapping.location = -2500, 500
+ else:
+ mapping.location = -1500, 500
+
+ uvmap = node_tree.nodes.new('ShaderNodeUVMap')
+ if vertex_color:
+ uvmap.location = -3000, 500
+ else:
+ uvmap.location = -2000, 500
+ if pypbr.base_color_texture.tex_coord is not None:
+ uvmap["gltf2_texcoord"] = pypbr.base_color_texture.tex_coord # Set custom flag to retrieve TexCoord
+ else:
+ uvmap["gltf2_texcoord"] = 0 # TODO set in pre_compute instead of here
+ # UV Map will be set after object/UVMap creation
+
+ # Create links
+ if vertex_color:
+ node_tree.links.new(vc_mult_node.inputs[2], attribute_node.outputs[0])
+ node_tree.links.new(vc_mult_node.inputs[1], text_node.outputs[0])
+ node_tree.links.new(principled.inputs[0], vc_mult_node.outputs[0])
+
+ else:
+ node_tree.links.new(principled.inputs[0], text_node.outputs[0])
+
+ # Common for both mode (non vertex color / vertex color)
+
+ node_tree.links.new(mapping.inputs[0], uvmap.outputs[0])
+ node_tree.links.new(text_node.inputs[0], mapping.outputs[0])
+
+ # Says metallic, but it means metallic & Roughness values
+ if pypbr.metallic_type == gltf.SIMPLE:
+ principled.inputs[4].default_value = pypbr.metallic_factor
+ principled.inputs[7].default_value = pypbr.roughness_factor
+
+ elif pypbr.metallic_type == gltf.TEXTURE:
+ BlenderTextureInfo.create(gltf, pypbr.metallic_roughness_texture.index)
+ metallic_text = node_tree.nodes.new('ShaderNodeTexImage')
+ metallic_text.image = bpy.data.images[gltf.data.images[
+ gltf.data.textures[pypbr.metallic_roughness_texture.index].source
+ ].blender_image_name]
+ metallic_text.color_space = 'NONE'
+ metallic_text.label = 'METALLIC ROUGHNESS'
+ metallic_text.location = -500, 0
+
+ metallic_separate = node_tree.nodes.new('ShaderNodeSeparateRGB')
+ metallic_separate.location = -250, 0
+
+ metallic_mapping = node_tree.nodes.new('ShaderNodeMapping')
+ metallic_mapping.location = -1000, 0
+
+ metallic_uvmap = node_tree.nodes.new('ShaderNodeUVMap')
+ metallic_uvmap.location = -1500, 0
+ if pypbr.metallic_roughness_texture.tex_coord is not None:
+ # Set custom flag to retrieve TexCoord
+ metallic_uvmap["gltf2_texcoord"] = pypbr.metallic_roughness_texture.tex_coord
+ else:
+ metallic_uvmap["gltf2_texcoord"] = 0 # TODO set in pre_compute instead of here
+
+ # links
+ node_tree.links.new(metallic_separate.inputs[0], metallic_text.outputs[0])
+ node_tree.links.new(principled.inputs[4], metallic_separate.outputs[2]) # metallic
+ node_tree.links.new(principled.inputs[7], metallic_separate.outputs[1]) # Roughness
+
+ node_tree.links.new(metallic_mapping.inputs[0], metallic_uvmap.outputs[0])
+ node_tree.links.new(metallic_text.inputs[0], metallic_mapping.outputs[0])
+
+ elif pypbr.metallic_type == gltf.TEXTURE_FACTOR:
+
+ BlenderTextureInfo.create(gltf, pypbr.metallic_roughness_texture.index)
+ metallic_text = node_tree.nodes.new('ShaderNodeTexImage')
+ metallic_text.image = bpy.data.images[gltf.data.images[
+ gltf.data.textures[pypbr.metallic_roughness_texture.index].source
+ ].blender_image_name]
+ metallic_text.color_space = 'NONE'
+ metallic_text.label = 'METALLIC ROUGHNESS'
+ metallic_text.location = -1000, 0
+
+ metallic_separate = node_tree.nodes.new('ShaderNodeSeparateRGB')
+ metallic_separate.location = -500, 0
+
+ metallic_math = node_tree.nodes.new('ShaderNodeMath')
+ metallic_math.operation = 'MULTIPLY'
+ metallic_math.inputs[1].default_value = pypbr.metallic_factor
+ metallic_math.location = -250, 100
+
+ roughness_math = node_tree.nodes.new('ShaderNodeMath')
+ roughness_math.operation = 'MULTIPLY'
+ roughness_math.inputs[1].default_value = pypbr.roughness_factor
+ roughness_math.location = -250, -100
+
+ metallic_mapping = node_tree.nodes.new('ShaderNodeMapping')
+ metallic_mapping.location = -1000, 0
+
+ metallic_uvmap = node_tree.nodes.new('ShaderNodeUVMap')
+ metallic_uvmap.location = -1500, 0
+ if pypbr.metallic_roughness_texture.tex_coord is not None:
+ # Set custom flag to retrieve TexCoord
+ metallic_uvmap["gltf2_texcoord"] = pypbr.metallic_roughness_texture.tex_coord
+ else:
+ metallic_uvmap["gltf2_texcoord"] = 0 # TODO set in pre_compute instead of here
+
+ # links
+ node_tree.links.new(metallic_separate.inputs[0], metallic_text.outputs[0])
+
+ # metallic
+ node_tree.links.new(metallic_math.inputs[0], metallic_separate.outputs[2])
+ node_tree.links.new(principled.inputs[4], metallic_math.outputs[0])
+
+ # roughness
+ node_tree.links.new(roughness_math.inputs[0], metallic_separate.outputs[1])
+ node_tree.links.new(principled.inputs[7], roughness_math.outputs[0])
+
+ node_tree.links.new(metallic_mapping.inputs[0], metallic_uvmap.outputs[0])
+ node_tree.links.new(metallic_text.inputs[0], metallic_mapping.outputs[0])
+
+ # link node to output
+ node_tree.links.new(output_node.inputs[0], principled.outputs[0])
+
diff --git a/io_scene_gltf2/blender/imp/gltf2_blender_primitive.py b/io_scene_gltf2/blender/imp/gltf2_blender_primitive.py
new file mode 100755
index 00000000..59e13391
--- /dev/null
+++ b/io_scene_gltf2/blender/imp/gltf2_blender_primitive.py
@@ -0,0 +1,181 @@
+# Copyright 2018 The glTF-Blender-IO authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import bpy
+from mathutils import Vector
+
+from .gltf2_blender_material import BlenderMaterial
+from ..com.gltf2_blender_conversion import loc_gltf_to_blender
+from ...io.imp.gltf2_io_binary import BinaryData
+
+
+class BlenderPrimitive():
+ """Blender Primitive."""
+ def __new__(cls, *args, **kwargs):
+ raise RuntimeError("%s should not be instantiated" % cls)
+
+ @staticmethod
+ def create(gltf, pyprimitive, verts, edges, faces):
+ """Primitive creation."""
+ pyprimitive.blender_texcoord = {}
+
+ # TODO mode of primitive 4 for now.
+ current_length = len(verts)
+ pos = BinaryData.get_data_from_accessor(gltf, pyprimitive.attributes['POSITION'])
+ if pyprimitive.indices is not None:
+ indices = BinaryData.get_data_from_accessor(gltf, pyprimitive.indices)
+ else:
+ indices = []
+ indices_ = range(0, len(pos))
+ for i in indices_:
+ indices.append((i,))
+
+ prim_verts = [loc_gltf_to_blender(vert) for vert in pos]
+ pyprimitive.vertices_length = len(prim_verts)
+ verts.extend(prim_verts)
+ prim_faces = []
+ for i in range(0, len(indices), 3):
+ vals = indices[i:i + 3]
+ new_vals = []
+ for y in vals:
+ new_vals.append(y[0] + current_length)
+ prim_faces.append(tuple(new_vals))
+ faces.extend(prim_faces)
+ pyprimitive.faces_length = len(prim_faces)
+
+ # manage material of primitive
+ if pyprimitive.material is not None:
+
+ vertex_color = None
+ if 'COLOR_0' in pyprimitive.attributes.keys():
+ vertex_color = 'COLOR_0'
+
+ # Create Blender material if needed
+ if vertex_color is None:
+ if None not in gltf.data.materials[pyprimitive.material].blender_material.keys():
+ BlenderMaterial.create(gltf, pyprimitive.material, vertex_color)
+ else:
+ if vertex_color not in gltf.data.materials[pyprimitive.material].blender_material.keys():
+ BlenderMaterial.create(gltf, pyprimitive.material, vertex_color)
+
+
+ return verts, edges, faces
+
+ def set_normals(gltf, pyprimitive, mesh, offset, custom_normals):
+ """Set Normal."""
+ if 'NORMAL' in pyprimitive.attributes.keys():
+ normal_data = BinaryData.get_data_from_accessor(gltf, pyprimitive.attributes['NORMAL'])
+ for poly in mesh.polygons:
+ if gltf.import_settings['import_shading'] == "NORMALS":
+ calc_norm_vertices = []
+ for loop_idx in range(poly.loop_start, poly.loop_start + poly.loop_total):
+ vert_idx = mesh.loops[loop_idx].vertex_index
+ if vert_idx in range(offset, offset + pyprimitive.vertices_length):
+ cpt_vert = vert_idx - offset
+ mesh.vertices[vert_idx].normal = normal_data[cpt_vert]
+ custom_normals[vert_idx] = list(normal_data[cpt_vert])
+ calc_norm_vertices.append(vert_idx)
+
+ if len(calc_norm_vertices) == 3:
+ # Calcul normal
+ vert0 = mesh.vertices[calc_norm_vertices[0]].co
+ vert1 = mesh.vertices[calc_norm_vertices[1]].co
+ vert2 = mesh.vertices[calc_norm_vertices[2]].co
+ calc_normal = (vert1 - vert0).cross(vert2 - vert0).normalized()
+
+ # Compare normal to vertex normal
+ for i in calc_norm_vertices:
+ cpt_vert = vert_idx - offset
+ vec = Vector(
+ (normal_data[cpt_vert][0], normal_data[cpt_vert][1], normal_data[cpt_vert][2])
+ )
+ if not calc_normal.dot(vec) > 0.9999999:
+ poly.use_smooth = True
+ break
+ elif gltf.import_settings['import_shading'] == "FLAT":
+ poly.use_smooth = False
+ elif gltf.import_settings['import_shading'] == "SMOOTH":
+ poly.use_smooth = True
+ else:
+ pass # Should not happend
+
+ offset = offset + pyprimitive.vertices_length
+ return offset
+
+ def set_UV(gltf, pyprimitive, obj, mesh, offset):
+ """Set UV Map."""
+ for texcoord in [attr for attr in pyprimitive.attributes.keys() if attr[:9] == "TEXCOORD_"]:
+ if texcoord not in mesh.uv_layers:
+ mesh.uv_layers.new(name=texcoord)
+ pyprimitive.blender_texcoord[int(texcoord[9:])] = texcoord
+
+ texcoord_data = BinaryData.get_data_from_accessor(gltf, pyprimitive.attributes[texcoord])
+ for poly in mesh.polygons:
+ for loop_idx in range(poly.loop_start, poly.loop_start + poly.loop_total):
+ vert_idx = mesh.loops[loop_idx].vertex_index
+ if vert_idx in range(offset, offset + pyprimitive.vertices_length):
+ obj.data.uv_layers[texcoord].data[loop_idx].uv = \
+ Vector((texcoord_data[vert_idx - offset][0], 1 - texcoord_data[vert_idx - offset][1]))
+
+ offset = offset + pyprimitive.vertices_length
+ return offset
+
+ def set_UV_in_mat(gltf, pyprimitive, obj, vertex_color):
+ """After nodetree creation, set UVMap in nodes."""
+ if pyprimitive.material is None:
+ return
+ if gltf.data.materials[pyprimitive.material].extensions \
+ and "KHR_materials_pbrSpecularGlossiness" in \
+ gltf.data.materials[pyprimitive.material].extensions.keys():
+ if pyprimitive.material is not None \
+ and gltf.data.materials[pyprimitive.material].extensions[
+ 'KHR_materials_pbrSpecularGlossiness'
+ ]['diffuse_type'] in [gltf.TEXTURE, gltf.TEXTURE_FACTOR]:
+ BlenderMaterial.set_uvmap(gltf, pyprimitive.material, pyprimitive, obj, vertex_color)
+ else:
+ if pyprimitive.material is not None \
+ and gltf.data.materials[pyprimitive.material].extensions[
+ 'KHR_materials_pbrSpecularGlossiness'
+ ]['specgloss_type'] in [gltf.TEXTURE, gltf.TEXTURE_FACTOR]:
+ BlenderMaterial.set_uvmap(gltf, pyprimitive.material, pyprimitive, obj, vertex_color)
+
+ else:
+ if pyprimitive.material is not None \
+ and gltf.data.materials[pyprimitive.material].pbr_metallic_roughness.color_type in \
+ [gltf.TEXTURE, gltf.TEXTURE_FACTOR]:
+ BlenderMaterial.set_uvmap(gltf, pyprimitive.material, pyprimitive, obj, vertex_color)
+ else:
+ if pyprimitive.material is not None \
+ and gltf.data.materials[pyprimitive.material].pbr_metallic_roughness.metallic_type in \
+ [gltf.TEXTURE, gltf.TEXTURE_FACTOR]:
+ BlenderMaterial.set_uvmap(gltf, pyprimitive.material, pyprimitive, obj, vertex_color)
+
+ def assign_material(gltf, pyprimitive, obj, bm, offset, cpt_index_mat):
+ """Assign material to faces of primitives."""
+ if pyprimitive.material is not None:
+
+ vertex_color = None
+ if 'COLOR_0' in pyprimitive.attributes.keys():
+ vertex_color = 'COLOR_0'
+
+ obj.data.materials.append(bpy.data.materials[gltf.data.materials[pyprimitive.material].blender_material[vertex_color]])
+ for vert in bm.verts:
+ if vert.index in range(offset, offset + pyprimitive.vertices_length):
+ for loop in vert.link_loops:
+ face = loop.face.index
+ bm.faces[face].material_index = cpt_index_mat
+ cpt_index_mat += 1
+ offset = offset + pyprimitive.vertices_length
+ return offset, cpt_index_mat
+
diff --git a/io_scene_gltf2/blender/imp/gltf2_blender_scene.py b/io_scene_gltf2/blender/imp/gltf2_blender_scene.py
new file mode 100755
index 00000000..6c2f14cb
--- /dev/null
+++ b/io_scene_gltf2/blender/imp/gltf2_blender_scene.py
@@ -0,0 +1,98 @@
+# Copyright 2018 The glTF-Blender-IO authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import bpy
+from math import sqrt
+from mathutils import Quaternion
+from .gltf2_blender_node import BlenderNode
+from .gltf2_blender_skin import BlenderSkin
+from .gltf2_blender_animation import BlenderAnimation
+
+
+class BlenderScene():
+ """Blender Scene."""
+ def __new__(cls, *args, **kwargs):
+ raise RuntimeError("%s should not be instantiated" % cls)
+
+ @staticmethod
+ def create(gltf, scene_idx):
+ """Scene creation."""
+ pyscene = gltf.data.scenes[scene_idx]
+
+ # Create a new scene only if not already exists in .blend file
+ # TODO : put in current scene instead ?
+ if pyscene.name not in [scene.name for scene in bpy.data.scenes]:
+ # TODO: There is a bug in 2.8 alpha that break CLEAR_KEEP_TRANSFORM
+ # if we are creating a new scene
+ scene = bpy.context.scene
+ scene.render.engine = "BLENDER_EEVEE"
+
+ gltf.blender_scene = scene.name
+ else:
+ gltf.blender_scene = pyscene.name
+
+ # Switch to newly created main scene
+ bpy.context.window.scene = bpy.data.scenes[gltf.blender_scene]
+
+ # Create Yup2Zup empty
+ obj_rotation = bpy.data.objects.new("Yup2Zup", None)
+ obj_rotation.rotation_mode = 'QUATERNION'
+ obj_rotation.rotation_quaternion = Quaternion((sqrt(2) / 2, sqrt(2) / 2, 0.0, 0.0))
+
+ bpy.data.scenes[gltf.blender_scene].collection.objects.link(obj_rotation)
+
+ if pyscene.nodes is not None:
+ for node_idx in pyscene.nodes:
+ BlenderNode.create(gltf, node_idx, None) # None => No parent
+
+ # Now that all mesh / bones are created, create vertex groups on mesh
+ if gltf.data.skins:
+ for skin_id, skin in enumerate(gltf.data.skins):
+ if hasattr(skin, "node_ids"):
+ BlenderSkin.create_vertex_groups(gltf, skin_id)
+
+ for skin_id, skin in enumerate(gltf.data.skins):
+ if hasattr(skin, "node_ids"):
+ BlenderSkin.assign_vertex_groups(gltf, skin_id)
+
+ for skin_id, skin in enumerate(gltf.data.skins):
+ if hasattr(skin, "node_ids"):
+ BlenderSkin.create_armature_modifiers(gltf, skin_id)
+
+ if gltf.data.animations:
+ for anim_idx, anim in enumerate(gltf.data.animations):
+ if pyscene.nodes is not None:
+ for node_idx in pyscene.nodes:
+ BlenderAnimation.anim(gltf, anim_idx, node_idx)
+
+ # Parent root node to rotation object
+ if pyscene.nodes is not None:
+ for node_idx in pyscene.nodes:
+ bpy.data.objects[gltf.data.nodes[node_idx].blender_object].parent = obj_rotation
+
+ if gltf.animation_object is False:
+
+
+ for node_idx in pyscene.nodes:
+ for obj_ in bpy.context.scene.objects:
+ obj_.select_set(False)
+ bpy.data.objects[gltf.data.nodes[node_idx].blender_object].select_set(True)
+ bpy.context.view_layer.objects.active = bpy.data.objects[gltf.data.nodes[node_idx].blender_object]
+
+ bpy.ops.object.parent_clear(type='CLEAR_KEEP_TRANSFORM')
+
+ # remove object
+ bpy.context.scene.collection.objects.unlink(obj_rotation)
+ bpy.data.objects.remove(obj_rotation)
+
diff --git a/io_scene_gltf2/blender/imp/gltf2_blender_skin.py b/io_scene_gltf2/blender/imp/gltf2_blender_skin.py
new file mode 100755
index 00000000..db0e50f9
--- /dev/null
+++ b/io_scene_gltf2/blender/imp/gltf2_blender_skin.py
@@ -0,0 +1,209 @@
+# Copyright 2018 The glTF-Blender-IO authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+import bpy
+from mathutils import Vector, Matrix
+from ..com.gltf2_blender_conversion import matrix_gltf_to_blender, scale_to_matrix
+from ...io.imp.gltf2_io_binary import BinaryData
+
+
+class BlenderSkin():
+ """Blender Skinning / Armature."""
+ def __new__(cls, *args, **kwargs):
+ raise RuntimeError("%s should not be instantiated" % cls)
+
+ @staticmethod
+ def create_armature(gltf, skin_id, parent):
+ """Armature creation."""
+ pyskin = gltf.data.skins[skin_id]
+
+ if pyskin.name is not None:
+ name = pyskin.name
+ else:
+ name = "Armature_" + str(skin_id)
+
+ armature = bpy.data.armatures.new(name)
+ obj = bpy.data.objects.new(name, armature)
+ bpy.data.scenes[gltf.blender_scene].collection.objects.link(obj)
+ pyskin.blender_armature_name = obj.name
+ if parent is not None:
+ obj.parent = bpy.data.objects[gltf.data.nodes[parent].blender_object]
+
+ @staticmethod
+ def set_bone_transforms(gltf, skin_id, bone, node_id, parent):
+ """Set bone transformations."""
+ pyskin = gltf.data.skins[skin_id]
+ pynode = gltf.data.nodes[node_id]
+
+ obj = bpy.data.objects[pyskin.blender_armature_name]
+
+ # Set bone bind_pose by inverting bindpose matrix
+ if node_id in pyskin.joints:
+ index_in_skel = pyskin.joints.index(node_id)
+ inverse_bind_matrices = BinaryData.get_data_from_accessor(gltf, pyskin.inverse_bind_matrices)
+ # Needed to keep scale in matrix, as bone.matrix seems to drop it
+ if index_in_skel < len(inverse_bind_matrices):
+ pynode.blender_bone_matrix = matrix_gltf_to_blender(
+ inverse_bind_matrices[index_in_skel]
+ ).inverted()
+ bone.matrix = pynode.blender_bone_matrix
+ else:
+ gltf.log.error("Error with inverseBindMatrix for skin " + pyskin)
+ else:
+ print('No invBindMatrix for bone ' + str(node_id))
+ pynode.blender_bone_matrix = Matrix()
+
+ # Parent the bone
+ if parent is not None and hasattr(gltf.data.nodes[parent], "blender_bone_name"):
+ bone.parent = obj.data.edit_bones[gltf.data.nodes[parent].blender_bone_name] # TODO if in another scene
+
+ # Switch to Pose mode
+ bpy.ops.object.mode_set(mode="POSE")
+ obj.data.pose_position = 'POSE'
+
+ # Set posebone location/rotation/scale (in armature space)
+ # location is actual bone location minus it's original (bind) location
+ bind_location = Matrix.Translation(pynode.blender_bone_matrix.to_translation())
+ bind_rotation = pynode.blender_bone_matrix.to_quaternion()
+ bind_scale = scale_to_matrix(pynode.blender_bone_matrix.to_scale())
+
+ location, rotation, scale = matrix_gltf_to_blender(pynode.transform).decompose()
+ if parent is not None and hasattr(gltf.data.nodes[parent], "blender_bone_matrix"):
+ parent_mat = gltf.data.nodes[parent].blender_bone_matrix
+
+ # Get armature space location (bindpose + pose)
+ # Then, remove original bind location from armspace location, and bind rotation
+ final_location = (bind_location.inverted() @ parent_mat @ Matrix.Translation(location)).to_translation()
+ obj.pose.bones[pynode.blender_bone_name].location = \
+ bind_rotation.inverted().to_matrix().to_4x4() @ final_location
+
+ # Do the same for rotation
+ obj.pose.bones[pynode.blender_bone_name].rotation_quaternion = \
+ (bind_rotation.to_matrix().to_4x4().inverted() @ parent_mat @
+ rotation.to_matrix().to_4x4()).to_quaternion()
+ obj.pose.bones[pynode.blender_bone_name].scale = \
+ (bind_scale.inverted() @ parent_mat @ scale_to_matrix(scale)).to_scale()
+
+ else:
+ obj.pose.bones[pynode.blender_bone_name].location = bind_location.inverted() @ location
+ obj.pose.bones[pynode.blender_bone_name].rotation_quaternion = bind_rotation.inverted() @ rotation
+ obj.pose.bones[pynode.blender_bone_name].scale = bind_scale.inverted() @ scale
+
+ @staticmethod
+ def create_bone(gltf, skin_id, node_id, parent):
+ """Bone creation."""
+ pyskin = gltf.data.skins[skin_id]
+ pynode = gltf.data.nodes[node_id]
+
+ scene = bpy.data.scenes[gltf.blender_scene]
+ obj = bpy.data.objects[pyskin.blender_armature_name]
+
+ bpy.context.window.scene = scene
+ bpy.context.view_layer.objects.active = obj
+ bpy.ops.object.mode_set(mode="EDIT")
+
+ if pynode.name:
+ name = pynode.name
+ else:
+ name = "Bone_" + str(node_id)
+
+ bone = obj.data.edit_bones.new(name)
+ pynode.blender_bone_name = bone.name
+ pynode.blender_armature_name = pyskin.blender_armature_name
+ bone.tail = Vector((0.0, 1.0, 0.0)) # Needed to keep bone alive
+
+ # set bind and pose transforms
+ BlenderSkin.set_bone_transforms(gltf, skin_id, bone, node_id, parent)
+ bpy.ops.object.mode_set(mode="OBJECT")
+
+ @staticmethod
+ def create_vertex_groups(gltf, skin_id):
+ """Vertex Group creation."""
+ pyskin = gltf.data.skins[skin_id]
+ for node_id in pyskin.node_ids:
+ obj = bpy.data.objects[gltf.data.nodes[node_id].blender_object]
+ for bone in pyskin.joints:
+ obj.vertex_groups.new(name=gltf.data.nodes[bone].blender_bone_name)
+
+ @staticmethod
+ def assign_vertex_groups(gltf, skin_id):
+ """Assign vertex groups to vertices."""
+ pyskin = gltf.data.skins[skin_id]
+ for node_id in pyskin.node_ids:
+ node = gltf.data.nodes[node_id]
+ obj = bpy.data.objects[node.blender_object]
+
+ offset = 0
+ for prim in gltf.data.meshes[node.mesh].primitives:
+ idx_already_done = {}
+
+ if 'JOINTS_0' in prim.attributes.keys() and 'WEIGHTS_0' in prim.attributes.keys():
+ joint_ = BinaryData.get_data_from_accessor(gltf, prim.attributes['JOINTS_0'])
+ weight_ = BinaryData.get_data_from_accessor(gltf, prim.attributes['WEIGHTS_0'])
+
+ for poly in obj.data.polygons:
+ for loop_idx in range(poly.loop_start, poly.loop_start + poly.loop_total):
+ vert_idx = obj.data.loops[loop_idx].vertex_index
+
+ if vert_idx in idx_already_done.keys():
+ continue
+ idx_already_done[vert_idx] = True
+
+ if vert_idx in range(offset, offset + prim.vertices_length):
+
+ tab_index = vert_idx - offset
+ cpt = 0
+ for joint_idx in joint_[tab_index]:
+ weight_val = weight_[tab_index][cpt]
+ if weight_val != 0.0: # It can be a problem to assign weights of 0
+ # for bone index 0, if there is always 4 indices in joint_
+ # tuple
+ group = obj.vertex_groups[gltf.data.nodes[
+ pyskin.joints[joint_idx]
+ ].blender_bone_name]
+ group.add([vert_idx], weight_val, 'REPLACE')
+ cpt += 1
+ else:
+ gltf.log.error("No Skinning ?????") # TODO
+
+ offset = offset + prim.vertices_length
+
+ @staticmethod
+ def create_armature_modifiers(gltf, skin_id):
+ """Create Armature modifier."""
+ pyskin = gltf.data.skins[skin_id]
+
+ if pyskin.blender_armature_name is None:
+ # TODO seems something is wrong
+ # For example, some joints are in skin 0, and are in another skin too
+ # Not sure this is glTF compliant, will check it
+ return
+
+ for node_id in pyskin.node_ids:
+ node = gltf.data.nodes[node_id]
+ obj = bpy.data.objects[node.blender_object]
+
+ for obj_sel in bpy.context.scene.objects:
+ obj_sel.select_set(False)
+ obj.select_set(True)
+ bpy.context.view_layer.objects.active = obj
+
+ # bpy.ops.object.parent_clear(type='CLEAR_KEEP_TRANSFORM')
+ # Reparent skinned mesh to it's armature to avoid breaking
+ # skinning with interleaved transforms
+ obj.parent = bpy.data.objects[pyskin.blender_armature_name]
+ arma = obj.modifiers.new(name="Armature", type="ARMATURE")
+ arma.object = bpy.data.objects[pyskin.blender_armature_name]
+
diff --git a/io_scene_gltf2/blender/imp/gltf2_blender_texture.py b/io_scene_gltf2/blender/imp/gltf2_blender_texture.py
new file mode 100755
index 00000000..c8983d9c
--- /dev/null
+++ b/io_scene_gltf2/blender/imp/gltf2_blender_texture.py
@@ -0,0 +1,39 @@
+# Copyright 2018 The glTF-Blender-IO authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from .gltf2_blender_image import BlenderImage
+
+
+class BlenderTextureInfo():
+ """Blender Texture info."""
+ def __new__(cls, *args, **kwargs):
+ raise RuntimeError("%s should not be instantiated" % cls)
+
+ @staticmethod
+ def create(gltf, pytextureinfo_idx):
+ """Create Texture info."""
+ BlenderTexture.create(gltf, pytextureinfo_idx)
+
+
+class BlenderTexture():
+ """Blender Texture."""
+ def __new__(cls, *args, **kwargs):
+ raise RuntimeError("%s should not be instantiated" % cls)
+
+ @staticmethod
+ def create(gltf, pytexture_idx):
+ """Create texture."""
+ pytexture = gltf.data.textures[pytexture_idx]
+ BlenderImage.create(gltf, pytexture.source)
+
diff --git a/io_scene_gltf2/io/__init__.py b/io_scene_gltf2/io/__init__.py
new file mode 100755
index 00000000..10973240
--- /dev/null
+++ b/io_scene_gltf2/io/__init__.py
@@ -0,0 +1,16 @@
+# Copyright 2018 The glTF-Blender-IO authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from .imp import *
+
diff --git a/io_scene_gltf2/io/com/gltf2_io.py b/io_scene_gltf2/io/com/gltf2_io.py
new file mode 100755
index 00000000..1332adf6
--- /dev/null
+++ b/io_scene_gltf2/io/com/gltf2_io.py
@@ -0,0 +1,1200 @@
+# Copyright 2018 The glTF-Blender-IO authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# NOTE: Generated from latest glTF 2.0 JSON Scheme specs using quicktype (https://github.com/quicktype/quicktype)
+# command used:
+# quicktype --src glTF.schema.json --src-lang schema -t gltf --lang python --python-version 3.5
+
+# TODO: add __slots__ to all classes by extending the generator
+
+# TODO: REMOVE traceback import
+import sys
+import traceback
+
+from io_scene_gltf2.io.com import gltf2_io_debug
+
+
+def from_int(x):
+ assert isinstance(x, int) and not isinstance(x, bool)
+ return x
+
+
+def from_none(x):
+ assert x is None
+ return x
+
+
+def from_union(fs, x):
+ tracebacks = []
+ for f in fs:
+ try:
+ return f(x)
+ except AssertionError:
+ _, _, tb = sys.exc_info()
+ tracebacks.append(tb)
+ for tb in tracebacks:
+ traceback.print_tb(tb) # Fixed format
+ tb_info = traceback.extract_tb(tb)
+ for tbi in tb_info:
+ filename, line, func, text = tbi
+ gltf2_io_debug.print_console('ERROR', 'An error occurred on line {} in statement {}'.format(line, text))
+ assert False
+
+
+def from_dict(f, x):
+ assert isinstance(x, dict)
+ return {k: f(v) for (k, v) in x.items()}
+
+
+def to_class(c, x):
+ assert isinstance(x, c)
+ return x.to_dict()
+
+
+def from_list(f, x):
+ assert isinstance(x, list)
+ return [f(y) for y in x]
+
+
+def from_float(x):
+ assert isinstance(x, (float, int)) and not isinstance(x, bool)
+ return float(x)
+
+
+def from_str(x):
+ assert isinstance(x, str)
+ return x
+
+
+def from_bool(x):
+ assert isinstance(x, bool)
+ return x
+
+
+def to_float(x):
+ assert isinstance(x, float)
+ return x
+
+
+class AccessorSparseIndices:
+ """Index array of size `count` that points to those accessor attributes that deviate from
+ their initialization value. Indices must strictly increase.
+
+ Indices of those attributes that deviate from their initialization value.
+ """
+
+ def __init__(self, buffer_view, byte_offset, component_type, extensions, extras):
+ self.buffer_view = buffer_view
+ self.byte_offset = byte_offset
+ self.component_type = component_type
+ self.extensions = extensions
+ self.extras = extras
+
+ @staticmethod
+ def from_dict(obj):
+ assert isinstance(obj, dict)
+ buffer_view = from_int(obj.get("bufferView"))
+ byte_offset = from_union([from_int, from_none], obj.get("byteOffset"))
+ component_type = from_int(obj.get("componentType"))
+ extensions = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none],
+ obj.get("extensions"))
+ extras = obj.get("extras")
+ return AccessorSparseIndices(buffer_view, byte_offset, component_type, extensions, extras)
+
+ def to_dict(self):
+ result = {}
+ result["bufferView"] = from_int(self.buffer_view)
+ result["byteOffset"] = from_union([from_int, from_none], self.byte_offset)
+ result["componentType"] = from_int(self.component_type)
+ result["extensions"] = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none],
+ self.extensions)
+ result["extras"] = self.extras
+ return result
+
+
+class AccessorSparseValues:
+ """Array of size `count` times number of components, storing the displaced accessor
+ attributes pointed by `indices`. Substituted values must have the same `componentType`
+ and number of components as the base accessor.
+
+ Array of size `accessor.sparse.count` times number of components storing the displaced
+ accessor attributes pointed by `accessor.sparse.indices`.
+ """
+
+ def __init__(self, buffer_view, byte_offset, extensions, extras):
+ self.buffer_view = buffer_view
+ self.byte_offset = byte_offset
+ self.extensions = extensions
+ self.extras = extras
+
+ @staticmethod
+ def from_dict(obj):
+ assert isinstance(obj, dict)
+ buffer_view = from_int(obj.get("bufferView"))
+ byte_offset = from_union([from_int, from_none], obj.get("byteOffset"))
+ extensions = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none],
+ obj.get("extensions"))
+ extras = obj.get("extras")
+ return AccessorSparseValues(buffer_view, byte_offset, extensions, extras)
+
+ def to_dict(self):
+ result = {}
+ result["bufferView"] = from_int(self.buffer_view)
+ result["byteOffset"] = from_union([from_int, from_none], self.byte_offset)
+ result["extensions"] = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none],
+ self.extensions)
+ result["extras"] = self.extras
+ return result
+
+
+class AccessorSparse:
+ """Sparse storage of attributes that deviate from their initialization value."""
+
+ def __init__(self, count, extensions, extras, indices, values):
+ self.count = count
+ self.extensions = extensions
+ self.extras = extras
+ self.indices = indices
+ self.values = values
+
+ @staticmethod
+ def from_dict(obj):
+ assert isinstance(obj, dict)
+ count = from_int(obj.get("count"))
+ extensions = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none],
+ obj.get("extensions"))
+ extras = obj.get("extras")
+ indices = AccessorSparseIndices.from_dict(obj.get("indices"))
+ values = AccessorSparseValues.from_dict(obj.get("values"))
+ return AccessorSparse(count, extensions, extras, indices, values)
+
+ def to_dict(self):
+ result = {}
+ result["count"] = from_int(self.count)
+ result["extensions"] = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none],
+ self.extensions)
+ result["extras"] = self.extras
+ result["indices"] = to_class(AccessorSparseIndices, self.indices)
+ result["values"] = to_class(AccessorSparseValues, self.values)
+ return result
+
+
+class Accessor:
+ """A typed view into a bufferView. A bufferView contains raw binary data. An accessor
+ provides a typed view into a bufferView or a subset of a bufferView similar to how
+ WebGL's `vertexAttribPointer()` defines an attribute in a buffer.
+ """
+
+ def __init__(self, buffer_view, byte_offset, component_type, count, extensions, extras, max, min, name, normalized,
+ sparse, type):
+ self.buffer_view = buffer_view
+ self.byte_offset = byte_offset
+ self.component_type = component_type
+ self.count = count
+ self.extensions = extensions
+ self.extras = extras
+ self.max = max
+ self.min = min
+ self.name = name
+ self.normalized = normalized
+ self.sparse = sparse
+ self.type = type
+
+ @staticmethod
+ def from_dict(obj):
+ assert isinstance(obj, dict)
+ buffer_view = from_union([from_int, from_none], obj.get("bufferView"))
+ byte_offset = from_union([from_int, from_none], obj.get("byteOffset"))
+ component_type = from_int(obj.get("componentType"))
+ count = from_int(obj.get("count"))
+ extensions = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none],
+ obj.get("extensions"))
+ extras = obj.get("extras")
+ max = from_union([lambda x: from_list(from_float, x), from_none], obj.get("max"))
+ min = from_union([lambda x: from_list(from_float, x), from_none], obj.get("min"))
+ name = from_union([from_str, from_none], obj.get("name"))
+ normalized = from_union([from_bool, from_none], obj.get("normalized"))
+ sparse = from_union([AccessorSparse.from_dict, from_none], obj.get("sparse"))
+ type = from_str(obj.get("type"))
+ return Accessor(buffer_view, byte_offset, component_type, count, extensions, extras, max, min, name, normalized,
+ sparse, type)
+
+ def to_dict(self):
+ result = {}
+ result["bufferView"] = from_union([from_int, from_none], self.buffer_view)
+ result["byteOffset"] = from_union([from_int, from_none], self.byte_offset)
+ result["componentType"] = from_int(self.component_type)
+ result["count"] = from_int(self.count)
+ result["extensions"] = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none],
+ self.extensions)
+ result["extras"] = self.extras
+ result["max"] = from_union([lambda x: from_list(to_float, x), from_none], self.max)
+ result["min"] = from_union([lambda x: from_list(to_float, x), from_none], self.min)
+ result["name"] = from_union([from_str, from_none], self.name)
+ result["normalized"] = from_union([from_bool, from_none], self.normalized)
+ result["sparse"] = from_union([lambda x: to_class(AccessorSparse, x), from_none], self.sparse)
+ result["type"] = from_str(self.type)
+ return result
+
+
+class AnimationChannelTarget:
+ """The index of the node and TRS property to target.
+
+ The index of the node and TRS property that an animation channel targets.
+ """
+
+ def __init__(self, extensions, extras, node, path):
+ self.extensions = extensions
+ self.extras = extras
+ self.node = node
+ self.path = path
+
+ @staticmethod
+ def from_dict(obj):
+ assert isinstance(obj, dict)
+ extensions = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none],
+ obj.get("extensions"))
+ extras = obj.get("extras")
+ node = from_union([from_int, from_none], obj.get("node"))
+ path = from_str(obj.get("path"))
+ return AnimationChannelTarget(extensions, extras, node, path)
+
+ def to_dict(self):
+ result = {}
+ result["extensions"] = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none],
+ self.extensions)
+ result["extras"] = self.extras
+ result["node"] = from_union([from_int, from_none], self.node)
+ result["path"] = from_str(self.path)
+ return result
+
+
+class AnimationChannel:
+ """Targets an animation's sampler at a node's property."""
+
+ def __init__(self, extensions, extras, sampler, target):
+ self.extensions = extensions
+ self.extras = extras
+ self.sampler = sampler
+ self.target = target
+
+ @staticmethod
+ def from_dict(obj):
+ assert isinstance(obj, dict)
+ extensions = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none],
+ obj.get("extensions"))
+ extras = obj.get("extras")
+ sampler = from_int(obj.get("sampler"))
+ target = AnimationChannelTarget.from_dict(obj.get("target"))
+ return AnimationChannel(extensions, extras, sampler, target)
+
+ def to_dict(self):
+ result = {}
+ result["extensions"] = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none],
+ self.extensions)
+ result["extras"] = self.extras
+ result["sampler"] = from_int(self.sampler)
+ result["target"] = to_class(AnimationChannelTarget, self.target)
+ return result
+
+
+class AnimationSampler:
+ """Combines input and output accessors with an interpolation algorithm to define a keyframe
+ graph (but not its target).
+ """
+
+ def __init__(self, extensions, extras, input, interpolation, output):
+ self.extensions = extensions
+ self.extras = extras
+ self.input = input
+ self.interpolation = interpolation
+ self.output = output
+
+ @staticmethod
+ def from_dict(obj):
+ assert isinstance(obj, dict)
+ extensions = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none],
+ obj.get("extensions"))
+ extras = obj.get("extras")
+ input = from_int(obj.get("input"))
+ interpolation = from_union([from_str, from_none], obj.get("interpolation"))
+ output = from_int(obj.get("output"))
+ return AnimationSampler(extensions, extras, input, interpolation, output)
+
+ def to_dict(self):
+ result = {}
+ result["extensions"] = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none],
+ self.extensions)
+ result["extras"] = self.extras
+ result["input"] = from_int(self.input)
+ result["interpolation"] = from_union([from_str, from_none], self.interpolation)
+ result["output"] = from_int(self.output)
+ return result
+
+
+class Animation:
+ """A keyframe animation."""
+
+ def __init__(self, channels, extensions, extras, name, samplers):
+ self.channels = channels
+ self.extensions = extensions
+ self.extras = extras
+ self.name = name
+ self.samplers = samplers
+
+ @staticmethod
+ def from_dict(obj):
+ assert isinstance(obj, dict)
+ channels = from_list(AnimationChannel.from_dict, obj.get("channels"))
+ extensions = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none],
+ obj.get("extensions"))
+ extras = obj.get("extras")
+ name = from_union([from_str, from_none], obj.get("name"))
+ samplers = from_list(AnimationSampler.from_dict, obj.get("samplers"))
+ return Animation(channels, extensions, extras, name, samplers)
+
+ def to_dict(self):
+ result = {}
+ result["channels"] = from_list(lambda x: to_class(AnimationChannel, x), self.channels)
+ result["extensions"] = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none],
+ self.extensions)
+ result["extras"] = self.extras
+ result["name"] = from_union([from_str, from_none], self.name)
+ result["samplers"] = from_list(lambda x: to_class(AnimationSampler, x), self.samplers)
+ return result
+
+
+class Asset:
+ """Metadata about the glTF asset."""
+
+ def __init__(self, copyright, extensions, extras, generator, min_version, version):
+ self.copyright = copyright
+ self.extensions = extensions
+ self.extras = extras
+ self.generator = generator
+ self.min_version = min_version
+ self.version = version
+
+ @staticmethod
+ def from_dict(obj):
+ assert isinstance(obj, dict)
+ copyright = from_union([from_str, from_none], obj.get("copyright"))
+ extensions = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none],
+ obj.get("extensions"))
+ extras = obj.get("extras")
+ generator = from_union([from_str, from_none], obj.get("generator"))
+ min_version = from_union([from_str, from_none], obj.get("minVersion"))
+ version = from_str(obj.get("version"))
+ return Asset(copyright, extensions, extras, generator, min_version, version)
+
+ def to_dict(self):
+ result = {}
+ result["copyright"] = from_union([from_str, from_none], self.copyright)
+ result["extensions"] = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none],
+ self.extensions)
+ result["extras"] = self.extras
+ result["generator"] = from_union([from_str, from_none], self.generator)
+ result["minVersion"] = from_union([from_str, from_none], self.min_version)
+ result["version"] = from_str(self.version)
+ return result
+
+
+class BufferView:
+ """A view into a buffer generally representing a subset of the buffer."""
+
+ def __init__(self, buffer, byte_length, byte_offset, byte_stride, extensions, extras, name, target):
+ self.buffer = buffer
+ self.byte_length = byte_length
+ self.byte_offset = byte_offset
+ self.byte_stride = byte_stride
+ self.extensions = extensions
+ self.extras = extras
+ self.name = name
+ self.target = target
+
+ @staticmethod
+ def from_dict(obj):
+ assert isinstance(obj, dict)
+ buffer = from_int(obj.get("buffer"))
+ byte_length = from_int(obj.get("byteLength"))
+ byte_offset = from_union([from_int, from_none], obj.get("byteOffset"))
+ byte_stride = from_union([from_int, from_none], obj.get("byteStride"))
+ extensions = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none],
+ obj.get("extensions"))
+ extras = obj.get("extras")
+ name = from_union([from_str, from_none], obj.get("name"))
+ target = from_union([from_int, from_none], obj.get("target"))
+ return BufferView(buffer, byte_length, byte_offset, byte_stride, extensions, extras, name, target)
+
+ def to_dict(self):
+ result = {}
+ result["buffer"] = from_int(self.buffer)
+ result["byteLength"] = from_int(self.byte_length)
+ result["byteOffset"] = from_union([from_int, from_none], self.byte_offset)
+ result["byteStride"] = from_union([from_int, from_none], self.byte_stride)
+ result["extensions"] = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none],
+ self.extensions)
+ result["extras"] = self.extras
+ result["name"] = from_union([from_str, from_none], self.name)
+ result["target"] = from_union([from_int, from_none], self.target)
+ return result
+
+
+class Buffer:
+ """A buffer points to binary geometry, animation, or skins."""
+
+ def __init__(self, byte_length, extensions, extras, name, uri):
+ self.byte_length = byte_length
+ self.extensions = extensions
+ self.extras = extras
+ self.name = name
+ self.uri = uri
+
+ @staticmethod
+ def from_dict(obj):
+ assert isinstance(obj, dict)
+ byte_length = from_int(obj.get("byteLength"))
+ extensions = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none],
+ obj.get("extensions"))
+ extras = obj.get("extras")
+ name = from_union([from_str, from_none], obj.get("name"))
+ uri = from_union([from_str, from_none], obj.get("uri"))
+ return Buffer(byte_length, extensions, extras, name, uri)
+
+ def to_dict(self):
+ result = {}
+ result["byteLength"] = from_int(self.byte_length)
+ result["extensions"] = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none],
+ self.extensions)
+ result["extras"] = self.extras
+ result["name"] = from_union([from_str, from_none], self.name)
+ result["uri"] = from_union([from_str, from_none], self.uri)
+ return result
+
+
+class CameraOrthographic:
+ """An orthographic camera containing properties to create an orthographic projection matrix."""
+
+ def __init__(self, extensions, extras, xmag, ymag, zfar, znear):
+ self.extensions = extensions
+ self.extras = extras
+ self.xmag = xmag
+ self.ymag = ymag
+ self.zfar = zfar
+ self.znear = znear
+
+ @staticmethod
+ def from_dict(obj):
+ assert isinstance(obj, dict)
+ extensions = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none],
+ obj.get("extensions"))
+ extras = obj.get("extras")
+ xmag = from_float(obj.get("xmag"))
+ ymag = from_float(obj.get("ymag"))
+ zfar = from_float(obj.get("zfar"))
+ znear = from_float(obj.get("znear"))
+ return CameraOrthographic(extensions, extras, xmag, ymag, zfar, znear)
+
+ def to_dict(self):
+ result = {}
+ result["extensions"] = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none],
+ self.extensions)
+ result["extras"] = self.extras
+ result["xmag"] = to_float(self.xmag)
+ result["ymag"] = to_float(self.ymag)
+ result["zfar"] = to_float(self.zfar)
+ result["znear"] = to_float(self.znear)
+ return result
+
+
+class CameraPerspective:
+ """A perspective camera containing properties to create a perspective projection matrix."""
+
+ def __init__(self, aspect_ratio, extensions, extras, yfov, zfar, znear):
+ self.aspect_ratio = aspect_ratio
+ self.extensions = extensions
+ self.extras = extras
+ self.yfov = yfov
+ self.zfar = zfar
+ self.znear = znear
+
+ @staticmethod
+ def from_dict(obj):
+ assert isinstance(obj, dict)
+ aspect_ratio = from_union([from_float, from_none], obj.get("aspectRatio"))
+ extensions = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none],
+ obj.get("extensions"))
+ extras = obj.get("extras")
+ yfov = from_float(obj.get("yfov"))
+ zfar = from_union([from_float, from_none], obj.get("zfar"))
+ znear = from_float(obj.get("znear"))
+ return CameraPerspective(aspect_ratio, extensions, extras, yfov, zfar, znear)
+
+ def to_dict(self):
+ result = {}
+ result["aspectRatio"] = from_union([to_float, from_none], self.aspect_ratio)
+ result["extensions"] = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none],
+ self.extensions)
+ result["extras"] = self.extras
+ result["yfov"] = to_float(self.yfov)
+ result["zfar"] = from_union([to_float, from_none], self.zfar)
+ result["znear"] = to_float(self.znear)
+ return result
+
+
+class Camera:
+ """A camera's projection. A node can reference a camera to apply a transform to place the
+ camera in the scene.
+ """
+
+ def __init__(self, extensions, extras, name, orthographic, perspective, type):
+ self.extensions = extensions
+ self.extras = extras
+ self.name = name
+ self.orthographic = orthographic
+ self.perspective = perspective
+ self.type = type
+
+ @staticmethod
+ def from_dict(obj):
+ assert isinstance(obj, dict)
+ extensions = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none],
+ obj.get("extensions"))
+ extras = obj.get("extras")
+ name = from_union([from_str, from_none], obj.get("name"))
+ orthographic = from_union([CameraOrthographic.from_dict, from_none], obj.get("orthographic"))
+ perspective = from_union([CameraPerspective.from_dict, from_none], obj.get("perspective"))
+ type = from_str(obj.get("type"))
+ return Camera(extensions, extras, name, orthographic, perspective, type)
+
+ def to_dict(self):
+ result = {}
+ result["extensions"] = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none],
+ self.extensions)
+ result["extras"] = self.extras
+ result["name"] = from_union([from_str, from_none], self.name)
+ result["orthographic"] = from_union([lambda x: to_class(CameraOrthographic, x), from_none], self.orthographic)
+ result["perspective"] = from_union([lambda x: to_class(CameraPerspective, x), from_none], self.perspective)
+ result["type"] = from_str(self.type)
+ return result
+
+
+class Image:
+ """Image data used to create a texture. Image can be referenced by URI or `bufferView`
+ index. `mimeType` is required in the latter case.
+ """
+
+ def __init__(self, buffer_view, extensions, extras, mime_type, name, uri):
+ self.buffer_view = buffer_view
+ self.extensions = extensions
+ self.extras = extras
+ self.mime_type = mime_type
+ self.name = name
+ self.uri = uri
+
+ @staticmethod
+ def from_dict(obj):
+ assert isinstance(obj, dict)
+ buffer_view = from_union([from_int, from_none], obj.get("bufferView"))
+ extensions = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none],
+ obj.get("extensions"))
+ extras = obj.get("extras")
+ mime_type = from_union([from_str, from_none], obj.get("mimeType"))
+ name = from_union([from_str, from_none], obj.get("name"))
+ uri = from_union([from_str, from_none], obj.get("uri"))
+ return Image(buffer_view, extensions, extras, mime_type, name, uri)
+
+ def to_dict(self):
+ result = {}
+ result["bufferView"] = from_union([from_int, from_none], self.buffer_view)
+ result["extensions"] = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none],
+ self.extensions)
+ result["extras"] = self.extras
+ result["mimeType"] = from_union([from_str, from_none], self.mime_type)
+ result["name"] = from_union([from_str, from_none], self.name)
+ result["uri"] = from_union([from_str, from_none], self.uri)
+ return result
+
+
+class TextureInfo:
+ """The emissive map texture.
+
+ The base color texture.
+
+ The metallic-roughness texture.
+
+ Reference to a texture.
+ """
+
+ def __init__(self, extensions, extras, index, tex_coord):
+ self.extensions = extensions
+ self.extras = extras
+ self.index = index
+ self.tex_coord = tex_coord
+
+ @staticmethod
+ def from_dict(obj):
+ assert isinstance(obj, dict)
+ extensions = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none],
+ obj.get("extensions"))
+ extras = obj.get("extras")
+ index = from_int(obj.get("index"))
+ tex_coord = from_union([from_int, from_none], obj.get("texCoord"))
+ return TextureInfo(extensions, extras, index, tex_coord)
+
+ def to_dict(self):
+ result = {}
+ result["extensions"] = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none],
+ self.extensions)
+ result["extras"] = self.extras
+ result["index"] = from_int(self.index)
+ result["texCoord"] = from_union([from_int, from_none], self.tex_coord)
+ return result
+
+
+class MaterialNormalTextureInfoClass:
+ """The normal map texture.
+
+ Reference to a texture.
+ """
+
+ def __init__(self, extensions, extras, index, scale, tex_coord):
+ self.extensions = extensions
+ self.extras = extras
+ self.index = index
+ self.scale = scale
+ self.tex_coord = tex_coord
+
+ @staticmethod
+ def from_dict(obj):
+ assert isinstance(obj, dict)
+ extensions = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none],
+ obj.get("extensions"))
+ extras = obj.get("extras")
+ index = from_int(obj.get("index"))
+ scale = from_union([from_float, from_none], obj.get("scale"))
+ tex_coord = from_union([from_int, from_none], obj.get("texCoord"))
+ return MaterialNormalTextureInfoClass(extensions, extras, index, scale, tex_coord)
+
+ def to_dict(self):
+ result = {}
+ result["extensions"] = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none],
+ self.extensions)
+ result["extras"] = self.extras
+ result["index"] = from_int(self.index)
+ result["scale"] = from_union([to_float, from_none], self.scale)
+ result["texCoord"] = from_union([from_int, from_none], self.tex_coord)
+ return result
+
+
+class MaterialOcclusionTextureInfoClass:
+ """The occlusion map texture.
+
+ Reference to a texture.
+ """
+
+ def __init__(self, extensions, extras, index, strength, tex_coord):
+ self.extensions = extensions
+ self.extras = extras
+ self.index = index
+ self.strength = strength
+ self.tex_coord = tex_coord
+
+ @staticmethod
+ def from_dict(obj):
+ assert isinstance(obj, dict)
+ extensions = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none],
+ obj.get("extensions"))
+ extras = obj.get("extras")
+ index = from_int(obj.get("index"))
+ strength = from_union([from_float, from_none], obj.get("strength"))
+ tex_coord = from_union([from_int, from_none], obj.get("texCoord"))
+ return MaterialOcclusionTextureInfoClass(extensions, extras, index, strength, tex_coord)
+
+ def to_dict(self):
+ result = {}
+ result["extensions"] = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none],
+ self.extensions)
+ result["extras"] = self.extras
+ result["index"] = from_int(self.index)
+ result["strength"] = from_union([to_float, from_none], self.strength)
+ result["texCoord"] = from_union([from_int, from_none], self.tex_coord)
+ return result
+
+
+class MaterialPBRMetallicRoughness:
+ """A set of parameter values that are used to define the metallic-roughness material model
+ from Physically-Based Rendering (PBR) methodology. When not specified, all the default
+ values of `pbrMetallicRoughness` apply.
+
+ A set of parameter values that are used to define the metallic-roughness material model
+ from Physically-Based Rendering (PBR) methodology.
+ """
+
+ def __init__(self, base_color_factor, base_color_texture, extensions, extras, metallic_factor,
+ metallic_roughness_texture, roughness_factor):
+ self.base_color_factor = base_color_factor
+ self.base_color_texture = base_color_texture
+ self.extensions = extensions
+ self.extras = extras
+ self.metallic_factor = metallic_factor
+ self.metallic_roughness_texture = metallic_roughness_texture
+ self.roughness_factor = roughness_factor
+
+ @staticmethod
+ def from_dict(obj):
+ assert isinstance(obj, dict)
+ base_color_factor = from_union([lambda x: from_list(from_float, x), from_none], obj.get("baseColorFactor"))
+ base_color_texture = from_union([TextureInfo.from_dict, from_none], obj.get("baseColorTexture"))
+ extensions = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none],
+ obj.get("extensions"))
+ extras = obj.get("extras")
+ metallic_factor = from_union([from_float, from_none], obj.get("metallicFactor"))
+ metallic_roughness_texture = from_union([TextureInfo.from_dict, from_none], obj.get("metallicRoughnessTexture"))
+ roughness_factor = from_union([from_float, from_none], obj.get("roughnessFactor"))
+ return MaterialPBRMetallicRoughness(base_color_factor, base_color_texture, extensions, extras, metallic_factor,
+ metallic_roughness_texture, roughness_factor)
+
+ def to_dict(self):
+ result = {}
+ result["baseColorFactor"] = from_union([lambda x: from_list(to_float, x), from_none], self.base_color_factor)
+ result["baseColorTexture"] = from_union([lambda x: to_class(TextureInfo, x), from_none],
+ self.base_color_texture)
+ result["extensions"] = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none],
+ self.extensions)
+ result["extras"] = self.extras
+ result["metallicFactor"] = from_union([to_float, from_none], self.metallic_factor)
+ result["metallicRoughnessTexture"] = from_union([lambda x: to_class(TextureInfo, x), from_none],
+ self.metallic_roughness_texture)
+ result["roughnessFactor"] = from_union([to_float, from_none], self.roughness_factor)
+ return result
+
+
+class Material:
+ """The material appearance of a primitive."""
+
+ def __init__(self, alpha_cutoff, alpha_mode, double_sided, emissive_factor, emissive_texture, extensions, extras,
+ name, normal_texture, occlusion_texture, pbr_metallic_roughness):
+ self.alpha_cutoff = alpha_cutoff
+ self.alpha_mode = alpha_mode
+ self.double_sided = double_sided
+ self.emissive_factor = emissive_factor
+ self.emissive_texture = emissive_texture
+ self.extensions = extensions
+ self.extras = extras
+ self.name = name
+ self.normal_texture = normal_texture
+ self.occlusion_texture = occlusion_texture
+ self.pbr_metallic_roughness = pbr_metallic_roughness
+
+ @staticmethod
+ def from_dict(obj):
+ assert isinstance(obj, dict)
+ alpha_cutoff = from_union([from_float, from_none], obj.get("alphaCutoff"))
+ alpha_mode = from_union([from_str, from_none], obj.get("alphaMode"))
+ double_sided = from_union([from_bool, from_none], obj.get("doubleSided"))
+ emissive_factor = from_union([lambda x: from_list(from_float, x), from_none], obj.get("emissiveFactor"))
+ emissive_texture = from_union([TextureInfo.from_dict, from_none], obj.get("emissiveTexture"))
+ extensions = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none],
+ obj.get("extensions"))
+ extras = obj.get("extras")
+ name = from_union([from_str, from_none], obj.get("name"))
+ normal_texture = from_union([MaterialNormalTextureInfoClass.from_dict, from_none], obj.get("normalTexture"))
+ occlusion_texture = from_union([MaterialOcclusionTextureInfoClass.from_dict, from_none],
+ obj.get("occlusionTexture"))
+ pbr_metallic_roughness = from_union([MaterialPBRMetallicRoughness.from_dict, from_none],
+ obj.get("pbrMetallicRoughness"))
+ return Material(alpha_cutoff, alpha_mode, double_sided, emissive_factor, emissive_texture, extensions, extras,
+ name, normal_texture, occlusion_texture, pbr_metallic_roughness)
+
+ def to_dict(self):
+ result = {}
+ result["alphaCutoff"] = from_union([to_float, from_none], self.alpha_cutoff)
+ result["alphaMode"] = from_union([from_str, from_none], self.alpha_mode)
+ result["doubleSided"] = from_union([from_bool, from_none], self.double_sided)
+ result["emissiveFactor"] = from_union([lambda x: from_list(to_float, x), from_none], self.emissive_factor)
+ result["emissiveTexture"] = from_union([lambda x: to_class(TextureInfo, x), from_none], self.emissive_texture)
+ result["extensions"] = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none],
+ self.extensions)
+ result["extras"] = self.extras
+ result["name"] = from_union([from_str, from_none], self.name)
+ result["normalTexture"] = from_union([lambda x: to_class(MaterialNormalTextureInfoClass, x), from_none],
+ self.normal_texture)
+ result["occlusionTexture"] = from_union([lambda x: to_class(MaterialOcclusionTextureInfoClass, x), from_none],
+ self.occlusion_texture)
+ result["pbrMetallicRoughness"] = from_union([lambda x: to_class(MaterialPBRMetallicRoughness, x), from_none],
+ self.pbr_metallic_roughness)
+ return result
+
+
+class MeshPrimitive:
+ """Geometry to be rendered with the given material."""
+
+ def __init__(self, attributes, extensions, extras, indices, material, mode, targets):
+ self.attributes = attributes
+ self.extensions = extensions
+ self.extras = extras
+ self.indices = indices
+ self.material = material
+ self.mode = mode
+ self.targets = targets
+
+ @staticmethod
+ def from_dict(obj):
+ assert isinstance(obj, dict)
+ attributes = from_dict(from_int, obj.get("attributes"))
+ extensions = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none],
+ obj.get("extensions"))
+ extras = obj.get("extras")
+ indices = from_union([from_int, from_none], obj.get("indices"))
+ material = from_union([from_int, from_none], obj.get("material"))
+ mode = from_union([from_int, from_none], obj.get("mode"))
+ targets = from_union([lambda x: from_list(lambda x: from_dict(from_int, x), x), from_none], obj.get("targets"))
+ return MeshPrimitive(attributes, extensions, extras, indices, material, mode, targets)
+
+ def to_dict(self):
+ result = {}
+ result["attributes"] = from_dict(from_int, self.attributes)
+ result["extensions"] = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none],
+ self.extensions)
+ result["extras"] = self.extras
+ result["indices"] = from_union([from_int, from_none], self.indices)
+ result["material"] = from_union([from_int, from_none], self.material)
+ result["mode"] = from_union([from_int, from_none], self.mode)
+ result["targets"] = from_union([lambda x: from_list(lambda x: from_dict(from_int, x), x), from_none],
+ self.targets)
+ return result
+
+
+class Mesh:
+ """A set of primitives to be rendered. A node can contain one mesh. A node's transform
+ places the mesh in the scene.
+ """
+
+ def __init__(self, extensions, extras, name, primitives, weights):
+ self.extensions = extensions
+ self.extras = extras
+ self.name = name
+ self.primitives = primitives
+ self.weights = weights
+
+ @staticmethod
+ def from_dict(obj):
+ assert isinstance(obj, dict)
+ extensions = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none],
+ obj.get("extensions"))
+ extras = obj.get("extras")
+ name = from_union([from_str, from_none], obj.get("name"))
+ primitives = from_list(MeshPrimitive.from_dict, obj.get("primitives"))
+ weights = from_union([lambda x: from_list(from_float, x), from_none], obj.get("weights"))
+ return Mesh(extensions, extras, name, primitives, weights)
+
+ def to_dict(self):
+ result = {}
+ result["extensions"] = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none],
+ self.extensions)
+ result["extras"] = self.extras
+ result["name"] = from_union([from_str, from_none], self.name)
+ result["primitives"] = from_list(lambda x: to_class(MeshPrimitive, x), self.primitives)
+ result["weights"] = from_union([lambda x: from_list(to_float, x), from_none], self.weights)
+ return result
+
+
+class Node:
+ """A node in the node hierarchy. When the node contains `skin`, all `mesh.primitives` must
+ contain `JOINTS_0` and `WEIGHTS_0` attributes. A node can have either a `matrix` or any
+ combination of `translation`/`rotation`/`scale` (TRS) properties. TRS properties are
+ converted to matrices and postmultiplied in the `T * R * S` order to compose the
+ transformation matrix; first the scale is applied to the vertices, then the rotation, and
+ then the translation. If none are provided, the transform is the identity. When a node is
+ targeted for animation (referenced by an animation.channel.target), only TRS properties
+ may be present; `matrix` will not be present.
+ """
+
+ def __init__(self, camera, children, extensions, extras, matrix, mesh, name, rotation, scale, skin, translation,
+ weights):
+ self.camera = camera
+ self.children = children
+ self.extensions = extensions
+ self.extras = extras
+ self.matrix = matrix
+ self.mesh = mesh
+ self.name = name
+ self.rotation = rotation
+ self.scale = scale
+ self.skin = skin
+ self.translation = translation
+ self.weights = weights
+
+ @staticmethod
+ def from_dict(obj):
+ assert isinstance(obj, dict)
+ camera = from_union([from_int, from_none], obj.get("camera"))
+ children = from_union([lambda x: from_list(from_int, x), from_none], obj.get("children"))
+ extensions = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none],
+ obj.get("extensions"))
+ extras = obj.get("extras")
+ matrix = from_union([lambda x: from_list(from_float, x), from_none], obj.get("matrix"))
+ mesh = from_union([from_int, from_none], obj.get("mesh"))
+ name = from_union([from_str, from_none], obj.get("name"))
+ rotation = from_union([lambda x: from_list(from_float, x), from_none], obj.get("rotation"))
+ scale = from_union([lambda x: from_list(from_float, x), from_none], obj.get("scale"))
+ skin = from_union([from_int, from_none], obj.get("skin"))
+ translation = from_union([lambda x: from_list(from_float, x), from_none], obj.get("translation"))
+ weights = from_union([lambda x: from_list(from_float, x), from_none], obj.get("weights"))
+ return Node(camera, children, extensions, extras, matrix, mesh, name, rotation, scale, skin, translation,
+ weights)
+
+ def to_dict(self):
+ result = {}
+ result["camera"] = from_union([from_int, from_none], self.camera)
+ result["children"] = from_union([lambda x: from_list(from_int, x), from_none], self.children)
+ result["extensions"] = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none],
+ self.extensions)
+ result["extras"] = self.extras
+ result["matrix"] = from_union([lambda x: from_list(to_float, x), from_none], self.matrix)
+ result["mesh"] = from_union([from_int, from_none], self.mesh)
+ result["name"] = from_union([from_str, from_none], self.name)
+ result["rotation"] = from_union([lambda x: from_list(to_float, x), from_none], self.rotation)
+ result["scale"] = from_union([lambda x: from_list(to_float, x), from_none], self.scale)
+ result["skin"] = from_union([from_int, from_none], self.skin)
+ result["translation"] = from_union([lambda x: from_list(to_float, x), from_none], self.translation)
+ result["weights"] = from_union([lambda x: from_list(to_float, x), from_none], self.weights)
+ return result
+
+
+class Sampler:
+ """Texture sampler properties for filtering and wrapping modes."""
+
+ def __init__(self, extensions, extras, mag_filter, min_filter, name, wrap_s, wrap_t):
+ self.extensions = extensions
+ self.extras = extras
+ self.mag_filter = mag_filter
+ self.min_filter = min_filter
+ self.name = name
+ self.wrap_s = wrap_s
+ self.wrap_t = wrap_t
+
+ @staticmethod
+ def from_dict(obj):
+ assert isinstance(obj, dict)
+ extensions = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none],
+ obj.get("extensions"))
+ extras = obj.get("extras")
+ mag_filter = from_union([from_int, from_none], obj.get("magFilter"))
+ min_filter = from_union([from_int, from_none], obj.get("minFilter"))
+ name = from_union([from_str, from_none], obj.get("name"))
+ wrap_s = from_union([from_int, from_none], obj.get("wrapS"))
+ wrap_t = from_union([from_int, from_none], obj.get("wrapT"))
+ return Sampler(extensions, extras, mag_filter, min_filter, name, wrap_s, wrap_t)
+
+ def to_dict(self):
+ result = {}
+ result["extensions"] = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none],
+ self.extensions)
+ result["extras"] = self.extras
+ result["magFilter"] = from_union([from_int, from_none], self.mag_filter)
+ result["minFilter"] = from_union([from_int, from_none], self.min_filter)
+ result["name"] = from_union([from_str, from_none], self.name)
+ result["wrapS"] = from_union([from_int, from_none], self.wrap_s)
+ result["wrapT"] = from_union([from_int, from_none], self.wrap_t)
+ return result
+
+
+class Scene:
+ """The root nodes of a scene."""
+
+ def __init__(self, extensions, extras, name, nodes):
+ self.extensions = extensions
+ self.extras = extras
+ self.name = name
+ self.nodes = nodes
+
+ @staticmethod
+ def from_dict(obj):
+ assert isinstance(obj, dict)
+ extensions = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none],
+ obj.get("extensions"))
+ extras = obj.get("extras")
+ name = from_union([from_str, from_none], obj.get("name"))
+ nodes = from_union([lambda x: from_list(from_int, x), from_none], obj.get("nodes"))
+ return Scene(extensions, extras, name, nodes)
+
+ def to_dict(self):
+ result = {}
+ result["extensions"] = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none],
+ self.extensions)
+ result["extras"] = self.extras
+ result["name"] = from_union([from_str, from_none], self.name)
+ result["nodes"] = from_union([lambda x: from_list(from_int, x), from_none], self.nodes)
+ return result
+
+
+class Skin:
+ """Joints and matrices defining a skin."""
+
+ def __init__(self, extensions, extras, inverse_bind_matrices, joints, name, skeleton):
+ self.extensions = extensions
+ self.extras = extras
+ self.inverse_bind_matrices = inverse_bind_matrices
+ self.joints = joints
+ self.name = name
+ self.skeleton = skeleton
+
+ @staticmethod
+ def from_dict(obj):
+ assert isinstance(obj, dict)
+ extensions = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none],
+ obj.get("extensions"))
+ extras = obj.get("extras")
+ inverse_bind_matrices = from_union([from_int, from_none], obj.get("inverseBindMatrices"))
+ joints = from_list(from_int, obj.get("joints"))
+ name = from_union([from_str, from_none], obj.get("name"))
+ skeleton = from_union([from_int, from_none], obj.get("skeleton"))
+ return Skin(extensions, extras, inverse_bind_matrices, joints, name, skeleton)
+
+ def to_dict(self):
+ result = {}
+ result["extensions"] = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none],
+ self.extensions)
+ result["extras"] = self.extras
+ result["inverseBindMatrices"] = from_union([from_int, from_none], self.inverse_bind_matrices)
+ result["joints"] = from_list(from_int, self.joints)
+ result["name"] = from_union([from_str, from_none], self.name)
+ result["skeleton"] = from_union([from_int, from_none], self.skeleton)
+ return result
+
+
+class Texture:
+ """A texture and its sampler."""
+
+ def __init__(self, extensions, extras, name, sampler, source):
+ self.extensions = extensions
+ self.extras = extras
+ self.name = name
+ self.sampler = sampler
+ self.source = source
+
+ @staticmethod
+ def from_dict(obj):
+ assert isinstance(obj, dict)
+ extensions = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none],
+ obj.get("extensions"))
+ extras = obj.get("extras")
+ name = from_union([from_str, from_none], obj.get("name"))
+ sampler = from_union([from_int, from_none], obj.get("sampler"))
+ source = from_int(obj.get("source"))
+ return Texture(extensions, extras, name, sampler, source)
+
+ def to_dict(self):
+ result = {}
+ result["extensions"] = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none],
+ self.extensions)
+ result["extras"] = self.extras
+ result["name"] = from_union([from_str, from_none], self.name)
+ result["sampler"] = from_union([from_int, from_none], self.sampler)
+ result["source"] = from_int(self.source)
+ return result
+
+
+class Gltf:
+ """The root object for a glTF asset."""
+
+ def __init__(self, accessors, animations, asset, buffers, buffer_views, cameras, extensions, extensions_required,
+ extensions_used, extras, images, materials, meshes, nodes, samplers, scene, scenes, skins, textures):
+ self.accessors = accessors
+ self.animations = animations
+ self.asset = asset
+ self.buffers = buffers
+ self.buffer_views = buffer_views
+ self.cameras = cameras
+ self.extensions = extensions
+ self.extensions_required = extensions_required
+ self.extensions_used = extensions_used
+ self.extras = extras
+ self.images = images
+ self.materials = materials
+ self.meshes = meshes
+ self.nodes = nodes
+ self.samplers = samplers
+ self.scene = scene
+ self.scenes = scenes
+ self.skins = skins
+ self.textures = textures
+
+ @staticmethod
+ def from_dict(obj):
+ assert isinstance(obj, dict)
+ accessors = from_union([lambda x: from_list(Accessor.from_dict, x), from_none], obj.get("accessors"))
+ animations = from_union([lambda x: from_list(Animation.from_dict, x), from_none], obj.get("animations"))
+ asset = Asset.from_dict(obj.get("asset"))
+ buffers = from_union([lambda x: from_list(Buffer.from_dict, x), from_none], obj.get("buffers"))
+ buffer_views = from_union([lambda x: from_list(BufferView.from_dict, x), from_none], obj.get("bufferViews"))
+ cameras = from_union([lambda x: from_list(Camera.from_dict, x), from_none], obj.get("cameras"))
+ extensions = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none],
+ obj.get("extensions"))
+ extensions_required = from_union([lambda x: from_list(from_str, x), from_none], obj.get("extensionsRequired"))
+ extensions_used = from_union([lambda x: from_list(from_str, x), from_none], obj.get("extensionsUsed"))
+ extras = obj.get("extras")
+ images = from_union([lambda x: from_list(Image.from_dict, x), from_none], obj.get("images"))
+ materials = from_union([lambda x: from_list(Material.from_dict, x), from_none], obj.get("materials"))
+ meshes = from_union([lambda x: from_list(Mesh.from_dict, x), from_none], obj.get("meshes"))
+ nodes = from_union([lambda x: from_list(Node.from_dict, x), from_none], obj.get("nodes"))
+ samplers = from_union([lambda x: from_list(Sampler.from_dict, x), from_none], obj.get("samplers"))
+ scene = from_union([from_int, from_none], obj.get("scene"))
+ scenes = from_union([lambda x: from_list(Scene.from_dict, x), from_none], obj.get("scenes"))
+ skins = from_union([lambda x: from_list(Skin.from_dict, x), from_none], obj.get("skins"))
+ textures = from_union([lambda x: from_list(Texture.from_dict, x), from_none], obj.get("textures"))
+ return Gltf(accessors, animations, asset, buffers, buffer_views, cameras, extensions, extensions_required,
+ extensions_used, extras, images, materials, meshes, nodes, samplers, scene, scenes, skins, textures)
+
+ def to_dict(self):
+ result = {}
+ result["accessors"] = from_union([lambda x: from_list(lambda x: to_class(Accessor, x), x), from_none],
+ self.accessors)
+ result["animations"] = from_union([lambda x: from_list(lambda x: to_class(Animation, x), x), from_none],
+ self.animations)
+ result["asset"] = to_class(Asset, self.asset)
+ result["buffers"] = from_union([lambda x: from_list(lambda x: to_class(Buffer, x), x), from_none], self.buffers)
+ result["bufferViews"] = from_union([lambda x: from_list(lambda x: to_class(BufferView, x), x), from_none],
+ self.buffer_views)
+ result["cameras"] = from_union([lambda x: from_list(lambda x: to_class(Camera, x), x), from_none], self.cameras)
+ result["extensions"] = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none],
+ self.extensions)
+ result["extensionsRequired"] = from_union([lambda x: from_list(from_str, x), from_none],
+ self.extensions_required)
+ result["extensionsUsed"] = from_union([lambda x: from_list(from_str, x), from_none], self.extensions_used)
+ result["extras"] = self.extras
+ result["images"] = from_union([lambda x: from_list(lambda x: to_class(Image, x), x), from_none], self.images)
+ result["materials"] = from_union([lambda x: from_list(lambda x: to_class(Material, x), x), from_none],
+ self.materials)
+ result["meshes"] = from_union([lambda x: from_list(lambda x: to_class(Mesh, x), x), from_none], self.meshes)
+ result["nodes"] = from_union([lambda x: from_list(lambda x: to_class(Node, x), x), from_none], self.nodes)
+ result["samplers"] = from_union([lambda x: from_list(lambda x: to_class(Sampler, x), x), from_none],
+ self.samplers)
+ result["scene"] = from_union([from_int, from_none], self.scene)
+ result["scenes"] = from_union([lambda x: from_list(lambda x: to_class(Scene, x), x), from_none], self.scenes)
+ result["skins"] = from_union([lambda x: from_list(lambda x: to_class(Skin, x), x), from_none], self.skins)
+ result["textures"] = from_union([lambda x: from_list(lambda x: to_class(Texture, x), x), from_none],
+ self.textures)
+ return result
+
+
+def gltf_from_dict(s):
+ return Gltf.from_dict(s)
+
+
+def gltf_to_dict(x):
+ return to_class(Gltf, x)
+
diff --git a/io_scene_gltf2/io/com/gltf2_io_constants.py b/io_scene_gltf2/io/com/gltf2_io_constants.py
new file mode 100755
index 00000000..c97908cd
--- /dev/null
+++ b/io_scene_gltf2/io/com/gltf2_io_constants.py
@@ -0,0 +1,132 @@
+# Copyright 2018 The glTF-Blender-IO authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from enum import IntEnum
+
+
+class ComponentType(IntEnum):
+ Byte = 5120
+ UnsignedByte = 5121
+ Short = 5122
+ UnsignedShort = 5123
+ UnsignedInt = 5125
+ Float = 5126
+
+ @classmethod
+ def to_type_code(cls, component_type):
+ return {
+ ComponentType.Byte: 'b',
+ ComponentType.UnsignedByte: 'B',
+ ComponentType.Short: 'h',
+ ComponentType.UnsignedShort: 'H',
+ ComponentType.UnsignedInt: 'I',
+ ComponentType.Float: 'f'
+ }[component_type]
+
+ @classmethod
+ def from_legacy_define(cls, type_define):
+ return {
+ GLTF_COMPONENT_TYPE_BYTE: ComponentType.Byte,
+ GLTF_COMPONENT_TYPE_UNSIGNED_BYTE: ComponentType.UnsignedByte,
+ GLTF_COMPONENT_TYPE_SHORT: ComponentType.Short,
+ GLTF_COMPONENT_TYPE_UNSIGNED_SHORT: ComponentType.UnsignedShort,
+ GLTF_COMPONENT_TYPE_UNSIGNED_INT: ComponentType.UnsignedInt,
+ GLTF_COMPONENT_TYPE_FLOAT: ComponentType.Float
+ }[type_define]
+
+ @classmethod
+ def get_size(cls, component_type):
+ return {
+ ComponentType.Byte: 1,
+ ComponentType.UnsignedByte: 1,
+ ComponentType.Short: 2,
+ ComponentType.UnsignedShort: 2,
+ ComponentType.UnsignedInt: 4,
+ ComponentType.Float: 4
+ }[component_type]
+
+
+class DataType:
+ Scalar = "SCALAR"
+ Vec2 = "VEC2"
+ Vec3 = "VEC3"
+ Vec4 = "VEC4"
+ Mat2 = "MAT2"
+ Mat3 = "MAT3"
+ Mat4 = "MAT4"
+
+ def __new__(cls, *args, **kwargs):
+ raise RuntimeError("{} should not be instantiated".format(cls.__name__))
+
+ @classmethod
+ def num_elements(cls, data_type):
+ return {
+ DataType.Scalar: 1,
+ DataType.Vec2: 2,
+ DataType.Vec3: 3,
+ DataType.Vec4: 4,
+ DataType.Mat2: 4,
+ DataType.Mat3: 9,
+ DataType.Mat4: 16
+ }[data_type]
+
+ @classmethod
+ def vec_type_from_num(cls, num_elems):
+ if not (0 < num_elems < 5):
+ raise ValueError("No vector type with {} elements".format(num_elems))
+ return {
+ 1: DataType.Scalar,
+ 2: DataType.Vec2,
+ 3: DataType.Vec3,
+ 4: DataType.Vec4
+ }[num_elems]
+
+ @classmethod
+ def mat_type_from_num(cls, num_elems):
+ if not (4 <= num_elems <= 16):
+ raise ValueError("No matrix type with {} elements".format(num_elems))
+ return {
+ 4: DataType.Mat2,
+ 9: DataType.Mat3,
+ 16: DataType.Mat4
+ }[num_elems]
+
+
+#################
+# LEGACY DEFINES
+
+GLTF_VERSION = "2.0"
+
+#
+# Component Types
+#
+GLTF_COMPONENT_TYPE_BYTE = "BYTE"
+GLTF_COMPONENT_TYPE_UNSIGNED_BYTE = "UNSIGNED_BYTE"
+GLTF_COMPONENT_TYPE_SHORT = "SHORT"
+GLTF_COMPONENT_TYPE_UNSIGNED_SHORT = "UNSIGNED_SHORT"
+GLTF_COMPONENT_TYPE_UNSIGNED_INT = "UNSIGNED_INT"
+GLTF_COMPONENT_TYPE_FLOAT = "FLOAT"
+
+
+#
+# Data types
+#
+GLTF_DATA_TYPE_SCALAR = "SCALAR"
+GLTF_DATA_TYPE_VEC2 = "VEC2"
+GLTF_DATA_TYPE_VEC3 = "VEC3"
+GLTF_DATA_TYPE_VEC4 = "VEC4"
+GLTF_DATA_TYPE_MAT2 = "MAT2"
+GLTF_DATA_TYPE_MAT3 = "MAT3"
+GLTF_DATA_TYPE_MAT4 = "MAT4"
+
diff --git a/io_scene_gltf2/io/com/gltf2_io_debug.py b/io_scene_gltf2/io/com/gltf2_io_debug.py
new file mode 100755
index 00000000..b9098eba
--- /dev/null
+++ b/io_scene_gltf2/io/com/gltf2_io_debug.py
@@ -0,0 +1,143 @@
+# Copyright 2018 The glTF-Blender-IO authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+#
+# Imports
+#
+
+import time
+import logging
+
+#
+# Globals
+#
+
+OUTPUT_LEVELS = ['ERROR', 'WARNING', 'INFO', 'PROFILE', 'DEBUG', 'VERBOSE']
+
+g_current_output_level = 'DEBUG'
+g_profile_started = False
+g_profile_start = 0.0
+g_profile_end = 0.0
+g_profile_delta = 0.0
+
+#
+# Functions
+#
+
+
+def set_output_level(level):
+ """Set an output debug level."""
+ global g_current_output_level
+
+ if OUTPUT_LEVELS.index(level) < 0:
+ return
+
+ g_current_output_level = level
+
+
+def print_console(level, output):
+ """Print to Blender console with a given header and output."""
+ global OUTPUT_LEVELS
+ global g_current_output_level
+
+ if OUTPUT_LEVELS.index(level) > OUTPUT_LEVELS.index(g_current_output_level):
+ return
+
+ print(get_timestamp() + " | " + level + ': ' + output)
+
+
+def print_newline():
+ """Print a new line to Blender console."""
+ print()
+
+
+def get_timestamp():
+ current_time = time.gmtime()
+ return time.strftime("%H:%M:%S", current_time)
+
+
+def print_timestamp(label=None):
+ """Print a timestamp to Blender console."""
+ output = 'Timestamp: ' + get_timestamp()
+
+ if label is not None:
+ output = output + ' (' + label + ')'
+
+ print_console('PROFILE', output)
+
+
+def profile_start():
+ """Start profiling by storing the current time."""
+ global g_profile_start
+ global g_profile_started
+
+ if g_profile_started:
+ print_console('ERROR', 'Profiling already started')
+ return
+
+ g_profile_started = True
+
+ g_profile_start = time.time()
+
+
+def profile_end(label=None):
+ """Stop profiling and printing out the delta time since profile start."""
+ global g_profile_end
+ global g_profile_delta
+ global g_profile_started
+
+ if not g_profile_started:
+ print_console('ERROR', 'Profiling not started')
+ return
+
+ g_profile_started = False
+
+ g_profile_end = time.time()
+ g_profile_delta = g_profile_end - g_profile_start
+
+ output = 'Delta time: ' + str(g_profile_delta)
+
+ if label is not None:
+ output = output + ' (' + label + ')'
+
+ print_console('PROFILE', output)
+
+
+# TODO: need to have a unique system for logging importer/exporter
+# TODO: this logger is used for importer, but in io and in blender part, but is written here in a _io_ file
+class Log:
+ def __init__(self, loglevel):
+ self.logger = logging.getLogger('glTFImporter')
+ self.hdlr = logging.StreamHandler()
+ formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
+ self.hdlr.setFormatter(formatter)
+ self.logger.addHandler(self.hdlr)
+ self.logger.setLevel(int(loglevel))
+
+ @staticmethod
+ def get_levels():
+ levels = [
+ (str(logging.CRITICAL), "Critical", "", logging.CRITICAL),
+ (str(logging.ERROR), "Error", "", logging.ERROR),
+ (str(logging.WARNING), "Warning", "", logging.WARNING),
+ (str(logging.INFO), "Info", "", logging.INFO),
+ (str(logging.NOTSET), "NotSet", "", logging.NOTSET)
+ ]
+
+ return levels
+
+ @staticmethod
+ def default():
+ return str(logging.ERROR)
+
diff --git a/io_scene_gltf2/io/com/gltf2_io_extensions.py b/io_scene_gltf2/io/com/gltf2_io_extensions.py
new file mode 100644
index 00000000..2422d205
--- /dev/null
+++ b/io_scene_gltf2/io/com/gltf2_io_extensions.py
@@ -0,0 +1,38 @@
+# Copyright 2018 The glTF-Blender-IO authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from typing import List, Dict, Any
+
+
+class Extension:
+ """Container for extensions. Allows to specify requiredness"""
+ def __init__(self, name: str, extension: Dict[str, Any], required: bool = True):
+ self.name = name
+ self.extension = extension
+ self.required = required
+
+
+class ChildOfRootExtension(Extension):
+ """Container object for extensions that should be appended to the root extensions"""
+ def __init__(self, path: List[str], name: str, extension: Dict[str, Any], required: bool = True):
+ """
+ Wrap a local extension entity into an object that will later be inserted into a root extension and converted
+ to a reference.
+ :param path: The path of the extension object in the root extension. E.g. ['lights'] for
+ KHR_lights_punctual. Must be a path to a list in the extensions dict.
+ :param extension: The data that should be placed into the extension list
+ """
+ self.path = path
+ super().__init__(name, extension, required)
+
diff --git a/io_scene_gltf2/io/com/gltf2_io_functional.py b/io_scene_gltf2/io/com/gltf2_io_functional.py
new file mode 100755
index 00000000..eb65112f
--- /dev/null
+++ b/io_scene_gltf2/io/com/gltf2_io_functional.py
@@ -0,0 +1,41 @@
+# Copyright 2018 The glTF-Blender-IO authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import typing
+
+
+def chunks(lst: typing.Sequence[typing.Any], n: int) -> typing.List[typing.Any]:
+ """
+ Generator that yields successive n sized chunks of the list l
+ :param lst: the list to be split
+ :param n: the length of the chunks
+ :return: a sublist of at most length n
+ """
+ result = []
+ for i in range(0, len(lst), n):
+ result.append(lst[i:i + n])
+ return result
+
+
+def unzip(*args: typing.Iterable[typing.Any]) -> typing.Iterable[typing.Iterable[typing.Any]]:
+ """
+ Unzip the list. Inverse of the builtin zip
+ :param args: a list of lists or multiple list arguments
+ :return: a list of unzipped lists
+ """
+ if len(args) == 1:
+ args = args[0]
+
+ return zip(*args)
+
diff --git a/io_scene_gltf2/io/com/gltf2_io_image.py b/io_scene_gltf2/io/com/gltf2_io_image.py
new file mode 100755
index 00000000..af86daeb
--- /dev/null
+++ b/io_scene_gltf2/io/com/gltf2_io_image.py
@@ -0,0 +1,154 @@
+# Copyright 2018 The glTF-Blender-IO authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+#
+# Imports
+#
+
+import struct
+import zlib
+
+
+class Image:
+ """
+ Image object class to represent a 4-channel RGBA image.
+
+ Pixel values are expected to be floating point in the range of [0.0 to 1.0]
+ """
+
+ def __init__(self, width, height, pixels):
+ self.width = width
+ self.height = height
+ self.channels = 4
+ self.pixels = pixels
+ self.name = ""
+ self.file_format = "PNG"
+
+ def to_png_data(self):
+ buf = bytearray([int(channel * 255.0) for channel in self.pixels])
+
+ #
+ # Taken from 'blender-thumbnailer.py' in Blender.
+ #
+
+ # reverse the vertical line order and add null bytes at the start
+ width_byte_4 = self.width * 4
+ raw_data = b"".join(
+ b'\x00' + buf[span:span + width_byte_4] for span in range(
+ (self.height - 1) * self.width * 4, -1, - width_byte_4))
+
+ def png_pack(png_tag, data):
+ chunk_head = png_tag + data
+ return struct.pack("!I", len(data)) + chunk_head + struct.pack("!I", 0xFFFFFFFF & zlib.crc32(chunk_head))
+
+ return b"".join([
+ b'\x89PNG\r\n\x1a\n',
+ png_pack(b'IHDR', struct.pack("!2I5B", self.width, self.height, 8, 6, 0, 0, 0)),
+ png_pack(b'IDAT', zlib.compress(raw_data, 9)),
+ png_pack(b'IEND', b'')])
+
+ def to_image_data(self, mime_type):
+ if mime_type == 'image/png':
+ return self.to_png_data()
+ raise ValueError("Unsupported image file type {}".format(mime_type))
+
+ def save_png(self, dst_path):
+ data = self.to_png_data()
+ with open(dst_path, 'wb') as f:
+ f.write(data)
+
+
+def create_img(width, height, r=0.0, g=0.0, b=0.0, a=1.0):
+ """
+ Create a new image object with 4 channels and initialize it with the given default values.
+
+ (if no arguments are given, these default to R=0, G=0, B=0, A=1.0)
+ Return the created image object.
+ """
+ return Image(width, height, [r, g, b, a] * (width * height))
+
+
+def create_img_from_pixels(width, height, pixels):
+ """
+ Create a new image object with 4 channels and initialize it using the given array of pixel data.
+
+ Return the created image object.
+ """
+ if pixels is None or len(pixels) != width * height * 4:
+ return None
+
+ return Image(width, height, pixels)
+
+
+def copy_img_channel(dst_image, dst_channel, src_image, src_channel):
+ """
+ Copy a single channel (identified by src_channel) from src_image to dst_image (overwriting dst_channel).
+
+ src_image and dst_image are expected to be image objects created using create_img.
+ Return True on success, False otherwise.
+ """
+ if dst_image is None or src_image is None:
+ return False
+
+ if dst_channel < 0 or dst_channel >= dst_image.channels or src_channel < 0 or src_channel >= src_image.channels:
+ return False
+
+ if src_image.width != dst_image.width or \
+ src_image.height != dst_image.height or \
+ src_image.channels != dst_image.channels:
+ return False
+
+ for i in range(0, len(dst_image.pixels), dst_image.channels):
+ dst_image.pixels[i + dst_channel] = src_image.pixels[i + src_channel]
+
+ return True
+
+
+def test_save_img(image, path):
+ """
+ Save the given image to a PNG file (specified by path).
+
+ Return True on success, False otherwise.
+ """
+ if image is None or image.channels != 4:
+ return False
+
+ width = image.width
+ height = image.height
+
+ buf = bytearray([int(channel * 255.0) for channel in image.pixels])
+
+ #
+ # Taken from 'blender-thumbnailer.py' in Blender.
+ #
+
+ # reverse the vertical line order and add null bytes at the start
+ width_byte_4 = width * 4
+ raw_data = b"".join(
+ b'\x00' + buf[span:span + width_byte_4] for span in range((height - 1) * width * 4, -1, - width_byte_4))
+
+ def png_pack(png_tag, data):
+ chunk_head = png_tag + data
+ return struct.pack("!I", len(data)) + chunk_head + struct.pack("!I", 0xFFFFFFFF & zlib.crc32(chunk_head))
+
+ data = b"".join([
+ b'\x89PNG\r\n\x1a\n',
+ png_pack(b'IHDR', struct.pack("!2I5B", width, height, 8, 6, 0, 0, 0)),
+ png_pack(b'IDAT', zlib.compress(raw_data, 9)),
+ png_pack(b'IEND', b'')])
+
+ with open(path, 'wb') as f:
+ f.write(data)
+ return True
+
diff --git a/io_scene_gltf2/io/com/gltf2_io_lights_punctual.py b/io_scene_gltf2/io/com/gltf2_io_lights_punctual.py
new file mode 100644
index 00000000..3dc8704c
--- /dev/null
+++ b/io_scene_gltf2/io/com/gltf2_io_lights_punctual.py
@@ -0,0 +1,76 @@
+# Copyright 2018 The glTF-Blender-IO authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from io_scene_gltf2.io.com.gltf2_io import *
+
+
+class LightSpot:
+ """light/spot"""
+ def __init__(self, inner_cone_angle, outer_cone_angle):
+ self.inner_cone_angle = inner_cone_angle
+ self.outer_cone_angle = outer_cone_angle
+
+ @staticmethod
+ def from_dict(obj):
+ assert isinstance(obj, dict)
+ inner_cone_angle = from_union([from_float, from_none], obj.get("innerConeAngle"))
+ outer_cone_angle = from_union([from_float, from_none], obj.get("outerConeAngle"))
+ return LightSpot(inner_cone_angle, outer_cone_angle)
+
+ def to_dict(self):
+ result = {}
+ result["innerConeAngle"] = from_union([from_float, from_none], self.inner_cone_angle)
+ result["outerConeAngle"] = from_union([from_float, from_none], self.outer_cone_angle)
+ return result
+
+
+class Light:
+ """defines a set of lights for use with glTF 2.0. Lights define light sources within a scene"""
+ def __init__(self, color, intensity, spot, type, range, name, extensions, extras):
+ self.color = color
+ self.intensity = intensity
+ self.spot = spot
+ self.type = type
+ self.range = range
+ self.name = name
+ self.extensions = extensions
+ self.extras = extras
+
+ @staticmethod
+ def from_dict(obj):
+ assert isinstance(obj, dict)
+ color = from_union([lambda x: from_list(from_float, x), from_none], obj.get("color"))
+ intensity = from_union([from_float, from_none], obj.get("intensity"))
+ spot = LightSpot.from_dict(obj.get("spot"))
+ type = from_str(obj.get("type"))
+ range = from_union([from_float, from_none], obj.get("range"))
+ name = from_union([from_str, from_none], obj.get("name"))
+ extensions = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none],
+ obj.get("extensions"))
+ extras = obj.get("extras")
+ return Light(color, intensity, spot, type, range, name, extensions, extras)
+
+ def to_dict(self):
+ result = {}
+ result["color"] = from_union([lambda x: from_list(to_float, x), from_none], self.color)
+ result["intensity"] = from_union([from_float, from_none], self.intensity)
+ result["spot"] = from_union([lambda x: to_class(LightSpot, x), from_none], self.spot)
+ result["type"] = from_str(self.type)
+ result["range"] = from_union([from_float, from_none], self.range)
+ result["name"] = from_union([from_str, from_none], self.name)
+ result["extensions"] = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none],
+ self.extensions)
+ result["extras"] = self.extras
+ return result
+
diff --git a/io_scene_gltf2/io/com/gltf2_io_trs.py b/io_scene_gltf2/io/com/gltf2_io_trs.py
new file mode 100755
index 00000000..59f30830
--- /dev/null
+++ b/io_scene_gltf2/io/com/gltf2_io_trs.py
@@ -0,0 +1,68 @@
+# Copyright 2018 The glTF-Blender-IO authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+class TRS:
+
+ def __new__(cls, *args, **kwargs):
+ raise RuntimeError("{} should not be instantiated".format(cls.__name__))
+
+ @staticmethod
+ def scale_to_matrix(scale):
+ # column major !
+ return [scale[0], 0, 0, 0,
+ 0, scale[1], 0, 0,
+ 0, 0, scale[2], 0,
+ 0, 0, 0, 1]
+
+ @staticmethod
+ def quaternion_to_matrix(q):
+ x, y, z, w = q
+ # TODO : is q normalized ? --> if not, multiply by 1/(w*w + x*x + y*y + z*z)
+ # column major !
+ return [
+ 1 - 2 * y * y - 2 * z * z, 2 * x * y + 2 * w * z, 2 * x * z - 2 * w * y, 0,
+ 2 * x * y - 2 * w * z, 1 - 2 * x * x - 2 * z * z, 2 * y * z + 2 * w * x, 0,
+ 2 * x * z + 2 * y * w, 2 * y * z - 2 * w * x, 1 - 2 * x * x - 2 * y * y, 0,
+ 0, 0, 0, 1]
+
+ @staticmethod
+ def matrix_multiply(m, n):
+ # column major !
+
+ return [
+ m[0] * n[0] + m[4] * n[1] + m[8] * n[2] + m[12] * n[3],
+ m[1] * n[0] + m[5] * n[1] + m[9] * n[2] + m[13] * n[3],
+ m[2] * n[0] + m[6] * n[1] + m[10] * n[2] + m[14] * n[3],
+ m[3] * n[0] + m[7] * n[1] + m[11] * n[2] + m[15] * n[3],
+ m[0] * n[4] + m[4] * n[5] + m[8] * n[6] + m[12] * n[7],
+ m[1] * n[4] + m[5] * n[5] + m[9] * n[6] + m[13] * n[7],
+ m[2] * n[4] + m[6] * n[5] + m[10] * n[6] + m[14] * n[7],
+ m[3] * n[4] + m[7] * n[5] + m[11] * n[6] + m[15] * n[7],
+ m[0] * n[8] + m[4] * n[9] + m[8] * n[10] + m[12] * n[11],
+ m[1] * n[8] + m[5] * n[9] + m[9] * n[10] + m[13] * n[11],
+ m[2] * n[8] + m[6] * n[9] + m[10] * n[10] + m[14] * n[11],
+ m[3] * n[8] + m[7] * n[9] + m[11] * n[10] + m[15] * n[11],
+ m[0] * n[12] + m[4] * n[13] + m[8] * n[14] + m[12] * n[15],
+ m[1] * n[12] + m[5] * n[13] + m[9] * n[14] + m[13] * n[15],
+ m[2] * n[12] + m[6] * n[13] + m[10] * n[14] + m[14] * n[15],
+ m[3] * n[12] + m[7] * n[13] + m[11] * n[14] + m[15] * n[15],
+ ]
+
+ @staticmethod
+ def translation_to_matrix(translation):
+ # column major !
+ return [1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0,
+ translation[0], translation[1], translation[2], 1.0]
+
diff --git a/io_scene_gltf2/io/exp/gltf2_io_binary_data.py b/io_scene_gltf2/io/exp/gltf2_io_binary_data.py
new file mode 100755
index 00000000..42f6d5d7
--- /dev/null
+++ b/io_scene_gltf2/io/exp/gltf2_io_binary_data.py
@@ -0,0 +1,36 @@
+# Copyright 2018 The glTF-Blender-IO authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import typing
+import array
+from io_scene_gltf2.io.com import gltf2_io_constants
+
+
+class BinaryData:
+ """Store for gltf binary data that can later be stored in a buffer."""
+
+ def __init__(self, data: bytes):
+ if not isinstance(data, bytes):
+ raise TypeError("Data is not a bytes array")
+ self.data = data
+
+ @classmethod
+ def from_list(cls, lst: typing.List[typing.Any], gltf_component_type: gltf2_io_constants.ComponentType):
+ format_char = gltf2_io_constants.ComponentType.to_type_code(gltf_component_type)
+ return BinaryData(array.array(format_char, lst).tobytes())
+
+ @property
+ def byte_length(self):
+ return len(self.data)
+
diff --git a/io_scene_gltf2/io/exp/gltf2_io_buffer.py b/io_scene_gltf2/io/exp/gltf2_io_buffer.py
new file mode 100755
index 00000000..694be11e
--- /dev/null
+++ b/io_scene_gltf2/io/exp/gltf2_io_buffer.py
@@ -0,0 +1,61 @@
+# Copyright 2018 The glTF-Blender-IO authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import base64
+
+from io_scene_gltf2.io.com import gltf2_io
+from io_scene_gltf2.io.exp import gltf2_io_binary_data
+
+
+class Buffer:
+ """Class representing binary data for use in a glTF file as 'buffer' property."""
+
+ def __init__(self, buffer_index=0):
+ self.__data = b""
+ self.__buffer_index = buffer_index
+
+ def add_and_get_view(self, binary_data: gltf2_io_binary_data.BinaryData) -> gltf2_io.BufferView:
+ """Add binary data to the buffer. Return a glTF BufferView."""
+ offset = len(self.__data)
+ self.__data += binary_data.data
+
+ # offsets should be a multiple of 4 --> therefore add padding if necessary
+ padding = (4 - (binary_data.byte_length % 4)) % 4
+ self.__data += b"\x00" * padding
+
+ buffer_view = gltf2_io.BufferView(
+ buffer=self.__buffer_index,
+ byte_length=binary_data.byte_length,
+ byte_offset=offset,
+ byte_stride=None,
+ extensions=None,
+ extras=None,
+ name=None,
+ target=None
+ )
+ return buffer_view
+
+ @property
+ def byte_length(self):
+ return len(self.__data)
+
+ def to_bytes(self):
+ return self.__data
+
+ def to_embed_string(self):
+ return 'data:application/octet-stream;base64,' + base64.b64encode(self.__data).decode('ascii')
+
+ def clear(self):
+ self.__data = b""
+
diff --git a/io_scene_gltf2/io/exp/gltf2_io_export.py b/io_scene_gltf2/io/exp/gltf2_io_export.py
new file mode 100755
index 00000000..286d4e75
--- /dev/null
+++ b/io_scene_gltf2/io/exp/gltf2_io_export.py
@@ -0,0 +1,97 @@
+# Copyright 2018 The glTF-Blender-IO authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+#
+# Imports
+#
+
+import json
+import struct
+
+#
+# Globals
+#
+
+#
+# Functions
+#
+
+
+def save_gltf(glTF, export_settings, encoder, glb_buffer):
+ indent = None
+ separators = separators = (',', ':')
+
+ if export_settings['gltf_format'] != 'GLB':
+ indent = 4
+ # The comma is typically followed by a newline, so no trailing whitespace is needed on it.
+ separators = separators = (',', ' : ')
+
+ glTF_encoded = json.dumps(glTF, indent=indent, separators=separators, sort_keys=True, cls=encoder, allow_nan=False)
+
+ #
+
+ if export_settings['gltf_format'] != 'GLB':
+ file = open(export_settings['gltf_filepath'], "w", encoding="utf8", newline="\n")
+ file.write(glTF_encoded)
+ file.write("\n")
+ file.close()
+
+ binary = export_settings['gltf_binary']
+ if len(binary) > 0 and not export_settings['gltf_embed_buffers']:
+ file = open(export_settings['gltf_filedirectory'] + export_settings['gltf_binaryfilename'], "wb")
+ file.write(binary)
+ file.close()
+
+ else:
+ file = open(export_settings['gltf_filepath'], "wb")
+
+ glTF_data = glTF_encoded.encode()
+ binary = glb_buffer
+
+ length_gtlf = len(glTF_data)
+ spaces_gltf = (4 - (length_gtlf & 3)) & 3
+ length_gtlf += spaces_gltf
+
+ length_bin = len(binary)
+ zeros_bin = (4 - (length_bin & 3)) & 3
+ length_bin += zeros_bin
+
+ length = 12 + 8 + length_gtlf
+ if length_bin > 0:
+ length += 8 + length_bin
+
+ # Header (Version 2)
+ file.write('glTF'.encode())
+ file.write(struct.pack("I", 2))
+ file.write(struct.pack("I", length))
+
+ # Chunk 0 (JSON)
+ file.write(struct.pack("I", length_gtlf))
+ file.write('JSON'.encode())
+ file.write(glTF_data)
+ for i in range(0, spaces_gltf):
+ file.write(' '.encode())
+
+ # Chunk 1 (BIN)
+ if length_bin > 0:
+ file.write(struct.pack("I", length_bin))
+ file.write('BIN\0'.encode())
+ file.write(binary)
+ for i in range(0, zeros_bin):
+ file.write('\0'.encode())
+
+ file.close()
+
+ return True
+
diff --git a/io_scene_gltf2/io/exp/gltf2_io_get.py b/io_scene_gltf2/io/exp/gltf2_io_get.py
new file mode 100755
index 00000000..35c65615
--- /dev/null
+++ b/io_scene_gltf2/io/exp/gltf2_io_get.py
@@ -0,0 +1,316 @@
+# Copyright 2018 The glTF-Blender-IO authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+#
+# Imports
+#
+
+import os
+
+#
+# Globals
+#
+
+#
+# Functions
+#
+
+
+def get_material_requires_texcoords(glTF, index):
+ """Query function, if a material "needs" texture coordinates. This is the case, if a texture is present and used."""
+ if glTF.materials is None:
+ return False
+
+ materials = glTF.materials
+
+ if index < 0 or index >= len(materials):
+ return False
+
+ material = materials[index]
+
+ # General
+
+ if material.emissive_texture is not None:
+ return True
+
+ if material.normal_texture is not None:
+ return True
+
+ if material.occlusion_texture is not None:
+ return True
+
+ # Metallic roughness
+
+ if material.pbr_metallic_roughness is not None and \
+ material.pbr_metallic_roughness.base_color_texture is not None:
+ return True
+
+ if material.pbr_metallic_roughness is not None and \
+ material.pbr_metallic_roughness.metallic_roughness_texture is not None:
+ return True
+
+ return False
+
+
+def get_material_requires_normals(glTF, index):
+ """
+ Query function, if a material "needs" normals. This is the case, if a texture is present and used.
+
+ At point of writing, same function as for texture coordinates.
+ """
+ return get_material_requires_texcoords(glTF, index)
+
+
+def get_material_index(glTF, name):
+ """Return the material index in the glTF array."""
+ if name is None:
+ return -1
+
+ if glTF.materials is None:
+ return -1
+
+ index = 0
+ for material in glTF.materials:
+ if material.name == name:
+ return index
+
+ index += 1
+
+ return -1
+
+
+def get_mesh_index(glTF, name):
+ """Return the mesh index in the glTF array."""
+ if glTF.meshes is None:
+ return -1
+
+ index = 0
+ for mesh in glTF.meshes:
+ if mesh.name == name:
+ return index
+
+ index += 1
+
+ return -1
+
+
+def get_skin_index(glTF, name, index_offset):
+ """Return the skin index in the glTF array."""
+ if glTF.skins is None:
+ return -1
+
+ skeleton = get_node_index(glTF, name)
+
+ index = 0
+ for skin in glTF.skins:
+ if skin.skeleton == skeleton:
+ return index + index_offset
+
+ index += 1
+
+ return -1
+
+
+def get_camera_index(glTF, name):
+ """Return the camera index in the glTF array."""
+ if glTF.cameras is None:
+ return -1
+
+ index = 0
+ for camera in glTF.cameras:
+ if camera.name == name:
+ return index
+
+ index += 1
+
+ return -1
+
+
+def get_light_index(glTF, name):
+ """Return the light index in the glTF array."""
+ if glTF.extensions is None:
+ return -1
+
+ extensions = glTF.extensions
+
+ if extensions.get('KHR_lights_punctual') is None:
+ return -1
+
+ khr_lights_punctual = extensions['KHR_lights_punctual']
+
+ if khr_lights_punctual.get('lights') is None:
+ return -1
+
+ lights = khr_lights_punctual['lights']
+
+ index = 0
+ for light in lights:
+ if light['name'] == name:
+ return index
+
+ index += 1
+
+ return -1
+
+
+def get_node_index(glTF, name):
+ """Return the node index in the glTF array."""
+ if glTF.nodes is None:
+ return -1
+
+ index = 0
+ for node in glTF.nodes:
+ if node.name == name:
+ return index
+
+ index += 1
+
+ return -1
+
+
+def get_scene_index(glTF, name):
+ """Return the scene index in the glTF array."""
+ if glTF.scenes is None:
+ return -1
+
+ index = 0
+ for scene in glTF.scenes:
+ if scene.name == name:
+ return index
+
+ index += 1
+
+ return -1
+
+
+def get_texture_index(glTF, filename):
+ """Return the texture index in the glTF array by a given file path."""
+ if glTF.textures is None:
+ return -1
+
+ image_index = get_image_index(glTF, filename)
+
+ if image_index == -1:
+ return -1
+
+ for texture_index, texture in enumerate(glTF.textures):
+ if image_index == texture.source:
+ return texture_index
+
+ return -1
+
+
+def get_image_index(glTF, filename):
+ """Return the image index in the glTF array."""
+ if glTF.images is None:
+ return -1
+
+ image_name = get_image_name(filename)
+
+ for index, current_image in enumerate(glTF.images):
+ if image_name == current_image.name:
+ return index
+
+ return -1
+
+
+def get_image_name(filename):
+ """Return user-facing, extension-agnostic name for image."""
+ return os.path.splitext(filename)[0]
+
+
+def get_scalar(default_value, init_value=0.0):
+ """Return scalar with a given default/fallback value."""
+ return_value = init_value
+
+ if default_value is None:
+ return return_value
+
+ return_value = default_value
+
+ return return_value
+
+
+def get_vec2(default_value, init_value=[0.0, 0.0]):
+ """Return vec2 with a given default/fallback value."""
+ return_value = init_value
+
+ if default_value is None or len(default_value) < 2:
+ return return_value
+
+ index = 0
+ for number in default_value:
+ return_value[index] = number
+
+ index += 1
+ if index == 2:
+ return return_value
+
+ return return_value
+
+
+def get_vec3(default_value, init_value=[0.0, 0.0, 0.0]):
+ """Return vec3 with a given default/fallback value."""
+ return_value = init_value
+
+ if default_value is None or len(default_value) < 3:
+ return return_value
+
+ index = 0
+ for number in default_value:
+ return_value[index] = number
+
+ index += 1
+ if index == 3:
+ return return_value
+
+ return return_value
+
+
+def get_vec4(default_value, init_value=[0.0, 0.0, 0.0, 1.0]):
+ """Return vec4 with a given default/fallback value."""
+ return_value = init_value
+
+ if default_value is None or len(default_value) < 4:
+ return return_value
+
+ index = 0
+ for number in default_value:
+ return_value[index] = number
+
+ index += 1
+ if index == 4:
+ return return_value
+
+ return return_value
+
+
+def get_index(elements, name):
+ """Return index of a glTF element by a given name."""
+ if elements is None or name is None:
+ return -1
+
+ index = 0
+ for element in elements:
+ if isinstance(element, dict):
+ if element.get('name') == name:
+ return index
+ else:
+ if element.name == name:
+ return index
+
+ index += 1
+
+ return -1
+
diff --git a/io_scene_gltf2/io/exp/gltf2_io_image_data.py b/io_scene_gltf2/io/exp/gltf2_io_image_data.py
new file mode 100755
index 00000000..92bdd09f
--- /dev/null
+++ b/io_scene_gltf2/io/exp/gltf2_io_image_data.py
@@ -0,0 +1,136 @@
+# Copyright 2018 The glTF-Blender-IO authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import typing
+import struct
+import re
+import zlib
+import numpy as np
+
+class ImageData:
+ """Contains channels of an image with raw pixel data."""
+ # TODO: refactor to only operate on numpy arrays
+ # FUTURE_WORK: as a method to allow the node graph to be better supported, we could model some of
+ # the node graph elements with numpy functions
+
+ def __init__(self, name: str, filepath: str, width: int, height: int, offset: int, channels: typing.Optional[typing.List[np.ndarray]] = []):
+ if width <= 0 or height <= 0:
+ raise ValueError("Image data can not have zero width or height")
+ if offset + len(channels) > 4:
+ raise ValueError("Image data can not have more than 4 channels")
+ self.channels = [None, None, None, None]
+ channels_length = len(channels)
+ for index in range(offset, offset + channels_length):
+ self.channels[index] = channels[index - offset]
+ self.name = name
+ self.filepath = filepath
+ self.width = width
+ self.height = height
+
+ def add_to_image(self, channel, image_data):
+ if self.width != image_data.width or self.height != image_data.height:
+ raise ValueError("Image dimensions do not match")
+ if channel < 0 or channel > 3:
+ raise ValueError("Can't append image: channels out of bounds")
+ if len(image_data.channels) != 4:
+ raise ValueError("Can't append image: incomplete image")
+
+ if self.name != image_data.name:
+ self.name += image_data.name
+ self.filepath = ""
+
+ # Replace channel.
+ self.channels[channel] = image_data.channels[channel]
+
+ @property
+ def r(self):
+ if len(self.channels) <= 0:
+ return None
+ return self.channels[0]
+
+ @property
+ def g(self):
+ if len(self.channels) <= 1:
+ return None
+ return self.channels[1]
+
+ @property
+ def b(self):
+ if len(self.channels) <= 2:
+ return None
+ return self.channels[2]
+
+ @property
+ def a(self):
+ if len(self.channels) <= 3:
+ return None
+ return self.channels[3]
+
+ def get_extension(self):
+ allowed_extensions = ['.png', '.jpg', '.jpeg']
+ fallback_extension = allowed_extensions[0]
+
+ matches = re.findall(r'\.\w+$', self.filepath)
+ extension = matches[0] if len(matches) > 0 else fallback_extension
+ return extension if extension.lower() in allowed_extensions else fallback_extension
+
+ def to_image_data(self, mime_type: str) -> bytes:
+ if mime_type == 'image/png':
+ return self.to_png_data()
+ raise ValueError("Unsupported image file type {}".format(mime_type))
+
+ def to_png_data(self) -> bytes:
+ channels = self.channels
+
+ # if there is no data, create a single pixel image
+ if not channels:
+ channels = np.ones((1, 1))
+ # fill all channels of the png
+ for _ in range(4 - len(channels)):
+ channels.append(np.ones_like(channels[0]))
+ else:
+ template_index = None
+ for index in range(0, 4):
+ if channels[index] is not None:
+ template_index = index
+ break
+ for index in range(0, 4):
+ if channels[index] is None:
+ channels[index] = np.ones_like(channels[template_index])
+
+ image = np.concatenate(channels, axis=1)
+ image = image.flatten()
+ image = (image * 255.0).astype(np.uint8)
+ buf = image.tobytes()
+
+ #
+ # Taken from 'blender-thumbnailer.py' in Blender.
+ #
+
+ # reverse the vertical line order and add null bytes at the start
+ width_byte_4 = self.width * 4
+ raw_data = b"".join(
+ b'\x00' + buf[span:span + width_byte_4] for span in range(
+ (self.height - 1) * self.width * 4, -1, - width_byte_4))
+
+ def png_pack(png_tag, data):
+ chunk_head = png_tag + data
+ return struct.pack("!I", len(data)) + chunk_head + struct.pack("!I", 0xFFFFFFFF & zlib.crc32(chunk_head))
+
+ return b"".join([
+ b'\x89PNG\r\n\x1a\n',
+ png_pack(b'IHDR', struct.pack("!2I5B", self.width, self.height, 8, 6, 0, 0, 0)),
+ png_pack(b'IDAT', zlib.compress(raw_data, 9)),
+ png_pack(b'IEND', b'')])
+
diff --git a/io_scene_gltf2/io/imp/__init__.py b/io_scene_gltf2/io/imp/__init__.py
new file mode 100755
index 00000000..d3c53771
--- /dev/null
+++ b/io_scene_gltf2/io/imp/__init__.py
@@ -0,0 +1,16 @@
+# Copyright 2018 The glTF-Blender-IO authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""IO imp package."""
+
diff --git a/io_scene_gltf2/io/imp/gltf2_io_binary.py b/io_scene_gltf2/io/imp/gltf2_io_binary.py
new file mode 100755
index 00000000..5f51d95d
--- /dev/null
+++ b/io_scene_gltf2/io/imp/gltf2_io_binary.py
@@ -0,0 +1,178 @@
+# Copyright 2018 The glTF-Blender-IO authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import struct
+import base64
+from os.path import dirname, join, isfile, basename
+
+
+class BinaryData():
+ """Binary reader."""
+ def __new__(cls, *args, **kwargs):
+ raise RuntimeError("%s should not be instantiated" % cls)
+
+ @staticmethod
+ def get_binary_from_accessor(gltf, accessor_idx):
+ """Get binary from accessor."""
+ accessor = gltf.data.accessors[accessor_idx]
+ bufferView = gltf.data.buffer_views[accessor.buffer_view] # TODO initialize with 0 when not present!
+ if bufferView.buffer in gltf.buffers.keys():
+ buffer = gltf.buffers[bufferView.buffer]
+ else:
+ # load buffer
+ gltf.load_buffer(bufferView.buffer)
+ buffer = gltf.buffers[bufferView.buffer]
+
+ accessor_offset = accessor.byte_offset
+ bufferview_offset = bufferView.byte_offset
+
+ if accessor_offset is None:
+ accessor_offset = 0
+ if bufferview_offset is None:
+ bufferview_offset = 0
+
+ return buffer[accessor_offset + bufferview_offset:accessor_offset + bufferview_offset + bufferView.byte_length]
+
+ @staticmethod
+ def get_data_from_accessor(gltf, accessor_idx):
+ """Get data from accessor."""
+ accessor = gltf.data.accessors[accessor_idx]
+
+ bufferView = gltf.data.buffer_views[accessor.buffer_view] # TODO initialize with 0 when not present!
+ buffer_data = BinaryData.get_binary_from_accessor(gltf, accessor_idx)
+
+ fmt_char = gltf.fmt_char_dict[accessor.component_type]
+ component_nb = gltf.component_nb_dict[accessor.type]
+ fmt = '<' + (fmt_char * component_nb)
+ stride_ = struct.calcsize(fmt)
+ # TODO data alignment stuff
+
+ if bufferView.byte_stride:
+ stride = bufferView.byte_stride
+ else:
+ stride = stride_
+
+ data = []
+ offset = 0
+ while len(data) < accessor.count:
+ element = struct.unpack_from(fmt, buffer_data, offset)
+ data.append(element)
+ offset += stride
+
+ if accessor.sparse:
+ sparse_indices_data = BinaryData.get_data_from_sparse(gltf, accessor.sparse, "indices")
+ sparse_values_values = BinaryData.get_data_from_sparse(
+ gltf,
+ accessor.sparse,
+ "values",
+ accessor.type,
+ accessor.component_type
+ )
+
+ # apply sparse
+ for cpt_idx, idx in enumerate(sparse_indices_data):
+ data[idx[0]] = sparse_values_values[cpt_idx]
+
+ # Normalization
+ if accessor.normalized:
+ for idx, tuple in enumerate(data):
+ new_tuple = ()
+ for i in tuple:
+ new_tuple += (float(i),)
+ data[idx] = new_tuple
+
+ return data
+
+ @staticmethod
+ def get_data_from_sparse(gltf, sparse, type_, type_val=None, comp_type=None):
+ """Get data from sparse."""
+ if type_ == "indices":
+ bufferView = gltf.data.buffer_views[sparse.indices.buffer_view]
+ offset = sparse.indices.byte_offset
+ component_nb = gltf.component_nb_dict['SCALAR']
+ fmt_char = gltf.fmt_char_dict[sparse.indices.component_type]
+ elif type_ == "values":
+ bufferView = gltf.data.buffer_views[sparse.values.buffer_view]
+ offset = sparse.values.byte_offset
+ component_nb = gltf.component_nb_dict[type_val]
+ fmt_char = gltf.fmt_char_dict[comp_type]
+
+ if bufferView.buffer in gltf.buffers.keys():
+ buffer = gltf.buffers[bufferView.buffer]
+ else:
+ # load buffer
+ gltf.load_buffer(bufferView.buffer)
+ buffer = gltf.buffers[bufferView.buffer]
+
+ bin_data = buffer[bufferView.byte_offset + offset:bufferView.byte_offset + offset + bufferView.byte_length]
+
+ fmt = '<' + (fmt_char * component_nb)
+ stride_ = struct.calcsize(fmt)
+ # TODO data alignment stuff ?
+
+ if bufferView.byte_stride:
+ stride = bufferView.byte_stride
+ else:
+ stride = stride_
+
+ data = []
+ offset = 0
+ while len(data) < sparse.count:
+ element = struct.unpack_from(fmt, bin_data, offset)
+ data.append(element)
+ offset += stride
+
+ return data
+
+ @staticmethod
+ def get_image_data(gltf, img_idx):
+ """Get data from image."""
+ pyimage = gltf.data.images[img_idx]
+
+ image_name = "Image_" + str(img_idx)
+
+ if pyimage.uri:
+ sep = ';base64,'
+ if pyimage.uri[:5] == 'data:':
+ idx = pyimage.uri.find(sep)
+ if idx != -1:
+ data = pyimage.uri[idx + len(sep):]
+ return base64.b64decode(data), image_name
+
+ if isfile(join(dirname(gltf.filename), pyimage.uri)):
+ with open(join(dirname(gltf.filename), pyimage.uri), 'rb') as f_:
+ return f_.read(), basename(join(dirname(gltf.filename), pyimage.uri))
+ else:
+ pyimage.gltf.log.error("Missing file (index " + str(img_idx) + "): " + pyimage.uri)
+ return None, None
+
+ if pyimage.buffer_view is None:
+ return None, None
+
+ bufferView = gltf.data.buffer_views[pyimage.buffer_view]
+
+ if bufferView.buffer in gltf.buffers.keys():
+ buffer = gltf.buffers[bufferView.buffer]
+ else:
+ # load buffer
+ gltf.load_buffer(bufferView.buffer)
+ buffer = gltf.buffers[bufferView.buffer]
+
+ bufferview_offset = bufferView.byte_offset
+
+ if bufferview_offset is None:
+ bufferview_offset = 0
+
+ return buffer[bufferview_offset:bufferview_offset + bufferView.byte_length], image_name
+
diff --git a/io_scene_gltf2/io/imp/gltf2_io_gltf.py b/io_scene_gltf2/io/imp/gltf2_io_gltf.py
new file mode 100755
index 00000000..1c9e67a2
--- /dev/null
+++ b/io_scene_gltf2/io/imp/gltf2_io_gltf.py
@@ -0,0 +1,199 @@
+# Copyright 2018 The glTF-Blender-IO authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from ..com.gltf2_io import gltf_from_dict
+from ..com.gltf2_io_debug import Log
+import logging
+import json
+import struct
+import base64
+from os.path import dirname, join, getsize, isfile
+
+
+class glTFImporter():
+ """glTF Importer class."""
+
+ def __init__(self, filename, import_settings):
+ """initialization."""
+ self.filename = filename
+ self.import_settings = import_settings
+ self.buffers = {}
+
+ if 'loglevel' not in self.import_settings.keys():
+ self.import_settings['loglevel'] = logging.ERROR
+
+ log = Log(import_settings['loglevel'])
+ self.log = log.logger
+ self.log_handler = log.hdlr
+
+ self.SIMPLE = 1
+ self.TEXTURE = 2
+ self.TEXTURE_FACTOR = 3
+
+ # TODO: move to a com place?
+ self.extensions_managed = [
+ 'KHR_materials_pbrSpecularGlossiness'
+ ]
+
+ # TODO : merge with io_constants
+ self.fmt_char_dict = {}
+ self.fmt_char_dict[5120] = 'b' # Byte
+ self.fmt_char_dict[5121] = 'B' # Unsigned Byte
+ self.fmt_char_dict[5122] = 'h' # Short
+ self.fmt_char_dict[5123] = 'H' # Unsigned Short
+ self.fmt_char_dict[5125] = 'I' # Unsigned Int
+ self.fmt_char_dict[5126] = 'f' # Float
+
+ self.component_nb_dict = {}
+ self.component_nb_dict['SCALAR'] = 1
+ self.component_nb_dict['VEC2'] = 2
+ self.component_nb_dict['VEC3'] = 3
+ self.component_nb_dict['VEC4'] = 4
+ self.component_nb_dict['MAT2'] = 4
+ self.component_nb_dict['MAT3'] = 9
+ self.component_nb_dict['MAT4'] = 16
+
+ @staticmethod
+ def bad_json_value(val):
+ """Bad Json value."""
+ raise ValueError('Json contains some unauthorized values')
+
+ def checks(self):
+ """Some checks."""
+ if self.data.asset.version != "2.0":
+ return False, "glTF version must be 2"
+
+ if self.data.extensions_required is not None:
+ for extension in self.data.extensions_required:
+ if extension not in self.data.extensions_used:
+ return False, "Extension required must be in Extension Used too"
+ if extension not in self.extensions_managed:
+ return False, "Extension " + extension + " is not available on this addon version"
+
+ if self.data.extensions_used is not None:
+ for extension in self.data.extensions_used:
+ if extension not in self.extensions_managed:
+ # Non blocking error #TODO log
+ pass
+
+ return True, None
+
+ def load_glb(self):
+ """Load binary glb."""
+ header = struct.unpack_from('<4sII', self.content)
+ self.format = header[0]
+ self.version = header[1]
+ self.file_size = header[2]
+
+ if self.format != b'glTF':
+ return False, "This file is not a glTF/glb file"
+
+ if self.version != 2:
+ return False, "glTF version doesn't match to 2"
+
+ if self.file_size != getsize(self.filename):
+ return False, "File size doesn't match"
+
+ offset = 12 # header size = 12
+
+ # TODO check json type for chunk 0, and BIN type for next ones
+
+ # json
+ type, len_, str_json, offset = self.load_chunk(offset)
+ if len_ != len(str_json):
+ return False, "Length of json part doesn't match"
+ try:
+ json_ = json.loads(str_json.decode('utf-8'), parse_constant=glTFImporter.bad_json_value)
+ self.data = gltf_from_dict(json_)
+ except ValueError as e:
+ return False, e.args[0]
+
+ # binary data
+ chunk_cpt = 0
+ while offset < len(self.content):
+ type, len_, data, offset = self.load_chunk(offset)
+ if len_ != len(data):
+ return False, "Length of bin buffer " + str(chunk_cpt) + " doesn't match"
+
+ self.buffers[chunk_cpt] = data
+ chunk_cpt += 1
+
+ self.content = None
+ return True, None
+
+ def load_chunk(self, offset):
+ """Load chunk."""
+ chunk_header = struct.unpack_from('<I4s', self.content, offset)
+ data_length = chunk_header[0]
+ data_type = chunk_header[1]
+ data = self.content[offset + 8: offset + 8 + data_length]
+
+ return data_type, data_length, data, offset + 8 + data_length
+
+ def read(self):
+ """Read file."""
+ # Check this is a file
+ if not isfile(self.filename):
+ return False, "Please select a file"
+
+ # Check if file is gltf or glb
+ with open(self.filename, 'rb') as f:
+ self.content = f.read()
+
+ self.is_glb_format = self.content[:4] == b'glTF'
+
+ # glTF file
+ if not self.is_glb_format:
+ self.content = None
+ with open(self.filename, 'r') as f:
+ content = f.read()
+ try:
+ self.data = gltf_from_dict(json.loads(content, parse_constant=glTFImporter.bad_json_value))
+ return True, None
+ except ValueError as e:
+ return False, e.args[0]
+
+ # glb file
+ else:
+ # Parsing glb file
+ success, txt = self.load_glb()
+ return success, txt
+
+ def is_node_joint(self, node_idx):
+ """Check if node is a joint."""
+ if not self.data.skins: # if no skin in gltf file
+ return False, None
+
+ for skin_idx, skin in enumerate(self.data.skins):
+ if node_idx in skin.joints:
+ return True, skin_idx
+
+ return False, None
+
+ def load_buffer(self, buffer_idx):
+ """Load buffer."""
+ buffer = self.data.buffers[buffer_idx]
+
+ if buffer.uri:
+ sep = ';base64,'
+ if buffer.uri[:5] == 'data:':
+ idx = buffer.uri.find(sep)
+ if idx != -1:
+ data = buffer.uri[idx + len(sep):]
+ self.buffers[buffer_idx] = base64.b64decode(data)
+ return
+
+ with open(join(dirname(self.filename), buffer.uri), 'rb') as f_:
+ self.buffers[buffer_idx] = f_.read()
+
diff --git a/io_scene_ms3d/__init__.py b/io_scene_ms3d/__init__.py
index 794aae6b..805b1d83 100644
--- a/io_scene_ms3d/__init__.py
+++ b/io_scene_ms3d/__init__.py
@@ -64,8 +64,8 @@ from bpy.utils import (
unregister_module,
)
from bpy.types import (
- INFO_MT_file_export,
- INFO_MT_file_import,
+ TOPBAR_MT_file_export,
+ TOPBAR_MT_file_import,
)
@@ -82,16 +82,16 @@ def register():
ms3d_ui.register()
register_module(__name__)
- INFO_MT_file_export.append(Ms3dExportOperator.menu_func)
- INFO_MT_file_import.append(Ms3dImportOperator.menu_func)
+ TOPBAR_MT_file_export.append(Ms3dExportOperator.menu_func)
+ TOPBAR_MT_file_import.append(Ms3dImportOperator.menu_func)
def unregister():
ms3d_ui.unregister()
unregister_module(__name__)
- INFO_MT_file_export.remove(Ms3dExportOperator.menu_func)
- INFO_MT_file_import.remove(Ms3dImportOperator.menu_func)
+ TOPBAR_MT_file_export.remove(Ms3dExportOperator.menu_func)
+ TOPBAR_MT_file_import.remove(Ms3dImportOperator.menu_func)
###############################################################################
diff --git a/io_scene_ms3d/ms3d_import.py b/io_scene_ms3d/ms3d_import.py
index a2205556..d5ef0015 100644
--- a/io_scene_ms3d/ms3d_import.py
+++ b/io_scene_ms3d/ms3d_import.py
@@ -234,7 +234,7 @@ class Ms3dImporter():
blender_scene = blender_context.scene
- blender_group = blender_context.blend_data.groups.new(
+ blender_group = blender_context.blend_data.collections.new(
FORMAT_GROUP.format(ms3d_model.name))
blender_empty_object = blender_context.blend_data.objects.new(
FORMAT_EMPTY_OBJECT.format(ms3d_model.name), None)
@@ -690,14 +690,14 @@ class Ms3dImporter():
blender_armature = blender_context.blend_data.armatures.new(
ms3d_armature_name)
blender_armature.ms3d.name = ms3d_model.name
- blender_armature.draw_type = 'STICK'
+ blender_armature.display_type = 'STICK'
blender_armature.show_axes = True
blender_armature.use_auto_ik = True
blender_armature_object = blender_context.blend_data.objects.new(
ms3d_armature_object_name, blender_armature)
blender_scene.objects.link(blender_armature_object)
#blender_armature_object.location = blender_scene.cursor_location
- blender_armature_object.show_x_ray = True
+ blender_armature_object.show_in_front = True
##########################
# create new modifier
@@ -771,7 +771,7 @@ class Ms3dImporter():
in ms3d_to_blender_vertex_groups.items():
ms3d_name = ms3d_model.joints[ms3d_bone_id].name
blender_vertex_group = blender_mesh_object.vertex_groups.new(
- ms3d_name)
+ name=ms3d_name)
for blender_vertex_id_weight in blender_vertex_index_weight_list:
blender_vertex_index = blender_vertex_id_weight[0]
blender_vertex_weight = blender_vertex_id_weight[1]
diff --git a/io_scene_ms3d/ms3d_ui.py b/io_scene_ms3d/ms3d_ui.py
index e7b1dfc4..c483df8a 100644
--- a/io_scene_ms3d/ms3d_ui.py
+++ b/io_scene_ms3d/ms3d_ui.py
@@ -210,7 +210,7 @@ class Ms3dUi:
return ms3d_value
###########################################################################
- ICON_OPTIONS = 'LAMP'
+ ICON_OPTIONS = 'LIGHT'
ICON_OBJECT = 'WORLD'
ICON_PROCESSING = 'OBJECT_DATAMODE'
ICON_MODIFIER = 'MODIFIER'
diff --git a/io_scene_obj/__init__.py b/io_scene_obj/__init__.py
index 3bebdecd..bb8c3e2c 100644
--- a/io_scene_obj/__init__.py
+++ b/io_scene_obj/__init__.py
@@ -21,8 +21,8 @@
bl_info = {
"name": "Wavefront OBJ format",
"author": "Campbell Barton, Bastien Montagne",
- "version": (2, 3, 7),
- "blender": (2, 78, 0),
+ "version": (3, 5, 2),
+ "blender": (2, 80, 0),
"location": "File > Import-Export",
"description": "Import-Export OBJ, Import OBJ mesh, UV's, materials and textures",
"warning": "",
@@ -48,70 +48,68 @@ from bpy.props import (
from bpy_extras.io_utils import (
ImportHelper,
ExportHelper,
- orientation_helper_factory,
+ orientation_helper,
path_reference_mode,
axis_conversion,
)
-IOOBJOrientationHelper = orientation_helper_factory("IOOBJOrientationHelper", axis_forward='-Z', axis_up='Y')
-
-
-class ImportOBJ(bpy.types.Operator, ImportHelper, IOOBJOrientationHelper):
+@orientation_helper(axis_forward='-Z', axis_up='Y')
+class ImportOBJ(bpy.types.Operator, ImportHelper):
"""Load a Wavefront OBJ File"""
bl_idname = "import_scene.obj"
bl_label = "Import OBJ"
bl_options = {'PRESET', 'UNDO'}
filename_ext = ".obj"
- filter_glob = StringProperty(
+ filter_glob: StringProperty(
default="*.obj;*.mtl",
options={'HIDDEN'},
)
- use_edges = BoolProperty(
+ use_edges: BoolProperty(
name="Lines",
description="Import lines and faces with 2 verts as edge",
default=True,
)
- use_smooth_groups = BoolProperty(
+ use_smooth_groups: BoolProperty(
name="Smooth Groups",
description="Surround smooth groups by sharp edges",
default=True,
)
- use_split_objects = BoolProperty(
+ use_split_objects: BoolProperty(
name="Object",
description="Import OBJ Objects into Blender Objects",
default=True,
)
- use_split_groups = BoolProperty(
+ use_split_groups: BoolProperty(
name="Group",
description="Import OBJ Groups into Blender Objects",
default=True,
)
- use_groups_as_vgroups = BoolProperty(
+ use_groups_as_vgroups: BoolProperty(
name="Poly Groups",
description="Import OBJ groups as vertex groups",
default=False,
)
- use_image_search = BoolProperty(
+ use_image_search: BoolProperty(
name="Image Search",
description="Search subdirs for any associated images "
"(Warning, may be slow)",
default=True,
)
- split_mode = EnumProperty(
+ split_mode: EnumProperty(
name="Split",
items=(('ON', "Split", "Split geometry, omits unused verts"),
('OFF', "Keep Vert Order", "Keep vertex order from file"),
),
)
- global_clamp_size = FloatProperty(
+ global_clight_size: FloatProperty(
name="Clamp Size",
description="Clamp bounds under this value (zero to disable)",
min=0.0, max=1000.0,
@@ -139,7 +137,6 @@ class ImportOBJ(bpy.types.Operator, ImportHelper, IOOBJOrientationHelper):
from_up=self.axis_up,
).to_4x4()
keywords["global_matrix"] = global_matrix
- keywords["use_cycles"] = (context.scene.render.engine == 'CYCLES')
if bpy.data.is_saved and context.user_preferences.filepaths.use_relative_paths:
import os
@@ -166,15 +163,16 @@ class ImportOBJ(bpy.types.Operator, ImportHelper, IOOBJOrientationHelper):
else:
row.prop(self, "use_groups_as_vgroups")
- row = layout.split(percentage=0.67)
- row.prop(self, "global_clamp_size")
+ row = layout.split(factor=0.67)
+ row.prop(self, "global_clight_size")
layout.prop(self, "axis_forward")
layout.prop(self, "axis_up")
layout.prop(self, "use_image_search")
-class ExportOBJ(bpy.types.Operator, ExportHelper, IOOBJOrientationHelper):
+@orientation_helper(axis_forward='-Z', axis_up='Y')
+class ExportOBJ(bpy.types.Operator, ExportHelper):
"""Save a Wavefront OBJ File"""
bl_idname = "export_scene.obj"
@@ -182,113 +180,113 @@ class ExportOBJ(bpy.types.Operator, ExportHelper, IOOBJOrientationHelper):
bl_options = {'PRESET'}
filename_ext = ".obj"
- filter_glob = StringProperty(
+ filter_glob: StringProperty(
default="*.obj;*.mtl",
options={'HIDDEN'},
)
# context group
- use_selection = BoolProperty(
+ use_selection: BoolProperty(
name="Selection Only",
description="Export selected objects only",
default=False,
)
- use_animation = BoolProperty(
+ use_animation: BoolProperty(
name="Animation",
description="Write out an OBJ for each frame",
default=False,
)
# object group
- use_mesh_modifiers = BoolProperty(
+ use_mesh_modifiers: BoolProperty(
name="Apply Modifiers",
description="Apply modifiers",
default=True,
)
- use_mesh_modifiers_render = BoolProperty(
+ use_mesh_modifiers_render: BoolProperty(
name="Use Modifiers Render Settings",
description="Use render settings when applying modifiers to mesh objects",
default=False,
)
# extra data group
- use_edges = BoolProperty(
+ use_edges: BoolProperty(
name="Include Edges",
description="",
default=True,
)
- use_smooth_groups = BoolProperty(
+ use_smooth_groups: BoolProperty(
name="Smooth Groups",
description="Write sharp edges as smooth groups",
default=False,
)
- use_smooth_groups_bitflags = BoolProperty(
+ use_smooth_groups_bitflags: BoolProperty(
name="Bitflag Smooth Groups",
description="Same as 'Smooth Groups', but generate smooth groups IDs as bitflags "
"(produces at most 32 different smooth groups, usually much less)",
default=False,
)
- use_normals = BoolProperty(
+ use_normals: BoolProperty(
name="Write Normals",
description="Export one normal per vertex and per face, to represent flat faces and sharp edges",
default=True,
)
- use_uvs = BoolProperty(
+ use_uvs: BoolProperty(
name="Include UVs",
description="Write out the active UV coordinates",
default=True,
)
- use_materials = BoolProperty(
+ use_materials: BoolProperty(
name="Write Materials",
description="Write out the MTL file",
default=True,
)
- use_triangles = BoolProperty(
+ use_triangles: BoolProperty(
name="Triangulate Faces",
description="Convert all faces to triangles",
default=False,
)
- use_nurbs = BoolProperty(
+ use_nurbs: BoolProperty(
name="Write Nurbs",
description="Write nurbs curves as OBJ nurbs rather than "
"converting to geometry",
default=False,
)
- use_vertex_groups = BoolProperty(
+ use_vertex_groups: BoolProperty(
name="Polygroups",
description="",
default=False,
)
# grouping group
- use_blen_objects = BoolProperty(
+ use_blen_objects: BoolProperty(
name="Objects as OBJ Objects",
description="",
default=True,
)
- group_by_object = BoolProperty(
+ group_by_object: BoolProperty(
name="Objects as OBJ Groups ",
description="",
default=False,
)
- group_by_material = BoolProperty(
+ group_by_material: BoolProperty(
name="Material Groups",
description="",
default=False,
)
- keep_vertex_order = BoolProperty(
+ keep_vertex_order: BoolProperty(
name="Keep Vertex Order",
description="",
default=False,
)
- global_scale = FloatProperty(
+ global_scale: FloatProperty(
name="Scale",
min=0.01, max=1000.0,
default=1.0,
)
- path_mode = path_reference_mode
+ path_mode: path_reference_mode
check_extension = True
@@ -303,7 +301,7 @@ class ExportOBJ(bpy.types.Operator, ExportHelper, IOOBJOrientationHelper):
"filter_glob",
))
- global_matrix = (Matrix.Scale(self.global_scale, 4) *
+ global_matrix = (Matrix.Scale(self.global_scale, 4) @
axis_conversion(to_forward=self.axis_forward,
to_up=self.axis_up,
).to_4x4())
@@ -330,13 +328,13 @@ def register():
for cls in classes:
bpy.utils.register_class(cls)
- bpy.types.INFO_MT_file_import.append(menu_func_import)
- bpy.types.INFO_MT_file_export.append(menu_func_export)
+ bpy.types.TOPBAR_MT_file_import.append(menu_func_import)
+ bpy.types.TOPBAR_MT_file_export.append(menu_func_export)
def unregister():
- bpy.types.INFO_MT_file_import.remove(menu_func_import)
- bpy.types.INFO_MT_file_export.remove(menu_func_export)
+ bpy.types.TOPBAR_MT_file_import.remove(menu_func_import)
+ bpy.types.TOPBAR_MT_file_export.remove(menu_func_export)
for cls in classes:
bpy.utils.unregister_class(cls)
diff --git a/io_scene_obj/export_obj.py b/io_scene_obj/export_obj.py
index 05ff4db2..0e905141 100644
--- a/io_scene_obj/export_obj.py
+++ b/io_scene_obj/export_obj.py
@@ -21,8 +21,8 @@
import os
import bpy
-import mathutils
-import bpy_extras.io_utils
+from mathutils import Matrix, Vector, Color
+from bpy_extras import io_utils, node_shader_utils
from bpy_extras.wm_utils.progress_report import (
ProgressReport,
@@ -47,13 +47,8 @@ def mesh_triangulate(me):
def write_mtl(scene, filepath, path_mode, copy_set, mtl_dict):
- from mathutils import Color, Vector
-
world = scene.world
- if world:
- world_amb = world.ambient_color
- else:
- world_amb = Color((0.0, 0.0, 0.0))
+ world_amb = Color((0.8, 0.8, 0.8))
source_dir = os.path.dirname(bpy.data.filepath)
dest_dir = os.path.dirname(filepath)
@@ -69,133 +64,100 @@ def write_mtl(scene, filepath, path_mode, copy_set, mtl_dict):
# Write material/image combinations we have used.
# Using mtl_dict.values() directly gives un-predictable order.
- for mtl_mat_name, mat, face_img in mtl_dict_values:
+ for mtl_mat_name, mat in mtl_dict_values:
# Get the Blender data for the material and the image.
# Having an image named None will make a bug, dont do it :)
fw('\nnewmtl %s\n' % mtl_mat_name) # Define a new material: matname_imgname
- if mat:
- use_mirror = mat.raytrace_mirror.use and mat.raytrace_mirror.reflect_factor != 0.0
+ mat_wrap = node_shader_utils.PrincipledBSDFWrapper(mat) if mat else None
- # convert from blenders spec to 0 - 1000 range.
- if mat.specular_shader == 'WARDISO':
- tspec = (0.4 - mat.specular_slope) / 0.0004
- else:
- tspec = (mat.specular_hardness - 1) / 0.51
- fw('Ns %.6f\n' % tspec)
- del tspec
+ if mat_wrap:
+ use_mirror = mat_wrap.metallic != 0.0
+ use_transparency = mat_wrap.transmission != 0.0
+
+ # XXX Totally empirical conversion, trying to adapt it
+ # (from 1.0 - 0.0 Principled BSDF range to 0.0 - 900.0 OBJ specular exponent range)...
+ spec = (1.0 - mat_wrap.roughness) * 30
+ spec *= spec
+ fw('Ns %.6f\n' % spec)
# Ambient
if use_mirror:
- fw('Ka %.6f %.6f %.6f\n' % (mat.raytrace_mirror.reflect_factor * mat.mirror_color)[:])
+ fw('Ka %.6f %.6f %.6f\n' % (mat_wrap.metallic, mat_wrap.metallic, mat_wrap.metallic))
else:
- fw('Ka %.6f %.6f %.6f\n' % (mat.ambient, mat.ambient, mat.ambient)) # Do not use world color!
- fw('Kd %.6f %.6f %.6f\n' % (mat.diffuse_intensity * mat.diffuse_color)[:]) # Diffuse
- fw('Ks %.6f %.6f %.6f\n' % (mat.specular_intensity * mat.specular_color)[:]) # Specular
+ fw('Ka %.6f %.6f %.6f\n' % (1.0, 1.0, 1.0))
+ fw('Kd %.6f %.6f %.6f\n' % mat_wrap.base_color[:3]) # Diffuse
+ # XXX TODO Find a way to handle tint and diffuse color, in a consistent way with import...
+ fw('Ks %.6f %.6f %.6f\n' % (mat_wrap.specular, mat_wrap.specular, mat_wrap.specular)) # Specular
# Emission, not in original MTL standard but seems pretty common, see T45766.
- # XXX Blender has no color emission, it's using diffuse color instead...
- fw('Ke %.6f %.6f %.6f\n' % (mat.emit * mat.diffuse_color)[:])
- if hasattr(mat, "raytrace_transparency") and hasattr(mat.raytrace_transparency, "ior"):
- fw('Ni %.6f\n' % mat.raytrace_transparency.ior) # Refraction index
- else:
- fw('Ni %.6f\n' % 1.0)
- fw('d %.6f\n' % mat.alpha) # Alpha (obj uses 'd' for dissolve)
+ # XXX Not supported by current Principled-based shader.
+ fw('Ke 0.0 0.0 0.0\n')
+ fw('Ni %.6f\n' % mat_wrap.ior) # Refraction index
+ fw('d %.6f\n' % (1.0 - mat_wrap.transmission)) # Alpha (obj uses 'd' for dissolve)
# See http://en.wikipedia.org/wiki/Wavefront_.obj_file for whole list of values...
# Note that mapping is rather fuzzy sometimes, trying to do our best here.
- if mat.use_shadeless:
- fw('illum 0\n') # ignore lighting
- elif mat.specular_intensity == 0:
+ if mat_wrap.specular == 0:
fw('illum 1\n') # no specular.
elif use_mirror:
- if mat.use_transparency and mat.transparency_method == 'RAYTRACE':
- if mat.raytrace_mirror.fresnel != 0.0:
- fw('illum 7\n') # Reflection, Transparency, Ray trace and Fresnel
- else:
- fw('illum 6\n') # Reflection, Transparency, Ray trace
- elif mat.raytrace_mirror.fresnel != 0.0:
- fw('illum 5\n') # Reflection, Ray trace and Fresnel
+ if use_transparency:
+ fw('illum 6\n') # Reflection, Transparency, Ray trace
else:
fw('illum 3\n') # Reflection and Ray trace
- elif mat.use_transparency and mat.transparency_method == 'RAYTRACE':
+ elif use_transparency:
fw('illum 9\n') # 'Glass' transparency and no Ray trace reflection... fuzzy matching, but...
else:
fw('illum 2\n') # light normally
- else:
- # Write a dummy material here?
- fw('Ns 0\n')
- fw('Ka %.6f %.6f %.6f\n' % world_amb[:]) # Ambient, uses mirror color,
- fw('Kd 0.8 0.8 0.8\n')
- fw('Ks 0.8 0.8 0.8\n')
- fw('d 1\n') # No alpha
- fw('illum 2\n') # light normally
-
- # Write images!
- if face_img: # We have an image on the face!
- filepath = face_img.filepath
- if filepath: # may be '' for generated images
- # write relative image path
- filepath = bpy_extras.io_utils.path_reference(filepath, source_dir, dest_dir,
- path_mode, "", copy_set, face_img.library)
- fw('map_Kd %s\n' % filepath) # Diffuse mapping image
- del filepath
- else:
- # so we write the materials image.
- face_img = None
-
- if mat: # No face image. if we havea material search for MTex image.
- image_map = {}
- # backwards so topmost are highest priority
- for mtex in reversed(mat.texture_slots):
- if mtex and mtex.texture and mtex.texture.type == 'IMAGE':
- image = mtex.texture.image
- if image:
- # texface overrides others
- if (mtex.use_map_color_diffuse and (face_img is None) and
- (mtex.use_map_warp is False) and (mtex.texture_coords != 'REFLECTION')):
- image_map["map_Kd"] = (mtex, image)
- if mtex.use_map_ambient:
- image_map["map_Ka"] = (mtex, image)
- # this is the Spec intensity channel but Ks stands for specular Color
- '''
- if mtex.use_map_specular:
- image_map["map_Ks"] = (mtex, image)
- '''
- if mtex.use_map_color_spec: # specular color
- image_map["map_Ks"] = (mtex, image)
- if mtex.use_map_hardness: # specular hardness/glossiness
- image_map["map_Ns"] = (mtex, image)
- if mtex.use_map_alpha:
- image_map["map_d"] = (mtex, image)
- if mtex.use_map_translucency:
- image_map["map_Tr"] = (mtex, image)
- if mtex.use_map_normal:
- image_map["map_Bump"] = (mtex, image)
- if mtex.use_map_displacement:
- image_map["disp"] = (mtex, image)
- if mtex.use_map_color_diffuse and (mtex.texture_coords == 'REFLECTION'):
- image_map["refl"] = (mtex, image)
- if mtex.use_map_emit:
- image_map["map_Ke"] = (mtex, image)
-
- for key, (mtex, image) in sorted(image_map.items()):
- filepath = bpy_extras.io_utils.path_reference(image.filepath, source_dir, dest_dir,
- path_mode, "", copy_set, image.library)
+ #### And now, the image textures...
+ image_map = {
+ "map_Kd": "base_color_texture",
+ "map_Ka": None, # ambient...
+ "map_Ks": "specular_texture",
+ "map_Ns": "roughness_texture",
+ "map_d": "transmission_texture",
+ "map_Tr": None, # transmission roughness?
+ "map_Bump": "normalmap_texture",
+ "disp": None, # displacement...
+ "refl": "metallic_texture",
+ "map_Ke": None # emission...
+ }
+
+ for key, mat_wrap_key in sorted(image_map.items()):
+ if mat_wrap_key is None:
+ continue
+ tex_wrap = getattr(mat_wrap, mat_wrap_key, None)
+ if tex_wrap is None:
+ continue
+ image = tex_wrap.image
+ if image is None:
+ continue
+
+ filepath = io_utils.path_reference(image.filepath, source_dir, dest_dir,
+ path_mode, "", copy_set, image.library)
options = []
if key == "map_Bump":
- if mtex.normal_factor != 1.0:
- options.append('-bm %.6f' % mtex.normal_factor)
- if mtex.offset != Vector((0.0, 0.0, 0.0)):
- options.append('-o %.6f %.6f %.6f' % mtex.offset[:])
- if mtex.scale != Vector((1.0, 1.0, 1.0)):
- options.append('-s %.6f %.6f %.6f' % mtex.scale[:])
+ if mat_wrap.normalmap_strengh != 1.0:
+ options.append('-bm %.6f' % mat_wrap.normalmap_strengh)
+ if tex_wrap.translation != Vector((0.0, 0.0, 0.0)):
+ options.append('-o %.6f %.6f %.6f' % tex_wrap.translation[:])
+ if tex_wrap.scale != Vector((1.0, 1.0, 1.0)):
+ options.append('-s %.6f %.6f %.6f' % tex_wrap.scale[:])
if options:
fw('%s %s %s\n' % (key, " ".join(options), repr(filepath)[1:-1]))
else:
fw('%s %s\n' % (key, repr(filepath)[1:-1]))
+ else:
+ # Write a dummy material here?
+ fw('Ns 500\n')
+ fw('Ka 0.8 0.8 0.8\n')
+ fw('Kd 0.8 0.8 0.8\n')
+ fw('Ks 0.8 0.8 0.8\n')
+ fw('d 1\n') # No alpha
+ fw('illum 2\n') # light normally
+
def test_nurbs_compat(ob):
if ob.type != 'CURVE':
@@ -236,7 +198,7 @@ def write_nurb(fw, ob, ob_mat):
do_endpoints = (do_closed == 0) and nu.use_endpoint_u
for pt in nu.points:
- fw('v %.6f %.6f %.6f\n' % (ob_mat * pt.co.to_3d())[:])
+ fw('v %.6f %.6f %.6f\n' % (ob_mat @ pt.co.to_3d())[:])
pt_num += 1
tot_verts += pt_num
@@ -274,7 +236,7 @@ def write_nurb(fw, ob, ob_mat):
return tot_verts
-def write_file(filepath, objects, scene,
+def write_file(filepath, objects, depsgraph, scene,
EXPORT_TRI=False,
EXPORT_EDGES=False,
EXPORT_SMOOTH_GROUPS=False,
@@ -301,7 +263,7 @@ def write_file(filepath, objects, scene,
write( 'c:\\test\\foobar.obj', Blender.Object.GetSelected() ) # Using default options.
"""
if EXPORT_GLOBAL_MATRIX is None:
- EXPORT_GLOBAL_MATRIX = mathutils.Matrix()
+ EXPORT_GLOBAL_MATRIX = Matrix()
def veckey3d(v):
return round(v.x, 4), round(v.y, 4), round(v.z, 4)
@@ -362,13 +324,13 @@ def write_file(filepath, objects, scene,
subprogress1.enter_substeps(len(objects))
for i, ob_main in enumerate(objects):
# ignore dupli children
- if ob_main.parent and ob_main.parent.dupli_type in {'VERTS', 'FACES'}:
+ if ob_main.parent and ob_main.parent.instance_type in {'VERTS', 'FACES'}:
# XXX
subprogress1.step("Ignoring %s, dupli child..." % ob_main.name)
continue
obs = [(ob_main, ob_main.matrix_world)]
- if ob_main.dupli_type != 'NONE':
+ if ob_main.instance_type != 'NONE':
# XXX
print('creating dupli_list on', ob_main.name)
ob_main.dupli_list_create(scene)
@@ -385,14 +347,13 @@ def write_file(filepath, objects, scene,
# Nurbs curve support
if EXPORT_CURVE_AS_NURBS and test_nurbs_compat(ob):
- ob_mat = EXPORT_GLOBAL_MATRIX * ob_mat
+ ob_mat = EXPORT_GLOBAL_MATRIX @ ob_mat
totverts += write_nurb(fw, ob, ob_mat)
continue
# END NURBS
try:
- me = ob.to_mesh(scene, EXPORT_APPLY_MODIFIERS, calc_tessface=False,
- settings='RENDER' if EXPORT_APPLY_MODIFIERS_RENDER else 'PREVIEW')
+ me = ob.to_mesh(depsgraph, EXPORT_APPLY_MODIFIERS)
except RuntimeError:
me = None
@@ -404,15 +365,14 @@ def write_file(filepath, objects, scene,
# _must_ do this first since it re-allocs arrays
mesh_triangulate(me)
- me.transform(EXPORT_GLOBAL_MATRIX * ob_mat)
+ me.transform(EXPORT_GLOBAL_MATRIX @ ob_mat)
# If negative scaling, we have to invert the normals...
if ob_mat.determinant() < 0.0:
me.flip_normals()
if EXPORT_UV:
- faceuv = len(me.uv_textures) > 0
+ faceuv = len(me.uv_layers) > 0
if faceuv:
- uv_texture = me.uv_textures.active.data[:]
uv_layer = me.uv_layers.active.data[:]
else:
faceuv = False
@@ -421,7 +381,6 @@ def write_file(filepath, objects, scene,
# Make our own list so it can be sorted to reduce context switching
face_index_pairs = [(face, index) for index, face in enumerate(me.polygons)]
- # faces = [ f for f in me.tessfaces ]
if EXPORT_EDGES:
edges = me.edges
@@ -459,16 +418,7 @@ def write_file(filepath, objects, scene,
if EXPORT_KEEP_VERT_ORDER:
pass
else:
- if faceuv:
- if smooth_groups:
- sort_func = lambda a: (a[0].material_index,
- hash(uv_texture[a[1]].image),
- smooth_groups[a[1]] if a[0].use_smooth else False)
- else:
- sort_func = lambda a: (a[0].material_index,
- hash(uv_texture[a[1]].image),
- a[0].use_smooth)
- elif len(materials) > 1:
+ if len(materials) > 1:
if smooth_groups:
sort_func = lambda a: (a[0].material_index,
smooth_groups[a[1]] if a[0].use_smooth else False)
@@ -562,9 +512,6 @@ def write_file(filepath, objects, scene,
else:
loops_to_normals = []
- if not faceuv:
- f_image = None
-
subprogress2.step()
# XXX
@@ -584,15 +531,8 @@ def write_file(filepath, objects, scene,
f_smooth = smooth_groups[f_index]
f_mat = min(f.material_index, len(materials) - 1)
- if faceuv:
- tface = uv_texture[f_index]
- f_image = tface.image
-
# MAKE KEY
- if faceuv and f_image: # Object is always true.
- key = material_names[f_mat], f_image.name
- else:
- key = material_names[f_mat], None # No image, use None instead.
+ key = material_names[f_mat], None # No image, use None instead.
# Write the vertex group
if EXPORT_POLYGROUPS:
@@ -638,7 +578,7 @@ def write_file(filepath, objects, scene,
i += 1
tmp_ext = "_%3d" % i
mtl_name += tmp_ext
- mat_data = mtl_dict[key] = mtl_name, materials[f_mat], f_image
+ mat_data = mtl_dict[key] = mtl_name, materials[f_mat]
mtl_rev_dict[mtl_name] = key
if EXPORT_GROUP_BY_MAT:
@@ -704,7 +644,7 @@ def write_file(filepath, objects, scene,
# clean up
bpy.data.meshes.remove(me)
- if ob_main.dupli_type != 'NONE':
+ if ob_main.instance_type != 'NONE':
ob_main.dupli_list_clear()
subprogress1.leave_substeps("Finished writing geometry of '%s'." % ob_main.name)
@@ -717,7 +657,7 @@ def write_file(filepath, objects, scene,
write_mtl(scene, mtlfilepath, EXPORT_PATH_MODE, copy_set, mtl_dict)
# copy all collected files.
- bpy_extras.io_utils.path_reference_copy(copy_set)
+ io_utils.path_reference_copy(copy_set)
def _write(context, filepath,
@@ -746,6 +686,7 @@ def _write(context, filepath,
base_name, ext = os.path.splitext(filepath)
context_name = [base_name, '', '', ext] # Base name, scene name, frame number, extension
+ depsgraph = context.depsgraph
scene = context.scene
# Exit edit mode before exporting, so current object states are exported properly.
@@ -766,7 +707,7 @@ def _write(context, filepath,
if EXPORT_ANIMATION: # Add frame to the filepath.
context_name[2] = '_%.6d' % frame
- scene.frame_set(frame, 0.0)
+ scene.frame_set(frame, subframe=0.0)
if EXPORT_SEL_ONLY:
objects = context.selected_objects
else:
@@ -777,7 +718,7 @@ def _write(context, filepath,
# erm... bit of a problem here, this can overwrite files when exporting frames. not too bad.
# EXPORT THE FILE.
progress.enter_substeps(1)
- write_file(full_path, objects, scene,
+ write_file(full_path, objects, depsgraph, scene,
EXPORT_TRI,
EXPORT_EDGES,
EXPORT_SMOOTH_GROUPS,
@@ -799,7 +740,7 @@ def _write(context, filepath,
)
progress.leave_substeps()
- scene.frame_set(orig_frame, 0.0)
+ scene.frame_set(orig_frame, subframe=0.0)
progress.leave_substeps()
diff --git a/io_scene_obj/import_obj.py b/io_scene_obj/import_obj.py
index 6e26a4fc..3185e5c2 100644
--- a/io_scene_obj/import_obj.py
+++ b/io_scene_obj/import_obj.py
@@ -87,21 +87,24 @@ def obj_image_load(context_imagepath_map, line, DIR, recursive, relpath):
def create_materials(filepath, relpath,
- material_libs, unique_materials, unique_material_images,
- use_image_search, use_cycles, float_func):
+ material_libs, unique_materials,
+ use_image_search, float_func):
"""
Create all the used materials in this obj,
assign colors and images to the materials from all referenced material libs
"""
+ from math import sqrt
+ from bpy_extras import node_shader_utils
+
DIR = os.path.dirname(filepath)
context_material_vars = set()
# Don't load the same image multiple times
context_imagepath_map = {}
- cycles_material_wrap_map = {}
+ nodal_material_wrap_map = {}
- def load_material_image(blender_material, mat_wrap, use_cycles, context_material_name, img_data, line, type):
+ def load_material_image(blender_material, mat_wrap, context_material_name, img_data, line, type):
"""
Set textures defined in .mtl file.
"""
@@ -120,105 +123,47 @@ def create_materials(filepath, relpath,
# Absolute path - c:\.. etc would work here
image = obj_image_load(context_imagepath_map, line, DIR, use_image_search, relpath)
- texture = bpy.data.textures.new(name=type, type='IMAGE')
- if image is not None:
- texture.image = image
-
map_offset = map_options.get(b'-o')
map_scale = map_options.get(b'-s')
+ def _generic_tex_set(nodetex, image, texcoords, translation, scale):
+ nodetex.image = image
+ nodetex.texcoords = texcoords
+ if translation is not None:
+ nodetex.translation = translation
+ if scale is not None:
+ nodetex.scale = scale
+
# Adds textures for materials (rendering)
if type == 'Kd':
- if use_cycles:
- mat_wrap.diffuse_image_set(image)
- mat_wrap.diffuse_mapping_set(coords='UV', translation=map_offset, scale=map_scale)
-
- mtex = blender_material.texture_slots.add()
- mtex.texture = texture
- mtex.texture_coords = 'UV'
- mtex.use_map_color_diffuse = True
-
- # adds textures to faces (Textured/Alt-Z mode)
- # Only apply the diffuse texture to the face if the image has not been set with the inline usemat func.
- unique_material_images[context_material_name] = image # set the texface image
+ _generic_tex_set(mat_wrap.base_color_texture, image, 'UV', map_offset, map_scale)
elif type == 'Ka':
- if use_cycles:
- # XXX Not supported?
- print("WARNING, currently unsupported ambient texture, skipped.")
-
- mtex = blender_material.texture_slots.add()
- mtex.use_map_color_diffuse = False
- mtex.texture = texture
- mtex.texture_coords = 'UV'
- mtex.use_map_ambient = True
+ # XXX Not supported?
+ print("WARNING, currently unsupported ambient texture, skipped.")
elif type == 'Ks':
- if use_cycles:
- mat_wrap.specular_image_set(image)
- mat_wrap.specular_mapping_set(coords='UV', translation=map_offset, scale=map_scale)
-
- mtex = blender_material.texture_slots.add()
- mtex.use_map_color_diffuse = False
- mtex.texture = texture
- mtex.texture_coords = 'UV'
- mtex.use_map_color_spec = True
+ _generic_tex_set(mat_wrap.specular_texture, image, 'UV', map_offset, map_scale)
elif type == 'Ke':
- if use_cycles:
- # XXX Not supported?
- print("WARNING, currently unsupported emit texture, skipped.")
-
- mtex = blender_material.texture_slots.add()
- mtex.use_map_color_diffuse = False
- mtex.texture = texture
- mtex.texture_coords = 'UV'
- mtex.use_map_emit = True
+ # XXX Not supported?
+ print("WARNING, currently unsupported emit texture, skipped.")
elif type == 'Bump':
bump_mult = map_options.get(b'-bm')
bump_mult = float(bump_mult[0]) if (bump_mult and len(bump_mult[0]) > 1) else 1.0
+ mat_wrap.normalmap_strength_set(bump_mult)
- if use_cycles:
- mat_wrap.normal_image_set(image)
- mat_wrap.normal_mapping_set(coords='UV', translation=map_offset, scale=map_scale)
- if bump_mult:
- mat_wrap.normal_factor_set(bump_mult)
-
- mtex = blender_material.texture_slots.add()
- mtex.use_map_color_diffuse = False
- mtex.texture = texture
- mtex.texture_coords = 'UV'
- mtex.use_map_normal = True
- if bump_mult:
- mtex.normal_factor = bump_mult
+ _generic_tex_set(mat_wrap.normalmap_texture, image, 'UV', map_offset, map_scale)
elif type == 'D':
- if use_cycles:
- mat_wrap.alpha_image_set(image)
- mat_wrap.alpha_mapping_set(coords='UV', translation=map_offset, scale=map_scale)
-
- mtex = blender_material.texture_slots.add()
- mtex.use_map_color_diffuse = False
- mtex.texture = texture
- mtex.texture_coords = 'UV'
- mtex.use_map_alpha = True
- blender_material.use_transparency = True
- blender_material.transparency_method = 'Z_TRANSPARENCY'
- if "alpha" not in context_material_vars:
- blender_material.alpha = 0.0
- # Todo, unset diffuse material alpha if it has an alpha channel
+ _generic_tex_set(mat_wrap.transmission_texture, image, 'UV', map_offset, map_scale)
elif type == 'disp':
- if use_cycles:
- mat_wrap.bump_image_set(image)
- mat_wrap.bump_mapping_set(coords='UV', translation=map_offset, scale=map_scale)
-
- mtex = blender_material.texture_slots.add()
- mtex.use_map_color_diffuse = False
- mtex.texture = texture
- mtex.texture_coords = 'UV'
- mtex.use_map_displacement = True
+ # XXX Not supported?
+ print("WARNING, currently unsupported displacement texture, skipped.")
+ # ~ mat_wrap.bump_image_set(image)
+ # ~ mat_wrap.bump_mapping_set(coords='UV', translation=map_offset, scale=map_scale)
elif type == 'refl':
map_type = map_options.get(b'-type')
@@ -226,54 +171,25 @@ def create_materials(filepath, relpath,
print("WARNING, unsupported reflection type '%s', defaulting to 'sphere'"
"" % ' '.join(i.decode() for i in map_type))
- if use_cycles:
- mat_wrap.diffuse_image_set(image, projection='SPHERE')
- mat_wrap.diffuse_mapping_set(coords='Reflection', translation=map_offset, scale=map_scale)
+ _generic_tex_set(mat_wrap.base_color_texture, image, 'Reflection', map_offset, map_scale)
+ mat_wrap.base_color_texture.projection = 'SPHERE'
- mtex = blender_material.texture_slots.add()
- mtex.use_map_color_diffuse = False
- mtex.texture = texture
- mtex.texture_coords = 'REFLECTION'
- mtex.use_map_color_diffuse = True
- mtex.mapping = 'SPHERE'
else:
raise Exception("invalid type %r" % type)
- if map_offset:
- mtex.offset.x = float(map_offset[0])
- if len(map_offset) >= 2:
- mtex.offset.y = float(map_offset[1])
- if len(map_offset) >= 3:
- mtex.offset.z = float(map_offset[2])
- if map_scale:
- mtex.scale.x = float(map_scale[0])
- if len(map_scale) >= 2:
- mtex.scale.y = float(map_scale[1])
- if len(map_scale) >= 3:
- mtex.scale.z = float(map_scale[2])
-
- # Add an MTL with the same name as the obj if no MTLs are spesified.
+ # Try to find a MTL with the same name as the OBJ if no MTLs are specified.
temp_mtl = os.path.splitext((os.path.basename(filepath)))[0] + ".mtl"
-
if os.path.exists(os.path.join(DIR, temp_mtl)):
material_libs.add(temp_mtl)
del temp_mtl
# Create new materials
for name in unique_materials: # .keys()
- if name is not None:
- ma = unique_materials[name] = bpy.data.materials.new(name.decode('utf-8', "replace"))
- unique_material_images[name] = None # assign None to all material images to start with, add to later.
- if use_cycles:
- from modules import cycles_shader_compat
- ma_wrap = cycles_shader_compat.CyclesShaderWrapper(ma)
- cycles_material_wrap_map[ma] = ma_wrap
-
-
- # XXX Why was this needed? Cannot find any good reason, and adds stupid empty matslot in case we do not separate
- # mesh (see T44947).
- #~ unique_materials[None] = None
- #~ unique_material_images[None] = None
+ ma_name = "Default OBJ" if name is None else name.decode('utf-8', "replace")
+ ma = unique_materials[name] = bpy.data.materials.new(ma_name)
+ ma_wrap = node_shader_utils.PrincipledBSDFWrapper(ma, is_readonly=False)
+ nodal_material_wrap_map[ma] = ma_wrap
+ ma_wrap.use_nodes = True
for libname in sorted(material_libs):
# print(libname)
@@ -281,13 +197,13 @@ def create_materials(filepath, relpath,
if not os.path.exists(mtlpath):
print("\tMaterial not found MTL: %r" % mtlpath)
else:
- do_ambient = True
+ # Note: with modern Principled BSDF shader, things like ambient, raytrace or fresnel are always 'ON'
+ # (i.e. automatically controlled by other parameters).
do_highlight = False
do_reflection = False
do_transparency = False
do_glass = False
- do_fresnel = False
- do_raytrace = False
+ spec_colors = [0.0, 0.0, 0.0]
emit_colors = [0.0, 0.0, 0.0]
# print('\t\tloading mtl: %e' % mtlpath)
@@ -305,133 +221,113 @@ def create_materials(filepath, relpath,
if line_id == b'newmtl':
# Finalize previous mat, if any.
if context_material:
+ if "specular" in context_material_vars:
+ # XXX This is highly approximated, not sure whether we can do better...
+ # TODO: Find a way to guesstimate best value from diffuse color...
+ # IDEA: Use standard deviation of both spec and diff colors (i.e. how far away they are
+ # from some grey), and apply the the proportion between those two as tint factor?
+ spec = sum(spec_colors) / 3.0
+ # ~ spec_var = math.sqrt(sum((c - spec) ** 2 for c in spec_color) / 3.0)
+ # ~ diff = sum(context_mat_wrap.base_color) / 3.0
+ # ~ diff_var = math.sqrt(sum((c - diff) ** 2 for c in context_mat_wrap.base_color) / 3.0)
+ # ~ tint = min(1.0, spec_var / diff_var)
+ context_mat_wrap.specular = spec
+ context_mat_wrap.specular_tint = 0.0
+ if "roughness" not in context_material_vars:
+ context_mat_wrap.roughness = 0.0
+
+
emit_value = sum(emit_colors) / 3.0
if emit_value > 1e-6:
- if use_cycles:
- print("WARNING, currently unsupported emit value, skipped.")
+ print("WARNING, emit value unsupported by Principled BSDF shader, skipped.")
# We have to adapt it to diffuse color too...
emit_value /= sum(context_material.diffuse_color) / 3.0
- context_material.emit = emit_value
-
- if not do_ambient:
- context_material.ambient = 0.0
+ # ~ context_material.emit = emit_value
+ # FIXME, how else to use this?
if do_highlight:
- if use_cycles:
- context_mat_wrap.hardness_value_set(1.0)
- # FIXME, how else to use this?
- context_material.specular_intensity = 1.0
+ if "specular" not in context_material_vars:
+ context_mat_wrap.specular = 1.0
+ if "roughness" not in context_material_vars:
+ context_mat_wrap.roughness = 0.0
else:
- if use_cycles:
- context_mat_wrap.hardness_value_set(0.0)
+ if "specular" not in context_material_vars:
+ context_mat_wrap.specular = 0.0
+ if "roughness" not in context_material_vars:
+ context_mat_wrap.roughness = 1.0
if do_reflection:
- if use_cycles:
- context_mat_wrap.reflect_factor_set(1.0)
- context_material.raytrace_mirror.use = True
- context_material.raytrace_mirror.reflect_factor = 1.0
+ if "metallic" not in context_material_vars:
+ context_mat_wrap.metallic = 1.0
if do_transparency:
- context_material.use_transparency = True
- context_material.transparency_method = 'RAYTRACE' if do_raytrace else 'Z_TRANSPARENCY'
- if "alpha" not in context_material_vars:
- if use_cycles:
- context_mat_wrap.alpha_value_set(0.0)
- context_material.alpha = 0.0
+ if "ior" not in context_material_vars:
+ context_mat_wrap.ior = 1.0
+ if "transmission" not in context_material_vars:
+ context_mat_wrap.transmission = 1.0
+ # EEVEE only
+ context_material.blend_method = 'BLEND'
if do_glass:
- if use_cycles:
- print("WARNING, currently unsupported glass material, skipped.")
if "ior" not in context_material_vars:
- context_material.raytrace_transparency.ior = 1.5
-
- if do_fresnel:
- if use_cycles:
- print("WARNING, currently unsupported fresnel option, skipped.")
- context_material.raytrace_mirror.fresnel = 1.0 # could be any value for 'ON'
-
- """
- if do_raytrace:
- context_material.use_raytrace = True
- else:
- context_material.use_raytrace = False
- """
- # XXX, this is not following the OBJ spec, but this was
- # written when raytracing wasnt default, annoying to disable for blender users.
- context_material.use_raytrace = True
+ context_mat_wrap.ior = 1.5
context_material_name = line_value(line_split)
context_material = unique_materials.get(context_material_name)
- if use_cycles and context_material is not None:
- context_mat_wrap = cycles_material_wrap_map[context_material]
+ if context_material is not None:
+ context_mat_wrap = nodal_material_wrap_map[context_material]
context_material_vars.clear()
+ spec_colors = [0.0, 0.0, 0.0]
emit_colors[:] = [0.0, 0.0, 0.0]
- do_ambient = True
do_highlight = False
do_reflection = False
do_transparency = False
do_glass = False
- do_fresnel = False
- do_raytrace = False
elif context_material:
# we need to make a material to assign properties to it.
if line_id == b'ka':
- col = (float_func(line_split[1]), float_func(line_split[2]), float_func(line_split[3]))
- if use_cycles:
- context_mat_wrap.reflect_color_set(col)
- context_material.mirror_color = col
- # This is highly approximated, but let's try to stick as close from exporter as possible... :/
- context_material.ambient = sum(context_material.mirror_color) / 3
+ refl = (float_func(line_split[1]) + float_func(line_split[2]) + float_func(line_split[3])) / 3.0
+ context_mat_wrap.metallic = refl
+ context_material_vars.add("metallic")
elif line_id == b'kd':
col = (float_func(line_split[1]), float_func(line_split[2]), float_func(line_split[3]))
- if use_cycles:
- context_mat_wrap.diffuse_color_set(col)
- context_material.diffuse_color = col
- context_material.diffuse_intensity = 1.0
+ context_mat_wrap.base_color = col
elif line_id == b'ks':
- col = (float_func(line_split[1]), float_func(line_split[2]), float_func(line_split[3]))
- if use_cycles:
- context_mat_wrap.specular_color_set(col)
- context_mat_wrap.hardness_value_set(1.0)
- context_material.specular_color = col
- context_material.specular_intensity = 1.0
+ spec_colors[:] = [
+ float_func(line_split[1]), float_func(line_split[2]), float_func(line_split[3])]
+ context_material_vars.add("specular")
elif line_id == b'ke':
# We cannot set context_material.emit right now, we need final diffuse color as well for this.
+ # XXX Unsupported currently
emit_colors[:] = [
float_func(line_split[1]), float_func(line_split[2]), float_func(line_split[3])]
elif line_id == b'ns':
- if use_cycles:
- context_mat_wrap.hardness_value_set(((float_func(line_split[1]) + 3.0) / 50.0) - 0.65)
- context_material.specular_hardness = int((float_func(line_split[1]) * 0.51) + 1)
- elif line_id == b'ni': # Refraction index (between 1 and 3).
- if use_cycles:
- print("WARNING, currently unsupported glass material, skipped.")
- context_material.raytrace_transparency.ior = max(1, min(float_func(line_split[1]), 3))
+ # XXX Totally empirical conversion, trying to adapt it
+ # (from 0.0 - 900.0 OBJ specular exponent range to 1.0 - 0.0 Principled BSDF range)...
+ context_mat_wrap.roughness = 1.0 - (sqrt(float_func(line_split[1])) / 30)
+ context_material_vars.add("roughness")
+ elif line_id == b'ni': # Refraction index (between 0.001 and 10).
+ context_mat_wrap.ior = float_func(line_split[1])
context_material_vars.add("ior")
elif line_id == b'd': # dissolve (transparency)
- if use_cycles:
- context_mat_wrap.alpha_value_set(float_func(line_split[1]))
- context_material.alpha = float_func(line_split[1])
- context_material.use_transparency = True
- context_material.transparency_method = 'Z_TRANSPARENCY'
- context_material_vars.add("alpha")
+ context_mat_wrap.transmission = 1.0 - float_func(line_split[1])
+ context_material_vars.add("transmission")
elif line_id == b'tr': # translucency
- if use_cycles:
- print("WARNING, currently unsupported translucency option, skipped.")
- context_material.translucency = float_func(line_split[1])
+ print("WARNING, currently unsupported 'tr' translucency option, skipped.")
elif line_id == b'tf':
# rgb, filter color, blender has no support for this.
- pass
+ print("WARNING, currently unsupported 'tf' filter color option, skipped.")
elif line_id == b'illum':
illum = get_int(line_split[1])
# inline comments are from the spec, v4.2
if illum == 0:
# Color on and Ambient off
- do_ambient = False
+ print("WARNING, Principled BSDF shader does not support illumination 0 mode "
+ "(colors with no ambient), skipped.")
elif illum == 1:
# Color on and Ambient on
pass
@@ -441,32 +337,25 @@ def create_materials(filepath, relpath,
elif illum == 3:
# Reflection on and Ray trace on
do_reflection = True
- do_raytrace = True
elif illum == 4:
# Transparency: Glass on
# Reflection: Ray trace on
do_transparency = True
do_reflection = True
do_glass = True
- do_raytrace = True
elif illum == 5:
# Reflection: Fresnel on and Ray trace on
do_reflection = True
- do_fresnel = True
- do_raytrace = True
elif illum == 6:
# Transparency: Refraction on
# Reflection: Fresnel off and Ray trace on
do_transparency = True
do_reflection = True
- do_raytrace = True
elif illum == 7:
# Transparency: Refraction on
# Reflection: Fresnel on and Ray trace on
do_transparency = True
do_reflection = True
- do_fresnel = True
- do_raytrace = True
elif illum == 8:
# Reflection on and Ray trace off
do_reflection = True
@@ -474,58 +363,58 @@ def create_materials(filepath, relpath,
# Transparency: Glass on
# Reflection: Ray trace off
do_transparency = True
- do_reflection = True
+ do_reflection = False
do_glass = True
elif illum == 10:
# Casts shadows onto invisible surfaces
-
- # blender can't do this
+ print("WARNING, Principled BSDF shader does not support illumination 10 mode "
+ "(cast shadows on invisible surfaces), skipped.")
pass
elif line_id == b'map_ka':
img_data = line.split()[1:]
if img_data:
- load_material_image(context_material, context_mat_wrap, use_cycles,
+ load_material_image(context_material, context_mat_wrap,
context_material_name, img_data, line, 'Ka')
elif line_id == b'map_ks':
img_data = line.split()[1:]
if img_data:
- load_material_image(context_material, context_mat_wrap, use_cycles,
+ load_material_image(context_material, context_mat_wrap,
context_material_name, img_data, line, 'Ks')
elif line_id == b'map_kd':
img_data = line.split()[1:]
if img_data:
- load_material_image(context_material, context_mat_wrap, use_cycles,
+ load_material_image(context_material, context_mat_wrap,
context_material_name, img_data, line, 'Kd')
elif line_id == b'map_ke':
img_data = line.split()[1:]
if img_data:
- load_material_image(context_material, context_mat_wrap, use_cycles,
+ load_material_image(context_material, context_mat_wrap,
context_material_name, img_data, line, 'Ke')
elif line_id in {b'map_bump', b'bump'}: # 'bump' is incorrect but some files use it.
img_data = line.split()[1:]
if img_data:
- load_material_image(context_material, context_mat_wrap, use_cycles,
+ load_material_image(context_material, context_mat_wrap,
context_material_name, img_data, line, 'Bump')
elif line_id in {b'map_d', b'map_tr'}: # Alpha map - Dissolve
img_data = line.split()[1:]
if img_data:
- load_material_image(context_material, context_mat_wrap, use_cycles,
+ load_material_image(context_material, context_mat_wrap,
context_material_name, img_data, line, 'D')
elif line_id in {b'map_disp', b'disp'}: # displacementmap
img_data = line.split()[1:]
if img_data:
- load_material_image(context_material, context_mat_wrap, use_cycles,
+ load_material_image(context_material, context_mat_wrap,
context_material_name, img_data, line, 'disp')
elif line_id in {b'map_refl', b'refl'}: # reflectionmap
img_data = line.split()[1:]
if img_data:
- load_material_image(context_material, context_mat_wrap, use_cycles,
+ load_material_image(context_material, context_mat_wrap,
context_material_name, img_data, line, 'refl')
else:
- print("\t%r:%r (ignored)" % (filepath, line))
+ print("WARNING: %r:%r (ignored)" % (filepath, line))
mtl.close()
@@ -538,8 +427,8 @@ def split_mesh(verts_loc, faces, unique_materials, filepath, SPLIT_OB_OR_GROUP):
filename = os.path.splitext((os.path.basename(filepath)))[0]
if not SPLIT_OB_OR_GROUP or not faces:
- use_verts_nor = any((False if f[1] is ... else True) for f in faces)
- use_verts_tex = any((False if f[2] is ... else True) for f in faces)
+ use_verts_nor = any(f[1] for f in faces)
+ use_verts_tex = any(f[2] for f in faces)
# use the filename for the object name since we aren't chopping up the mesh.
return [(verts_loc, faces, unique_materials, filename, use_verts_nor, use_verts_tex)]
@@ -556,7 +445,15 @@ def split_mesh(verts_loc, faces, unique_materials, filepath, SPLIT_OB_OR_GROUP):
oldkey = -1 # initialize to a value that will never match the key
for face in faces:
- key = face[5]
+ (face_vert_loc_indices,
+ face_vert_nor_indices,
+ face_vert_tex_indices,
+ context_material,
+ context_smooth_group,
+ context_object,
+ face_invalid_blenpoly,
+ ) = face
+ key = context_object
if oldkey != key:
# Check the key has changed.
@@ -564,27 +461,25 @@ def split_mesh(verts_loc, faces, unique_materials, filepath, SPLIT_OB_OR_GROUP):
use_verts_nor, use_verts_tex) = face_split_dict.setdefault(key, ([], [], {}, {}, [], []))
oldkey = key
- face_vert_loc_indices = face[0]
- if not use_verts_nor and face[1] is not ...:
+ if not use_verts_nor and face_vert_nor_indices:
use_verts_nor.append(True)
- if not use_verts_tex and face[2] is not ...:
+ if not use_verts_tex and face_vert_tex_indices:
use_verts_tex.append(True)
# Remap verts to new vert list and add where needed
- for enum, i in enumerate(face_vert_loc_indices):
- map_index = vert_remap.get(i)
+ for loop_idx, vert_idx in enumerate(face_vert_loc_indices):
+ map_index = vert_remap.get(vert_idx)
if map_index is None:
map_index = len(verts_split)
- vert_remap[i] = map_index # set the new remapped index so we only add once and can reference next time.
- verts_split.append(verts_loc[i]) # add the vert to the local verts
+ vert_remap[vert_idx] = map_index # set the new remapped index so we only add once and can reference next time.
+ verts_split.append(verts_loc[vert_idx]) # add the vert to the local verts
- face_vert_loc_indices[enum] = map_index # remap to the local index
+ face_vert_loc_indices[loop_idx] = map_index # remap to the local index
- matname = face[3]
- if matname and matname not in unique_materials_split:
- unique_materials_split[matname] = unique_materials[matname]
+ if context_material not in unique_materials_split:
+ unique_materials_split[context_material] = unique_materials[context_material]
faces_split.append(face)
@@ -601,7 +496,6 @@ def create_mesh(new_objects,
verts_tex,
faces,
unique_materials,
- unique_material_images,
unique_smooth_groups,
vertex_groups,
dataname,
@@ -664,7 +558,7 @@ def create_mesh(new_objects,
# ignore triangles with invalid indices
if len(face_vert_loc_indices) > 3:
from bpy_extras.mesh_utils import ngon_tessellate
- ngon_face_indices = ngon_tessellate(verts_loc, face_vert_loc_indices)
+ ngon_face_indices = ngon_tessellate(verts_loc, face_vert_loc_indices, debug_print=bpy.app.debug)
faces.extend([([face_vert_loc_indices[ngon[0]],
face_vert_loc_indices[ngon[1]],
face_vert_loc_indices[ngon[2]],
@@ -734,68 +628,37 @@ def create_mesh(new_objects,
# verts_loc is a list of (x, y, z) tuples
me.vertices.foreach_set("co", unpack_list(verts_loc))
- loops_vert_idx = []
+ loops_vert_idx = tuple(vidx for (face_vert_loc_indices, _, _, _, _, _, _) in faces for vidx in face_vert_loc_indices)
faces_loop_start = []
- faces_loop_total = []
lidx = 0
for f in faces:
- vidx = f[0]
- nbr_vidx = len(vidx)
- loops_vert_idx.extend(vidx)
+ face_vert_loc_indices = f[0]
+ nbr_vidx = len(face_vert_loc_indices)
faces_loop_start.append(lidx)
- faces_loop_total.append(nbr_vidx)
lidx += nbr_vidx
+ faces_loop_total = tuple(len(face_vert_loc_indices) for (face_vert_loc_indices, _, _, _, _, _, _) in faces)
me.loops.foreach_set("vertex_index", loops_vert_idx)
me.polygons.foreach_set("loop_start", faces_loop_start)
me.polygons.foreach_set("loop_total", faces_loop_total)
+ faces_ma_index = tuple(material_mapping[context_material] for (_, _, _, context_material, _, _, _) in faces)
+ me.polygons.foreach_set("material_index", faces_ma_index)
+
+ faces_use_smooth = tuple(bool(context_smooth_group) for (_, _, _, _, context_smooth_group, _, _) in faces)
+ me.polygons.foreach_set("use_smooth", faces_use_smooth)
+
if verts_nor and me.loops:
# Note: we store 'temp' normals in loops, since validate() may alter final mesh,
# we can only set custom lnors *after* calling it.
me.create_normals_split()
+ loops_nor = tuple(no for (_, face_vert_nor_indices, _, _, _, _, _) in faces for face_noidx in face_vert_nor_indices for no in verts_nor[face_noidx])
+ me.loops.foreach_set("normal", loops_nor)
if verts_tex and me.polygons:
- me.uv_textures.new()
-
- context_material_old = -1 # avoid a dict lookup
- mat = 0 # rare case it may be un-initialized.
-
- for i, (face, blen_poly) in enumerate(zip(faces, me.polygons)):
- if len(face[0]) < 3:
- raise Exception("bad face") # Shall not happen, we got rid of those earlier!
-
- (face_vert_loc_indices,
- face_vert_nor_indices,
- face_vert_tex_indices,
- context_material,
- context_smooth_group,
- context_object,
- face_invalid_blenpoly,
- ) = face
-
- if context_smooth_group:
- blen_poly.use_smooth = True
-
- if context_material:
- if context_material_old is not context_material:
- mat = material_mapping[context_material]
- context_material_old = context_material
- blen_poly.material_index = mat
-
- if verts_nor and face_vert_nor_indices:
- for face_noidx, lidx in zip(face_vert_nor_indices, blen_poly.loop_indices):
- me.loops[lidx].normal[:] = verts_nor[0 if (face_noidx is ...) else face_noidx]
-
- if verts_tex and face_vert_tex_indices:
- if context_material:
- image = unique_material_images[context_material]
- if image: # Can be none if the material dosnt have an image.
- me.uv_textures[0].data[i].image = image
-
- blen_uvs = me.uv_layers[0]
- for face_uvidx, lidx in zip(face_vert_tex_indices, blen_poly.loop_indices):
- blen_uvs.data[lidx].uv = verts_tex[0 if (face_uvidx is ...) else face_uvidx]
+ me.uv_layers.new()
+ loops_uv = tuple(uv for (_, _, face_vert_tex_indices, _, _, _, _) in faces for face_uvidx in face_vert_tex_indices for uv in verts_tex[face_uvidx])
+ me.uv_layers[0].data.foreach_set("uv", loops_uv)
use_edges = use_edges and bool(edges)
if use_edges:
@@ -829,7 +692,6 @@ def create_mesh(new_objects,
for e in me.edges:
if e.key in sharp_edges:
e.use_edge_sharp = True
- me.show_edge_sharp = True
if verts_nor:
clnors = array.array('f', [0.0] * (len(me.loops) * 3))
@@ -840,7 +702,6 @@ def create_mesh(new_objects,
me.normals_split_custom_set(tuple(zip(*(iter(clnors),) * 3)))
me.use_auto_smooth = True
- me.show_edge_sharp = True
ob = bpy.data.objects.new(me.name, me)
new_objects.append(ob)
@@ -849,7 +710,7 @@ def create_mesh(new_objects,
# content of the vertex_groups. If the user selects to NOT have vertex groups saved then
# the following test will never run
for group_name, group_indices in vertex_groups.items():
- group = ob.vertex_groups.new(group_name.decode('utf-8', "replace"))
+ group = ob.vertex_groups.new(name=group_name.decode('utf-8', "replace"))
group.add(group_indices, 1.0, 'REPLACE')
@@ -966,14 +827,13 @@ def get_int(svalue):
def load(context,
filepath,
*,
- global_clamp_size=0.0,
+ global_clight_size=0.0,
use_smooth_groups=True,
use_edges=True,
use_split_objects=True,
use_split_groups=True,
use_image_search=True,
use_groups_as_vgroups=False,
- use_cycles=True,
relpath=None,
global_matrix=None
):
@@ -1041,8 +901,8 @@ def load(context,
context_parm = b'' # used by nurbs too but could be used elsewhere
# Until we can use sets
+ use_default_material = False
unique_materials = {}
- unique_material_images = {}
unique_smooth_groups = {}
# unique_obects= {} - no use for this variable since the objects are stored in the face.
@@ -1057,15 +917,19 @@ def load(context,
face_vert_nor_indices = None
face_vert_tex_indices = None
face_vert_nor_valid = face_vert_tex_valid = False
+ verts_loc_len = verts_nor_len = verts_tex_len = 0
face_items_usage = set()
face_invalid_blenpoly = None
prev_vidx = None
face = None
vec = []
+ quick_vert_failures = 0
+ skip_quick_vert = False
+
progress.enter_substeps(3, "Parsing OBJ file...")
with open(filepath, 'rb') as f:
- for line in f: # .readlines():
+ for line in f:
line_split = line.split()
if not line_split:
@@ -1073,17 +937,39 @@ def load(context,
line_start = line_split[0] # we compare with this a _lot_
- if line_start == b'v' or context_multi_line == b'v':
- context_multi_line = handle_vec(line_start, context_multi_line, line_split, b'v', verts_loc, vec, 3)
-
- elif line_start == b'vn' or context_multi_line == b'vn':
- context_multi_line = handle_vec(line_start, context_multi_line, line_split, b'vn', verts_nor, vec, 3)
+ # Handling vertex data are pretty similar, factorize that.
+ # Also, most OBJ files store all those on a single line, so try fast parsing for that first,
+ # and only fallback to full multi-line parsing when needed, this gives significant speed-up
+ # (~40% on affected code).
+ if line_start == b'v':
+ vdata, vdata_len, do_quick_vert = (verts_loc, 3, not skip_quick_vert)
+ elif line_start == b'vn':
+ vdata, vdata_len, do_quick_vert = (verts_nor, 3, not skip_quick_vert)
+ elif line_start == b'vt':
+ vdata, vdata_len, do_quick_vert = verts_tex, 2, not skip_quick_vert
+ elif context_multi_line == b'v':
+ vdata, vdata_len, do_quick_vert = verts_loc, 3, False
+ elif context_multi_line == b'vn':
+ vdata, vdata_len, do_quick_vert = verts_nor, 3, False
+ elif context_multi_line == b'vt':
+ vdata, vdata_len, do_quick_vert = verts_tex, 2, False
+ else:
+ vdata_len = 0
+
+ if vdata_len:
+ if do_quick_vert:
+ try:
+ vdata.append(tuple(map(float_func, line_split[1:vdata_len + 1])))
+ except:
+ do_quick_vert = False
+ # In case we get too many failures on quick parsing, force fallback to full multi-line one.
+ # Exception handling can become costly...
+ quick_vert_failures += 1
+ if quick_vert_failures > 10000:
+ skip_quick_vert = True
+ if not do_quick_vert:
+ context_multi_line = handle_vec(line_start, context_multi_line, line_split, b'v', vdata, vec, vdata_len)
- elif line_start == b'vt' or context_multi_line == b'vt':
- context_multi_line = handle_vec(line_start, context_multi_line, line_split, b'vt', verts_tex, vec, 2)
-
- # Handle faces lines (as faces) and the second+ lines of fa multiline face here
- # use 'f' not 'f ' because some objs (very rare have 'fo ' for faces)
elif line_start == b'f' or context_multi_line == b'f':
if not context_multi_line:
line_split = line_split[1:]
@@ -1093,14 +979,19 @@ def load(context,
_1, _2, _3, face_invalid_blenpoly) = face
faces.append(face)
face_items_usage.clear()
+ verts_loc_len = len(verts_loc)
+ verts_nor_len = len(verts_nor)
+ verts_tex_len = len(verts_tex)
+ if context_material is None:
+ use_default_material = True
# Else, use face_vert_loc_indices and face_vert_tex_indices previously defined and used the obj_face
context_multi_line = b'f' if strip_slash(line_split) else b''
for v in line_split:
obj_vert = v.split(b'/')
- idx = int(obj_vert[0]) - 1
- vert_loc_index = (idx + len(verts_loc) + 1) if (idx < 0) else idx
+ idx = int(obj_vert[0]) # Note that we assume here we cannot get OBJ invalid 0 index...
+ vert_loc_index = (idx + verts_loc_len) if (idx < 1) else idx - 1
# Add the vertex to the current group
# *warning*, this wont work for files that have groups defined around verts
if use_groups_as_vgroups and context_vgroup:
@@ -1118,18 +1009,18 @@ def load(context,
# formatting for faces with normals and textures is
# loc_index/tex_index/nor_index
if len(obj_vert) > 1 and obj_vert[1] and obj_vert[1] != b'0':
- idx = int(obj_vert[1]) - 1
- face_vert_tex_indices.append((idx + len(verts_tex) + 1) if (idx < 0) else idx)
+ idx = int(obj_vert[1])
+ face_vert_tex_indices.append((idx + verts_tex_len) if (idx < 1) else idx - 1)
face_vert_tex_valid = True
else:
- face_vert_tex_indices.append(...)
+ face_vert_tex_indices.append(0)
if len(obj_vert) > 2 and obj_vert[2] and obj_vert[2] != b'0':
- idx = int(obj_vert[2]) - 1
- face_vert_nor_indices.append((idx + len(verts_nor) + 1) if (idx < 0) else idx)
+ idx = int(obj_vert[2])
+ face_vert_nor_indices.append((idx + verts_nor_len) if (idx < 1) else idx - 1)
face_vert_nor_valid = True
else:
- face_vert_nor_indices.append(...)
+ face_vert_nor_indices.append(0)
if not context_multi_line:
# Clear nor/tex indices in case we had none defined for this face.
@@ -1263,8 +1154,10 @@ def load(context,
progress.step("Done, loading materials and images...")
+ if use_default_material:
+ unique_materials[None] = None
create_materials(filepath, relpath, material_libs, unique_materials,
- unique_material_images, use_image_search, use_cycles, float_func)
+ use_image_search, float_func)
progress.step("Done, building geometries (verts:%i faces:%i materials: %i smoothgroups:%i) ..." %
(len(verts_loc), len(faces), len(unique_materials), len(unique_smooth_groups)))
@@ -1290,7 +1183,6 @@ def load(context,
verts_tex if use_vtex else [],
faces_split,
unique_materials_split,
- unique_material_images,
unique_smooth_groups,
vertex_groups,
dataname,
@@ -1300,10 +1192,13 @@ def load(context,
for context_nurbs in nurbs:
create_nurbs(context_nurbs, verts_loc, new_objects)
+ view_layer = context.view_layer
+ collection = view_layer.active_layer_collection.collection
+
# Create new obj
for obj in new_objects:
- base = scene.objects.link(obj)
- base.select = True
+ collection.objects.link(obj)
+ obj.select_set(True)
# we could apply this anywhere before scaling.
obj.matrix_world = global_matrix
@@ -1313,7 +1208,7 @@ def load(context,
axis_min = [1000000000] * 3
axis_max = [-1000000000] * 3
- if global_clamp_size:
+ if global_clight_size:
# Get all object bounds
for ob in new_objects:
for v in ob.bound_box:
@@ -1327,7 +1222,7 @@ def load(context,
max_axis = max(axis_max[0] - axis_min[0], axis_max[1] - axis_min[1], axis_max[2] - axis_min[2])
scale = 1.0
- while global_clamp_size < max_axis * scale:
+ while global_clight_size < max_axis * scale:
scale = scale / 10.0
for obj in new_objects:
diff --git a/io_scene_vrml2/__init__.py b/io_scene_vrml2/__init__.py
index d6c0da2f..4a420f70 100644
--- a/io_scene_vrml2/__init__.py
+++ b/io_scene_vrml2/__init__.py
@@ -47,16 +47,14 @@ from bpy.props import (
)
from bpy_extras.io_utils import (
ExportHelper,
- orientation_helper_factory,
+ orientation_helper,
path_reference_mode,
axis_conversion,
)
-ExportVRMLOrientationHelper = orientation_helper_factory("ExportVRMLOrientationHelper", axis_forward='Z', axis_up='Y')
-
-
-class ExportVRML(bpy.types.Operator, ExportHelper, ExportVRMLOrientationHelper):
+@orientation_helper(axis_forward='Z', axis_up='Y')
+class ExportVRML(bpy.types.Operator, ExportHelper):
"""Export mesh objects as a VRML2, colors and texture coordinates"""
bl_idname = "export_scene.vrml2"
bl_label = "Export VRML2"
@@ -151,13 +149,13 @@ def menu_func_export(self, context):
def register():
bpy.utils.register_module(__name__)
- bpy.types.INFO_MT_file_export.append(menu_func_export)
+ bpy.types.TOPBAR_MT_file_export.append(menu_func_export)
def unregister():
bpy.utils.unregister_module(__name__)
- bpy.types.INFO_MT_file_export.remove(menu_func_export)
+ bpy.types.TOPBAR_MT_file_export.remove(menu_func_export)
if __name__ == "__main__":
register()
diff --git a/io_scene_vrml2/export_vrml2.py b/io_scene_vrml2/export_vrml2.py
index 06f79fc1..22114582 100644
--- a/io_scene_vrml2/export_vrml2.py
+++ b/io_scene_vrml2/export_vrml2.py
@@ -163,7 +163,7 @@ def save_object(fw, global_matrix,
if is_editmode:
bpy.ops.object.editmode_toggle()
- me = obj.to_mesh(scene, True, 'PREVIEW', calc_tessface=False)
+ me = obj.to_mesh(scene, True, 'PREVIEW')
bm = bmesh.new()
bm.from_mesh(me)
diff --git a/io_scene_x/__init__.py b/io_scene_x/__init__.py
index 001dca61..404d9dc2 100644
--- a/io_scene_x/__init__.py
+++ b/io_scene_x/__init__.py
@@ -176,13 +176,13 @@ def menu_func(self, context):
def register():
bpy.utils.register_module(__name__)
- bpy.types.INFO_MT_file_export.append(menu_func)
+ bpy.types.TOPBAR_MT_file_export.append(menu_func)
def unregister():
bpy.utils.unregister_module(__name__)
- bpy.types.INFO_MT_file_export.remove(menu_func)
+ bpy.types.TOPBAR_MT_file_export.remove(menu_func)
if __name__ == "__main__":
diff --git a/io_scene_x3d/__init__.py b/io_scene_x3d/__init__.py
index d5c555a1..06a9a122 100644
--- a/io_scene_x3d/__init__.py
+++ b/io_scene_x3d/__init__.py
@@ -48,16 +48,14 @@ from bpy.props import (
from bpy_extras.io_utils import (
ImportHelper,
ExportHelper,
- orientation_helper_factory,
+ orientation_helper,
axis_conversion,
path_reference_mode,
)
-IOX3DOrientationHelper = orientation_helper_factory("IOX3DOrientationHelper", axis_forward='Z', axis_up='Y')
-
-
-class ImportX3D(bpy.types.Operator, ImportHelper, IOX3DOrientationHelper):
+@orientation_helper(axis_forward='Z', axis_up='Y')
+class ImportX3D(bpy.types.Operator, ImportHelper):
"""Import an X3D or VRML2 file"""
bl_idname = "import_scene.x3d"
bl_label = "Import X3D/VRML2"
@@ -81,7 +79,8 @@ class ImportX3D(bpy.types.Operator, ImportHelper, IOX3DOrientationHelper):
return import_x3d.load(context, **keywords)
-class ExportX3D(bpy.types.Operator, ExportHelper, IOX3DOrientationHelper):
+@orientation_helper(axis_forward='Z', axis_up='Y')
+class ExportX3D(bpy.types.Operator, ExportHelper):
"""Export selection to Extensible 3D file (.x3d)"""
bl_idname = "export_scene.x3d"
bl_label = 'Export X3D'
@@ -172,15 +171,15 @@ def menu_func_export(self, context):
def register():
bpy.utils.register_module(__name__)
- bpy.types.INFO_MT_file_import.append(menu_func_import)
- bpy.types.INFO_MT_file_export.append(menu_func_export)
+ bpy.types.TOPBAR_MT_file_import.append(menu_func_import)
+ bpy.types.TOPBAR_MT_file_export.append(menu_func_export)
def unregister():
bpy.utils.unregister_module(__name__)
- bpy.types.INFO_MT_file_import.remove(menu_func_import)
- bpy.types.INFO_MT_file_export.remove(menu_func_export)
+ bpy.types.TOPBAR_MT_file_import.remove(menu_func_import)
+ bpy.types.TOPBAR_MT_file_export.remove(menu_func_export)
# NOTES
# - blender version is hardcoded
diff --git a/io_scene_x3d/export_x3d.py b/io_scene_x3d/export_x3d.py
index c2ebfca7..d6c4a293 100644
--- a/io_scene_x3d/export_x3d.py
+++ b/io_scene_x3d/export_x3d.py
@@ -48,7 +48,7 @@ H3D_CAMERA_FOLLOW = 'CAMERA_FOLLOW_TRANSFORM'
H3D_VIEW_MATRIX = 'view_matrix'
-def clamp_color(col):
+def clight_color(col):
return tuple([max(min(c, 1.0), 0.0) for c in col])
@@ -164,14 +164,14 @@ def h3d_shader_glsl_frag_patch(filepath, scene, global_vars, frag_uniform_var_ma
lines.append("%s\n" % v)
lines.append("// h3d custom vars end\n")
lines.append("\n")
- elif l.lstrip().startswith("lamp_visibility_other("):
+ elif l.lstrip().startswith("light_visibility_other("):
w = l.split(', ')
last_transform = w[1] + "_transform" # XXX - HACK!!!
w[1] = '(view_matrix * %s_transform * vec4(%s.x, %s.y, %s.z, 1.0)).xyz' % (w[1], w[1], w[1], w[1])
l = ", ".join(w)
- elif l.lstrip().startswith("lamp_visibility_sun_hemi("):
+ elif l.lstrip().startswith("light_visibility_sun_hemi("):
w = l.split(', ')
- w[0] = w[0][len("lamp_visibility_sun_hemi(") + 1:]
+ w[0] = w[0][len("light_visibility_sun_hemi(") + 1:]
if not h3d_is_object_view(scene, frag_uniform_var_map[w[0]]):
w[0] = '(mat3(normalize(view_matrix[0].xyz), normalize(view_matrix[1].xyz), normalize(view_matrix[2].xyz)) * -%s)' % w[0]
@@ -179,10 +179,10 @@ def h3d_shader_glsl_frag_patch(filepath, scene, global_vars, frag_uniform_var_ma
w[0] = ('(mat3(normalize((view_matrix*%s)[0].xyz), normalize((view_matrix*%s)[1].xyz), normalize((view_matrix*%s)[2].xyz)) * -%s)' %
(last_transform, last_transform, last_transform, w[0]))
- l = "\tlamp_visibility_sun_hemi(" + ", ".join(w)
- elif l.lstrip().startswith("lamp_visibility_spot_circle("):
+ l = "\tlight_visibility_sun_hemi(" + ", ".join(w)
+ elif l.lstrip().startswith("light_visibility_spot_circle("):
w = l.split(', ')
- w[0] = w[0][len("lamp_visibility_spot_circle(") + 1:]
+ w[0] = w[0][len("light_visibility_spot_circle(") + 1:]
if not h3d_is_object_view(scene, frag_uniform_var_map[w[0]]):
w[0] = '(mat3(normalize(view_matrix[0].xyz), normalize(view_matrix[1].xyz), normalize(view_matrix[2].xyz)) * -%s)' % w[0]
@@ -190,7 +190,7 @@ def h3d_shader_glsl_frag_patch(filepath, scene, global_vars, frag_uniform_var_ma
w[0] = ('(mat3(normalize((view_matrix*%s)[0].xyz), normalize((view_matrix*%s)[1].xyz), normalize((view_matrix*%s)[2].xyz)) * %s)' %
(last_transform, last_transform, last_transform, w[0]))
- l = "\tlamp_visibility_spot_circle(" + ", ".join(w)
+ l = "\tlight_visibility_spot_circle(" + ", ".join(w)
lines.append(l)
@@ -241,7 +241,7 @@ def export(file,
# since objects of different types will always have
# different decorated names.
uuid_cache_object = {} # object
- uuid_cache_lamp = {} # 'LA_' + object.name
+ uuid_cache_light = {} # 'LA_' + object.name
uuid_cache_view = {} # object, different namespace
uuid_cache_mesh = {} # mesh
uuid_cache_material = {} # material
@@ -261,7 +261,7 @@ def export(file,
# prevent uuid collisions.
uuid_cache = {}
uuid_cache_object = uuid_cache # object
- uuid_cache_lamp = uuid_cache # 'LA_' + object.name
+ uuid_cache_light = uuid_cache # 'LA_' + object.name
uuid_cache_view = uuid_cache # object, different namespace
uuid_cache_mesh = uuid_cache # mesh
uuid_cache_material = uuid_cache # material
@@ -370,16 +370,16 @@ def export(file,
ident_step = ident + (' ' * (-len(ident) + \
fw('%s<Fog ' % ident)))
fw('fogType="%s"\n' % ('LINEAR' if (mtype == 'LINEAR') else 'EXPONENTIAL'))
- fw(ident_step + 'color="%.3f %.3f %.3f"\n' % clamp_color(world.horizon_color))
+ fw(ident_step + 'color="%.3f %.3f %.3f"\n' % clight_color(world.horizon_color))
fw(ident_step + 'visibilityRange="%.3f"\n' % mparam.depth)
fw(ident_step + '/>\n')
else:
return
- def writeNavigationInfo(ident, scene, has_lamp):
+ def writeNavigationInfo(ident, scene, has_light):
ident_step = ident + (' ' * (-len(ident) + \
fw('%s<NavigationInfo ' % ident)))
- fw('headlight="%s"\n' % bool_as_str(not has_lamp))
+ fw('headlight="%s"\n' % bool_as_str(not has_light))
fw(ident_step + 'visibilityLimit="0.0"\n')
fw(ident_step + 'type=\'"EXAMINE", "ANY"\'\n')
fw(ident_step + 'avatarSize="0.25, 1.75, 0.75"\n')
@@ -411,8 +411,8 @@ def export(file,
return ident
def writeSpotLight(ident, obj, matrix, lamp, world):
- # note, lamp_id is not re-used
- lamp_id = quoteattr(unique_name(obj, LA_ + obj.name, uuid_cache_lamp, clean_func=clean_def, sep="_"))
+ # note, light_id is not re-used
+ light_id = quoteattr(unique_name(obj, LA_ + obj.name, uuid_cache_light, clean_func=clean_def, sep="_"))
if world:
ambi = world.ambient_color
@@ -435,11 +435,11 @@ def export(file,
# radius = lamp.dist*math.cos(beamWidth)
ident_step = ident + (' ' * (-len(ident) + \
fw('%s<SpotLight ' % ident)))
- fw('DEF=%s\n' % lamp_id)
+ fw('DEF=%s\n' % light_id)
fw(ident_step + 'radius="%.4f"\n' % radius)
fw(ident_step + 'ambientIntensity="%.4f"\n' % amb_intensity)
fw(ident_step + 'intensity="%.4f"\n' % intensity)
- fw(ident_step + 'color="%.4f %.4f %.4f"\n' % clamp_color(lamp.color))
+ fw(ident_step + 'color="%.4f %.4f %.4f"\n' % clight_color(lamp.color))
fw(ident_step + 'beamWidth="%.4f"\n' % beamWidth)
fw(ident_step + 'cutOffAngle="%.4f"\n' % cutOffAngle)
fw(ident_step + 'direction="%.4f %.4f %.4f"\n' % orientation)
@@ -447,8 +447,8 @@ def export(file,
fw(ident_step + '/>\n')
def writeDirectionalLight(ident, obj, matrix, lamp, world):
- # note, lamp_id is not re-used
- lamp_id = quoteattr(unique_name(obj, LA_ + obj.name, uuid_cache_lamp, clean_func=clean_def, sep="_"))
+ # note, light_id is not re-used
+ light_id = quoteattr(unique_name(obj, LA_ + obj.name, uuid_cache_light, clean_func=clean_def, sep="_"))
if world:
ambi = world.ambient_color
@@ -464,16 +464,16 @@ def export(file,
ident_step = ident + (' ' * (-len(ident) + \
fw('%s<DirectionalLight ' % ident)))
- fw('DEF=%s\n' % lamp_id)
+ fw('DEF=%s\n' % light_id)
fw(ident_step + 'ambientIntensity="%.4f"\n' % amb_intensity)
- fw(ident_step + 'color="%.4f %.4f %.4f"\n' % clamp_color(lamp.color))
+ fw(ident_step + 'color="%.4f %.4f %.4f"\n' % clight_color(lamp.color))
fw(ident_step + 'intensity="%.4f"\n' % intensity)
fw(ident_step + 'direction="%.4f %.4f %.4f"\n' % orientation)
fw(ident_step + '/>\n')
def writePointLight(ident, obj, matrix, lamp, world):
- # note, lamp_id is not re-used
- lamp_id = quoteattr(unique_name(obj, LA_ + obj.name, uuid_cache_lamp, clean_func=clean_def, sep="_"))
+ # note, light_id is not re-used
+ light_id = quoteattr(unique_name(obj, LA_ + obj.name, uuid_cache_light, clean_func=clean_def, sep="_"))
if world:
ambi = world.ambient_color
@@ -488,9 +488,9 @@ def export(file,
ident_step = ident + (' ' * (-len(ident) + \
fw('%s<PointLight ' % ident)))
- fw('DEF=%s\n' % lamp_id)
+ fw('DEF=%s\n' % light_id)
fw(ident_step + 'ambientIntensity="%.4f"\n' % amb_intensity)
- fw(ident_step + 'color="%.4f %.4f %.4f"\n' % clamp_color(lamp.color))
+ fw(ident_step + 'color="%.4f %.4f %.4f"\n' % clight_color(lamp.color))
fw(ident_step + 'intensity="%.4f"\n' % intensity)
fw(ident_step + 'radius="%.4f" \n' % lamp.distance)
@@ -1002,9 +1002,9 @@ def export(file,
ident_step = ident + (' ' * (-len(ident) + \
fw('%s<Material ' % ident)))
fw('DEF=%s\n' % material_id)
- fw(ident_step + 'diffuseColor="%.3f %.3f %.3f"\n' % clamp_color(diffuseColor))
- fw(ident_step + 'specularColor="%.3f %.3f %.3f"\n' % clamp_color(specColor))
- fw(ident_step + 'emissiveColor="%.3f %.3f %.3f"\n' % clamp_color(emitColor))
+ fw(ident_step + 'diffuseColor="%.3f %.3f %.3f"\n' % clight_color(diffuseColor))
+ fw(ident_step + 'specularColor="%.3f %.3f %.3f"\n' % clight_color(specColor))
+ fw(ident_step + 'emissiveColor="%.3f %.3f %.3f"\n' % clight_color(emitColor))
fw(ident_step + 'ambientIntensity="%.3f"\n' % ambient)
fw(ident_step + 'shininess="%.3f"\n' % shininess)
fw(ident_step + 'transparency="%s"\n' % transp)
@@ -1035,12 +1035,12 @@ def export(file,
#~ GPU_DATA_4F 5
#~ GPU_DATA_4UB 8
#~ GPU_DATA_9F 6
- #~ GPU_DYNAMIC_LAMP_DYNCO 7
- #~ GPU_DYNAMIC_LAMP_DYNCOL 11
- #~ GPU_DYNAMIC_LAMP_DYNENERGY 10
- #~ GPU_DYNAMIC_LAMP_DYNIMAT 8
- #~ GPU_DYNAMIC_LAMP_DYNPERSMAT 9
- #~ GPU_DYNAMIC_LAMP_DYNVEC 6
+ #~ GPU_DYNAMIC_LIGHT_DYNCO 7
+ #~ GPU_DYNAMIC_LIGHT_DYNCOL 11
+ #~ GPU_DYNAMIC_LIGHT_DYNENERGY 10
+ #~ GPU_DYNAMIC_LIGHT_DYNIMAT 8
+ #~ GPU_DYNAMIC_LIGHT_DYNPERSMAT 9
+ #~ GPU_DYNAMIC_LIGHT_DYNVEC 6
#~ GPU_DYNAMIC_OBJECT_COLOR 5
#~ GPU_DYNAMIC_OBJECT_IMAT 4
#~ GPU_DYNAMIC_OBJECT_MAT 2
@@ -1134,45 +1134,45 @@ def export(file,
writeImageTexture(ident + '\t', uniform['image'])
fw('%s</field>\n' % ident)
- elif uniform['type'] == gpu.GPU_DYNAMIC_LAMP_DYNCO:
- lamp_obj = uniform['lamp']
- frag_uniform_var_map[uniform['varname']] = lamp_obj
+ elif uniform['type'] == gpu.GPU_DYNAMIC_LIGHT_DYNCO:
+ light_obj = uniform['lamp']
+ frag_uniform_var_map[uniform['varname']] = light_obj
if uniform['datatype'] == gpu.GPU_DATA_3F: # should always be true!
- lamp_obj_id = quoteattr(unique_name(lamp_obj, LA_ + lamp_obj.name, uuid_cache_lamp, clean_func=clean_def, sep="_"))
- lamp_obj_transform_id = quoteattr(unique_name(lamp_obj, lamp_obj.name, uuid_cache_object, clean_func=clean_def, sep="_"))
+ light_obj_id = quoteattr(unique_name(light_obj, LA_ + light_obj.name, uuid_cache_light, clean_func=clean_def, sep="_"))
+ light_obj_transform_id = quoteattr(unique_name(light_obj, light_obj.name, uuid_cache_object, clean_func=clean_def, sep="_"))
- value = '%.6f %.6f %.6f' % (global_matrix * lamp_obj.matrix_world).to_translation()[:]
- field_descr = " <!--- Lamp DynCo '%s' -->" % lamp_obj.name
+ value = '%.6f %.6f %.6f' % (global_matrix * light_obj.matrix_world).to_translation()[:]
+ field_descr = " <!--- Lamp DynCo '%s' -->" % light_obj.name
fw('%s<field name="%s" type="SFVec3f" accessType="inputOutput" value="%s" />%s\n' % (ident, uniform['varname'], value, field_descr))
# ------------------------------------------------------
# shader-patch
- field_descr = " <!--- Lamp DynCo '%s' (shader patch) -->" % lamp_obj.name
+ field_descr = " <!--- Lamp DynCo '%s' (shader patch) -->" % light_obj.name
fw('%s<field name="%s_transform" type="SFMatrix4f" accessType="inputOutput" />%s\n' % (ident, uniform['varname'], field_descr))
# transform
frag_vars.append("uniform mat4 %s_transform;" % uniform['varname'])
h3d_material_route.append(
'<ROUTE fromNode=%s fromField="accumulatedForward" toNode=%s toField="%s_transform" />%s' %
- (suffix_quoted_str(lamp_obj_transform_id, _TRANSFORM), material_id, uniform['varname'], field_descr))
+ (suffix_quoted_str(light_obj_transform_id, _TRANSFORM), material_id, uniform['varname'], field_descr))
h3d_material_route.append(
'<ROUTE fromNode=%s fromField="location" toNode=%s toField="%s" /> %s' %
- (lamp_obj_id, material_id, uniform['varname'], field_descr))
+ (light_obj_id, material_id, uniform['varname'], field_descr))
# ------------------------------------------------------
else:
assert(0)
- elif uniform['type'] == gpu.GPU_DYNAMIC_LAMP_DYNCOL:
+ elif uniform['type'] == gpu.GPU_DYNAMIC_LIGHT_DYNCOL:
# odd we have both 3, 4 types.
- lamp_obj = uniform['lamp']
- frag_uniform_var_map[uniform['varname']] = lamp_obj
+ light_obj = uniform['lamp']
+ frag_uniform_var_map[uniform['varname']] = light_obj
- lamp = lamp_obj.data
+ lamp = light_obj.data
value = '%.6f %.6f %.6f' % (lamp.color * lamp.energy)[:]
- field_descr = " <!--- Lamp DynColor '%s' -->" % lamp_obj.name
+ field_descr = " <!--- Lamp DynColor '%s' -->" % light_obj.name
if uniform['datatype'] == gpu.GPU_DATA_3F:
fw('%s<field name="%s" type="SFVec3f" accessType="inputOutput" value="%s" />%s\n' % (ident, uniform['varname'], value, field_descr))
elif uniform['datatype'] == gpu.GPU_DATA_4F:
@@ -1180,26 +1180,26 @@ def export(file,
else:
assert(0)
- elif uniform['type'] == gpu.GPU_DYNAMIC_LAMP_DYNENERGY:
+ elif uniform['type'] == gpu.GPU_DYNAMIC_LIGHT_DYNENERGY:
# not used ?
assert(0)
- elif uniform['type'] == gpu.GPU_DYNAMIC_LAMP_DYNVEC:
- lamp_obj = uniform['lamp']
- frag_uniform_var_map[uniform['varname']] = lamp_obj
+ elif uniform['type'] == gpu.GPU_DYNAMIC_LIGHT_DYNVEC:
+ light_obj = uniform['lamp']
+ frag_uniform_var_map[uniform['varname']] = light_obj
if uniform['datatype'] == gpu.GPU_DATA_3F:
- lamp_obj = uniform['lamp']
- value = '%.6f %.6f %.6f' % ((global_matrix * lamp_obj.matrix_world).to_quaternion() * mathutils.Vector((0.0, 0.0, 1.0))).normalized()[:]
- field_descr = " <!--- Lamp DynDirection '%s' -->" % lamp_obj.name
+ light_obj = uniform['lamp']
+ value = '%.6f %.6f %.6f' % ((global_matrix * light_obj.matrix_world).to_quaternion() * mathutils.Vector((0.0, 0.0, 1.0))).normalized()[:]
+ field_descr = " <!--- Lamp DynDirection '%s' -->" % light_obj.name
fw('%s<field name="%s" type="SFVec3f" accessType="inputOutput" value="%s" />%s\n' % (ident, uniform['varname'], value, field_descr))
# route so we can have the lamp update the view
- if h3d_is_object_view(scene, lamp_obj):
- lamp_id = quoteattr(unique_name(lamp_obj, LA_ + lamp_obj.name, uuid_cache_lamp, clean_func=clean_def, sep="_"))
+ if h3d_is_object_view(scene, light_obj):
+ light_id = quoteattr(unique_name(light_obj, LA_ + light_obj.name, uuid_cache_light, clean_func=clean_def, sep="_"))
h3d_material_route.append(
'<ROUTE fromNode=%s fromField="direction" toNode=%s toField="%s" />%s' %
- (lamp_id, material_id, uniform['varname'], field_descr))
+ (light_id, material_id, uniform['varname'], field_descr))
else:
assert(0)
@@ -1330,9 +1330,9 @@ def export(file,
blending = world.use_sky_blend, world.use_sky_paper, world.use_sky_real
- grd_triple = clamp_color(world.horizon_color)
- sky_triple = clamp_color(world.zenith_color)
- mix_triple = clamp_color((grd_triple[i] + sky_triple[i]) / 2.0 for i in range(3))
+ grd_triple = clight_color(world.horizon_color)
+ sky_triple = clight_color(world.zenith_color)
+ mix_triple = clight_color((grd_triple[i] + sky_triple[i]) / 2.0 for i in range(3))
ident_step = ident + (' ' * (-len(ident) + \
fw('%s<Background ' % ident)))
@@ -1469,7 +1469,7 @@ def export(file,
if do_remove:
bpy.data.meshes.remove(me)
- elif obj_type == 'LAMP':
+ elif obj_type == 'LIGHT':
data = obj.data
datatype = data.type
if datatype == 'POINT':
@@ -1521,7 +1521,7 @@ def export(file,
ident = ''
ident = writeHeader(ident)
- writeNavigationInfo(ident, scene, any(obj.type == 'LAMP' for obj in objects))
+ writeNavigationInfo(ident, scene, any(obj.type == 'LIGHT' for obj in objects))
writeBackground(ident, world)
writeFog(ident, world)
diff --git a/io_scene_x3d/import_x3d.py b/io_scene_x3d/import_x3d.py
index 83dcc83d..d2592ecd 100644
--- a/io_scene_x3d/import_x3d.py
+++ b/io_scene_x3d/import_x3d.py
@@ -2790,9 +2790,9 @@ def appearance_LoadTexture(tex_node, ancestry, node):
if bpyima: # Loading can still fail
repeat_s = tex_node.getFieldAsBool('repeatS', True, ancestry)
- bpyima.use_clamp_x = not repeat_s
+ bpyima.use_clight_x = not repeat_s
repeat_t = tex_node.getFieldAsBool('repeatT', True, ancestry)
- bpyima.use_clamp_y = not repeat_t
+ bpyima.use_clight_y = not repeat_t
# Update the desc-based cache
if desc:
@@ -3148,7 +3148,7 @@ def importLamp_PointLight(node, ancestry):
# is_on = node.getFieldAsBool('on', True, ancestry) # TODO
radius = node.getFieldAsFloat('radius', 100.0, ancestry)
- bpylamp = bpy.data.lamps.new(vrmlname, 'POINT')
+ bpylamp = bpy.data.lights.new(vrmlname, 'POINT')
bpylamp.energy = intensity
bpylamp.distance = radius
bpylamp.color = color
@@ -3169,7 +3169,7 @@ def importLamp_DirectionalLight(node, ancestry):
intensity = node.getFieldAsFloat('intensity', 1.0, ancestry) # max is documented to be 1.0 but some files have higher.
# is_on = node.getFieldAsBool('on', True, ancestry) # TODO
- bpylamp = bpy.data.lamps.new(vrmlname, 'SUN')
+ bpylamp = bpy.data.lights.new(vrmlname, 'SUN')
bpylamp.energy = intensity
bpylamp.color = color
@@ -3197,7 +3197,7 @@ def importLamp_SpotLight(node, ancestry):
# is_on = node.getFieldAsBool('on', True, ancestry) # TODO
radius = node.getFieldAsFloat('radius', 100.0, ancestry)
- bpylamp = bpy.data.lamps.new(vrmlname, 'SPOT')
+ bpylamp = bpy.data.lights.new(vrmlname, 'SPOT')
bpylamp.energy = intensity
bpylamp.distance = radius
bpylamp.color = color
@@ -3271,8 +3271,8 @@ def importTransform(bpyscene, node, ancestry, global_matrix):
bpyob.matrix_world = getFinalMatrix(node, None, ancestry, global_matrix)
# so they are not too annoying
- bpyob.empty_draw_type = 'PLAIN_AXES'
- bpyob.empty_draw_size = 0.2
+ bpyob.empty_display_type = 'PLAIN_AXES'
+ bpyob.empty_display_size = 0.2
#def importTimeSensor(node):
diff --git a/io_shape_mdd/__init__.py b/io_shape_mdd/__init__.py
index 99623241..b6f0e734 100644
--- a/io_shape_mdd/__init__.py
+++ b/io_shape_mdd/__init__.py
@@ -22,7 +22,7 @@ bl_info = {
"name": "NewTek MDD format",
"author": "Bill L.Nieuwendorp",
"version": (1, 0, 1),
- "blender": (2, 57, 0),
+ "blender": (2, 80, 0),
"location": "File > Import-Export",
"description": "Import-Export MDD as mesh shape keys",
"warning": "",
@@ -57,17 +57,17 @@ class ImportMDD(bpy.types.Operator, ImportHelper):
filename_ext = ".mdd"
- filter_glob = StringProperty(
+ filter_glob: StringProperty(
default="*.mdd",
options={'HIDDEN'},
)
- frame_start = IntProperty(
+ frame_start: IntProperty(
name="Start Frame",
description="Start frame for inserting animation",
min=-300000, max=300000,
default=0,
)
- frame_step = IntProperty(
+ frame_step: IntProperty(
name="Step",
min=1, max=1000,
default=1,
@@ -97,7 +97,7 @@ class ExportMDD(bpy.types.Operator, ExportHelper):
bl_label = "Export MDD"
filename_ext = ".mdd"
- filter_glob = StringProperty(default="*.mdd", options={'HIDDEN'})
+ filter_glob: StringProperty(default="*.mdd", options={'HIDDEN'})
# get first scene to get min and max properties for frames, fps
@@ -108,25 +108,25 @@ class ExportMDD(bpy.types.Operator, ExportHelper):
# List of operator properties, the attributes will be assigned
# to the class instance from the operator settings before calling.
- fps = FloatProperty(
+ fps: FloatProperty(
name="Frames Per Second",
description="Number of frames/second",
min=minfps, max=maxfps,
default=25.0,
)
- frame_start = IntProperty(
+ frame_start: IntProperty(
name="Start Frame",
description="Start frame for baking",
min=minframe, max=maxframe,
default=1,
)
- frame_end = IntProperty(
+ frame_end: IntProperty(
name="End Frame",
description="End frame for baking",
min=minframe, max=maxframe,
default=250,
)
- use_rest_frame = BoolProperty(
+ use_rest_frame: BoolProperty(
name="Rest Frame",
description="Write the rest state at the first frame",
default=False,
@@ -164,18 +164,25 @@ def menu_func_export(self, context):
)
+classes = (
+ ImportMDD,
+ ExportMDD
+)
+
def register():
- bpy.utils.register_module(__name__)
+ for cls in classes:
+ bpy.utils.register_class(cls)
- bpy.types.INFO_MT_file_import.append(menu_func_import)
- bpy.types.INFO_MT_file_export.append(menu_func_export)
+ bpy.types.TOPBAR_MT_file_import.append(menu_func_import)
+ bpy.types.TOPBAR_MT_file_export.append(menu_func_export)
def unregister():
- bpy.utils.unregister_module(__name__)
+ for cls in classes:
+ bpy.utils.unregister_class(cls)
- bpy.types.INFO_MT_file_import.remove(menu_func_import)
- bpy.types.INFO_MT_file_export.remove(menu_func_export)
+ bpy.types.TOPBAR_MT_file_import.remove(menu_func_import)
+ bpy.types.TOPBAR_MT_file_export.remove(menu_func_export)
if __name__ == "__main__":
register()
diff --git a/io_shape_mdd/export_mdd.py b/io_shape_mdd/export_mdd.py
index 04ca8238..48e34f29 100644
--- a/io_shape_mdd/export_mdd.py
+++ b/io_shape_mdd/export_mdd.py
@@ -67,7 +67,7 @@ def save(context, filepath="", frame_start=1, frame_end=300, fps=25.0, use_rest_
orig_frame = scene.frame_current
scene.frame_set(frame_start)
- me = obj.to_mesh(scene, True, 'PREVIEW')
+ me = obj.to_mesh(context.depsgraph, True)
#Flip y and z
'''
@@ -95,21 +95,21 @@ def save(context, filepath="", frame_start=1, frame_end=300, fps=25.0, use_rest_
if use_rest_frame:
check_vertcount(me, numverts)
- me.transform(mat_flip * obj.matrix_world)
+ me.transform(mat_flip @ obj.matrix_world)
f.write(pack(">%df" % (numverts * 3), *[axis for v in me.vertices for axis in v.co]))
- bpy.data.meshes.remove(me, do_unlink=True)
+ bpy.data.meshes.remove(me)
for frame in range(frame_start, frame_end + 1): # in order to start at desired frame
scene.frame_set(frame)
- me = obj.to_mesh(scene, True, 'PREVIEW')
+ me = obj.to_mesh(context.depsgraph, True)
check_vertcount(me, numverts)
- me.transform(mat_flip * obj.matrix_world)
+ me.transform(mat_flip @ obj.matrix_world)
# Write the vertex data
f.write(pack(">%df" % (numverts * 3), *[axis for v in me.vertices for axis in v.co]))
- bpy.data.meshes.remove(me, do_unlink=True)
+ bpy.data.meshes.remove(me)
f.close()
diff --git a/light_field_tools/light_field_tools.py b/light_field_tools/light_field_tools.py
index 82d86ba4..5ab2092f 100644
--- a/light_field_tools/light_field_tools.py
+++ b/light_field_tools/light_field_tools.py
@@ -189,7 +189,7 @@ class OBJECT_OT_create_lightfield_rig(Operator):
cam.data.angle = scene.lightfield.angle
# display options of the camera
- cam.data.draw_size = 0.15
+ cam.data.display_size = 0.15
cam.data.lens_unit = 'FOV'
# handler parent
@@ -228,7 +228,7 @@ class OBJECT_OT_create_lightfield_rig(Operator):
def createSpot(self, index, textured=False):
scene = bpy.context.scene
- bpy.ops.object.lamp_add(
+ bpy.ops.object.light_add(
type='SPOT')
spot = bpy.context.active_object
diff --git a/materials_utils/__init__.py b/materials_utils/__init__.py
index 5b5cda6f..94975e6b 100644
--- a/materials_utils/__init__.py
+++ b/materials_utils/__init__.py
@@ -2506,7 +2506,7 @@ def check_mat_name_unique(name_id="Material_new"):
def included_object_types(objects):
# Pass the bpy.data.objects.type to avoid needless assigning/removing
# included - type that can have materials
- included = ['MESH', 'CURVE', 'SURFACE', 'FONT', 'META']
+ included = ['MESH', 'CURVE', 'SURFACE', 'FONT', 'META', 'GPENCIL']
obj = objects
return bool(obj and obj in included)
diff --git a/measureit/__init__.py b/measureit/__init__.py
index a030fe73..2d248d9c 100644
--- a/measureit/__init__.py
+++ b/measureit/__init__.py
@@ -29,8 +29,8 @@ bl_info = {
"name": "MeasureIt",
"author": "Antonio Vazquez (antonioya)",
"location": "View3D > Tools Panel /Properties panel",
- "version": (1, 7, 1),
- "blender": (2, 79, 0),
+ "version": (1, 8, 0),
+ "blender": (2, 80, 0),
"description": "Tools for measuring objects.",
"wiki_url": "https://wiki.blender.org/index.php/Extensions:2.6/"
"Py/Scripts/3D_interaction/Measureit",
@@ -74,10 +74,10 @@ from bpy.props import (
# Define Panel classes for updating
panels = (
- measureit_main.MeasureitEditPanel,
- measureit_main.MeasureitMainPanel,
- measureit_main.MeasureitConfPanel,
- measureit_main.MeasureitRenderPanel,
+ measureit_main.MEASUREIT_PT_Edit,
+ measureit_main.MEASUREIT_PT_Main,
+ measureit_main.MEASUREIT_PT_Conf,
+ measureit_main.MEASUREIT_PT_Render,
)
@@ -120,29 +120,35 @@ class Measure_Pref(AddonPreferences):
# Define menu
# noinspection PyUnusedLocal
+classes = (
+ measureit_main.MEASUREIT_OT_RunHintDisplay,
+ measureit_main.MEASUREIT_OT_AddSegment,
+ measureit_main.MEASUREIT_OT_AddArea,
+ measureit_main.MEASUREIT_OT_AddSegmentOrto,
+ measureit_main.MEASUREIT_OT_AddAngle,
+ measureit_main.MEASUREIT_OT_AddArc,
+ measureit_main.MEASUREIT_OT_AddLabel,
+ measureit_main.MEASUREIT_OT_AddNote,
+ measureit_main.MEASUREIT_OT_AddLink,
+ measureit_main.MEASUREIT_OT_AddOrigin,
+ measureit_main.MEASUREIT_OT_DeleteSegment,
+ measureit_main.MEASUREIT_OT_DeleteAllSegment,
+ measureit_main.MEASUREIT_OT_DeleteAllSum,
+ measureit_main.MEASUREIT_OT_RenderSegment,
+ measureit_main.MEASUREIT_OT_ExpandAllSegment,
+ measureit_main.MEASUREIT_OT_CollapseAllSegment,
+ measureit_main.MEASUREIT_PT_Main,
+ measureit_main.MEASUREIT_PT_Edit,
+ measureit_main.MEASUREIT_PT_Conf,
+ measureit_main.MEASUREIT_PT_Render,
+# Measure_Pref,
+)
+
def register():
- bpy.utils.register_class(measureit_main.RunHintDisplayButton)
- bpy.utils.register_class(measureit_main.AddSegmentButton)
- bpy.utils.register_class(measureit_main.AddAreaButton)
- bpy.utils.register_class(measureit_main.AddSegmentOrtoButton)
- bpy.utils.register_class(measureit_main.AddAngleButton)
- bpy.utils.register_class(measureit_main.AddArcButton)
- bpy.utils.register_class(measureit_main.AddLabelButton)
- bpy.utils.register_class(measureit_main.AddNoteButton)
- bpy.utils.register_class(measureit_main.AddLinkButton)
- bpy.utils.register_class(measureit_main.AddOriginButton)
- bpy.utils.register_class(measureit_main.DeleteSegmentButton)
- bpy.utils.register_class(measureit_main.DeleteAllSegmentButton)
- bpy.utils.register_class(measureit_main.DeleteAllSumButton)
- bpy.utils.register_class(measureit_main.MeasureitEditPanel)
- bpy.utils.register_class(measureit_main.MeasureitMainPanel)
- bpy.utils.register_class(measureit_main.MeasureitConfPanel)
- bpy.utils.register_class(measureit_main.MeasureitRenderPanel)
- bpy.utils.register_class(measureit_main.RenderSegmentButton)
- bpy.utils.register_class(measureit_main.ExpandAllSegmentButton)
- bpy.utils.register_class(measureit_main.CollapseAllSegmentButton)
- bpy.utils.register_class(Measure_Pref)
- update_panel(None, bpy.context)
+ from bpy.utils import register_class
+ for cls in classes:
+ register_class(cls)
+
# Define properties
Scene.measureit_default_color = FloatVectorProperty(
name="Default color",
@@ -244,11 +250,8 @@ def register():
description="Save an image with measures over"
" render image",
default=False)
- Scene.measureit_render_type = EnumProperty(items=(('1', "*Current", "Use current render"),
- ('2', "OpenGL", ""),
- ('3', "Animation OpenGL", ""),
- ('4', "Image", ""),
- ('5', "Animation", "")),
+ Scene.measureit_render_type = EnumProperty(items=(('1', "Frame", "Render current frame"),
+ ('2', "Animation", "")),
name="Render type",
description="Type of render image")
Scene.measureit_sum = EnumProperty(items=(('99', "-", "Select a group for sum"),
@@ -420,27 +423,9 @@ def register():
def unregister():
- bpy.utils.unregister_class(measureit_main.RunHintDisplayButton)
- bpy.utils.unregister_class(measureit_main.AddSegmentButton)
- bpy.utils.unregister_class(measureit_main.AddAreaButton)
- bpy.utils.unregister_class(measureit_main.AddSegmentOrtoButton)
- bpy.utils.unregister_class(measureit_main.AddAngleButton)
- bpy.utils.unregister_class(measureit_main.AddArcButton)
- bpy.utils.unregister_class(measureit_main.AddLabelButton)
- bpy.utils.unregister_class(measureit_main.AddNoteButton)
- bpy.utils.unregister_class(measureit_main.AddLinkButton)
- bpy.utils.unregister_class(measureit_main.AddOriginButton)
- bpy.utils.unregister_class(measureit_main.DeleteSegmentButton)
- bpy.utils.unregister_class(measureit_main.DeleteAllSegmentButton)
- bpy.utils.unregister_class(measureit_main.DeleteAllSumButton)
- bpy.utils.unregister_class(measureit_main.MeasureitEditPanel)
- bpy.utils.unregister_class(measureit_main.MeasureitMainPanel)
- bpy.utils.unregister_class(measureit_main.MeasureitConfPanel)
- bpy.utils.unregister_class(measureit_main.MeasureitRenderPanel)
- bpy.utils.unregister_class(measureit_main.RenderSegmentButton)
- bpy.utils.unregister_class(measureit_main.ExpandAllSegmentButton)
- bpy.utils.unregister_class(measureit_main.CollapseAllSegmentButton)
- bpy.utils.unregister_class(Measure_Pref)
+ from bpy.utils import unregister_class
+ for cls in reversed(classes):
+ unregister_class(cls)
# Remove properties
del Scene.measureit_default_color
@@ -501,7 +486,7 @@ def unregister():
del Scene.measureit_font_align
# remove OpenGL data
- measureit_main.RunHintDisplayButton.handle_remove(measureit_main.RunHintDisplayButton, bpy.context)
+ measureit_main.MEASUREIT_OT_RunHintDisplay.handle_remove(measureit_main.MEASUREIT_OT_RunHintDisplay, bpy.context)
wm = bpy.context.window_manager
p = 'measureit_run_opengl'
if p in wm:
diff --git a/measureit/measureit_geometry.py b/measureit/measureit_geometry.py
index e0b036d6..6340a890 100644
--- a/measureit/measureit_geometry.py
+++ b/measureit/measureit_geometry.py
@@ -26,8 +26,6 @@
# noinspection PyUnresolvedReferences
import bpy
# noinspection PyUnresolvedReferences
-import bgl
-# noinspection PyUnresolvedReferences
import blf
from blf import ROTATION
from math import fabs, degrees, radians, sqrt, cos, sin, pi
@@ -36,13 +34,16 @@ from bmesh import from_edit_mesh
from bpy_extras import view3d_utils, mesh_utils
import bpy_extras.object_utils as object_utils
from sys import exc_info
+# GPU
+import bgl
+import gpu
+from gpu_extras.batch import batch_for_shader
+shader = gpu.shader.from_builtin('2D_UNIFORM_COLOR')
# -------------------------------------------------------------
# Draw segments
#
-# rgb: Color
-# fsize: Font size
# -------------------------------------------------------------
# noinspection PyUnresolvedReferences,PyUnboundLocalVariable
def draw_segments(context, myobj, op, region, rv3d):
@@ -60,6 +61,7 @@ def draw_segments(context, myobj, op, region, rv3d):
ovrline = scene.measureit_ovr_width
units = scene.measureit_units
fang = get_angle_in_rad(scene.measureit_font_rotation)
+
# --------------------
# Scene Scale
# --------------------
@@ -72,6 +74,7 @@ def draw_segments(context, myobj, op, region, rv3d):
draw_text(myobj, pos_2d,
tx_scale, scene.measureit_scale_color, scene.measureit_scale_font,
text_rot=fang)
+
# --------------------
# Loop
# --------------------
@@ -96,9 +99,10 @@ def draw_segments(context, myobj, op, region, rv3d):
# noinspection PyBroadException
try:
if ovr is False:
- rgb = ms.glcolor
+ rgba = ms.glcolor
else:
- rgb = ovrcolor
+ rgba = ovrcolor
+
# ----------------------
# Segment or Label
# ----------------------
@@ -108,6 +112,7 @@ def draw_segments(context, myobj, op, region, rv3d):
if ms.glpointa <= len(obverts) and ms.glpointb <= len(obverts):
a_p1 = get_point(obverts[ms.glpointa].co, myobj)
b_p1 = get_point(obverts[ms.glpointb].co, myobj)
+
# ----------------------
# Segment or Label
# ----------------------
@@ -326,8 +331,6 @@ def draw_segments(context, myobj, op, region, rv3d):
else:
bgl.glLineWidth(ovrline)
- bgl.glColor4f(rgb[0], rgb[1], rgb[2], rgb[3])
-
# ------------------------------------
# Text (distance)
# ------------------------------------
@@ -366,7 +369,7 @@ def draw_segments(context, myobj, op, region, rv3d):
if scene.measureit_gl_show_n is True and ms.glnames is True:
msg += ms.gltxt
if scene.measureit_gl_show_d is True or scene.measureit_gl_show_n is True:
- draw_text(myobj, txtpoint2d, msg, rgb, fsize, faln, fang)
+ draw_text(myobj, txtpoint2d, msg, rgba, fsize, faln, fang)
# ------------------------------
# if axis loc, show a indicator
@@ -381,7 +384,7 @@ def draw_segments(context, myobj, op, region, rv3d):
if ms.glocz is True:
txt += "Z"
txt += "]"
- draw_text(myobj, txtpoint2d, txt, rgb, fsize - 1, text_rot=fang)
+ draw_text(myobj, txtpoint2d, txt, rgba, fsize - 1, text_rot=fang)
except:
pass
@@ -445,7 +448,7 @@ def draw_segments(context, myobj, op, region, rv3d):
tmp_point = get_2d_point(region, rv3d, gap3d)
if tmp_point is not None:
txtpoint2d = tmp_point[0] + ms.glfontx, tmp_point[1] + ms.glfonty
- draw_text(myobj, txtpoint2d, msg, rgb, fsize, faln, fang)
+ draw_text(myobj, txtpoint2d, msg, rgba, fsize, faln, fang)
# Radius
if scene.measureit_gl_show_d is True and ms.gldist is True and \
ms.glarc_rad is True:
@@ -461,7 +464,7 @@ def draw_segments(context, myobj, op, region, rv3d):
tmp_point = get_2d_point(region, rv3d, gap3d)
if tmp_point is not None:
txtpoint2d = tmp_point[0] + ms.glfontx, tmp_point[1] + ms.glfonty
- draw_text(myobj, txtpoint2d, tx_dist, rgb, fsize, faln, fang)
+ draw_text(myobj, txtpoint2d, tx_dist, rgba, fsize, faln, fang)
except:
pass
# ------------------------------------
@@ -475,31 +478,30 @@ def draw_segments(context, myobj, op, region, rv3d):
tmp_point = get_2d_point(region, rv3d, gap3d)
if tmp_point is not None:
txtpoint2d = tmp_point[0] + ms.glfontx, tmp_point[1] + ms.glfonty
- draw_text(myobj, txtpoint2d, tx_dist, rgb, fsize, faln, fang)
+ draw_text(myobj, txtpoint2d, tx_dist, rgba, fsize, faln, fang)
# ------------------------------------
# Draw lines
# ------------------------------------
bgl.glEnable(bgl.GL_BLEND)
- bgl.glColor4f(rgb[0], rgb[1], rgb[2], rgb[3])
if ms.gltype == 1: # Segment
- draw_line(screen_point_ap1, screen_point_v11)
- draw_line(screen_point_bp1, screen_point_v22)
- draw_arrow(screen_point_v1, screen_point_v2, a_size, a_type, b_type)
+ draw_line(screen_point_ap1, screen_point_v11, rgba)
+ draw_line(screen_point_bp1, screen_point_v22, rgba)
+ draw_arrow(screen_point_v1, screen_point_v2, rgba, a_size, a_type, b_type)
if ms.gltype == 12 or ms.gltype == 13 or ms.gltype == 14: # Segment to origin
- draw_line(screen_point_ap1, screen_point_v11)
- draw_line(screen_point_bp1, screen_point_v22)
- draw_arrow(screen_point_v1, screen_point_v2, a_size, a_type, b_type)
+ draw_line(screen_point_ap1, screen_point_v11, rgba)
+ draw_line(screen_point_bp1, screen_point_v22, rgba)
+ draw_arrow(screen_point_v1, screen_point_v2, rgba, a_size, a_type, b_type)
if ms.gltype == 2: # Label
- draw_line(screen_point_v11a, screen_point_v11b)
- draw_arrow(screen_point_ap1, screen_point_v11, a_size, a_type, b_type)
+ draw_line(screen_point_v11a, screen_point_v11b, rgba)
+ draw_arrow(screen_point_ap1, screen_point_v11, rgba, a_size, a_type, b_type)
if ms.gltype == 3 or ms.gltype == 4 or ms.gltype == 5 or ms.gltype == 8 \
or ms.gltype == 6 or ms.gltype == 7: # Origin and Links
- draw_arrow(screen_point_ap1, screen_point_bp1, a_size, a_type, b_type)
+ draw_arrow(screen_point_ap1, screen_point_bp1, rgba, a_size, a_type, b_type)
if ms.gltype == 9: # Angle
dist, distloc = distance(an_p1, an_p2)
@@ -512,16 +514,16 @@ def draw_segments(context, myobj, op, region, rv3d):
screen_point_an_p2 = get_2d_point(region, rv3d, an_p2)
screen_point_an_p3 = get_2d_point(region, rv3d, mp2)
- draw_line(screen_point_an_p1, screen_point_an_p2)
- draw_line(screen_point_an_p2, screen_point_an_p3)
- draw_line(screen_point_an_p1, screen_point_an_p3)
+ draw_line(screen_point_an_p1, screen_point_an_p2, rgba)
+ draw_line(screen_point_an_p2, screen_point_an_p3, rgba)
+ draw_line(screen_point_an_p1, screen_point_an_p3, rgba)
if ms.gltype == 11: # arc
# draw line from center of arc second point
c = Vector(a_p1)
if ms.glarc_rad is True:
if ms.glarc_extrad is False:
- draw_arrow(screen_point_ap1, screen_point_bp1, a_size, a_type, b_type)
+ draw_arrow(screen_point_ap1, screen_point_bp1, rgba, a_size, a_type, b_type)
else:
vne = Vector((b_p1[0] - a_p1[0],
b_p1[1] - a_p1[1],
@@ -530,7 +532,7 @@ def draw_segments(context, myobj, op, region, rv3d):
vie = vne * ms.glspace
pe = (b_p1[0] + vie[0], b_p1[1] + vie[1], b_p1[2] + vie[2])
screen_point_pe = get_2d_point(region, rv3d, pe)
- draw_arrow(screen_point_ap1, screen_point_pe, a_size, a_type, b_type)
+ draw_arrow(screen_point_ap1, screen_point_pe, rgba, a_size, a_type, b_type)
# create arc around the centerpoint
# rotation matrix around normal vector at center point
@@ -558,7 +560,7 @@ def draw_segments(context, myobj, op, region, rv3d):
p_02b = None
# draw the arc
for i in range(int(n_step)):
- p2 = mat_trans2 * mat_rot1 * mat_trans1 * p1
+ p2 = mat_trans2 @ mat_rot1 @ mat_trans1 @ p1
p1_ = (p1[0] + vi[0], p1[1] + vi[1], p1[2] + vi[2])
# First Point
if i == 0:
@@ -577,11 +579,11 @@ def draw_segments(context, myobj, op, region, rv3d):
screen_point_p1 = get_2d_point(region, rv3d, p1_)
screen_point_p2 = get_2d_point(region, rv3d, p2_)
if i == 0:
- draw_arrow(screen_point_p1, screen_point_p2, ms.glarc_s, ms.glarc_a, "99")
+ draw_arrow(screen_point_p1, screen_point_p2, rgba, ms.glarc_s, ms.glarc_a, "99")
elif i == int(n_step) - 1:
- draw_arrow(screen_point_p1, screen_point_p2, ms.glarc_s, "99", ms.glarc_b)
+ draw_arrow(screen_point_p1, screen_point_p2, rgba, ms.glarc_s, "99", ms.glarc_b)
else:
- draw_line(screen_point_p1, screen_point_p2)
+ draw_line(screen_point_p1, screen_point_p2, rgba)
p1 = p2.copy()
@@ -597,8 +599,8 @@ def draw_segments(context, myobj, op, region, rv3d):
screen_point_p2a = get_2d_point(region, rv3d, p_02a)
screen_point_p2b = get_2d_point(region, rv3d, p_02b)
- draw_line(screen_point_p1a, screen_point_p1b)
- draw_line(screen_point_p2a, screen_point_p2b)
+ draw_line(screen_point_p1a, screen_point_p1b, rgba)
+ draw_line(screen_point_p2a, screen_point_p2b, rgba)
if ms.gltype == 20: # Area
obverts = get_mesh_vertices(myobj)
@@ -608,7 +610,7 @@ def draw_segments(context, myobj, op, region, rv3d):
for v in face.measureit_index:
myvertices.append(v.glidx)
- area = get_area_and_paint(myvertices, myobj, obverts, region, rv3d)
+ area = get_area_and_paint(myvertices, myobj, obverts, region, rv3d, rgba)
tot += area
# Draw Area number over first face
if len(ms.measureit_faces) > 0:
@@ -657,7 +659,7 @@ def draw_segments(context, myobj, op, region, rv3d):
# Get polygon area and paint area
#
# ------------------------------------------
-def get_area_and_paint(myvertices, myobj, obverts, region, rv3d):
+def get_area_and_paint(myvertices, myobj, obverts, region, rv3d, rgba):
mymesh = myobj.data
totarea = 0
if len(myvertices) > 3:
@@ -680,9 +682,11 @@ def get_area_and_paint(myvertices, myobj, obverts, region, rv3d):
screen_point_p1 = get_2d_point(region, rv3d, p1)
screen_point_p2 = get_2d_point(region, rv3d, p2)
screen_point_p3 = get_2d_point(region, rv3d, p3)
- draw_triangle(screen_point_p1, screen_point_p2, screen_point_p3)
+
+ draw_triangle(screen_point_p1, screen_point_p2, screen_point_p3, rgba)
# Area
+
area = get_triangle_area(p1, p2, p3)
totarea += area
@@ -695,7 +699,7 @@ def get_area_and_paint(myvertices, myobj, obverts, region, rv3d):
screen_point_p1 = get_2d_point(region, rv3d, p1)
screen_point_p2 = get_2d_point(region, rv3d, p2)
screen_point_p3 = get_2d_point(region, rv3d, p3)
- draw_triangle(screen_point_p1, screen_point_p2, screen_point_p3)
+ draw_triangle(screen_point_p1, screen_point_p2, screen_point_p3, rgba)
# Area
area = get_triangle_area(p1, p2, p3)
@@ -798,7 +802,7 @@ def get_group_sum(myobj, tag):
# Create OpenGL text
#
# -------------------------------------------------------------
-def draw_text(myobj, pos2d, display_text, rgb, fsize, align='L', text_rot=0.0):
+def draw_text(myobj, pos2d, display_text, rgba, fsize, align='L', text_rot=0.0):
if pos2d is None:
return
@@ -843,7 +847,7 @@ def draw_text(myobj, pos2d, display_text, rgb, fsize, align='L', text_rot=0.0):
new_y = y_pos + (mheight * idx)
# Draw
blf.position(font_id, newx, new_y, 0)
- bgl.glColor4f(rgb[0], rgb[1], rgb[2], rgb[3])
+ blf.color(font_id, rgba[0], rgba[1], rgba[2], rgba[3])
blf.draw(font_id, " " + line)
# sub line
idx -= 1
@@ -861,14 +865,16 @@ def draw_text(myobj, pos2d, display_text, rgb, fsize, align='L', text_rot=0.0):
# Draw an OpenGL line
#
# -------------------------------------------------------------
-def draw_line(v1, v2):
+def draw_line(v1, v2, rgba):
+ coords = [(v1[0], v1[1]), (v2[0], v2[1])]
+ batch = batch_for_shader(shader, 'LINES', {"pos": coords})
+
# noinspection PyBroadException
try:
if v1 is not None and v2 is not None:
- bgl.glBegin(bgl.GL_LINES)
- bgl.glVertex2f(*v1)
- bgl.glVertex2f(*v2)
- bgl.glEnd()
+ shader.bind()
+ shader.uniform_float("color", rgba)
+ batch.draw(shader)
except:
pass
@@ -877,15 +883,16 @@ def draw_line(v1, v2):
# Draw an OpenGL triangle
#
# -------------------------------------------------------------
-def draw_triangle(v1, v2, v3):
+def draw_triangle(v1, v2, v3, rgba):
+ coords = [(v1[0], v1[1]), (v2[0], v2[1]), (v3[0], v3[1])]
+ batch = batch_for_shader(shader, 'TRIS', {"pos": coords})
+
# noinspection PyBroadException
try:
if v1 is not None and v2 is not None and v3 is not None:
- bgl.glBegin(bgl.GL_TRIANGLES)
- bgl.glVertex2f(*v1)
- bgl.glVertex2f(*v2)
- bgl.glVertex2f(*v3)
- bgl.glEnd()
+ shader.bind()
+ shader.uniform_float("color", rgba)
+ batch.draw(shader)
except:
pass
@@ -894,7 +901,7 @@ def draw_triangle(v1, v2, v3):
# Draw an Arrow
#
# -------------------------------------------------------------
-def draw_arrow(v1, v2, size=20, a_typ="1", b_typ="1"):
+def draw_arrow(v1, v2, rgba, size=20, a_typ="1", b_typ="1"):
if v1 is None or v2 is None:
return
@@ -938,19 +945,19 @@ def draw_arrow(v1, v2, size=20, a_typ="1", b_typ="1"):
# Triangle o Lines
if a_typ == "1" or a_typ == "3":
- draw_line(v1, v1a)
- draw_line(v1, v1b)
+ draw_line(v1, v1a, rgba)
+ draw_line(v1, v1b, rgba)
if b_typ == "1" or b_typ == "3":
- draw_line(v2, v2a)
- draw_line(v2, v2b)
+ draw_line(v2, v2a, rgba)
+ draw_line(v2, v2b, rgba)
if a_typ == "2":
- draw_triangle(v1, v1a, v1b)
+ draw_triangle(v1, v1a, v1b, rgba)
if b_typ == "2":
- draw_triangle(v2, v2a, v2b)
+ draw_triangle(v2, v2a, v2b, rgba)
- draw_line(v1, v2)
+ draw_line(v1, v2, rgba)
# -------------------------------------------------------------
@@ -958,16 +965,16 @@ def draw_arrow(v1, v2, size=20, a_typ="1", b_typ="1"):
#
# v1, v2 are corners (bottom left / top right)
# -------------------------------------------------------------
-def draw_rectangle(v1, v2):
+def draw_rectangle(v1, v2, rgba):
# noinspection PyBroadException
try:
if v1 is not None and v2 is not None:
v1b = (v2[0], v1[1])
v2b = (v1[0], v2[1])
- draw_line(v1, v1b)
- draw_line(v1b, v2)
- draw_line(v2, v2b)
- draw_line(v2b, v1)
+ draw_line(v1, v1b, rgba)
+ draw_line(v1b, v2, rgba)
+ draw_line(v2, v2b, rgba)
+ draw_line(v2b, v1, rgba)
except:
pass
@@ -996,7 +1003,7 @@ def format_point(mypoint, pr):
# noinspection PyUnresolvedReferences,PyUnboundLocalVariable,PyUnusedLocal
def draw_object(context, myobj, region, rv3d):
scene = bpy.context.scene
- rgb = scene.measureit_debug_obj_color
+ rgba = scene.measureit_debug_obj_color
fsize = scene.measureit_debug_font
precision = scene.measureit_debug_precision
# --------------------
@@ -1010,8 +1017,6 @@ def draw_object(context, myobj, region, rv3d):
if objs[o].select is False:
continue
a_p1 = Vector(get_location(objs[o]))
- # colour
- bgl.glColor4f(rgb[0], rgb[1], rgb[2], rgb[3])
# Text
txt = ''
if scene.measureit_debug_objects is True:
@@ -1020,7 +1025,7 @@ def draw_object(context, myobj, region, rv3d):
txt += format_point(a_p1, precision)
# converting to screen coordinates
txtpoint2d = get_2d_point(region, rv3d, a_p1)
- draw_text(myobj, txtpoint2d, txt, rgb, fsize)
+ draw_text(myobj, txtpoint2d, txt, rgba, fsize)
return
@@ -1035,7 +1040,7 @@ def draw_vertices(context, myobj, region, rv3d):
return
scene = bpy.context.scene
- rgb = scene.measureit_debug_vert_color
+ rgba = scene.measureit_debug_vert_color
fsize = scene.measureit_debug_font
precision = scene.measureit_debug_precision
# --------------------
@@ -1044,7 +1049,7 @@ def draw_vertices(context, myobj, region, rv3d):
if scene.measureit_debug_vert_loc_toggle == '1':
co_mult = lambda c: c
else: # if global, convert local c to global
- co_mult = lambda c: myobj.matrix_world * c
+ co_mult = lambda c: myobj.matrix_world @ c
if myobj.mode == 'EDIT':
bm = from_edit_mesh(myobj.data)
@@ -1060,8 +1065,6 @@ def draw_vertices(context, myobj, region, rv3d):
# noinspection PyBroadException
# try:
a_p1 = get_point(v.co, myobj)
- # colour
- bgl.glColor4f(rgb[0], rgb[1], rgb[2], rgb[3])
# converting to screen coordinates
txtpoint2d = get_2d_point(region, rv3d, a_p1)
# Text
@@ -1070,7 +1073,7 @@ def draw_vertices(context, myobj, region, rv3d):
txt += str(v.index)
if scene.measureit_debug_vert_loc is True:
txt += format_point(co_mult(v.co), precision)
- draw_text(myobj, txtpoint2d, txt, rgb, fsize)
+ draw_text(myobj, txtpoint2d, txt, rgba, fsize)
# except:
# print("Unexpected error:" + str(exc_info()))
# pass
@@ -1089,7 +1092,7 @@ def draw_edges(context, myobj, region, rv3d):
return
scene = bpy.context.scene
- rgb = scene.measureit_debug_edge_color
+ rgba = scene.measureit_debug_edge_color
fsize = scene.measureit_debug_font
precision = scene.measureit_debug_precision
# --------------------
@@ -1115,11 +1118,9 @@ def draw_edges(context, myobj, region, rv3d):
continue
a_mp = midf(e, obverts)
a_p1 = get_point(a_mp, myobj)
- # colour
- bgl.glColor4f(rgb[0], rgb[1], rgb[2], rgb[3])
# converting to screen coordinates
txtpoint2d = get_2d_point(region, rv3d, a_p1)
- draw_text(myobj, txtpoint2d, str(e.index), rgb, fsize)
+ draw_text(myobj, txtpoint2d, str(e.index), rgba, fsize)
return
@@ -1134,8 +1135,8 @@ def draw_faces(context, myobj, region, rv3d):
return
scene = bpy.context.scene
- rgb = scene.measureit_debug_face_color
- rgb2 = scene.measureit_debug_norm_color
+ rgba = scene.measureit_debug_face_color
+ rgba2 = scene.measureit_debug_norm_color
fsize = scene.measureit_debug_font
ln = scene.measureit_debug_normal_size
th = scene.measureit_debug_width
@@ -1166,21 +1167,19 @@ def draw_faces(context, myobj, region, rv3d):
a_p1 = get_point(f.center, myobj)
a_p2 = (a_p1[0] + normal[0] * ln, a_p1[1] + normal[1] * ln, a_p1[2] + normal[2] * ln)
- # colour + line setup
+ # line setup
bgl.glEnable(bgl.GL_BLEND)
bgl.glLineWidth(th)
- bgl.glColor4f(rgb[0], rgb[1], rgb[2], rgb[3])
# converting to screen coordinates
txtpoint2d = get_2d_point(region, rv3d, a_p1)
point2 = get_2d_point(region, rv3d, a_p2)
# Text
if scene.measureit_debug_faces is True:
- draw_text(myobj, txtpoint2d, str(f.index), rgb, fsize)
+ draw_text(myobj, txtpoint2d, str(f.index), rgba, fsize)
# Draw Normal
if scene.measureit_debug_normals is True:
bgl.glEnable(bgl.GL_BLEND)
- bgl.glColor4f(rgb2[0], rgb2[1], rgb2[2], rgb2[3])
- draw_arrow(txtpoint2d, point2, 10, "99", "1")
+ draw_arrow(txtpoint2d, point2, rgba, 10, "99", "1")
if len(obverts) > 2 and scene.measureit_debug_normal_details is True:
if myobj.mode == 'EDIT':
@@ -1200,11 +1199,11 @@ def draw_faces(context, myobj, region, rv3d):
b2d = get_2d_point(region, rv3d, a_p2)
c2d = get_2d_point(region, rv3d, a_p3)
# draw vectors
- draw_arrow(a2d, b2d, 10, "99", "1")
- draw_arrow(b2d, c2d, 10, "99", "1")
+ draw_arrow(a2d, b2d, rgba, 10, "99", "1")
+ draw_arrow(b2d, c2d, rgba, 10, "99", "1")
# Normal vector data
txt = format_point(normal, precision)
- draw_text(myobj, point2, txt, rgb2, fsize)
+ draw_text(myobj, point2, txt, rgba2, fsize)
except:
print("Unexpected error:" + str(exc_info()))
@@ -1271,7 +1270,7 @@ def get_point(v1, mainobject):
# Using World Matrix
vt = Vector((v1[0], v1[1], v1[2], 1))
m4 = mainobject.matrix_world
- vt2 = m4 * vt
+ vt2 = m4 @ vt
v2 = [vt2[0], vt2[1], vt2[2]]
return v2
diff --git a/measureit/measureit_main.py b/measureit/measureit_main.py
index 2ea41da1..aa5ded6d 100644
--- a/measureit/measureit_main.py
+++ b/measureit/measureit_main.py
@@ -46,7 +46,7 @@ from .measureit_render import *
# noinspection PyUnusedLocal
@persistent
def load_handler(dummy):
- RunHintDisplayButton.handle_remove(None, bpy.context)
+ MEASUREIT_OT_RunHintDisplay.handle_remove(None, bpy.context)
# ------------------------------------------------------
@@ -88,8 +88,7 @@ bpy.app.handlers.save_pre.append(save_handler)
# Define property group class for measureit faces index
# ------------------------------------------------------------------
class MeasureitIndex(PropertyGroup):
- glidx = IntProperty(name="index",
- description="vertex index")
+ glidx: IntProperty(name="index", description="vertex index")
# Register
@@ -100,10 +99,9 @@ bpy.utils.register_class(MeasureitIndex)
# Define property group class for measureit faces
# ------------------------------------------------------------------
class MeasureitFaces(PropertyGroup):
- glface = IntProperty(name="glface",
- description="Face number")
+ glface: IntProperty(name="glface", description="Face number")
# Array of index
- measureit_index = CollectionProperty(type=MeasureitIndex)
+ measureit_index: CollectionProperty(type=MeasureitIndex)
# Register
@@ -114,86 +112,86 @@ bpy.utils.register_class(MeasureitFaces)
# Define property group class for measureit data
# ------------------------------------------------------------------
class MeasureitProperties(PropertyGroup):
- gltype = IntProperty(name="gltype",
- description="Measure type (1-Segment, 2-Label, etc..)", default=1)
- glpointa = IntProperty(name="glpointa",
- description="Hidden property for opengl")
- glpointb = IntProperty(name="glpointb",
- description="Hidden property for opengl")
- glpointc = IntProperty(name="glpointc",
- description="Hidden property for opengl")
- glcolor = FloatVectorProperty(name="glcolor",
+ gltype: IntProperty(name="gltype",
+ description="Measure type (1-Segment, 2-Label, etc..)", default=1)
+ glpointa: IntProperty(name="glpointa",
+ description="Hidden property for opengl")
+ glpointb: IntProperty(name="glpointb",
+ description="Hidden property for opengl")
+ glpointc: IntProperty(name="glpointc",
+ description="Hidden property for opengl")
+ glcolor: FloatVectorProperty(name="glcolor",
description="Color for the measure",
default=(0.173, 0.545, 1.0, 1.0),
min=0.1,
max=1,
subtype='COLOR',
size=4)
- glview = BoolProperty(name="glview",
+ glview: BoolProperty(name="glview",
description="Measure visible/hide",
default=True)
- glspace = FloatProperty(name='glspace', min=-100, max=100, default=0.1,
+ glspace: FloatProperty(name='glspace', min=-100, max=100, default=0.1,
precision=3,
description='Distance to display measure')
- glwidth = IntProperty(name='glwidth', min=1, max=10, default=1,
+ glwidth: IntProperty(name='glwidth', min=1, max=10, default=1,
description='line width')
- glfree = BoolProperty(name="glfree",
+ glfree: BoolProperty(name="glfree",
description="This measure is free and can be deleted",
default=False)
- gltxt = StringProperty(name="gltxt", maxlen=256,
+ gltxt: StringProperty(name="gltxt", maxlen=256,
description="Short description (use | for line break)")
- gladvance = BoolProperty(name="gladvance",
+ gladvance: BoolProperty(name="gladvance",
description="Advanced options as line width or position",
default=False)
- gldefault = BoolProperty(name="gldefault",
+ gldefault: BoolProperty(name="gldefault",
description="Display measure in position calculated by default",
default=True)
- glnormalx = FloatProperty(name="glnormalx",
+ glnormalx: FloatProperty(name="glnormalx",
description="Change orientation in X axis",
default=1, min=-1, max=1, precision=2)
- glnormaly = FloatProperty(name="glnormaly",
+ glnormaly: FloatProperty(name="glnormaly",
description="Change orientation in Y axis",
default=0, min=-1, max=1, precision=2)
- glnormalz = FloatProperty(name="glnormalz",
+ glnormalz: FloatProperty(name="glnormalz",
description="Change orientation in Z axis",
default=0, min=-1, max=1, precision=2)
- glfont_size = IntProperty(name="Text Size",
+ glfont_size: IntProperty(name="Text Size",
description="Text size",
default=14, min=6, max=150)
- glfont_align = EnumProperty(items=(('L', "Left Align", ""),
+ glfont_align: EnumProperty(items=(('L', "Left Align", ""),
('C', "Center Align", ""),
('R', "Right Align", "")),
name="Align Font",
description="Set Font Alignment")
- glfont_rotat = IntProperty(name='Rotate', min=0, max=360, default=0,
+ glfont_rotat: IntProperty(name='Rotate', min=0, max=360, default=0,
description="Text rotation in degrees")
- gllink = StringProperty(name="gllink",
+ gllink: StringProperty(name="gllink",
description="linked object for linked measures")
- glocwarning = BoolProperty(name="glocwarning",
+ glocwarning: BoolProperty(name="glocwarning",
description="Display a warning if some axis is not used in distance",
default=True)
- glocx = BoolProperty(name="glocx",
+ glocx: BoolProperty(name="glocx",
description="Include changes in X axis for calculating the distance",
default=True)
- glocy = BoolProperty(name="glocy",
+ glocy: BoolProperty(name="glocy",
description="Include changes in Y axis for calculating the distance",
default=True)
- glocz = BoolProperty(name="glocz",
+ glocz: BoolProperty(name="glocz",
description="Include changes in Z axis for calculating the distance",
default=True)
- glfontx = IntProperty(name="glfontx",
+ glfontx: IntProperty(name="glfontx",
description="Change font position in X axis",
default=0, min=-3000, max=3000)
- glfonty = IntProperty(name="glfonty",
+ glfonty: IntProperty(name="glfonty",
description="Change font position in Y axis",
default=0, min=-3000, max=3000)
- gldist = BoolProperty(name="gldist",
+ gldist: BoolProperty(name="gldist",
description="Display distance for this measure",
default=True)
- glnames = BoolProperty(name="glnames",
+ glnames: BoolProperty(name="glnames",
description="Display text for this measure",
default=True)
- gltot = EnumProperty(items=(('99', "-", "Select a group for sum"),
+ gltot: EnumProperty(items=(('99', "-", "Select a group for sum"),
('0', "A", ""),
('1', "B", ""),
('2', "C", ""),
@@ -222,74 +220,74 @@ class MeasureitProperties(PropertyGroup):
('25', "Z", "")),
name="Sum in Group",
description="Add segment length in selected group")
- glorto = EnumProperty(items=(('99', "None", ""),
+ glorto: EnumProperty(items=(('99', "None", ""),
('0', "A", "Point A must use selected point B location"),
('1', "B", "Point B must use selected point A location")),
name="Orthogonal",
description="Display point selected as orthogonal (select axis to copy)")
- glorto_x = BoolProperty(name="ox",
+ glorto_x: BoolProperty(name="ox",
description="Copy X location",
default=False)
- glorto_y = BoolProperty(name="oy",
+ glorto_y: BoolProperty(name="oy",
description="Copy Y location",
default=False)
- glorto_z = BoolProperty(name="oz",
+ glorto_z: BoolProperty(name="oz",
description="Copy Z location",
default=False)
- glarrow_a = EnumProperty(items=(('99', "--", "No arrow"),
+ glarrow_a: EnumProperty(items=(('99', "--", "No arrow"),
('1', "Line", "The point of the arrow are lines"),
('2', "Triangle", "The point of the arrow is triangle"),
('3', "TShape", "The point of the arrow is a T")),
name="A end",
description="Add arrows to point A")
- glarrow_b = EnumProperty(items=(('99', "--", "No arrow"),
+ glarrow_b: EnumProperty(items=(('99', "--", "No arrow"),
('1', "Line", "The point of the arrow are lines"),
('2', "Triangle", "The point of the arrow is triangle"),
('3', "TShape", "The point of the arrow is a T")),
name="B end",
description="Add arrows to point B")
- glarrow_s = IntProperty(name="Size",
+ glarrow_s: IntProperty(name="Size",
description="Arrow size",
default=15, min=6, max=500)
- glarc_full = BoolProperty(name="arcfull",
+ glarc_full: BoolProperty(name="arcfull",
description="Create full circunference",
default=False)
- glarc_extrad = BoolProperty(name="arcextrad",
+ glarc_extrad: BoolProperty(name="arcextrad",
description="Adapt radio length to arc line",
default=True)
- glarc_rad = BoolProperty(name="arc rad",
+ glarc_rad: BoolProperty(name="arc rad",
description="Show arc radius",
default=True)
- glarc_len = BoolProperty(name="arc len",
+ glarc_len: BoolProperty(name="arc len",
description="Show arc length",
default=True)
- glarc_ang = BoolProperty(name="arc ang",
+ glarc_ang: BoolProperty(name="arc ang",
description="Show arc angle",
default=True)
- glarc_a = EnumProperty(items=(('99', "--", "No arrow"),
+ glarc_a: EnumProperty(items=(('99', "--", "No arrow"),
('1', "Line", "The point of the arrow are lines"),
('2', "Triangle", "The point of the arrow is triangle"),
('3', "TShape", "The point of the arrow is a T")),
name="Ar end",
description="Add arrows to point A")
- glarc_b = EnumProperty(items=(('99', "--", "No arrow"),
+ glarc_b: EnumProperty(items=(('99', "--", "No arrow"),
('1', "Line", "The point of the arrow are lines"),
('2', "Triangle", "The point of the arrow is triangle"),
('3', "TShape", "The point of the arrow is a T")),
name="Br end",
description="Add arrows to point B")
- glarc_s = IntProperty(name="Size",
+ glarc_s: IntProperty(name="Size",
description="Arrow size",
default=15, min=6, max=500)
- glarc_txradio = StringProperty(name="txradio",
+ glarc_txradio: StringProperty(name="txradio",
description="Text for radius", default="r=")
- glarc_txlen = StringProperty(name="txlen",
+ glarc_txlen: StringProperty(name="txlen",
description="Text for length", default="L=")
- glarc_txang = StringProperty(name="txang",
+ glarc_txang: StringProperty(name="txang",
description="Text for angle", default="A=")
- glcolorarea = FloatVectorProperty(name="glcolorarea",
+ glcolorarea: FloatVectorProperty(name="glcolorarea",
description="Color for the measure of area",
default=(0.1, 0.1, 0.1, 1.0),
min=0.1,
@@ -298,7 +296,7 @@ class MeasureitProperties(PropertyGroup):
size=4)
# Array of faces
- measureit_faces = CollectionProperty(type=MeasureitFaces)
+ measureit_faces: CollectionProperty(type=MeasureitFaces)
# Register
@@ -310,10 +308,10 @@ bpy.utils.register_class(MeasureitProperties)
# Measureit
# ------------------------------------------------------------------
class MeasureContainer(PropertyGroup):
- measureit_num = IntProperty(name='Number of measures', min=0, max=1000, default=0,
+ measureit_num: IntProperty(name='Number of measures', min=0, max=1000, default=0,
description='Number total of measureit elements')
# Array of segments
- measureit_segments = CollectionProperty(type=MeasureitProperties)
+ measureit_segments: CollectionProperty(type=MeasureitProperties)
bpy.utils.register_class(MeasureContainer)
@@ -324,12 +322,13 @@ Object.MeasureGenerator = CollectionProperty(type=MeasureContainer)
# Define UI class
# Measureit
# ------------------------------------------------------------------
-class MeasureitEditPanel(Panel):
- bl_idname = "measureit.editpanel"
- bl_label = "Measureit"
+class MEASUREIT_PT_Edit(Panel):
+ bl_idname = "MEASUREIT_PT_Edit"
+ bl_label = "Items"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
- bl_category = 'MeasureIt'
+ bl_category= 'View'
+ bl_parent_id = 'measureit_main_panel'
# -----------------------------------------------------
# Verify if visible
@@ -359,12 +358,12 @@ class MeasureitEditPanel(Panel):
if 'MeasureGenerator' in context.object:
box = layout.box()
row = box.row()
- row.label(context.object.name)
+ row.label(text=context.object.name)
row = box.row()
row.prop(scene, 'measureit_gl_precision', text="Precision")
row.prop(scene, 'measureit_units')
row = box.row()
- row.prop(scene, 'measureit_gl_show_d', text="Distances", toggle=True, icon="ALIGN")
+ row.prop(scene, 'measureit_gl_show_d', text="Distances", toggle=True, icon="ALIGN_CENTER")
row.prop(scene, 'measureit_gl_show_n', text="Texts", toggle=True, icon="FONT_DATA")
row = box.row()
row.prop(scene, 'measureit_hide_units', text="Hide measurement unit")
@@ -372,7 +371,7 @@ class MeasureitEditPanel(Panel):
row = box.row()
row.prop(scene, 'measureit_scale', text="Scale")
if scene.measureit_scale is True:
- split = row.split(percentage=0.25, align=False)
+ split = row.split(factor=0.25, align=False)
split.prop(scene, 'measureit_scale_color', text="")
split.prop(scene, 'measureit_scale_factor', text="1")
row = box.row()
@@ -389,7 +388,7 @@ class MeasureitEditPanel(Panel):
row = box.row()
row.prop(scene, 'measureit_ovr', text="Override")
if scene.measureit_ovr is True:
- split = row.split(percentage=0.25, align=False)
+ split = row.split(factor=0.25, align=False)
split.prop(scene, 'measureit_ovr_color', text="")
split.prop(scene, 'measureit_ovr_width', text="Width")
row = box.row()
@@ -405,15 +404,15 @@ class MeasureitEditPanel(Panel):
# -----------------
if mp.measureit_num > 0:
box = layout.box()
- row = box.row(True)
- row.operator("measureit.expandallsegmentbutton", text="Expand all", icon="ZOOMIN")
- row.operator("measureit.collapseallsegmentbutton", text="Collapse all", icon="ZOOMOUT")
+ row = box.row(align=True)
+ row.operator("measureit.expandallsegment", text="Expand all", icon="ZOOM_IN")
+ row.operator("measureit.collapseallsegment", text="Collapse all", icon="ZOOM_OUT")
for idx in range(mp.measureit_num):
if mp.measureit_segments[idx].glfree is False:
add_item(box, idx, mp.measureit_segments[idx])
row = box.row()
- row.operator("measureit.deleteallsegmentbutton", text="Delete all", icon="X")
+ row.operator("measureit.deleteallsegment", text="Delete all", icon="X")
# -----------------
# Sum loop segments
# -----------------
@@ -476,25 +475,25 @@ class MeasureitEditPanel(Panel):
if ac[idx] is True:
final += tot[idx]
tx_dist = format_distance(fmt, units, tot[idx])
- row = box.row(True)
- row.label("Group " + tx[idx] + ":")
- row.label(" ")
- row.label(tx_dist)
+ row = box.row(align=True)
+ row.label(text="Group " + tx[idx] + ":")
+ row.label(text=" ")
+ row.label(text=tx_dist)
# Grand total
- row = box.row(True)
- row.label("")
- row.label(" ")
- row.label("-" * 20)
+ row = box.row(align=True)
+ row.label(text="")
+ row.label(text=" ")
+ row.label(text="-" * 20)
tx_dist = format_distance(fmt, units, final)
- row = box.row(True)
- row.label("")
- row.label(" ")
- row.label(tx_dist)
+ row = box.row(align=True)
+ row.label(text="")
+ row.label(text=" ")
+ row.label(text=tx_dist)
# delete all
row = box.row()
- row.operator("measureit.deleteallsumbutton", text="Delete all", icon="X")
+ row.operator("measureit.deleteallsum", text="Delete all", icon="X")
# -----------------------------------------------------
@@ -502,32 +501,32 @@ class MeasureitEditPanel(Panel):
# -----------------------------------------------------
def add_item(box, idx, segment):
scene = bpy.context.scene
- row = box.row(True)
+ row = box.row(align=True)
if segment.glview is True:
icon = "VISIBLE_IPO_ON"
else:
icon = "VISIBLE_IPO_OFF"
row.prop(segment, 'glview', text="", toggle=True, icon=icon)
- row.prop(segment, 'gladvance', text="", toggle=True, icon="SCRIPTWIN")
+ row.prop(segment, 'gladvance', text="", toggle=True, icon="PREFERENCES")
if segment.gltype == 20: # Area special
- split = row.split(percentage=0.15, align=True)
+ split = row.split(factor=0.15, align=True)
split.prop(segment, 'glcolorarea', text="")
- split = split.split(percentage=0.20, align=True)
+ split = split.split(factor=0.20, align=True)
split.prop(segment, 'glcolor', text="")
else:
- split = row.split(percentage=0.25, align=True)
+ split = row.split(factor=0.25, align=True)
split.prop(segment, 'glcolor', text="")
split.prop(segment, 'gltxt', text="")
- op = row.operator("measureit.deletesegmentbutton", text="", icon="X")
+ op = row.operator("measureit.deletesegment", text="", icon="X")
op.tag = idx # saves internal data
if segment.gladvance is True:
- row = box.row(True)
+ row = box.row(align=True)
row.prop(segment, 'glfont_size', text="Font")
row.prop(segment, 'glfont_align', text="")
if segment.glfont_align == 'L':
row.prop(segment, 'glfont_rotat', text="Rotate")
- row = box.row(True)
+ row = box.row(align=True)
if segment.gltype != 9 and segment.gltype != 10 and segment.gltype != 20:
row.prop(segment, 'glspace', text="Distance")
row.prop(segment, 'glfontx', text="X")
@@ -535,16 +534,16 @@ def add_item(box, idx, segment):
# Arrows
if segment.gltype != 9 and segment.gltype != 10 and segment.gltype != 20:
- row = box.row(True)
+ row = box.row(align=True)
row.prop(segment, 'glarrow_a', text="")
row.prop(segment, 'glarrow_b', text="")
if segment.glarrow_a != '99' or segment.glarrow_b != '99':
row.prop(segment, 'glarrow_s', text="Size")
if segment.gltype != 2 and segment.gltype != 10:
- row = box.row(True)
+ row = box.row(align=True)
if scene.measureit_gl_show_d is True and segment.gltype != 9:
- row.prop(segment, 'gldist', text="Distance", toggle=True, icon="ALIGN")
+ row.prop(segment, 'gldist', text="Distance", toggle=True, icon="ALIGN_CENTER")
if scene.measureit_gl_show_n is True:
row.prop(segment, 'glnames', text="Text", toggle=True, icon="FONT_DATA")
# sum distances
@@ -552,11 +551,11 @@ def add_item(box, idx, segment):
row.prop(segment, 'gltot', text="Sum")
if segment.gltype != 9 and segment.gltype != 10 and segment.gltype != 20:
- row = box.row(True)
+ row = box.row(align=True)
row.prop(segment, 'glwidth', text="Line")
row.prop(segment, 'gldefault', text="Automatic position")
if segment.gldefault is False:
- row = box.row(True)
+ row = box.row(align=True)
row.prop(segment, 'glnormalx', text="X")
row.prop(segment, 'glnormaly', text="Y")
row.prop(segment, 'glnormalz', text="Z")
@@ -565,7 +564,7 @@ def add_item(box, idx, segment):
if segment.gltype != 2 and segment.gltype != 9 and segment.gltype != 10 \
and segment.gltype != 11 and segment.gltype != 12 and segment.gltype != 13 \
and segment.gltype != 14 and segment.gltype != 20:
- row = box.row(True)
+ row = box.row(align=True)
row.prop(segment, 'glocx', text="X", toggle=True)
row.prop(segment, 'glocy', text="Y", toggle=True)
row.prop(segment, 'glocz', text="Z", toggle=True)
@@ -577,28 +576,28 @@ def add_item(box, idx, segment):
# ortogonal (only segments)
if segment.gltype == 1:
if segment.glorto != "99":
- row = box.row(True)
+ row = box.row(align=True)
row.prop(segment, 'glorto_x', text="X", toggle=True)
row.prop(segment, 'glorto_y', text="Y", toggle=True)
row.prop(segment, 'glorto_z', text="Z", toggle=True)
# Arc special
if segment.gltype == 11:
- row = box.row(True)
+ row = box.row(align=True)
row.prop(segment, 'glarc_rad', text="Radius")
row.prop(segment, 'glarc_len', text="Length")
row.prop(segment, 'glarc_ang', text="Angle")
- row = box.row(True)
+ row = box.row(align=True)
row.prop(segment, 'glarc_txradio', text="")
row.prop(segment, 'glarc_txlen', text="")
row.prop(segment, 'glarc_txang', text="")
- row = box.row(True)
+ row = box.row(align=True)
row.prop(segment, 'glarc_full', text="Full Circle")
if segment.glarc_rad is True:
row.prop(segment, 'glarc_extrad', text="Adapt radio")
- row = box.row(True)
+ row = box.row(align=True)
row.prop(segment, 'glarc_a', text="")
row.prop(segment, 'glarc_b', text="")
if segment.glarc_a != '99' or segment.glarc_b != '99':
@@ -608,12 +607,12 @@ def add_item(box, idx, segment):
# ------------------------------------------------------------------
# Define panel class for main functions.
# ------------------------------------------------------------------
-class MeasureitMainPanel(Panel):
+class MEASUREIT_PT_Main(Panel):
bl_idname = "measureit_main_panel"
bl_label = "MeasureIt Tools"
bl_space_type = 'VIEW_3D'
- bl_region_type = "TOOLS"
- bl_category = 'Measureit'
+ bl_region_type = 'UI'
+ bl_category= 'View'
# ------------------------------
# Draw UI
@@ -637,45 +636,45 @@ class MeasureitMainPanel(Panel):
icon = "PAUSE"
txt = 'Hide'
- row.operator("measureit.runopenglbutton", text=txt, icon=icon)
+ row.operator("measureit.runopengl", text=txt, icon=icon)
row.prop(scene, "measureit_gl_ghost", text="", icon='GHOST_ENABLED')
# Tools
box = layout.box()
- box.label("Add Measures")
+ box.label(text="Add Measures")
row = box.row()
- row.operator("measureit.addsegmentbutton", text="Segment", icon="ALIGN")
+ row.operator("measureit.addsegment", text="Segment")
row.prop(scene, "measureit_sum", text="Sum")
# To origin
row = box.row()
- op = row.operator("measureit.addsegmentortobutton", text="X", icon="ALIGN")
+ op = row.operator("measureit.addsegmentorto", text="X")
op.tag = 0 # saves internal data
- op = row.operator("measureit.addsegmentortobutton", text="Y", icon="ALIGN")
+ op = row.operator("measureit.addsegmentorto", text="Y")
op.tag = 1 # saves internal data
- op = row.operator("measureit.addsegmentortobutton", text="Z", icon="ALIGN")
+ op = row.operator("measureit.addsegmentorto", text="Z")
op.tag = 2 # saves internal data
row = box.row()
- row.operator("measureit.addanglebutton", text="Angle", icon="LINCURVE")
- row.operator("measureit.addarcbutton", text="Arc", icon="MAN_ROT")
+ row.operator("measureit.addangle", text="Angle", icon="LINCURVE")
+ row.operator("measureit.addarc", text="Arc")
row = box.row()
- row.operator("measureit.addlabelbutton", text="Label", icon="FONT_DATA")
- row.operator("measureit.addnotebutton", text="Annotation", icon="NEW")
+ row.operator("measureit.addlabel", text="Label", icon="FONT_DATA")
+ row.operator("measureit.addnote", text="Annotation")
row = box.row()
- row.operator("measureit.addlinkbutton", text="Link", icon="ROTATECENTER")
- row.operator("measureit.addoriginbutton", text="Origin", icon="CURSOR")
+ row.operator("measureit.addlink", text="Link")
+ row.operator("measureit.addorigin", text="Origin")
row = box.row()
- row.operator("measureit.addareabutton", text="Area", icon="MESH_GRID")
+ row.operator("measureit.addarea", text="Area", icon="MESH_GRID")
# ------------------------------
# Debug data
# ------------------------------
box = layout.box()
- row = box.row(False)
+ row = box.row(align=False)
if scene.measureit_debug is False:
row.prop(scene, "measureit_debug", icon="TRIA_RIGHT",
text="Mesh Debug", emboss=False)
@@ -684,43 +683,43 @@ class MeasureitMainPanel(Panel):
text="Mesh Debug", emboss=False)
row = box.row()
- split = row.split(percentage=0.10, align=True)
+ split = row.split(factor=0.10, align=True)
split.prop(scene, 'measureit_debug_obj_color', text="")
split.prop(scene, "measureit_debug_objects", icon="OBJECT_DATA")
split.prop(scene, "measureit_debug_object_loc", icon="EMPTY_DATA")
row = box.row()
- split = row.split(percentage=0.10, align=True)
+ split = row.split(factor=0.10, align=True)
split.prop(scene, 'measureit_debug_vert_color', text="")
- split.prop(scene, "measureit_debug_vertices", icon="LOOPSEL")
+ split.prop(scene, "measureit_debug_vertices", icon="VERTEXSEL")
split.prop(scene, "measureit_debug_vert_loc", icon="EMPTY_DATA")
if scene.measureit_debug_vert_loc is True:
split.prop(scene, 'measureit_debug_vert_loc_toggle', text="")
row = box.row()
- split = row.split(percentage=0.10, align=True)
+ split = row.split(factor=0.10, align=True)
split.prop(scene, 'measureit_debug_edge_color', text="")
- split = split.split(percentage=0.5, align=True)
+ split = split.split(factor=0.5, align=True)
split.prop(scene, "measureit_debug_edges", icon="EDGESEL")
row = box.row()
- split = row.split(percentage=0.10, align=True)
+ split = row.split(factor=0.10, align=True)
split.prop(scene, 'measureit_debug_face_color', text="")
- split = split.split(percentage=0.5, align=True)
+ split = split.split(factor=0.5, align=True)
split.prop(scene, "measureit_debug_faces", icon="FACESEL")
row = box.row()
- split = row.split(percentage=0.10, align=True)
+ split = row.split(factor=0.10, align=True)
split.prop(scene, 'measureit_debug_norm_color', text="")
if scene.measureit_debug_normals is False:
- split = split.split(percentage=0.50, align=True)
- split.prop(scene, "measureit_debug_normals", icon="MAN_TRANS")
+ split = split.split(factor=0.50, align=True)
+ split.prop(scene, "measureit_debug_normals", icon="OBJECT_ORIGIN")
else:
- split = split.split(percentage=0.5, align=True)
- split.prop(scene, "measureit_debug_normals", icon="MAN_TRANS")
+ split = split.split(factor=0.5, align=True)
+ split.prop(scene, "measureit_debug_normals", icon="OBJECT_ORIGIN")
split.prop(scene, "measureit_debug_normal_size")
row = box.row()
- split = row.split(percentage=0.10, align=True)
+ split = row.split(factor=0.10, align=True)
split.separator()
split.prop(scene, "measureit_debug_normal_details")
split.prop(scene, 'measureit_debug_width', text="Thickness")
@@ -734,12 +733,13 @@ class MeasureitMainPanel(Panel):
# ------------------------------------------------------------------
# Define panel class for conf functions.
# ------------------------------------------------------------------
-class MeasureitConfPanel(Panel):
+class MEASUREIT_PT_Conf(Panel):
bl_idname = "measureit_conf_panel"
- bl_label = "MeasureIt Configuration"
+ bl_label = "Configuration"
bl_space_type = 'VIEW_3D'
- bl_region_type = "TOOLS"
- bl_category = 'Measureit'
+ bl_region_type = 'UI'
+ bl_category= 'View'
+ bl_parent_id = 'measureit_main_panel'
bl_options = {'DEFAULT_CLOSED'}
# ------------------------------
@@ -752,21 +752,21 @@ class MeasureitConfPanel(Panel):
# Configuration data
box = layout.box()
row = box.row()
- split = row.split(percentage=0.2, align=True)
- split.label("Text")
- split = split.split(percentage=0.2, align=True)
+ split = row.split(factor=0.2, align=True)
+ split.label(text="Text")
+ split = split.split(factor=0.2, align=True)
split.prop(scene, "measureit_default_color", text="")
split.prop(scene, "measureit_gl_txt", text="")
- row = box.row(True)
+ row = box.row(align=True)
row.prop(scene, "measureit_hint_space")
row.prop(scene, "measureit_font_align", text="")
# Arrow
- row = box.row(True)
+ row = box.row(align=True)
row.prop(scene, "measureit_glarrow_a", text="")
row.prop(scene, "measureit_glarrow_b", text="")
if scene.measureit_glarrow_a != '99' or scene.measureit_glarrow_b != '99':
row.prop(scene, "measureit_glarrow_s", text="Size")
- row = box.row(True)
+ row = box.row(align=True)
row.prop(scene, "measureit_font_size")
if scene.measureit_font_align == 'L':
row.prop(scene, "measureit_font_rotation", text="Rotate")
@@ -775,12 +775,13 @@ class MeasureitConfPanel(Panel):
# ------------------------------------------------------------------
# Define panel class for render functions.
# ------------------------------------------------------------------
-class MeasureitRenderPanel(Panel):
+class MEASUREIT_PT_Render(Panel):
bl_idname = "measureit_render_panel"
- bl_label = "MeasureIt Render"
+ bl_label = "Render"
bl_space_type = 'VIEW_3D'
- bl_region_type = "TOOLS"
- bl_category = 'Measureit'
+ bl_region_type = 'UI'
+ bl_category= 'View'
+ bl_parent_id = 'measureit_main_panel'
bl_options = {'DEFAULT_CLOSED'}
# ------------------------------
@@ -795,7 +796,7 @@ class MeasureitRenderPanel(Panel):
row = box.row()
row.prop(scene, "measureit_render_type")
row = box.row()
- row.operator("measureit.rendersegmentbutton", icon='SCRIPT')
+ row.operator("measureit.rendersegment", icon='SCRIPT')
row = box.row()
row.prop(scene, "measureit_render", text="Save render image")
row = box.row()
@@ -811,11 +812,10 @@ class MeasureitRenderPanel(Panel):
# Defines button that adds a measure segment
#
# -------------------------------------------------------------
-class AddSegmentButton(Operator):
- bl_idname = "measureit.addsegmentbutton"
+class MEASUREIT_OT_AddSegment(Operator):
+ bl_idname = "measureit.addsegment"
bl_label = "Add"
bl_description = "(EDITMODE only) Add a new measure segment between 2 vertices (select 2 vertices or more)"
- bl_category = 'Measureit'
# ------------------------------
# Poll
@@ -900,11 +900,10 @@ class AddSegmentButton(Operator):
# Defines button that adds an area measure
#
# -------------------------------------------------------------
-class AddAreaButton(Operator):
- bl_idname = "measureit.addareabutton"
+class MEASUREIT_OT_AddArea(Operator):
+ bl_idname = "measureit.addarea"
bl_label = "Area"
bl_description = "(EDITMODE only) Add a new measure for area (select 1 o more faces)"
- bl_category = 'Measureit'
# ------------------------------
# Poll
@@ -988,13 +987,12 @@ class AddAreaButton(Operator):
# Defines button that adds a measure segment to x/y/z origin
#
# -------------------------------------------------------------
-class AddSegmentOrtoButton(Operator):
- bl_idname = "measureit.addsegmentortobutton"
+class MEASUREIT_OT_AddSegmentOrto(Operator):
+ bl_idname = "measureit.addsegmentorto"
bl_label = "Add"
bl_description = "(EDITMODE only) Add a new measure segment from vertex to object origin for one " \
"axis (select 1 vertex)"
- bl_category = 'Measureit'
- tag = IntProperty()
+ tag: IntProperty()
# ------------------------------
# Poll
@@ -1079,11 +1077,10 @@ class AddSegmentOrtoButton(Operator):
# Defines button that adds an angle measure
#
# -------------------------------------------------------------
-class AddAngleButton(Operator):
- bl_idname = "measureit.addanglebutton"
+class MEASUREIT_OT_AddAngle(Operator):
+ bl_idname = "measureit.addangle"
bl_label = "Angle"
bl_description = "(EDITMODE only) Add a new angle measure (select 3 vertices, 2nd is angle vertex)"
- bl_category = 'Measureit'
# ------------------------------
# Poll
@@ -1160,12 +1157,11 @@ class AddAngleButton(Operator):
# Defines button that adds an arc measure
#
# -------------------------------------------------------------
-class AddArcButton(Operator):
- bl_idname = "measureit.addarcbutton"
+class MEASUREIT_OT_AddArc(Operator):
+ bl_idname = "measureit.addarc"
bl_label = "Angle"
bl_description = "(EDITMODE only) Add a new arc measure (select 3 vertices of the arc," \
" vertices 1st and 3rd are arc extremes)"
- bl_category = 'Measureit'
# ------------------------------
# Poll
@@ -1245,11 +1241,10 @@ class AddArcButton(Operator):
# Defines button that adds a label segment
#
# -------------------------------------------------------------
-class AddLabelButton(Operator):
- bl_idname = "measureit.addlabelbutton"
+class MEASUREIT_OT_AddLabel(Operator):
+ bl_idname = "measureit.addlabel"
bl_label = "Add"
bl_description = "(EDITMODE only) Add a new measure label (select 1 vertex)"
- bl_category = 'Measureit'
# ------------------------------
# Poll
@@ -1328,12 +1323,11 @@ class AddLabelButton(Operator):
# Defines button that adds a link
#
# -------------------------------------------------------------
-class AddLinkButton(Operator):
- bl_idname = "measureit.addlinkbutton"
+class MEASUREIT_OT_AddLink(Operator):
+ bl_idname = "measureit.addlink"
bl_label = "Add"
bl_description = "(OBJECT mode only) Add a new measure between objects (select 2 " \
"objects and optionally 1 or 2 vertices)"
- bl_category = 'Measureit'
# ------------------------------
# Poll
@@ -1344,7 +1338,7 @@ class AddLinkButton(Operator):
if o is None:
return False
else:
- if o.type == "MESH" or o.type == "EMPTY" or o.type == "CAMERA" or o.type == "LAMP":
+ if o.type == "MESH" or o.type == "EMPTY" or o.type == "CAMERA" or o.type == "LIGHT":
if bpy.context.mode == 'OBJECT':
return True
else:
@@ -1475,11 +1469,10 @@ class AddLinkButton(Operator):
# Defines button that adds an origin segment
#
# -------------------------------------------------------------
-class AddOriginButton(Operator):
- bl_idname = "measureit.addoriginbutton"
+class MEASUREIT_OT_AddOrigin(Operator):
+ bl_idname = "measureit.addorigin"
bl_label = "Add"
bl_description = "(OBJECT mode only) Add a new measure to origin (select object and optionally 1 vertex)"
- bl_category = 'Measureit'
# ------------------------------
# Poll
@@ -1490,7 +1483,7 @@ class AddOriginButton(Operator):
if o is None:
return False
else:
- if o.type == "MESH" or o.type == "EMPTY" or o.type == "CAMERA" or o.type == "LAMP":
+ if o.type == "MESH" or o.type == "EMPTY" or o.type == "CAMERA" or o.type == "LIGHT":
if bpy.context.mode == 'OBJECT':
return True
else:
@@ -1574,12 +1567,11 @@ class AddOriginButton(Operator):
# Defines button that deletes a measure segment
#
# -------------------------------------------------------------
-class DeleteSegmentButton(Operator):
- bl_idname = "measureit.deletesegmentbutton"
+class MEASUREIT_OT_DeleteSegment(Operator):
+ bl_idname = "measureit.deletesegment"
bl_label = "Delete"
bl_description = "Delete a measure"
- bl_category = 'Measureit'
- tag = IntProperty()
+ tag: IntProperty()
# ------------------------------
# Execute button action
@@ -1608,12 +1600,11 @@ class DeleteSegmentButton(Operator):
# Defines button that deletes all measure segments
#
# -------------------------------------------------------------
-class DeleteAllSegmentButton(Operator):
- bl_idname = "measureit.deleteallsegmentbutton"
+class MEASUREIT_OT_DeleteAllSegment(Operator):
+ bl_idname = "measureit.deleteallsegment"
bl_label = "Delete"
bl_description = "Delete all measures (it cannot be undone)"
- bl_category = 'Measureit'
- tag = IntProperty()
+ tag: IntProperty()
# ------------------------------
# Execute button action
@@ -1643,12 +1634,11 @@ class DeleteAllSegmentButton(Operator):
# Defines button that deletes all measure segment sums
#
# -------------------------------------------------------------
-class DeleteAllSumButton(Operator):
- bl_idname = "measureit.deleteallsumbutton"
+class MEASUREIT_OT_DeleteAllSum(Operator):
+ bl_idname = "measureit.deleteallsum"
bl_label = "Delete"
bl_description = "Delete all sum groups"
- bl_category = 'Measureit'
- tag = IntProperty()
+ tag: IntProperty()
# ------------------------------
# Execute button action
@@ -1669,12 +1659,11 @@ class DeleteAllSumButton(Operator):
# Defines button that expands all measure segments
#
# -------------------------------------------------------------
-class ExpandAllSegmentButton(Operator):
- bl_idname = "measureit.expandallsegmentbutton"
+class MEASUREIT_OT_ExpandAllSegment(Operator):
+ bl_idname = "measureit.expandallsegment"
bl_label = "Expand"
bl_description = "Expand all measure properties"
- bl_category = 'Measureit'
- tag = IntProperty()
+ tag: IntProperty()
# ------------------------------
# Execute button action
@@ -1700,12 +1689,11 @@ class ExpandAllSegmentButton(Operator):
# Defines button that collapses all measure segments
#
# -------------------------------------------------------------
-class CollapseAllSegmentButton(Operator):
- bl_idname = "measureit.collapseallsegmentbutton"
+class MEASUREIT_OT_CollapseAllSegment(Operator):
+ bl_idname = "measureit.collapseallsegment"
bl_label = "Collapse"
bl_description = "Collapses all measure properties"
- bl_category = 'Measureit'
- tag = IntProperty()
+ tag: IntProperty()
# ------------------------------
# Execute button action
@@ -1731,12 +1719,11 @@ class CollapseAllSegmentButton(Operator):
# Defines button for render option
#
# -------------------------------------------------------------
-class RenderSegmentButton(Operator):
- bl_idname = "measureit.rendersegmentbutton"
+class MEASUREIT_OT_RenderSegment(Operator):
+ bl_idname = "measureit.rendersegment"
bl_label = "Render"
bl_description = "Create a render image with measures. Use UV/Image editor to view image generated"
- bl_category = 'Measureit'
- tag = IntProperty()
+ tag: IntProperty()
# ------------------------------
# Execute button action
@@ -1753,75 +1740,22 @@ class RenderSegmentButton(Operator):
self.report({'ERROR'}, camera_msg)
return {'FINISHED'}
# -----------------------------
- # Use default render
+ # Frame render
# -----------------------------
if scene.measureit_render_type == "1":
# noinspection PyBroadException
- try:
- result = bpy.data.images['Render Result']
- bpy.ops.render.render()
- except:
- bpy.ops.render.render()
- print("MeasureIt: Using current render image on buffer")
if render_main(self, context) is True:
self.report({'INFO'}, msg)
-
- # -----------------------------
- # OpenGL image
- # -----------------------------
- if scene.measureit_render_type == "2":
- self.set_camera_view()
- self.set_only_render(True)
-
- print("MeasureIt: Rendering opengl image")
- bpy.ops.render.opengl()
- if render_main(self, context) is True:
- self.report({'INFO'}, msg)
-
- self.set_only_render(False)
-
- # -----------------------------
- # OpenGL Animation
- # -----------------------------
- if scene.measureit_render_type == "3":
- oldframe = scene.frame_current
- self.set_camera_view()
- self.set_only_render(True)
- flag = False
- # loop frames
- for frm in range(scene.frame_start, scene.frame_end + 1):
- scene.frame_set(frm)
- print("MeasureIt: Rendering opengl frame %04d" % frm)
- bpy.ops.render.opengl()
- flag = render_main(self, context, True)
- if flag is False:
- break
-
- self.set_only_render(False)
- scene.frame_current = oldframe
- if flag is True:
- self.report({'INFO'}, msg)
-
- # -----------------------------
- # Image
- # -----------------------------
- if scene.measureit_render_type == "4":
- print("MeasureIt: Rendering image")
- bpy.ops.render.render()
- if render_main(self, context) is True:
- self.report({'INFO'}, msg)
-
# -----------------------------
# Animation
# -----------------------------
- if scene.measureit_render_type == "5":
+ if scene.measureit_render_type == "2":
oldframe = scene.frame_current
flag = False
# loop frames
for frm in range(scene.frame_start, scene.frame_end + 1):
scene.frame_set(frm)
print("MeasureIt: Rendering frame %04d" % frm)
- bpy.ops.render.render()
flag = render_main(self, context, True)
if flag is False:
break
@@ -1866,12 +1800,11 @@ class RenderSegmentButton(Operator):
# Defines a new note
#
# -------------------------------------------------------------
-class AddNoteButton(Operator):
- bl_idname = "measureit.addnotebutton"
+class MEASUREIT_OT_AddNote(Operator):
+ bl_idname = "measureit.addnote"
bl_label = "Note"
bl_description = "(OBJECT mode only) Add a new annotation"
- bl_category = 'Measureit'
- tag = IntProperty()
+ tag: IntProperty()
# ------------------------------
# Poll
@@ -1892,7 +1825,7 @@ class AddNoteButton(Operator):
bpy.ops.object.empty_add(type='PLAIN_AXES')
myempty = bpy.data.objects[bpy.context.active_object.name]
myempty.location = bpy.context.scene.cursor_location
- myempty.empty_draw_size = 0.01
+ myempty.empty_display_size = 0.01
myempty.name = "Annotation"
# Add properties
scene = context.scene
@@ -1936,11 +1869,10 @@ class AddNoteButton(Operator):
# Defines button that enables/disables the tip display
#
# -------------------------------------------------------------
-class RunHintDisplayButton(Operator):
- bl_idname = "measureit.runopenglbutton"
+class MEASUREIT_OT_RunHintDisplay(Operator):
+ bl_idname = "measureit.runopengl"
bl_label = "Display hint data manager"
bl_description = "Main control for enabling or disabling the display of measurements in the viewport"
- bl_category = 'Measureit'
_handle = None # keep function handler
@@ -1949,8 +1881,8 @@ class RunHintDisplayButton(Operator):
# ----------------------------------
@staticmethod
def handle_add(self, context):
- if RunHintDisplayButton._handle is None:
- RunHintDisplayButton._handle = SpaceView3D.draw_handler_add(draw_callback_px, (self, context),
+ if MEASUREIT_OT_RunHintDisplay._handle is None:
+ MEASUREIT_OT_RunHintDisplay._handle = SpaceView3D.draw_handler_add(draw_callback_px, (self, context),
'WINDOW',
'POST_PIXEL')
context.window_manager.measureit_run_opengl = True
@@ -1961,9 +1893,9 @@ class RunHintDisplayButton(Operator):
# noinspection PyUnusedLocal
@staticmethod
def handle_remove(self, context):
- if RunHintDisplayButton._handle is not None:
- SpaceView3D.draw_handler_remove(RunHintDisplayButton._handle, 'WINDOW')
- RunHintDisplayButton._handle = None
+ if MEASUREIT_OT_RunHintDisplay._handle is not None:
+ SpaceView3D.draw_handler_remove(MEASUREIT_OT_RunHintDisplay._handle, 'WINDOW')
+ MEASUREIT_OT_RunHintDisplay._handle = None
context.window_manager.measureit_run_opengl = False
# ------------------------------
@@ -2011,22 +1943,9 @@ def draw_main(context):
rv3d = context.space_data.region_quadviews[i]
scene = bpy.context.scene
- local_view = context.area.spaces.active.local_view is not None
- layers = []
- if local_view is False:
- # Get visible layers
- if bpy.context.space_data.lock_camera_and_layers is True:
- for x in range(20):
- if bpy.context.scene.layers[x] is True:
- layers.append(x)
- else:
- # Lock disabled, use view dependent visible layers
- for x in range(20):
- if bpy.context.space_data.layers[x] is True:
- layers.append(x)
# Display selected or all
- if scene.measureit_gl_ghost is False or local_view is True:
+ if scene.measureit_gl_ghost is False:
objlist = context.selected_objects
else:
objlist = context.scene.objects
@@ -2037,20 +1956,11 @@ def draw_main(context):
# Generate all OpenGL calls for measures
# ---------------------------------------
for myobj in objlist:
- if myobj.hide is False:
+ if myobj.visible_get() is True:
if 'MeasureGenerator' in myobj:
- if local_view is False:
- # verify visible layer
- for x in range(20):
- if myobj.layers[x] is True and x in layers:
- op = myobj.MeasureGenerator[0]
- draw_segments(context, myobj, op, region, rv3d)
- break
- else:
- # Layer check not needed here, selected objects are not
- # added to context.selected_objects if in disabled layers
- op = myobj.MeasureGenerator[0]
- draw_segments(context, myobj, op, region, rv3d)
+ op = myobj.MeasureGenerator[0]
+ draw_segments(context, myobj, op, region, rv3d)
+
# ---------------------------------------
# Generate all OpenGL calls for debug
# ---------------------------------------
@@ -2075,7 +1985,6 @@ def draw_main(context):
# -----------------------
bgl.glLineWidth(1)
bgl.glDisable(bgl.GL_BLEND)
- bgl.glColor4f(0.0, 0.0, 0.0, 1.0)
# -------------------------------------------------------------
@@ -2118,7 +2027,7 @@ def get_selected_vertex(myobject):
# meshes
# --------------------
oldobj = bpy.context.object
- bpy.context.scene.objects.active = myobject
+ bpy.context.view_layer.objects.active = myobject
flag = False
if myobject.mode != 'EDIT':
bpy.ops.object.mode_set(mode='EDIT')
@@ -2133,7 +2042,7 @@ def get_selected_vertex(myobject):
if flag is True:
bpy.ops.object.editmode_toggle()
# Back context object
- bpy.context.scene.objects.active = oldobj
+ bpy.context.view_layer.objects.active = oldobj
# if select all vertices, then use origin
if tv == len(mylist):
@@ -2154,7 +2063,7 @@ def get_selected_vertex_history(myobject):
# meshes
# --------------------
oldobj = bpy.context.object
- bpy.context.scene.objects.active = myobject
+ bpy.context.view_layer.objects.active = myobject
flag = False
if myobject.mode != 'EDIT':
bpy.ops.object.mode_set(mode='EDIT')
@@ -2167,7 +2076,7 @@ def get_selected_vertex_history(myobject):
if flag is True:
bpy.ops.object.editmode_toggle()
# Back context object
- bpy.context.scene.objects.active = oldobj
+ bpy.context.view_layer.objects.active = oldobj
return mylist
@@ -2184,7 +2093,7 @@ def get_smart_selected(myobject):
# meshes
# --------------------
oldobj = bpy.context.object
- bpy.context.scene.objects.active = myobject
+ bpy.context.view_layer.objects.active = myobject
flag = False
if myobject.mode != 'EDIT':
bpy.ops.object.mode_set(mode='EDIT')
@@ -2199,7 +2108,7 @@ def get_smart_selected(myobject):
if flag is True:
bpy.ops.object.editmode_toggle()
# Back context object
- bpy.context.scene.objects.active = oldobj
+ bpy.context.view_layer.objects.active = oldobj
return mylist
@@ -2216,7 +2125,7 @@ def get_selected_faces(myobject):
# meshes
# --------------------
oldobj = bpy.context.object
- bpy.context.scene.objects.active = myobject
+ bpy.context.view_layer.objects.active = myobject
flag = False
if myobject.mode != 'EDIT':
bpy.ops.object.mode_set(mode='EDIT')
@@ -2234,6 +2143,6 @@ def get_selected_faces(myobject):
if flag is True:
bpy.ops.object.editmode_toggle()
# Back context object
- bpy.context.scene.objects.active = oldobj
+ bpy.context.view_layer.objects.active = oldobj
return mylist
diff --git a/measureit/measureit_render.py b/measureit/measureit_render.py
index d5eb6430..dd7826a4 100644
--- a/measureit/measureit_render.py
+++ b/measureit/measureit_render.py
@@ -25,7 +25,7 @@
# ----------------------------------------------------------
# noinspection PyUnresolvedReferences
import bpy
-# noinspection PyUnresolvedReferences
+import gpu
import bgl
# noinspection PyUnresolvedReferences
import blf
@@ -46,264 +46,111 @@ from .measureit_geometry import *
#
# -------------------------------------------------------------
def render_main(self, context, animation=False):
- # noinspection PyBroadException,PyBroadException
# Save old info
settings = bpy.context.scene.render.image_settings
depth = settings.color_depth
settings.color_depth = '8'
- # noinspection PyBroadException
- try:
- # Get visible layers
- layers = []
- scene = context.scene
- for x in range(20):
- if scene.layers[x] is True:
- layers.append(x)
-
- # Get object list
- objlist = context.scene.objects
- # --------------------
- # Get resolution
- # --------------------
- scene = bpy.context.scene
- render_scale = scene.render.resolution_percentage / 100
- width = int(scene.render.resolution_x * render_scale)
- height = int(scene.render.resolution_y * render_scale)
- # ---------------------------------------
- # Get output path
- # ---------------------------------------
- temp_path = path.realpath(bpy.app.tempdir)
- if len(temp_path) > 0:
- outpath = path.join(temp_path, "measureit_tmp_render.png")
- else:
- self.report({'ERROR'},
- "MeasureIt: Unable to save temporary render image. Define a valid temp path")
- settings.color_depth = depth
- return False
-
- # Get Render Image
- img = get_render_image(outpath)
- if img is None:
- self.report({'ERROR'},
- "MeasureIt: Unable to save temporary render image. Define a valid temp path")
- settings.color_depth = depth
- return False
+ # Get object list
+ scene = context.scene
+ objlist = context.scene.objects
+ # --------------------
+ # Get resolution
+ # --------------------
+ render_scale = scene.render.resolution_percentage / 100
+ width = int(scene.render.resolution_x * render_scale)
+ height = int(scene.render.resolution_y * render_scale)
+
+ # --------------------------------------
+ # Loop to draw all lines in Offsecreen
+ # --------------------------------------
+ offscreen = gpu.types.GPUOffScreen(width, height)
+ view_matrix = Matrix([
+ [2 / width, 0, 0, -1],
+ [0, 2 / height, 0, -1],
+ [0, 0, 1, 0],
+ [0, 0, 0, 1]])
+
+ with offscreen.bind():
+ bgl.glClear(bgl.GL_COLOR_BUFFER_BIT)
+ gpu.matrix.reset()
+ gpu.matrix.load_matrix(view_matrix)
+ gpu.matrix.load_projection_matrix(Matrix.Identity(4))
# -----------------------------
- # Calculate rows and columns
+ # Loop to draw all objects
# -----------------------------
- tile_x = 240
- tile_y = 216
- row_num = ceil(height / tile_y)
- col_num = ceil(width / tile_x)
- print("MeasureIt: Image divided in " + str(row_num) + "x" + str(col_num) + " tiles")
-
- # pixels out of visible area
- cut4 = (col_num * tile_x * 4) - width * 4 # pixels aout of drawing area
- totpixel4 = width * height * 4 # total pixels RGBA
-
- viewport_info = bgl.Buffer(bgl.GL_INT, 4)
- bgl.glGetIntegerv(bgl.GL_VIEWPORT, viewport_info)
-
- # Load image on memory
- img.gl_load(0, bgl.GL_NEAREST, bgl.GL_NEAREST)
- tex = img.bindcode[0]
-
- # --------------------------------------------
- # Create output image (to apply texture)
- # --------------------------------------------
- if "measureit_output" in bpy.data.images:
- out_img = bpy.data.images["measureit_output"]
- if out_img is not None:
- bpy.data.images.remove(out_img)
-
- out = bpy.data.images.new("measureit_output", width, height)
- tmp_pixels = [1] * totpixel4
-
- # --------------------------------
- # Loop for all tiles
- # --------------------------------
- for row in range(row_num):
- for col in range(col_num):
- buffer = bgl.Buffer(bgl.GL_FLOAT, width * height * 4)
- bgl.glDisable(bgl.GL_SCISSOR_TEST) # if remove this line, get blender screenshot not image
- bgl.glViewport(0, 0, tile_x, tile_y)
-
- bgl.glMatrixMode(bgl.GL_PROJECTION)
- bgl.glLoadIdentity()
-
- # defines ortographic view for single tile
- x1 = tile_x * col
- y1 = tile_y * row
- bgl.gluOrtho2D(x1, x1 + tile_x, y1, y1 + tile_y)
-
- # Clear
- bgl.glClearColor(0.0, 0.0, 0.0, 0.0)
- bgl.glClear(bgl.GL_COLOR_BUFFER_BIT | bgl.GL_DEPTH_BUFFER_BIT)
-
- bgl.glEnable(bgl.GL_TEXTURE_2D)
- bgl.glBindTexture(bgl.GL_TEXTURE_2D, tex)
-
- # defines drawing area
- bgl.glBegin(bgl.GL_QUADS)
-
- bgl.glColor3f(1.0, 1.0, 1.0)
- bgl.glTexCoord2f(0.0, 0.0)
- bgl.glVertex2f(0.0, 0.0)
-
- bgl.glTexCoord2f(1.0, 0.0)
- bgl.glVertex2f(width, 0.0)
-
- bgl.glTexCoord2f(1.0, 1.0)
- bgl.glVertex2f(width, height)
-
- bgl.glTexCoord2f(0.0, 1.0)
- bgl.glVertex2f(0.0, height)
-
- bgl.glEnd()
-
- # -----------------------------
- # Loop to draw all lines
- # -----------------------------
- for myobj in objlist:
- if myobj.hide is False:
- if 'MeasureGenerator' in myobj:
- # verify visible layer
- for x in range(20):
- if myobj.layers[x] is True:
- if x in layers:
- op = myobj.MeasureGenerator[0]
- draw_segments(context, myobj, op, None, None)
- break
-
- # -----------------------------
- # Loop to draw all debug
- # -----------------------------
- if scene.measureit_debug is True:
- selobj = bpy.context.selected_objects
- for myobj in selobj:
- if scene.measureit_debug_objects is True:
- draw_object(context, myobj, None, None)
- elif scene.measureit_debug_object_loc is True:
- draw_object(context, myobj, None, None)
- if scene.measureit_debug_vertices is True:
- draw_vertices(context, myobj, None, None)
- elif scene.measureit_debug_vert_loc is True:
- draw_vertices(context, myobj, None, None)
- if scene.measureit_debug_edges is True:
- draw_edges(context, myobj, None, None)
- if scene.measureit_debug_faces is True or scene.measureit_debug_normals is True:
- draw_faces(context, myobj, None, None)
-
- if scene.measureit_rf is True:
- bgl.glColor3f(1.0, 1.0, 1.0)
- rfcolor = scene.measureit_rf_color
- rfborder = scene.measureit_rf_border
- rfline = scene.measureit_rf_line
-
- bgl.glLineWidth(rfline)
- bgl.glColor4f(rfcolor[0], rfcolor[1], rfcolor[2], rfcolor[3])
-
- x1 = rfborder
- x2 = width - rfborder
- y1 = int(ceil(rfborder / (width / height)))
- y2 = height - y1
- draw_rectangle((x1, y1), (x2, y2))
-
- # --------------------------------
- # copy pixels to temporary area
- # --------------------------------
- bgl.glFinish()
- bgl.glReadPixels(0, 0, width, height, bgl.GL_RGBA, bgl.GL_FLOAT, buffer) # read image data
- for y in range(tile_y):
- # final image pixels position
- p1 = (y * width * 4) + (row * tile_y * width * 4) + (col * tile_x * 4)
- p2 = p1 + (tile_x * 4)
- # buffer pixels position
- b1 = y * width * 4
- b2 = b1 + (tile_x * 4)
-
- if p1 < totpixel4: # avoid pixel row out of area
- if col == col_num - 1: # avoid pixel columns out of area
- p2 -= cut4
- b2 -= cut4
-
- tmp_pixels[p1:p2] = buffer[b1:b2]
-
- # -----------------------
- # Copy temporary to final
- # -----------------------
- out.pixels = tmp_pixels[:] # Assign image data
- img.gl_free() # free opengl image memory
-
- # delete image
- bpy.data.images.remove(img)
- # remove temp file
- remove(outpath)
- # reset
- bgl.glEnable(bgl.GL_SCISSOR_TEST)
- # -----------------------
- # restore opengl defaults
- # -----------------------
- bgl.glLineWidth(1)
- bgl.glDisable(bgl.GL_BLEND)
- bgl.glColor4f(0.0, 0.0, 0.0, 1.0)
- # Saves image
- if out is not None and (scene.measureit_render is True or animation is True):
- ren_path = bpy.context.scene.render.filepath
- filename = "mit_frame"
- if len(ren_path) > 0:
- if ren_path.endswith(path.sep):
- initpath = path.realpath(ren_path) + path.sep
- else:
- (initpath, filename) = path.split(ren_path)
-
- ftxt = "%04d" % scene.frame_current
- outpath = path.realpath(path.join(initpath, filename + ftxt + ".png"))
-
- save_image(self, outpath, out)
-
- settings.color_depth = depth
- return True
-
- except:
- settings.color_depth = depth
- print("Unexpected error:" + str(exc_info()))
- self.report({'ERROR'}, "MeasureIt: Unable to create render image. Be sure the output render path is correct")
- return False
-
-
-# --------------------------------------------------------------------
-# Get the final render image and return as image object
-#
-# return None if no render available
-# --------------------------------------------------------------------
-def get_render_image(outpath):
- saved = False
- # noinspection PyBroadException
- try:
- # noinspection PyBroadException
- try:
- result = bpy.data.images['Render Result']
- if result.has_data is False:
- # this save produce to fill data image
- result.save_render(outpath)
- saved = True
- except:
- print("No render image found")
- return None
-
- # Save and reload
- if saved is False:
- result.save_render(outpath)
-
- img = img_utils.load_image(outpath)
-
- return img
- except:
- print("Unexpected render image error")
- return None
+ for myobj in objlist:
+ if myobj.visible_get() is True:
+ if 'MeasureGenerator' in myobj:
+ op = myobj.MeasureGenerator[0]
+ draw_segments(context, myobj, op, None, None)
+ # -----------------------------
+ # Loop to draw all debug
+ # -----------------------------
+ if scene.measureit_debug is True:
+ selobj = bpy.context.selected_objects
+ for myobj in selobj:
+ if scene.measureit_debug_objects is True:
+ draw_object(context, myobj, None, None)
+ elif scene.measureit_debug_object_loc is True:
+ draw_object(context, myobj, None, None)
+ if scene.measureit_debug_vertices is True:
+ draw_vertices(context, myobj, None, None)
+ elif scene.measureit_debug_vert_loc is True:
+ draw_vertices(context, myobj, None, None)
+ if scene.measureit_debug_edges is True:
+ draw_edges(context, myobj, None, None)
+ if scene.measureit_debug_faces is True or scene.measureit_debug_normals is True:
+ draw_faces(context, myobj, None, None)
+ # -----------------------------
+ # Draw a rectangle frame
+ # -----------------------------
+ if scene.measureit_rf is True:
+ rfcolor = scene.measureit_rf_color
+ rfborder = scene.measureit_rf_border
+ rfline = scene.measureit_rf_line
+
+ bgl.glLineWidth(rfline)
+ x1 = rfborder
+ x2 = width - rfborder
+ y1 = int(ceil(rfborder / (width / height)))
+ y2 = height - y1
+ draw_rectangle((x1, y1), (x2, y2), rfcolor)
+
+ buffer = bgl.Buffer(bgl.GL_BYTE, width * height * 4)
+ bgl.glReadBuffer(bgl.GL_COLOR_ATTACHMENT0)
+ bgl.glReadPixels(0, 0, width, height, bgl.GL_RGBA, bgl.GL_UNSIGNED_BYTE, buffer)
+
+ offscreen.free()
+
+ # -----------------------------
+ # Create image
+ # -----------------------------
+ image_name = "measureit_output"
+ if not image_name in bpy.data.images:
+ bpy.data.images.new(image_name, width, height)
+
+ image = bpy.data.images[image_name]
+ image.scale(width, height)
+ image.pixels = [v / 255 for v in buffer]
+
+ # Saves image
+ if image is not None and (scene.measureit_render is True or animation is True):
+ ren_path = bpy.context.scene.render.filepath
+ filename = "mit_frame"
+ if len(ren_path) > 0:
+ if ren_path.endswith(path.sep):
+ initpath = path.realpath(ren_path) + path.sep
+ else:
+ (initpath, filename) = path.split(ren_path)
+
+ ftxt = "%04d" % scene.frame_current
+ outpath = path.realpath(path.join(initpath, filename + ftxt + ".png"))
+ save_image(self, outpath, image)
+
+ # restore default value
+ settings.color_depth = depth
# -------------------------------------
diff --git a/mesh_carver.py b/mesh_carver.py
index 74624abd..7d453ad8 100644
--- a/mesh_carver.py
+++ b/mesh_carver.py
@@ -1618,7 +1618,7 @@ def update_bevel(context):
mod.limit_method = 'WEIGHT'
mod.width = 0.01
mod.profile = 0.699099
- mod.use_clamp_overlap = False
+ mod.use_clight_overlap = False
mod.segments = 3
mod.loop_slide = False
@@ -1676,7 +1676,7 @@ def CreateBevel(context, CurrentObject):
mod.limit_method = 'WEIGHT'
mod.width = 0.01
mod.profile = 0.699099
- mod.use_clamp_overlap = False
+ mod.use_clight_overlap = False
mod.segments = 3
mod.loop_slide = False
@@ -1707,7 +1707,7 @@ def Picking(context, event):
if obj.type == 'MESH':
yield (obj, obj.matrix_world.copy())
- if obj.dupli_type != 'NONE':
+ if obj.instance_type != 'NONE':
obj.dupli_list_create(scene)
for dob in obj.dupli_list:
obj_dupli = dob.object
@@ -1964,12 +1964,12 @@ def duplicateObject(self):
ob_new.rotation_quaternion = qRot
ob_new.rotation_mode = 'XYZ'
- if (ob_new.draw_type == "WIRE") and (self.BrushSolidify is False):
+ if (ob_new.display_type == "WIRE") and (self.BrushSolidify is False):
ob_new.hide = True
if self.BrushSolidify:
- ob_new.draw_type = "SOLID"
- ob_new.show_x_ray = False
+ ob_new.display_type = "SOLID"
+ ob_new.show_in_front = False
for o in bpy.context.selected_objects:
UndoAdd(self, "DUPLICATE", o)
@@ -2070,20 +2070,20 @@ def boolean_operation(bool_type="DIFFERENCE"):
)
BoolMod.object = bpy.context.selected_objects[sel_index]
BoolMod.operation = bool_type
- bpy.context.selected_objects[sel_index].draw_type = 'WIRE'
+ bpy.context.selected_objects[sel_index].display_type = 'WIRE'
def Rebool(context, self):
LastObj = context.active_object
Brush = context.selected_objects[0]
- Brush.draw_type = "WIRE"
+ Brush.display_type = "WIRE"
obj = context.selected_objects[1]
bpy.ops.object.select_all(action='TOGGLE')
context.scene.objects.active = obj
- obj.draw_type = "SOLID"
+ obj.display_type = "SOLID"
obj.select = True
bpy.ops.object.duplicate_move(
OBJECT_OT_duplicate={
@@ -2124,7 +2124,7 @@ def Rebool(context, self):
mb.show_viewport = False
if self.ObjectBrush or self.ProfileBrush:
- LastObjectCreated.show_x_ray = False
+ LastObjectCreated.show_in_front = False
try:
bpy.ops.object.modifier_apply(apply_as='DATA', modifier="CT_SOLIDIFY")
except:
@@ -2177,7 +2177,7 @@ def createMeshFromData(self):
scn.objects.active = ob
ob.select = True
ob.location = Vector((10000.0, 0.0, 0.0))
- ob.draw_type = "WIRE"
+ ob.display_type = "WIRE"
self.SolidifyPossible = True
else:
@@ -2390,7 +2390,7 @@ class Carver(bpy.types.Operator):
self.ProfileBrush.select = True
context.scene.objects.active = self.ProfileBrush
# Set xRay
- self.ProfileBrush.show_x_ray = True
+ self.ProfileBrush.show_in_front = True
bpy.ops.object.modifier_add(type='SOLIDIFY')
context.object.modifiers["Solidify"].name = "CT_SOLIDIFY"
@@ -2407,8 +2407,8 @@ class Carver(bpy.types.Operator):
self.ObjectBrush.scale = self.InitBrushScale
self.ObjectBrush.rotation_quaternion = self.InitBrushQRotation
self.ObjectBrush.rotation_euler = self.InitBrushERotation
- self.ObjectBrush.draw_type = self.ObjectBrush_DT
- self.ObjectBrush.show_x_ray = self.XRay
+ self.ObjectBrush.display_type = self.ObjectBrush_DT
+ self.ObjectBrush.show_in_front = self.XRay
# Remove solidify modifier
Selection_Save(self)
@@ -2430,7 +2430,7 @@ class Carver(bpy.types.Operator):
self.ObjectBrush.select = True
context.scene.objects.active = self.ObjectBrush
# Set xRay
- self.ObjectBrush.show_x_ray = True
+ self.ObjectBrush.show_in_front = True
bpy.ops.object.modifier_add(type='SOLIDIFY')
context.object.modifiers["Solidify"].name = "CT_SOLIDIFY"
context.object.modifiers["CT_SOLIDIFY"].thickness = 0.1
@@ -2529,12 +2529,12 @@ class Carver(bpy.types.Operator):
self.ObjectBrush.select = True
context.scene.objects.active = self.ObjectBrush
# Active le xray
- self.ObjectBrush.show_x_ray = True
+ self.ObjectBrush.show_in_front = True
else:
self.ProfileBrush.select = True
context.scene.objects.active = self.ProfileBrush
# Active le xray
- self.ProfileBrush.show_x_ray = True
+ self.ProfileBrush.show_in_front = True
bpy.ops.object.modifier_add(type='SOLIDIFY')
context.object.modifiers["Solidify"].name = "CT_SOLIDIFY"
@@ -2773,8 +2773,8 @@ class Carver(bpy.types.Operator):
self.ObjectBrush.scale = self.InitBrushScale
self.ObjectBrush.rotation_quaternion = self.InitBrushQRotation
self.ObjectBrush.rotation_euler = self.InitBrushERotation
- self.ObjectBrush.draw_type = self.ObjectBrush_DT
- self.ObjectBrush.show_x_ray = self.XRay
+ self.ObjectBrush.display_type = self.ObjectBrush_DT
+ self.ObjectBrush.show_in_front = self.XRay
# remove solidify
Selection_Save(self)
@@ -2844,8 +2844,8 @@ class Carver(bpy.types.Operator):
self.ObjectBrush.scale = self.InitBrushScale
self.ObjectBrush.rotation_quaternion = self.InitBrushQRotation
self.ObjectBrush.rotation_euler = self.InitBrushERotation
- self.ObjectBrush.draw_type = self.ObjectBrush_DT
- self.ObjectBrush.show_x_ray = self.XRay
+ self.ObjectBrush.display_type = self.ObjectBrush_DT
+ self.ObjectBrush.show_in_front = self.XRay
# Remove solidify modifier
Selection_Save(self)
@@ -2884,7 +2884,7 @@ class Carver(bpy.types.Operator):
traceback.print_exc()
context.window.cursor_modal_set("DEFAULT")
- context.area.header_text_set("")
+ context.area.header_text_set(None)
bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
self.report({'WARNING'},
@@ -3089,8 +3089,8 @@ class Carver(bpy.types.Operator):
self.InitBrushScale = self.ObjectBrush.scale.copy()
self.InitBrushQRotation = self.ObjectBrush.rotation_quaternion.copy()
self.InitBrushERotation = self.ObjectBrush.rotation_euler.copy()
- self.ObjectBrush_DT = self.ObjectBrush.draw_type
- self.XRay = self.ObjectBrush.show_x_ray
+ self.ObjectBrush_DT = self.ObjectBrush.display_type
+ self.XRay = self.ObjectBrush.show_in_front
# Test if flat object
z = self.ObjectBrush.data.vertices[0].co.z
ErrorMarge = 0.01
@@ -3363,7 +3363,7 @@ class Carver(bpy.types.Operator):
bpy.ops.object.delete(use_global=False)
else:
if self.ObjectMode:
- self.ObjectBrush.draw_type = self.ObjectBrush_DT
+ self.ObjectBrush.display_type = self.ObjectBrush_DT
if len(context.selected_objects) > 0:
bpy.ops.object.select_all(action='TOGGLE')
@@ -3392,9 +3392,9 @@ class Carver(bpy.types.Operator):
# If object has children, set "Wire" draw type
if self.ObjectBrush is not None:
if len(self.ObjectBrush.children) > 0:
- self.ObjectBrush.draw_type = "WIRE"
+ self.ObjectBrush.display_type = "WIRE"
if self.ProfileMode:
- self.ProfileBrush.draw_type = "WIRE"
+ self.ProfileBrush.display_type = "WIRE"
if bLocalView:
bpy.ops.view3d.localview()
diff --git a/mesh_custom_normals_tools.py b/mesh_custom_normals_tools.py
deleted file mode 100644
index 462b7609..00000000
--- a/mesh_custom_normals_tools.py
+++ /dev/null
@@ -1,90 +0,0 @@
-# ***** BEGIN GPL LICENSE BLOCK *****
-#
-#
-# This program is free software; you can redistribute it and/or
-# modify it under the terms of the GNU General Public License
-# as published by the Free Software Foundation; either version 2
-# of the License, or (at your option) any later version.
-#
-# This program 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 General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software Foundation,
-# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-#
-# ***** END GPL LICENCE BLOCK *****
-
-bl_info = {
- "name": "Custom Normals Tools",
- "author": "Bastien Montagne (mont29)",
- "version": (0, 0, 1),
- "blender": (2, 75, 0),
- "location": "3DView > Tools",
- "description": "Various tools/helpers for custom normals",
- "warning": "",
- "support": 'OFFICIAL',
- "category": "Mesh",
-}
-
-
-import bpy
-
-
-class MESH_OT_flip_custom_normals(bpy.types.Operator):
- """Flip active mesh's normals, including custom ones (only in Object mode)"""
- bl_idname = "mesh.flip_custom_normals"
- bl_label = "Flip Custom Normals"
- bl_options = {'UNDO'}
-
- @classmethod
- def poll(cls, context):
- return context.object and context.object.type == 'MESH' and context.object.mode == 'OBJECT'
-
- def execute(self, context):
- me = context.object.data
-
- if me.has_custom_normals:
- me.calc_normals_split()
- clnors = [0.0] * 3 * len(me.loops)
- me.loops.foreach_get("normal", clnors)
-
- bpy.ops.object.mode_set(mode='EDIT')
- bpy.ops.mesh.select_all(action='SELECT')
- bpy.ops.mesh.flip_normals()
- bpy.ops.object.mode_set(mode='OBJECT')
-
- me = context.object.data
- if me.has_custom_normals:
- clnors[:] = list(zip(*[(-n for n in clnors)] * 3))
- # We also have to take in account that the winding was reverted...
- for p in me.polygons:
- ls = p.loop_start + 1
- le = ls + p.loop_total - 1
- clnors[ls:le] = reversed(clnors[ls:le])
- me.normals_split_custom_set(clnors)
-
- context.scene.update()
- return {'FINISHED'}
-
-
-def flip_custom_normals_draw_func(self, context):
- if isinstance(self, bpy.types.Panel):
- self.layout.label("Custom Normal Tools:")
- self.layout.operator(MESH_OT_flip_custom_normals.bl_idname)
-
-
-def register():
- bpy.utils.register_module(__name__)
- bpy.types.VIEW3D_PT_tools_object.append(flip_custom_normals_draw_func)
-
-
-def unregister():
- bpy.types.VIEW3D_PT_tools_object.remove(flip_custom_normals_draw_func)
- bpy.utils.unregister_module(__name__)
-
-
-if __name__ == "__main__":
- register()
diff --git a/mesh_extra_tools/face_inset_fillet.py b/mesh_extra_tools/face_inset_fillet.py
index 2f46115d..eb3e34c2 100644
--- a/mesh_extra_tools/face_inset_fillet.py
+++ b/mesh_extra_tools/face_inset_fillet.py
@@ -62,7 +62,7 @@ def face_inset_fillet(bme, face_index_list, inset_amount, distance,
bme.faces.ensure_lookup_table()
# loops through the faces...
f = bme.faces[faceindex]
- f.select_set(0)
+ f.select_set(False)
list_del.append(f)
f.normal_update()
vertex_index_list = [v.index for v in f.verts]
diff --git a/mesh_extra_tools/mesh_extrude_and_reshape.py b/mesh_extra_tools/mesh_extrude_and_reshape.py
index f4eef683..a7ca0fdd 100644
--- a/mesh_extra_tools/mesh_extrude_and_reshape.py
+++ b/mesh_extra_tools/mesh_extrude_and_reshape.py
@@ -304,7 +304,7 @@ class Extrude_and_Reshape(Operator):
nf = bmesh.utils.face_split(f, v1, v2)
# sp_faces2.update({f, nf[0]})
- bmesh.update_edit_mesh(self.mesh, tessface=True, destructive=True)
+ bmesh.update_edit_mesh(self.mesh, loop_triangles=True, destructive=True)
return {'FINISHED'}
if self.cancel:
return {'FINISHED'}
@@ -345,7 +345,7 @@ class Extrude_and_Reshape(Operator):
dfaces = bmesh.ops.dissolve_edges(
self.bm, edges=geom, use_verts=True, use_face_split=False
)
- bmesh.update_edit_mesh(self.mesh, tessface=True, destructive=True)
+ bmesh.update_edit_mesh(self.mesh, loop_triangles=True, destructive=True)
bpy.ops.transform.translate(
'INVOKE_DEFAULT', constraint_axis=(False, False, True),
constraint_orientation='NORMAL', release_confirm=True
diff --git a/mesh_extra_tools/mesh_fastloop.py b/mesh_extra_tools/mesh_fastloop.py
index 61e9b0d2..0bfcc405 100644
--- a/mesh_extra_tools/mesh_fastloop.py
+++ b/mesh_extra_tools/mesh_fastloop.py
@@ -79,7 +79,7 @@ class OBJECT_OT_FastLoop(Operator):
def modal(self, context, event):
if event.type == 'ESC':
- context.area.header_text_set("")
+ context.area.header_text_set(None)
return {'CANCELLED'}
elif event.type == 'LEFTMOUSE' and event.value == 'RELEASE':
diff --git a/mesh_extra_tools/mesh_filletplus.py b/mesh_extra_tools/mesh_filletplus.py
index 442adb54..f3c9cbdf 100644
--- a/mesh_extra_tools/mesh_filletplus.py
+++ b/mesh_extra_tools/mesh_filletplus.py
@@ -201,7 +201,7 @@ def fillets(list_0, startv, vertlist, face, adj, n, out, flip, radius):
bm.edges.index_update()
bm.faces.index_update()
- me.update(calc_edges=True, calc_tessface=True)
+ me.update(calc_edges=True, calc_loop_triangles=True)
bmesh.ops.recalc_face_normals(bm, faces=bm.faces)
except Exception as e:
diff --git a/mesh_extra_tools/mesh_offset_edges.py b/mesh_extra_tools/mesh_offset_edges.py
index 19eccd3b..e92c5430 100644
--- a/mesh_extra_tools/mesh_offset_edges.py
+++ b/mesh_extra_tools/mesh_offset_edges.py
@@ -786,7 +786,7 @@ class OffsetEdges(Operator):
def restore_original_and_free(self, context):
self.caches_valid = False # Make caches invalid
- context.area.header_text_set("")
+ context.area.header_text_set(None)
me = context.edit_object.data
bpy.ops.object.mode_set(mode="OBJECT")
@@ -794,7 +794,7 @@ class OffsetEdges(Operator):
bpy.ops.object.mode_set(mode="EDIT")
self._bm_orig.free()
- context.area.header_text_set("")
+ context.area.header_text_set(None)
def invoke(self, context, event):
# In edit mode
diff --git a/mesh_extra_tools/mesh_vertex_chamfer.py b/mesh_extra_tools/mesh_vertex_chamfer.py
index 62383c38..a80ba270 100644
--- a/mesh_extra_tools/mesh_vertex_chamfer.py
+++ b/mesh_extra_tools/mesh_vertex_chamfer.py
@@ -144,7 +144,7 @@ class VertexChamfer(Operator):
else:
v.co += displace * v.normal
- me.calc_tessface()
+ me.calc_loop_triangles()
return {'FINISHED'}
diff --git a/mesh_f2.py b/mesh_f2.py
index 88f503ea..9af421f7 100644
--- a/mesh_f2.py
+++ b/mesh_f2.py
@@ -22,8 +22,8 @@ bl_info = {
"name": "F2",
"author": "Bart Crouch, Alexander Nedovizin, Paul Kotelevets "
"(concept design)",
- "version": (1, 7, 2),
- "blender": (2, 70, 0),
+ "version": (1, 7, 3),
+ "blender": (2, 80, 0),
"location": "Editmode > F",
"warning": "",
"description": "Extends the 'Make Edge/Face' functionality",
@@ -40,31 +40,6 @@ import mathutils
from bpy_extras import view3d_utils
-# returns a custom data layer of the UV map, or None
-def get_uv_layer(ob, bm, mat_index):
- uv = None
- uv_layer = None
- if not ob.material_slots:
- me = ob.data
- if me.uv_textures:
- uv = me.uv_textures.active.name
- else:
- mat = ob.material_slots[mat_index].material
- if mat is not None:
- slot = mat.texture_slots[mat.active_texture_index]
- if slot and slot.uv_layer:
- uv = slot.uv_layer
- else:
- for tex_slot in mat.texture_slots:
- if tex_slot and tex_slot.uv_layer:
- uv = tex_slot.uv_layer
- break
- if uv:
- uv_layer = bm.loops.layers.uv.get(uv)
-
- return(uv_layer)
-
-
# create a face from a single selected edge
def quad_from_edge(bm, edge_sel, context, event):
ob = context.active_object
@@ -87,7 +62,7 @@ def quad_from_edge(bm, edge_sel, context, event):
min_dist = False
for edge in edges:
vert = [vert for vert in edge.verts if not vert.select][0]
- world_pos = ob.matrix_world * vert.co.copy()
+ world_pos = ob.matrix_world @ vert.co.copy()
screen_pos = view3d_utils.location_3d_to_region_2d(region,
region_3d, world_pos)
dist = (mouse_pos - screen_pos).length
@@ -177,8 +152,7 @@ def quad_from_edge(bm, edge_sel, context, event):
if __name__ != '__main__':
addon_prefs = context.user_preferences.addons[__name__].preferences
if addon_prefs.adjustuv:
- uv_layer = get_uv_layer(ob, bm, mat_index)
- if uv_layer:
+ for (key, uv_layer) in bm.loops.layers.uv.items():
uv_ori = {}
for vert in [v1, v2, v3, v4]:
for loop in vert.link_loops:
@@ -214,7 +188,7 @@ def quad_from_vertex(bm, vert_sel, context, event):
mid_other = (other_verts[0].co.copy() + other_verts[1].co.copy()) \
/ 2
new_pos = 2 * (mid_other - vert_sel.co.copy()) + vert_sel.co.copy()
- world_pos = ob.matrix_world * new_pos
+ world_pos = ob.matrix_world @ new_pos
screen_pos = view3d_utils.location_3d_to_region_2d(region, region_3d,
world_pos)
dist = (mouse_pos - screen_pos).length
@@ -277,8 +251,7 @@ def quad_from_vertex(bm, vert_sel, context, event):
if __name__ != '__main__':
addon_prefs = context.user_preferences.addons[__name__].preferences
if addon_prefs.adjustuv:
- uv_layer = get_uv_layer(ob, bm, mat_index)
- if uv_layer:
+ for (key, uv_layer) in bm.loops.layers.uv.items():
uv_others = {}
uv_sel = None
uv_new = None
@@ -317,11 +290,11 @@ def quad_from_vertex(bm, vert_sel, context, event):
# autograb preference in addons panel
class F2AddonPreferences(bpy.types.AddonPreferences):
bl_idname = __name__
- adjustuv = bpy.props.BoolProperty(
+ adjustuv: bpy.props.BoolProperty(
name = "Adjust UV",
description = "Automatically update UV unwrapping",
default = True)
- autograb = bpy.props.BoolProperty(
+ autograb: bpy.props.BoolProperty(
name = "Auto Grab",
description = "Automatically puts a newly created vertex in grab mode",
default = False)
diff --git a/mesh_looptools.py b/mesh_looptools.py
index 62417f21..25c63f9e 100644
--- a/mesh_looptools.py
+++ b/mesh_looptools.py
@@ -20,7 +20,7 @@ bl_info = {
"name": "LoopTools",
"author": "Bart Crouch",
"version": (4, 6, 7),
- "blender": (2, 72, 2),
+ "blender": (2, 80, 0),
"location": "View3D > Toolbar and View3D > Specials (W-key)",
"warning": "",
"description": "Mesh modelling toolkit. Several tools to aid modelling",
@@ -110,10 +110,11 @@ loops, derived, mapping):
del looptools_cache[tool]
# prepare values to be saved to cache
input = [v.index for v in bm.verts if v.select and not v.hide]
- modifiers = [mod.name for mod in object.modifiers if mod.show_viewport and
- mod.type == 'MIRROR']
+ modifiers = [mod.name for mod in object.modifiers if mod.show_viewport
+ and mod.type == 'MIRROR']
# update cache
- looptools_cache[tool] = {"input": input, "object": object.name,
+ looptools_cache[tool] = {
+ "input": input, "object": object.name,
"input_method": input_method, "boundaries": boundaries,
"single_loops": single_loops, "loops": loops,
"derived": derived, "mapping": mapping, "modifiers": modifiers}
@@ -276,7 +277,7 @@ def calculate_plane(bm_mod, loop, method="best_fit", object=False):
vec2 = mathutils.Vector((1.0, 1.0, 1.0))
for i in range(itermax):
vec = vec2
- vec2 = mat * vec
+ vec2 = mat @ vec
if vec2.length != 0:
vec2 /= vec2.length
if vec2 == vec:
@@ -425,9 +426,9 @@ def face_edgekeys(face):
# calculate input loops
-def get_connected_input(object, bm, scene, input):
+def get_connected_input(object, bm, input):
# get mesh with modifiers applied
- derived, bm_mod = get_derived_bmesh(object, bm, scene)
+ derived, bm_mod = get_derived_bmesh(object, bm)
# calculate selected loops
edge_keys = [edgekey(edge) for edge in bm_mod.edges if edge.select and not edge.hide]
@@ -514,7 +515,7 @@ def get_connected_selections(edge_keys):
# get the derived mesh data, if there is a mirror modifier
-def get_derived_bmesh(object, bm, scene):
+def get_derived_bmesh(object, bm):
# check for mirror modifiers
if 'MIRROR' in [mod.type for mod in object.modifiers if mod.show_viewport]:
derived = True
@@ -525,7 +526,7 @@ def get_derived_bmesh(object, bm, scene):
mod.show_viewport = False
# get derived mesh
bm_mod = bmesh.new()
- mesh_mod = object.to_mesh(scene, True, 'PREVIEW')
+ mesh_mod = object.to_mesh(bpy.context.depsgraph, True)
bm_mod.from_mesh(mesh_mod)
bpy.context.blend_data.meshes.remove(mesh_mod)
# re-enable other modifiers
@@ -831,7 +832,7 @@ def terminate(global_undo):
# update editmesh cached data
obj = bpy.context.active_object
if obj.mode == 'EDIT':
- bmesh.update_edit_mesh(obj.data, tessface=True, destructive=True)
+ bmesh.update_edit_mesh(obj.data, loop_triangles=True, destructive=True)
bpy.context.user_preferences.edit.use_global_undo = global_undo
@@ -1043,11 +1044,11 @@ def bridge_calculate_lines(bm, loops, mode, twist, reverse):
itermax = 500
iter = 0
vec = mathutils.Vector((1.0, 1.0, 1.0))
- vec2 = (mat * vec) / (mat * vec).length
+ vec2 = (mat @ vec) / (mat @ vec).length
while vec != vec2 and iter < itermax:
iter += 1
vec = vec2
- vec2 = mat * vec
+ vec2 = mat @ vec
if vec2.length != 0:
vec2 /= vec2.length
if vec2.length == 0:
@@ -1079,7 +1080,7 @@ def bridge_calculate_lines(bm, loops, mode, twist, reverse):
# match start vertex of loop1 with loop2
target_vector = bm.verts[loop2[0]].co - center2
- dif_angles = [[(rotation_matrix * (bm.verts[vertex].co - center1)
+ dif_angles = [[(rotation_matrix @ (bm.verts[vertex].co - center1)
).angle(target_vector, 0), False, i] for
i, vertex in enumerate(loop1)]
dif_angles.sort()
@@ -1161,7 +1162,7 @@ def bridge_calculate_lines(bm, loops, mode, twist, reverse):
shifting = False
break
to_last, to_first = [
- (rotation_matrix * (bm.verts[loop1[-1]].co - center1)).angle(
+ (rotation_matrix @ (bm.verts[loop1[-1]].co - center1)).angle(
(bm.verts[loop2[i]].co - center2), 0) for i in [-1, 0]
]
if to_first < to_last:
@@ -1802,7 +1803,7 @@ def circle_calculate_best_fit(locs_2d):
jmat2.invert()
except:
pass
- dx0, dy0, dr = jmat2 * k2
+ dx0, dy0, dr = jmat2 @ k2
x0 += dx0
y0 += dy0
r += dr
@@ -1980,9 +1981,9 @@ def circle_flatten_singles(bm_mod, com, p, q, normal, single_loop):
# calculate input loops
-def circle_get_input(object, bm, scene):
+def circle_get_input(object, bm):
# get mesh with modifiers applied
- derived, bm_mod = get_derived_bmesh(object, bm, scene)
+ derived, bm_mod = get_derived_bmesh(object, bm)
# create list of edge-keys based on selection state
faces = False
@@ -2317,9 +2318,9 @@ def curve_cut_boundaries(bm_mod, loops):
# calculate input loops
-def curve_get_input(object, bm, boundaries, scene):
+def curve_get_input(object, bm, boundaries):
# get mesh with modifiers applied
- derived, bm_mod = get_derived_bmesh(object, bm, scene)
+ derived, bm_mod = get_derived_bmesh(object, bm)
# vertices that still need a loop to run through it
verts_unsorted = [
@@ -2610,7 +2611,7 @@ def gstretch_align_pairs(ls_pairs, object, bm_mod, method):
else:
relative_distance = relative_lengths[i]
- loc1 = object.matrix_world * bm_mod.verts[v_index].co
+ loc1 = object.matrix_world @ bm_mod.verts[v_index].co
loc2, stroke_lengths_cache = gstretch_eval_stroke(stroke,
relative_distance, stroke_lengths_cache)
total_distance += (loc2 - loc1).length
@@ -2683,7 +2684,7 @@ def gstretch_calculate_verts(loop, stroke, object, bm_mod, method):
relative_distance = relative_lengths[i]
loc, stroke_lengths_cache = gstretch_eval_stroke(stroke,
relative_distance, stroke_lengths_cache)
- loc = matrix_inverse * loc
+ loc = matrix_inverse @ loc
move.append([v_index, loc])
return(move)
@@ -2828,8 +2829,8 @@ def gstretch_eval_stroke(stroke, distance, stroke_lengths_cache=False):
def gstretch_get_fake_strokes(object, bm_mod, loops):
strokes = []
for loop in loops:
- p1 = object.matrix_world * bm_mod.verts[loop[0][0]].co
- p2 = object.matrix_world * bm_mod.verts[loop[0][-1]].co
+ p1 = object.matrix_world @ bm_mod.verts[loop[0][0]].co
+ p2 = object.matrix_world @ bm_mod.verts[loop[0][-1]].co
strokes.append(gstretch_fake_stroke([p1, p2]))
return(strokes)
@@ -2866,7 +2867,7 @@ def gstretch_match_loops_strokes(loops, strokes, object, bm_mod):
for v_index in loop[0]:
center += bm_mod.verts[v_index].co
center /= len(loop[0])
- center = object.matrix_world * center
+ center = object.matrix_world @ center
loop_centers.append([center, loop])
# calculate stroke centers
@@ -3189,14 +3190,14 @@ class Bridge(Operator):
bl_description = "Bridge two, or loft several, loops of vertices"
bl_options = {'REGISTER', 'UNDO'}
- cubic_strength = FloatProperty(
+ cubic_strength: FloatProperty(
name="Strength",
description="Higher strength results in more fluid curves",
default=1.0,
soft_min=-3.0,
soft_max=3.0
)
- interpolation = EnumProperty(
+ interpolation: EnumProperty(
name="Interpolation mode",
items=(('cubic', "Cubic", "Gives curved results"),
('linear', "Linear", "Basic, fast, straight interpolation")),
@@ -3204,18 +3205,18 @@ class Bridge(Operator):
"segments",
default='cubic'
)
- loft = BoolProperty(
+ loft: BoolProperty(
name="Loft",
description="Loft multiple loops, instead of considering them as "
"a multi-input for bridging",
default=False
)
- loft_loop = BoolProperty(
+ loft_loop: BoolProperty(
name="Loop",
description="Connect the first and the last loop with each other",
default=False
)
- min_width = IntProperty(
+ min_width: IntProperty(
name="Minimum width",
description="Segments with an edge smaller than this are merged "
"(compared to base edge)",
@@ -3224,32 +3225,32 @@ class Bridge(Operator):
max=100,
subtype='PERCENTAGE'
)
- mode = EnumProperty(
+ mode: EnumProperty(
name="Mode",
items=(('basic', "Basic", "Fast algorithm"),
('shortest', "Shortest edge", "Slower algorithm with better vertex matching")),
description="Algorithm used for bridging",
default='shortest'
)
- remove_faces = BoolProperty(
+ remove_faces: BoolProperty(
name="Remove faces",
description="Remove faces that are internal after bridging",
default=True
)
- reverse = BoolProperty(
+ reverse: BoolProperty(
name="Reverse",
description="Manually override the direction in which the loops "
"are bridged. Only use if the tool gives the wrong result",
default=False
)
- segments = IntProperty(
+ segments: IntProperty(
name="Segments",
description="Number of segments used to bridge the gap (0=automatic)",
default=1,
min=0,
soft_max=20
)
- twist = IntProperty(
+ twist: IntProperty(
name="Twist",
description="Twist what vertices are connected to each other",
default=0
@@ -3367,7 +3368,7 @@ class Bridge(Operator):
if self.remove_faces and old_selected_faces:
bridge_remove_internal_faces(bm, old_selected_faces)
# make sure normals are facing outside
- bmesh.update_edit_mesh(object.data, tessface=False,
+ bmesh.update_edit_mesh(object.data, loop_triangles=False,
destructive=True)
bpy.ops.mesh.normals_make_consistent()
@@ -3384,24 +3385,24 @@ class Circle(Operator):
bl_description = "Move selected vertices into a circle shape"
bl_options = {'REGISTER', 'UNDO'}
- custom_radius = BoolProperty(
+ custom_radius: BoolProperty(
name="Radius",
description="Force a custom radius",
default=False
)
- fit = EnumProperty(
+ fit: EnumProperty(
name="Method",
items=(("best", "Best fit", "Non-linear least squares"),
("inside", "Fit inside", "Only move vertices towards the center")),
description="Method used for fitting a circle to the vertices",
default='best'
)
- flatten = BoolProperty(
+ flatten: BoolProperty(
name="Flatten",
description="Flatten the circle, instead of projecting it on the mesh",
default=True
)
- influence = FloatProperty(
+ influence: FloatProperty(
name="Influence",
description="Force of the tool",
default=100.0,
@@ -3410,28 +3411,28 @@ class Circle(Operator):
precision=1,
subtype='PERCENTAGE'
)
- lock_x = BoolProperty(
+ lock_x: BoolProperty(
name="Lock X",
description="Lock editing of the x-coordinate",
default=False
)
- lock_y = BoolProperty(
+ lock_y: BoolProperty(
name="Lock Y",
description="Lock editing of the y-coordinate",
default=False
)
- lock_z = BoolProperty(name="Lock Z",
+ lock_z: BoolProperty(name="Lock Z",
description="Lock editing of the z-coordinate",
default=False
)
- radius = FloatProperty(
+ radius: FloatProperty(
name="Radius",
description="Custom radius for circle",
default=1.0,
min=0.0,
soft_max=1000.0
)
- regular = BoolProperty(
+ regular: BoolProperty(
name="Regular",
description="Distribute vertices at constant distances along the circle",
default=True
@@ -3487,11 +3488,11 @@ class Circle(Operator):
cached, single_loops, loops, derived, mapping = cache_read("Circle",
object, bm, False, False)
if cached:
- derived, bm_mod = get_derived_bmesh(object, bm, context.scene)
+ derived, bm_mod = get_derived_bmesh(object, bm)
else:
# find loops
derived, bm_mod, single_vertices, single_loops, loops = \
- circle_get_input(object, bm, context.scene)
+ circle_get_input(object, bm)
mapping = get_mapping(derived, bm, bm_mod, single_vertices,
False, loops)
single_loops, loops = circle_check_loops(single_loops, loops,
@@ -3557,12 +3558,12 @@ class Curve(Operator):
bl_description = "Turn a loop into a smooth curve"
bl_options = {'REGISTER', 'UNDO'}
- boundaries = BoolProperty(
+ boundaries: BoolProperty(
name="Boundaries",
description="Limit the tool to work within the boundaries of the selected vertices",
default=False
)
- influence = FloatProperty(
+ influence: FloatProperty(
name="Influence",
description="Force of the tool",
default=100.0,
@@ -3571,34 +3572,34 @@ class Curve(Operator):
precision=1,
subtype='PERCENTAGE'
)
- interpolation = EnumProperty(
+ interpolation: EnumProperty(
name="Interpolation",
items=(("cubic", "Cubic", "Natural cubic spline, smooth results"),
("linear", "Linear", "Simple and fast linear algorithm")),
description="Algorithm used for interpolation",
default='cubic'
)
- lock_x = BoolProperty(
+ lock_x: BoolProperty(
name="Lock X",
description="Lock editing of the x-coordinate",
default=False
)
- lock_y = BoolProperty(
+ lock_y: BoolProperty(
name="Lock Y",
description="Lock editing of the y-coordinate",
default=False
)
- lock_z = BoolProperty(
+ lock_z: BoolProperty(
name="Lock Z",
description="Lock editing of the z-coordinate",
default=False
)
- regular = BoolProperty(
+ regular: BoolProperty(
name="Regular",
description="Distribute vertices at constant distances along the curve",
default=True
)
- restriction = EnumProperty(
+ restriction: EnumProperty(
name="Restriction",
items=(("none", "None", "No restrictions on vertex movement"),
("extrude", "Extrude only", "Only allow extrusions (no indentations)"),
@@ -3651,11 +3652,10 @@ class Curve(Operator):
cached, single_loops, loops, derived, mapping = cache_read("Curve",
object, bm, False, self.boundaries)
if cached:
- derived, bm_mod = get_derived_bmesh(object, bm, context.scene)
+ derived, bm_mod = get_derived_bmesh(object, bm)
else:
# find loops
- derived, bm_mod, loops = curve_get_input(object, bm,
- self.boundaries, context.scene)
+ derived, bm_mod, loops = curve_get_input(object, bm, self.boundaries)
mapping = get_mapping(derived, bm, bm_mod, False, True, loops)
loops = check_loops(loops, mapping, bm_mod)
verts_selected = [
@@ -3702,7 +3702,7 @@ class Flatten(Operator):
bl_description = "Flatten vertices on a best-fitting plane"
bl_options = {'REGISTER', 'UNDO'}
- influence = FloatProperty(
+ influence: FloatProperty(
name="Influence",
description="Force of the tool",
default=100.0,
@@ -3711,21 +3711,21 @@ class Flatten(Operator):
precision=1,
subtype='PERCENTAGE'
)
- lock_x = BoolProperty(
+ lock_x: BoolProperty(
name="Lock X",
description="Lock editing of the x-coordinate",
default=False
)
- lock_y = BoolProperty(
+ lock_y: BoolProperty(
name="Lock Y",
description="Lock editing of the y-coordinate",
default=False
)
- lock_z = BoolProperty(name="Lock Z",
+ lock_z: BoolProperty(name="Lock Z",
description="Lock editing of the z-coordinate",
default=False
)
- plane = EnumProperty(
+ plane: EnumProperty(
name="Plane",
items=(("best_fit", "Best fit", "Calculate a best fitting plane"),
("normal", "Normal", "Derive plane from averaging vertex normals"),
@@ -3733,7 +3733,7 @@ class Flatten(Operator):
description="Plane on which vertices are flattened",
default='best_fit'
)
- restriction = EnumProperty(
+ restriction: EnumProperty(
name="Restriction",
items=(("none", "None", "No restrictions on vertex movement"),
("bounding_box", "Bounding box", "Vertices are restricted to "
@@ -3841,7 +3841,7 @@ class GStretch(Operator):
bl_description = "Stretch selected vertices to Grease Pencil stroke"
bl_options = {'REGISTER', 'UNDO'}
- conversion = EnumProperty(
+ conversion: EnumProperty(
name="Conversion",
items=(("distance", "Distance", "Set the distance between vertices "
"of the converted grease pencil stroke"),
@@ -3856,7 +3856,7 @@ class GStretch(Operator):
"use this simplification method",
default='limit_vertices'
)
- conversion_distance = FloatProperty(
+ conversion_distance: FloatProperty(
name="Distance",
description="Absolute distance between vertices along the converted "
"grease pencil stroke",
@@ -3865,7 +3865,7 @@ class GStretch(Operator):
soft_min=0.01,
soft_max=100
)
- conversion_max = IntProperty(
+ conversion_max: IntProperty(
name="Max Vertices",
description="Maximum number of vertices grease pencil strokes will "
"have, when they are converted to geomtery",
@@ -3874,7 +3874,7 @@ class GStretch(Operator):
soft_max=500,
update=gstretch_update_min
)
- conversion_min = IntProperty(
+ conversion_min: IntProperty(
name="Min Vertices",
description="Minimum number of vertices grease pencil strokes will "
"have, when they are converted to geomtery",
@@ -3883,7 +3883,7 @@ class GStretch(Operator):
soft_max=500,
update=gstretch_update_max
)
- conversion_vertices = IntProperty(
+ conversion_vertices: IntProperty(
name="Vertices",
description="Number of vertices grease pencil strokes will "
"have, when they are converted to geometry. If strokes have less "
@@ -3892,13 +3892,13 @@ class GStretch(Operator):
min=3,
soft_max=500
)
- delete_strokes = BoolProperty(
+ delete_strokes: BoolProperty(
name="Delete strokes",
description="Remove Grease Pencil strokes if they have been used "
"for Gstretch. WARNING: DOES NOT SUPPORT UNDO",
default=False
)
- influence = FloatProperty(
+ influence: FloatProperty(
name="Influence",
description="Force of the tool",
default=100.0,
@@ -3907,22 +3907,22 @@ class GStretch(Operator):
precision=1,
subtype='PERCENTAGE'
)
- lock_x = BoolProperty(
+ lock_x: BoolProperty(
name="Lock X",
description="Lock editing of the x-coordinate",
default=False
)
- lock_y = BoolProperty(
+ lock_y: BoolProperty(
name="Lock Y",
description="Lock editing of the y-coordinate",
default=False
)
- lock_z = BoolProperty(
+ lock_z: BoolProperty(
name="Lock Z",
description="Lock editing of the z-coordinate",
default=False
)
- method = EnumProperty(
+ method: EnumProperty(
name="Method",
items=(("project", "Project", "Project vertices onto the stroke, "
"using vertex normals and connected edges"),
@@ -4010,13 +4010,12 @@ class GStretch(Operator):
bm_mod.faces.ensure_lookup_table()
strokes = gstretch_get_fake_strokes(object, bm_mod, loops)
if not straightening:
- derived, bm_mod = get_derived_bmesh(object, bm, context.scene)
+ derived, bm_mod = get_derived_bmesh(object, bm)
else:
# get loops and strokes
if get_grease_pencil(object, context):
# find loops
- derived, bm_mod, loops = get_connected_input(object, bm,
- context.scene, input='selected')
+ derived, bm_mod, loops = get_connected_input(object, bm, input='selected')
mapping = get_mapping(derived, bm, bm_mod, False, False, loops)
loops = check_loops(loops, mapping, bm_mod)
# get strokes
@@ -4086,7 +4085,7 @@ class GStretch(Operator):
lock = [self.lock_x, self.lock_y, self.lock_z]
else:
lock = False
- bmesh.update_edit_mesh(object.data, tessface=True, destructive=True)
+ bmesh.update_edit_mesh(object.data, loop_triangles=True, destructive=True)
move_verts(object, bm, mapping, move, lock, self.influence)
# cleaning up
@@ -4104,7 +4103,7 @@ class Relax(Operator):
bl_description = "Relax the loop, so it is smoother"
bl_options = {'REGISTER', 'UNDO'}
- input = EnumProperty(
+ input: EnumProperty(
name="Input",
items=(("all", "Parallel (all)", "Also use non-selected "
"parallel loops as input"),
@@ -4112,14 +4111,14 @@ class Relax(Operator):
description="Loops that are relaxed",
default='selected'
)
- interpolation = EnumProperty(
+ interpolation: EnumProperty(
name="Interpolation",
items=(("cubic", "Cubic", "Natural cubic spline, smooth results"),
("linear", "Linear", "Simple and fast linear algorithm")),
description="Algorithm used for interpolation",
default='cubic'
)
- iterations = EnumProperty(
+ iterations: EnumProperty(
name="Iterations",
items=(("1", "1", "One"),
("3", "3", "Three"),
@@ -4129,7 +4128,7 @@ class Relax(Operator):
description="Number of times the loop is relaxed",
default="1"
)
- regular = BoolProperty(
+ regular: BoolProperty(
name="Regular",
description="Distribute vertices at constant distances along the loop",
default=True
@@ -4162,11 +4161,10 @@ class Relax(Operator):
cached, single_loops, loops, derived, mapping = cache_read("Relax",
object, bm, self.input, False)
if cached:
- derived, bm_mod = get_derived_bmesh(object, bm, context.scene)
+ derived, bm_mod = get_derived_bmesh(object, bm)
else:
# find loops
- derived, bm_mod, loops = get_connected_input(object, bm,
- context.scene, self.input)
+ derived, bm_mod, loops = get_connected_input(object, bm, self.input)
mapping = get_mapping(derived, bm, bm_mod, False, False, loops)
loops = check_loops(loops, mapping, bm_mod)
knots, points = relax_calculate_knots(loops)
@@ -4203,7 +4201,7 @@ class Space(Operator):
bl_description = "Space the vertices in a regular distrubtion on the loop"
bl_options = {'REGISTER', 'UNDO'}
- influence = FloatProperty(
+ influence: FloatProperty(
name="Influence",
description="Force of the tool",
default=100.0,
@@ -4212,7 +4210,7 @@ class Space(Operator):
precision=1,
subtype='PERCENTAGE'
)
- input = EnumProperty(
+ input: EnumProperty(
name="Input",
items=(("all", "Parallel (all)", "Also use non-selected "
"parallel loops as input"),
@@ -4220,24 +4218,24 @@ class Space(Operator):
description="Loops that are spaced",
default='selected'
)
- interpolation = EnumProperty(
+ interpolation: EnumProperty(
name="Interpolation",
items=(("cubic", "Cubic", "Natural cubic spline, smooth results"),
("linear", "Linear", "Vertices are projected on existing edges")),
description="Algorithm used for interpolation",
default='cubic'
)
- lock_x = BoolProperty(
+ lock_x: BoolProperty(
name="Lock X",
description="Lock editing of the x-coordinate",
default=False
)
- lock_y = BoolProperty(
+ lock_y: BoolProperty(
name="Lock Y",
description="Lock editing of the y-coordinate",
default=False
)
- lock_z = BoolProperty(
+ lock_z: BoolProperty(
name="Lock Z",
description="Lock editing of the z-coordinate",
default=False
@@ -4285,11 +4283,10 @@ class Space(Operator):
cached, single_loops, loops, derived, mapping = cache_read("Space",
object, bm, self.input, False)
if cached:
- derived, bm_mod = get_derived_bmesh(object, bm, context.scene)
+ derived, bm_mod = get_derived_bmesh(object, bm)
else:
# find loops
- derived, bm_mod, loops = get_connected_input(object, bm,
- context.scene, self.input)
+ derived, bm_mod, loops = get_connected_input(object, bm, self.input)
mapping = get_mapping(derived, bm, bm_mod, False, False, loops)
loops = check_loops(loops, mapping, bm_mod)
@@ -4347,8 +4344,8 @@ class VIEW3D_MT_edit_mesh_looptools(Menu):
# panel containing all tools
class VIEW3D_PT_tools_looptools(Panel):
bl_space_type = 'VIEW_3D'
- bl_region_type = 'TOOLS'
- bl_category = 'Tools'
+ bl_region_type = 'UI'
+ bl_category = 'View'
bl_context = "mesh_edit"
bl_label = "LoopTools"
bl_options = {'DEFAULT_CLOSED'}
@@ -4359,7 +4356,7 @@ class VIEW3D_PT_tools_looptools(Panel):
lt = context.window_manager.looptools
# bridge - first line
- split = col.split(percentage=0.15, align=True)
+ split = col.split(factor=0.15, align=True)
if lt.display_bridge:
split.prop(lt, "display_bridge", text="", icon='DOWNARROW_HLT')
else:
@@ -4395,7 +4392,7 @@ class VIEW3D_PT_tools_looptools(Panel):
row.prop(lt, "bridge_reverse")
# circle - first line
- split = col.split(percentage=0.15, align=True)
+ split = col.split(factor=0.15, align=True)
if lt.display_circle:
split.prop(lt, "display_circle", text="", icon='DOWNARROW_HLT')
else:
@@ -4433,7 +4430,7 @@ class VIEW3D_PT_tools_looptools(Panel):
col_move.prop(lt, "circle_influence")
# curve - first line
- split = col.split(percentage=0.15, align=True)
+ split = col.split(factor=0.15, align=True)
if lt.display_curve:
split.prop(lt, "display_curve", text="", icon='DOWNARROW_HLT')
else:
@@ -4465,7 +4462,7 @@ class VIEW3D_PT_tools_looptools(Panel):
col_move.prop(lt, "curve_influence")
# flatten - first line
- split = col.split(percentage=0.15, align=True)
+ split = col.split(factor=0.15, align=True)
if lt.display_flatten:
split.prop(lt, "display_flatten", text="", icon='DOWNARROW_HLT')
else:
@@ -4495,7 +4492,7 @@ class VIEW3D_PT_tools_looptools(Panel):
col_move.prop(lt, "flatten_influence")
# gstretch - first line
- split = col.split(percentage=0.15, align=True)
+ split = col.split(factor=0.15, align=True)
if lt.display_gstretch:
split.prop(lt, "display_gstretch", text="", icon='DOWNARROW_HLT')
else:
@@ -4536,7 +4533,7 @@ class VIEW3D_PT_tools_looptools(Panel):
box.operator("remove.gp", text="Delete GP Strokes")
# loft - first line
- split = col.split(percentage=0.15, align=True)
+ split = col.split(factor=0.15, align=True)
if lt.display_loft:
split.prop(lt, "display_loft", text="", icon='DOWNARROW_HLT')
else:
@@ -4573,7 +4570,7 @@ class VIEW3D_PT_tools_looptools(Panel):
row.prop(lt, "bridge_reverse")
# relax - first line
- split = col.split(percentage=0.15, align=True)
+ split = col.split(factor=0.15, align=True)
if lt.display_relax:
split.prop(lt, "display_relax", text="", icon='DOWNARROW_HLT')
else:
@@ -4588,7 +4585,7 @@ class VIEW3D_PT_tools_looptools(Panel):
box.prop(lt, "relax_regular")
# space - first line
- split = col.split(percentage=0.15, align=True)
+ split = col.split(factor=0.15, align=True)
if lt.display_space:
split.prop(lt, "display_space", text="", icon='DOWNARROW_HLT')
else:
@@ -4625,74 +4622,74 @@ class LoopToolsProps(PropertyGroup):
bpy.context.window_manager.looptools
"""
# general display properties
- display_bridge = BoolProperty(
+ display_bridge: BoolProperty(
name="Bridge settings",
description="Display settings of the Bridge tool",
default=False
)
- display_circle = BoolProperty(
+ display_circle: BoolProperty(
name="Circle settings",
description="Display settings of the Circle tool",
default=False
)
- display_curve = BoolProperty(
+ display_curve: BoolProperty(
name="Curve settings",
description="Display settings of the Curve tool",
default=False
)
- display_flatten = BoolProperty(
+ display_flatten: BoolProperty(
name="Flatten settings",
description="Display settings of the Flatten tool",
default=False
)
- display_gstretch = BoolProperty(
+ display_gstretch: BoolProperty(
name="Gstretch settings",
description="Display settings of the Gstretch tool",
default=False
)
- display_loft = BoolProperty(
+ display_loft: BoolProperty(
name="Loft settings",
description="Display settings of the Loft tool",
default=False
)
- display_relax = BoolProperty(
+ display_relax: BoolProperty(
name="Relax settings",
description="Display settings of the Relax tool",
default=False
)
- display_space = BoolProperty(
+ display_space: BoolProperty(
name="Space settings",
description="Display settings of the Space tool",
default=False
)
# bridge properties
- bridge_cubic_strength = FloatProperty(
+ bridge_cubic_strength: FloatProperty(
name="Strength",
description="Higher strength results in more fluid curves",
default=1.0,
soft_min=-3.0,
soft_max=3.0
)
- bridge_interpolation = EnumProperty(
+ bridge_interpolation: EnumProperty(
name="Interpolation mode",
items=(('cubic', "Cubic", "Gives curved results"),
('linear', "Linear", "Basic, fast, straight interpolation")),
description="Interpolation mode: algorithm used when creating segments",
default='cubic'
)
- bridge_loft = BoolProperty(
+ bridge_loft: BoolProperty(
name="Loft",
description="Loft multiple loops, instead of considering them as "
"a multi-input for bridging",
default=False
)
- bridge_loft_loop = BoolProperty(
+ bridge_loft_loop: BoolProperty(
name="Loop",
description="Connect the first and the last loop with each other",
default=False
)
- bridge_min_width = IntProperty(
+ bridge_min_width: IntProperty(
name="Minimum width",
description="Segments with an edge smaller than this are merged "
"(compared to base edge)",
@@ -4701,7 +4698,7 @@ class LoopToolsProps(PropertyGroup):
max=100,
subtype='PERCENTAGE'
)
- bridge_mode = EnumProperty(
+ bridge_mode: EnumProperty(
name="Mode",
items=(('basic', "Basic", "Fast algorithm"),
('shortest', "Shortest edge", "Slower algorithm with "
@@ -4709,49 +4706,49 @@ class LoopToolsProps(PropertyGroup):
description="Algorithm used for bridging",
default='shortest'
)
- bridge_remove_faces = BoolProperty(
+ bridge_remove_faces: BoolProperty(
name="Remove faces",
description="Remove faces that are internal after bridging",
default=True
)
- bridge_reverse = BoolProperty(
+ bridge_reverse: BoolProperty(
name="Reverse",
description="Manually override the direction in which the loops "
"are bridged. Only use if the tool gives the wrong result",
default=False
)
- bridge_segments = IntProperty(
+ bridge_segments: IntProperty(
name="Segments",
description="Number of segments used to bridge the gap (0=automatic)",
default=1,
min=0,
soft_max=20
)
- bridge_twist = IntProperty(
+ bridge_twist: IntProperty(
name="Twist",
description="Twist what vertices are connected to each other",
default=0
)
# circle properties
- circle_custom_radius = BoolProperty(
+ circle_custom_radius: BoolProperty(
name="Radius",
description="Force a custom radius",
default=False
)
- circle_fit = EnumProperty(
+ circle_fit: EnumProperty(
name="Method",
items=(("best", "Best fit", "Non-linear least squares"),
("inside", "Fit inside", "Only move vertices towards the center")),
description="Method used for fitting a circle to the vertices",
default='best'
)
- circle_flatten = BoolProperty(
+ circle_flatten: BoolProperty(
name="Flatten",
description="Flatten the circle, instead of projecting it on the mesh",
default=True
)
- circle_influence = FloatProperty(
+ circle_influence: FloatProperty(
name="Influence",
description="Force of the tool",
default=100.0,
@@ -4760,41 +4757,41 @@ class LoopToolsProps(PropertyGroup):
precision=1,
subtype='PERCENTAGE'
)
- circle_lock_x = BoolProperty(
+ circle_lock_x: BoolProperty(
name="Lock X",
description="Lock editing of the x-coordinate",
default=False
)
- circle_lock_y = BoolProperty(
+ circle_lock_y: BoolProperty(
name="Lock Y",
description="Lock editing of the y-coordinate",
default=False
)
- circle_lock_z = BoolProperty(
+ circle_lock_z: BoolProperty(
name="Lock Z",
description="Lock editing of the z-coordinate",
default=False
)
- circle_radius = FloatProperty(
+ circle_radius: FloatProperty(
name="Radius",
description="Custom radius for circle",
default=1.0,
min=0.0,
soft_max=1000.0
)
- circle_regular = BoolProperty(
+ circle_regular: BoolProperty(
name="Regular",
description="Distribute vertices at constant distances along the circle",
default=True
)
# curve properties
- curve_boundaries = BoolProperty(
+ curve_boundaries: BoolProperty(
name="Boundaries",
description="Limit the tool to work within the boundaries of the "
"selected vertices",
default=False
)
- curve_influence = FloatProperty(
+ curve_influence: FloatProperty(
name="Influence",
description="Force of the tool",
default=100.0,
@@ -4803,34 +4800,34 @@ class LoopToolsProps(PropertyGroup):
precision=1,
subtype='PERCENTAGE'
)
- curve_interpolation = EnumProperty(
+ curve_interpolation: EnumProperty(
name="Interpolation",
items=(("cubic", "Cubic", "Natural cubic spline, smooth results"),
("linear", "Linear", "Simple and fast linear algorithm")),
description="Algorithm used for interpolation",
default='cubic'
)
- curve_lock_x = BoolProperty(
+ curve_lock_x: BoolProperty(
name="Lock X",
description="Lock editing of the x-coordinate",
default=False
)
- curve_lock_y = BoolProperty(
+ curve_lock_y: BoolProperty(
name="Lock Y",
description="Lock editing of the y-coordinate",
default=False
)
- curve_lock_z = BoolProperty(
+ curve_lock_z: BoolProperty(
name="Lock Z",
description="Lock editing of the z-coordinate",
default=False
)
- curve_regular = BoolProperty(
+ curve_regular: BoolProperty(
name="Regular",
description="Distribute vertices at constant distances along the curve",
default=True
)
- curve_restriction = EnumProperty(
+ curve_restriction: EnumProperty(
name="Restriction",
items=(("none", "None", "No restrictions on vertex movement"),
("extrude", "Extrude only", "Only allow extrusions (no indentations)"),
@@ -4840,7 +4837,7 @@ class LoopToolsProps(PropertyGroup):
)
# flatten properties
- flatten_influence = FloatProperty(
+ flatten_influence: FloatProperty(
name="Influence",
description="Force of the tool",
default=100.0,
@@ -4849,20 +4846,20 @@ class LoopToolsProps(PropertyGroup):
precision=1,
subtype='PERCENTAGE'
)
- flatten_lock_x = BoolProperty(
+ flatten_lock_x: BoolProperty(
name="Lock X",
description="Lock editing of the x-coordinate",
default=False)
- flatten_lock_y = BoolProperty(name="Lock Y",
+ flatten_lock_y: BoolProperty(name="Lock Y",
description="Lock editing of the y-coordinate",
default=False
)
- flatten_lock_z = BoolProperty(
+ flatten_lock_z: BoolProperty(
name="Lock Z",
description="Lock editing of the z-coordinate",
default=False
)
- flatten_plane = EnumProperty(
+ flatten_plane: EnumProperty(
name="Plane",
items=(("best_fit", "Best fit", "Calculate a best fitting plane"),
("normal", "Normal", "Derive plane from averaging vertex "
@@ -4872,7 +4869,7 @@ class LoopToolsProps(PropertyGroup):
description="Plane on which vertices are flattened",
default='best_fit'
)
- flatten_restriction = EnumProperty(
+ flatten_restriction: EnumProperty(
name="Restriction",
items=(("none", "None", "No restrictions on vertex movement"),
("bounding_box", "Bounding box", "Vertices are restricted to "
@@ -4882,7 +4879,7 @@ class LoopToolsProps(PropertyGroup):
)
# gstretch properties
- gstretch_conversion = EnumProperty(
+ gstretch_conversion: EnumProperty(
name="Conversion",
items=(("distance", "Distance", "Set the distance between vertices "
"of the converted grease pencil stroke"),
@@ -4897,7 +4894,7 @@ class LoopToolsProps(PropertyGroup):
"use this simplification method",
default='limit_vertices'
)
- gstretch_conversion_distance = FloatProperty(
+ gstretch_conversion_distance: FloatProperty(
name="Distance",
description="Absolute distance between vertices along the converted "
"grease pencil stroke",
@@ -4906,7 +4903,7 @@ class LoopToolsProps(PropertyGroup):
soft_min=0.01,
soft_max=100
)
- gstretch_conversion_max = IntProperty(
+ gstretch_conversion_max: IntProperty(
name="Max Vertices",
description="Maximum number of vertices grease pencil strokes will "
"have, when they are converted to geomtery",
@@ -4915,7 +4912,7 @@ class LoopToolsProps(PropertyGroup):
soft_max=500,
update=gstretch_update_min
)
- gstretch_conversion_min = IntProperty(
+ gstretch_conversion_min: IntProperty(
name="Min Vertices",
description="Minimum number of vertices grease pencil strokes will "
"have, when they are converted to geomtery",
@@ -4924,7 +4921,7 @@ class LoopToolsProps(PropertyGroup):
soft_max=500,
update=gstretch_update_max
)
- gstretch_conversion_vertices = IntProperty(
+ gstretch_conversion_vertices: IntProperty(
name="Vertices",
description="Number of vertices grease pencil strokes will "
"have, when they are converted to geometry. If strokes have less "
@@ -4933,13 +4930,13 @@ class LoopToolsProps(PropertyGroup):
min=3,
soft_max=500
)
- gstretch_delete_strokes = BoolProperty(
+ gstretch_delete_strokes: BoolProperty(
name="Delete strokes",
description="Remove Grease Pencil strokes if they have been used "
"for Gstretch. WARNING: DOES NOT SUPPORT UNDO",
default=False
)
- gstretch_influence = FloatProperty(
+ gstretch_influence: FloatProperty(
name="Influence",
description="Force of the tool",
default=100.0,
@@ -4948,22 +4945,22 @@ class LoopToolsProps(PropertyGroup):
precision=1,
subtype='PERCENTAGE'
)
- gstretch_lock_x = BoolProperty(
+ gstretch_lock_x: BoolProperty(
name="Lock X",
description="Lock editing of the x-coordinate",
default=False
)
- gstretch_lock_y = BoolProperty(
+ gstretch_lock_y: BoolProperty(
name="Lock Y",
description="Lock editing of the y-coordinate",
default=False
)
- gstretch_lock_z = BoolProperty(
+ gstretch_lock_z: BoolProperty(
name="Lock Z",
description="Lock editing of the z-coordinate",
default=False
)
- gstretch_method = EnumProperty(
+ gstretch_method: EnumProperty(
name="Method",
items=(("project", "Project", "Project vertices onto the stroke, "
"using vertex normals and connected edges"),
@@ -4977,21 +4974,21 @@ class LoopToolsProps(PropertyGroup):
)
# relax properties
- relax_input = EnumProperty(name="Input",
+ relax_input: EnumProperty(name="Input",
items=(("all", "Parallel (all)", "Also use non-selected "
"parallel loops as input"),
("selected", "Selection", "Only use selected vertices as input")),
description="Loops that are relaxed",
default='selected'
)
- relax_interpolation = EnumProperty(
+ relax_interpolation: EnumProperty(
name="Interpolation",
items=(("cubic", "Cubic", "Natural cubic spline, smooth results"),
("linear", "Linear", "Simple and fast linear algorithm")),
description="Algorithm used for interpolation",
default='cubic'
)
- relax_iterations = EnumProperty(name="Iterations",
+ relax_iterations: EnumProperty(name="Iterations",
items=(("1", "1", "One"),
("3", "3", "Three"),
("5", "5", "Five"),
@@ -5000,14 +4997,14 @@ class LoopToolsProps(PropertyGroup):
description="Number of times the loop is relaxed",
default="1"
)
- relax_regular = BoolProperty(
+ relax_regular: BoolProperty(
name="Regular",
description="Distribute vertices at constant distances along the loop",
default=True
)
# space properties
- space_influence = FloatProperty(
+ space_influence: FloatProperty(
name="Influence",
description="Force of the tool",
default=100.0,
@@ -5016,7 +5013,7 @@ class LoopToolsProps(PropertyGroup):
precision=1,
subtype='PERCENTAGE'
)
- space_input = EnumProperty(
+ space_input: EnumProperty(
name="Input",
items=(("all", "Parallel (all)", "Also use non-selected "
"parallel loops as input"),
@@ -5024,24 +5021,24 @@ class LoopToolsProps(PropertyGroup):
description="Loops that are spaced",
default='selected'
)
- space_interpolation = EnumProperty(
+ space_interpolation: EnumProperty(
name="Interpolation",
items=(("cubic", "Cubic", "Natural cubic spline, smooth results"),
("linear", "Linear", "Vertices are projected on existing edges")),
description="Algorithm used for interpolation",
default='cubic'
)
- space_lock_x = BoolProperty(
+ space_lock_x: BoolProperty(
name="Lock X",
description="Lock editing of the x-coordinate",
default=False
)
- space_lock_y = BoolProperty(
+ space_lock_y: BoolProperty(
name="Lock Y",
description="Lock editing of the y-coordinate",
default=False
)
- space_lock_z = BoolProperty(
+ space_lock_z: BoolProperty(
name="Lock Z",
description="Lock editing of the z-coordinate",
default=False
@@ -5083,7 +5080,7 @@ class LoopPreferences(AddonPreferences):
# when defining this in a submodule of a python package.
bl_idname = __name__
- category = StringProperty(
+ category: StringProperty(
name="Tab Category",
description="Choose a name for the category of the panel",
default="Tools",
@@ -5127,12 +5124,13 @@ def register():
# unregistering and removing menus
def unregister():
- for cls in classes:
+ for cls in reversed(classes):
bpy.utils.unregister_class(cls)
bpy.types.VIEW3D_MT_edit_mesh_specials.remove(menu_func)
try:
del bpy.types.WindowManager.looptools
- except:
+ except Exception as e:
+ print('unregister fail:\n', e)
pass
diff --git a/mesh_snap_utilities_line.py b/mesh_snap_utilities_line.py
deleted file mode 100644
index 8f2f2a94..00000000
--- a/mesh_snap_utilities_line.py
+++ /dev/null
@@ -1,1191 +0,0 @@
-# ##### BEGIN GPL LICENSE BLOCK #####
-#
-# This program is free software; you can redistribute it and/or
-# modify it under the terms of the GNU General Public License
-# as published by the Free Software Foundation; either version 3
-# of the License, or (at your option) any later version.
-#
-# This program 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 General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-#
-# ##### END GPL LICENSE BLOCK #####
-
-# Contact for more information about the Addon:
-# Email: germano.costa@ig.com.br
-# Twitter: wii_mano @mano_wii
-
-
-bl_info = {
- "name": "Snap Utilities Line",
- "author": "Germano Cavalcante",
- "version": (5, 7, 6),
- "blender": (2, 75, 0),
- "location": "View3D > TOOLS > Snap Utilities > snap utilities",
- "description": "Extends Blender Snap controls",
- "wiki_url": "https://wiki.blender.org/index.php/Extensions:2.6/Py/Scripts/Modeling/Snap_Utils_Line",
- "category": "Mesh"}
-
-import bpy
-import bgl
-import bmesh
-from mathutils import Vector
-from mathutils.geometry import (
- intersect_point_line,
- intersect_line_line,
- intersect_line_plane,
- intersect_ray_tri
- )
-from bpy.types import (
- Operator,
- Panel,
- AddonPreferences,
- )
-from bpy.props import (
- BoolProperty,
- FloatProperty,
- FloatVectorProperty,
- StringProperty,
- )
-
-##DEBUG = False
-##if DEBUG:
-## from .snap_framebuffer_debug import screenTexture
-## from .snap_context import mesh_drawing
-
-
-def get_units_info(scale, unit_system, separate_units):
- if unit_system == 'METRIC':
- scale_steps = ((1000, 'km'), (1, 'm'), (1 / 100, 'cm'),
- (1 / 1000, 'mm'), (1 / 1000000, '\u00b5m'))
- elif unit_system == 'IMPERIAL':
- scale_steps = ((5280, 'mi'), (1, '\''),
- (1 / 12, '"'), (1 / 12000, 'thou'))
- scale /= 0.3048 # BU to feet
- else:
- scale_steps = ((1, ' BU'),)
- separate_units = False
-
- return (scale, scale_steps, separate_units)
-
-
-def convert_distance(val, units_info, precision=5):
- scale, scale_steps, separate_units = units_info
- sval = val * scale
- idx = 0
- while idx < len(scale_steps) - 1:
- if sval >= scale_steps[idx][0]:
- break
- idx += 1
- factor, suffix = scale_steps[idx]
- sval /= factor
- if not separate_units or idx == len(scale_steps) - 1:
- dval = str(round(sval, precision)) + suffix
- else:
- ival = int(sval)
- dval = str(round(ival, precision)) + suffix
- fval = sval - ival
- idx += 1
- while idx < len(scale_steps):
- fval *= scale_steps[idx - 1][0] / scale_steps[idx][0]
- if fval >= 1:
- dval += ' ' \
- + ("%.1f" % fval) \
- + scale_steps[idx][1]
- break
- idx += 1
-
- return dval
-
-
-def location_3d_to_region_2d(region, rv3d, coord):
- prj = rv3d.perspective_matrix * Vector((coord[0], coord[1], coord[2], 1.0))
- width_half = region.width / 2.0
- height_half = region.height / 2.0
- return Vector((width_half + width_half * (prj.x / prj.w),
- height_half + height_half * (prj.y / prj.w),
- prj.z / prj.w
- ))
-
-
-def out_Location(rv3d, region, orig, vector):
- view_matrix = rv3d.view_matrix
- v1 = Vector((int(view_matrix[0][0] * 1.5), int(view_matrix[0][1] * 1.5), int(view_matrix[0][2] * 1.5)))
- v2 = Vector((int(view_matrix[1][0] * 1.5), int(view_matrix[1][1] * 1.5), int(view_matrix[1][2] * 1.5)))
-
- hit = intersect_ray_tri(Vector((1, 0, 0)), Vector((0, 1, 0)), Vector(), (vector), (orig), False)
- if hit is None:
- hit = intersect_ray_tri(v1, v2, Vector(), (vector), (orig), False)
- if hit is None:
- hit = intersect_ray_tri(v1, v2, Vector(), (-vector), (orig), False)
- if hit is None:
- hit = Vector()
- return hit
-
-
-def get_closest_edge(bm, point, dist):
- r_edge = None
- for edge in bm.edges:
- v1 = edge.verts[0].co
- v2 = edge.verts[1].co
- # Test the BVH (AABB) first
- for i in range(3):
- if v1[i] <= v2[i]:
- isect = v1[i] - dist <= point[i] <= v2[i] + dist
- else:
- isect = v2[i] - dist <= point[i] <= v1[i] + dist
-
- if not isect:
- break
- else:
- ret = intersect_point_line(point, v1, v2)
-
- if ret[1] < 0.0:
- tmp = v1
- elif ret[1] > 1.0:
- tmp = v2
- else:
- tmp = ret[0]
-
- new_dist = (point - tmp).length
- if new_dist <= dist:
- dist = new_dist
- r_edge = edge
-
- return r_edge
-
-
-class SnapCache():
- bvert = None
- vco = None
-
- bedge = None
- v0 = None
- v1 = None
- vmid = None
- vperp = None
- v2d0 = None
- v2d1 = None
- v2dmid = None
- v2dperp = None
-
- bm_geom_selected = None
-
-
-def snap_utilities(
- sctx, obj,
- cache, context, obj_matrix_world,
- bm, mcursor,
- constrain = None,
- previous_vert = None,
- increment = 0.0):
-
- rv3d = context.region_data
- region = context.region
- scene = context.scene
- is_increment = False
- r_loc = None
- r_type = None
- r_len = 0.0
- bm_geom = None
-
- if cache.bm_geom_selected:
- try:
- cache.bm_geom_selected.select = False
- except ReferenceError as e:
- print(e)
-
- snp_obj, loc, elem = sctx.snap_get(mcursor)
- view_vector, orig = sctx.last_ray
-
- if not snp_obj:
- is_increment = True
- if constrain:
- end = orig + view_vector
- t_loc = intersect_line_line(constrain[0], constrain[1], orig, end)
- if t_loc is None:
- t_loc = constrain
- r_loc = t_loc[0]
- else:
- r_type = 'OUT'
- r_loc = out_Location(rv3d, region, orig, view_vector)
-
- elif snp_obj.data[0] != obj: #OUT
- r_loc = loc
-
- if constrain:
- is_increment = False
- r_loc = intersect_point_line(r_loc, constrain[0], constrain[1])[0]
- if not r_loc:
- r_loc = out_Location(rv3d, region, orig, view_vector)
- elif len(elem) == 1:
- is_increment = False
- r_type = 'VERT'
- elif len(elem) == 2:
- is_increment = True
- r_type = 'EDGE'
- else:
- is_increment = True
- r_type = 'FACE'
-
- elif len(elem) == 1:
- r_type = 'VERT'
- bm_geom = bm.verts[elem[0]]
-
- if cache.bvert != bm_geom:
- cache.bvert = bm_geom
- cache.vco = loc
- #cache.v2d = location_3d_to_region_2d(region, rv3d, cache.vco)
-
- if constrain:
- r_loc = intersect_point_line(cache.vco, constrain[0], constrain[1])[0]
- else:
- r_loc = cache.vco
-
- elif len(elem) == 2:
- v1 = bm.verts[elem[0]]
- v2 = bm.verts[elem[1]]
- bm_geom = bm.edges.get([v1, v2])
-
- if cache.bedge != bm_geom:
- cache.bedge = bm_geom
- cache.v0 = obj_matrix_world * v1.co
- cache.v1 = obj_matrix_world * v2.co
- cache.vmid = 0.5 * (cache.v0 + cache.v1)
- cache.v2d0 = location_3d_to_region_2d(region, rv3d, cache.v0)
- cache.v2d1 = location_3d_to_region_2d(region, rv3d, cache.v1)
- cache.v2dmid = location_3d_to_region_2d(region, rv3d, cache.vmid)
-
- if previous_vert and previous_vert not in {v1, v2}:
- pvert_co = obj_matrix_world * previous_vert.co
- perp_point = intersect_point_line(pvert_co, cache.v0, cache.v1)
- cache.vperp = perp_point[0]
- #factor = point_perpendicular[1]
- cache.v2dperp = location_3d_to_region_2d(region, rv3d, perp_point[0])
-
- #else: cache.v2dperp = None
-
- if constrain:
- t_loc = intersect_line_line(constrain[0], constrain[1], cache.v0, cache.v1)
- if t_loc is None:
- is_increment = True
- end = orig + view_vector
- t_loc = intersect_line_line(constrain[0], constrain[1], orig, end)
- r_loc = t_loc[0]
-
- elif cache.v2dperp and\
- abs(cache.v2dperp[0] - mcursor[0]) < 10 and abs(cache.v2dperp[1] - mcursor[1]) < 10:
- r_type = 'PERPENDICULAR'
- r_loc = cache.vperp
-
- elif abs(cache.v2dmid[0] - mcursor[0]) < 10 and abs(cache.v2dmid[1] - mcursor[1]) < 10:
- r_type = 'CENTER'
- r_loc = cache.vmid
-
- else:
- if increment and previous_vert in cache.bedge.verts:
- is_increment = True
-
- r_type = 'EDGE'
- r_loc = loc
-
- elif len(elem) == 3:
- is_increment = True
- r_type = 'FACE'
- tri = [
- bm.verts[elem[0]],
- bm.verts[elem[1]],
- bm.verts[elem[2]],
- ]
-
- faces = set(tri[0].link_faces).intersection(tri[1].link_faces, tri[2].link_faces)
- if len(faces) == 1:
- bm_geom = faces.pop()
- else:
- i = -2
- edge = None
- while not edge and i != 1:
- edge = bm.edges.get([tri[i], tri[i + 1]])
- i += 1
- if edge:
- for l in edge.link_loops:
- if l.link_loop_next.vert == tri[i] or l.link_loop_prev.vert == tri[i - 2]:
- bm_geom = l.face
- break
- else: # This should never happen!!!!
- raise
- bm_geom = faces.pop()
-
- r_loc = loc
-
- if constrain:
- is_increment = False
- r_loc = intersect_point_line(r_loc, constrain[0], constrain[1])[0]
-
- if previous_vert:
- pv_co = obj_matrix_world * previous_vert.co
- vec = r_loc - pv_co
- if is_increment and increment:
- r_len = round((1 / increment) * vec.length) * increment
- r_loc = r_len * vec.normalized() + pv_co
- else:
- r_len = vec.length
-
- if bm_geom:
- bm_geom.select = True
-
- cache.bm_geom_selected = bm_geom
-
- return r_loc, r_type, bm_geom, r_len
-
-
-def get_loose_linked_edges(bmvert):
- linked = [e for e in bmvert.link_edges if not e.link_faces]
- for e in linked:
- linked += [le for v in e.verts if not v.link_faces for le in v.link_edges if le not in linked]
- return linked
-
-
-def draw_line(self, obj, bm, bm_geom, location):
- split_faces = set()
-
- drawing_is_dirt = False
- update_edit_mesh = False
- tessface = False
-
- if bm_geom is None:
- vert = bm.verts.new(location)
- self.list_verts.append(vert)
-
- elif isinstance(bm_geom, bmesh.types.BMVert):
- if (bm_geom.co - location).length_squared < .001:
- if self.list_verts == [] or self.list_verts[-1] != bm_geom:
- self.list_verts.append(bm_geom)
- else:
- vert = bm.verts.new(location)
- self.list_verts.append(vert)
- drawing_is_dirt = True
-
- elif isinstance(bm_geom, bmesh.types.BMEdge):
- self.list_edges.append(bm_geom)
- ret = intersect_point_line(location, bm_geom.verts[0].co, bm_geom.verts[1].co)
-
- if (ret[0] - location).length_squared < .001:
- if ret[1] == 0.0:
- vert = bm_geom.verts[0]
- elif ret[1] == 1.0:
- vert = bm_geom.verts[1]
- else:
- edge, vert = bmesh.utils.edge_split(bm_geom, bm_geom.verts[0], ret[1])
- drawing_is_dirt = True
- self.list_verts.append(vert)
- # self.list_edges.append(edge)
-
- else: # constrain point is near
- vert = bm.verts.new(location)
- self.list_verts.append(vert)
- drawing_is_dirt = True
-
- elif isinstance(bm_geom, bmesh.types.BMFace):
- split_faces.add(bm_geom)
- vert = bm.verts.new(location)
- self.list_verts.append(vert)
- drawing_is_dirt = True
-
- # draw, split and create face
- if len(self.list_verts) >= 2:
- v1, v2 = self.list_verts[-2:]
- # v2_link_verts = [x for y in [a.verts for a in v2.link_edges] for x in y if x != v2]
- edge = bm.edges.get([v1, v2])
- if edge:
- self.list_edges.append(edge)
-
- else: # if v1 not in v2_link_verts:
- if not v2.link_edges:
- edge = bm.edges.new([v1, v2])
- self.list_edges.append(edge)
- drawing_is_dirt = True
- else: # split face
- v1_link_faces = v1.link_faces
- v2_link_faces = v2.link_faces
- if v1_link_faces and v2_link_faces:
- split_faces.update(set(v1_link_faces).intersection(v2_link_faces))
-
- else:
- if v1_link_faces:
- faces = v1_link_faces
- co2 = v2.co.copy()
- else:
- faces = v2_link_faces
- co2 = v1.co.copy()
-
- for face in faces:
- if bmesh.geometry.intersect_face_point(face, co2):
- co = co2 - face.calc_center_median()
- if co.dot(face.normal) < 0.001:
- split_faces.add(face)
-
- if split_faces:
- edge = bm.edges.new([v1, v2])
- self.list_edges.append(edge)
- ed_list = get_loose_linked_edges(v2)
- for face in split_faces:
- facesp = bmesh.utils.face_split_edgenet(face, ed_list)
- del split_faces
- update_edit_mesh = True
- tessface = True
- else:
- if self.intersect:
- facesp = bmesh.ops.connect_vert_pair(bm, verts=[v1, v2], verts_exclude=bm.verts)
- # print(facesp)
- if not self.intersect or not facesp['edges']:
- edge = bm.edges.new([v1, v2])
- self.list_edges.append(edge)
- drawing_is_dirt = True
- else:
- for edge in facesp['edges']:
- self.list_edges.append(edge)
- update_edit_mesh = True
- tessface = True
-
- # create face
- if self.create_face:
- ed_list = set(self.list_edges)
- for edge in v2.link_edges:
- for vert in edge.verts:
- if vert != v2 and vert in self.list_verts:
- ed_list.add(edge)
- break
- else:
- continue
- # Inner loop had a break, break the outer
- break
-
- ed_list.update(get_loose_linked_edges(v2))
-
- bmesh.ops.edgenet_fill(bm, edges=list(ed_list))
- update_edit_mesh = True
- tessface = True
- # print('face created')
- if update_edit_mesh:
- bmesh.update_edit_mesh(obj.data, tessface = tessface)
- self.sctx.update_drawn_snap_object(self.snap_obj)
- #bm.verts.index_update()
- elif drawing_is_dirt:
- self.obj.update_from_editmode()
- self.sctx.update_drawn_snap_object(self.snap_obj)
-
- return [obj.matrix_world * v.co for v in self.list_verts]
-
-
-class NavigationKeys:
- def __init__(self, context):
- # TO DO:
- # 'View Orbit', 'View Pan', 'NDOF Orbit View', 'NDOF Pan View'
- self._rotate = set()
- self._move = set()
- self._zoom = set()
- for key in context.window_manager.keyconfigs.user.keymaps['3D View'].keymap_items:
- if key.idname == 'view3d.rotate':
- #self.keys_rotate[key.id]={'Alt': key.alt, 'Ctrl': key.ctrl, 'Shift':key.shift, 'Type':key.type, 'Value':key.value}
- self._rotate.add((key.alt, key.ctrl, key.shift, key.type, key.value))
- if key.idname == 'view3d.move':
- self._move.add((key.alt, key.ctrl, key.shift, key.type, key.value))
- if key.idname == 'view3d.zoom':
- if key.type == 'WHEELINMOUSE':
- self._zoom.add((key.alt, key.ctrl, key.shift, 'WHEELUPMOUSE', key.value, key.properties.delta))
- elif key.type == 'WHEELOUTMOUSE':
- self._zoom.add((key.alt, key.ctrl, key.shift, 'WHEELDOWNMOUSE', key.value, key.properties.delta))
- else:
- self._zoom.add((key.alt, key.ctrl, key.shift, key.type, key.value, key.properties.delta))
-
-
-class CharMap:
- ascii = {
- ".", ",", "-", "+", "1", "2", "3",
- "4", "5", "6", "7", "8", "9", "0",
- "c", "m", "d", "k", "h", "a",
- " ", "/", "*", "'", "\""
- # "="
- }
- type = {
- 'BACK_SPACE', 'DEL',
- 'LEFT_ARROW', 'RIGHT_ARROW'
- }
-
- @staticmethod
- def modal(self, context, event):
- c = event.ascii
- if c:
- if c == ",":
- c = "."
- self.length_entered = self.length_entered[:self.line_pos] + c + self.length_entered[self.line_pos:]
- self.line_pos += 1
- if self.length_entered:
- if event.type == 'BACK_SPACE':
- self.length_entered = self.length_entered[:self.line_pos - 1] + self.length_entered[self.line_pos:]
- self.line_pos -= 1
-
- elif event.type == 'DEL':
- self.length_entered = self.length_entered[:self.line_pos] + self.length_entered[self.line_pos + 1:]
-
- elif event.type == 'LEFT_ARROW':
- self.line_pos = (self.line_pos - 1) % (len(self.length_entered) + 1)
-
- elif event.type == 'RIGHT_ARROW':
- self.line_pos = (self.line_pos + 1) % (len(self.length_entered) + 1)
-
-
-class SnapUtilitiesLine(Operator):
- bl_idname = "mesh.snap_utilities_line"
- bl_label = "Line Tool"
- bl_description = "Draw edges. Connect them to split faces"
- bl_options = {'REGISTER', 'UNDO'}
-
- constrain_keys = {
- 'X': Vector((1, 0, 0)),
- 'Y': Vector((0, 1, 0)),
- 'Z': Vector((0, 0, 1)),
- 'RIGHT_SHIFT': 'shift',
- 'LEFT_SHIFT': 'shift',
- }
-
- @classmethod
- def poll(cls, context):
- preferences = context.user_preferences.addons[__name__].preferences
- return (context.mode in {'EDIT_MESH', 'OBJECT'} and
- preferences.create_new_obj or
- (context.object is not None and
- context.object.type == 'MESH'))
-
-
- def draw_callback_px(self, context):
- # draw 3d point OpenGL in the 3D View
- bgl.glEnable(bgl.GL_BLEND)
- bgl.glDisable(bgl.GL_DEPTH_TEST)
- # bgl.glPushMatrix()
- # bgl.glMultMatrixf(self.obj_glmatrix)
-
-## if DEBUG:
-## mesh_drawing._store_current_shader_state(mesh_drawing.PreviousGLState)
-## self.screen.Draw(self.sctx._texture)
-## mesh_drawing._restore_shader_state(mesh_drawing.PreviousGLState)
-
- if self.vector_constrain:
- vc = self.vector_constrain
- if hasattr(self, 'preloc') and self.type in {'VERT', 'FACE'}:
- bgl.glColor4f(1.0, 1.0, 1.0, 0.5)
- bgl.glPointSize(5)
- bgl.glBegin(bgl.GL_POINTS)
- bgl.glVertex3f(*self.preloc)
- bgl.glEnd()
- if vc[2] == 'X':
- Color4f = (self.axis_x_color + (1.0,))
- elif vc[2] == 'Y':
- Color4f = (self.axis_y_color + (1.0,))
- elif vc[2] == 'Z':
- Color4f = (self.axis_z_color + (1.0,))
- else:
- Color4f = self.constrain_shift_color
- else:
- if self.type == 'OUT':
- Color4f = self.out_color
- elif self.type == 'FACE':
- Color4f = self.face_color
- elif self.type == 'EDGE':
- Color4f = self.edge_color
- elif self.type == 'VERT':
- Color4f = self.vert_color
- elif self.type == 'CENTER':
- Color4f = self.center_color
- elif self.type == 'PERPENDICULAR':
- Color4f = self.perpendicular_color
- else: # self.type == None
- Color4f = self.out_color
-
- bgl.glColor4f(*Color4f)
- bgl.glPointSize(10)
- bgl.glBegin(bgl.GL_POINTS)
- bgl.glVertex3f(*self.location)
- bgl.glEnd()
-
- # draw 3d line OpenGL in the 3D View
- bgl.glEnable(bgl.GL_DEPTH_TEST)
- bgl.glDepthRange(0, 0.9999)
- bgl.glColor4f(1.0, 0.8, 0.0, 1.0)
- bgl.glLineWidth(2)
- bgl.glEnable(bgl.GL_LINE_STIPPLE)
- bgl.glBegin(bgl.GL_LINE_STRIP)
- for vert_co in self.list_verts_co:
- bgl.glVertex3f(*vert_co)
- bgl.glVertex3f(*self.location)
- bgl.glEnd()
-
- # restore opengl defaults
- # bgl.glPopMatrix()
- bgl.glDepthRange(0, 1)
- bgl.glPointSize(1)
- bgl.glLineWidth(1)
- bgl.glDisable(bgl.GL_BLEND)
- bgl.glDisable(bgl.GL_LINE_STIPPLE)
- bgl.glColor4f(0.0, 0.0, 0.0, 1.0)
-
-
- def modal_navigation(self, context, event):
- evkey = (event.alt, event.ctrl, event.shift, event.type, event.value)
- if evkey in self.navigation_keys._rotate:
- bpy.ops.view3d.rotate('INVOKE_DEFAULT')
- return True
- elif evkey in self.navigation_keys._move:
- if event.shift and self.vector_constrain and \
- self.vector_constrain[2] in {'RIGHT_SHIFT', 'LEFT_SHIFT', 'shift'}:
- self.vector_constrain = None
- bpy.ops.view3d.move('INVOKE_DEFAULT')
- return True
- else:
- for key in self.navigation_keys._zoom:
- if evkey == key[0:5]:
- if True: # TODO: Use Zoom to mouse position
- v3d = context.space_data
- dist_range = (v3d.clip_start, v3d.clip_end)
- rv3d = context.region_data
- if (key[5] < 0 and rv3d.view_distance < dist_range[1]) or\
- (key[5] > 0 and rv3d.view_distance > dist_range[0]):
- rv3d.view_location += key[5] * (self.location - rv3d.view_location) / 6
- rv3d.view_distance -= key[5] * rv3d.view_distance / 6
- else:
- bpy.ops.view3d.zoom('INVOKE_DEFAULT', delta = key[5])
- return True
- #break
-
- return False
-
-
- def modal(self, context, event):
- if self.modal_navigation(context, event):
- return {'RUNNING_MODAL'}
-
- context.area.tag_redraw()
-
- if event.ctrl and event.type == 'Z' and event.value == 'PRESS':
- bpy.ops.ed.undo()
- self.vector_constrain = None
- self.list_verts_co = []
- self.list_verts = []
- self.list_edges = []
- self.obj = bpy.context.active_object
- self.obj_matrix = self.obj.matrix_world.copy()
- self.bm = bmesh.from_edit_mesh(self.obj.data)
- self.sctx.update_drawn_snap_object(self.snap_obj)
- return {'RUNNING_MODAL'}
-
- if event.type == 'MOUSEMOVE' or self.bool_update:
- if self.rv3d.view_matrix != self.rotMat:
- self.rotMat = self.rv3d.view_matrix.copy()
- self.bool_update = True
- self.cache.bedge = None
- else:
- self.bool_update = False
-
- mval = Vector((event.mouse_region_x, event.mouse_region_y))
-
- self.location, self.type, self.geom, self.len = snap_utilities(
- self.sctx, self.obj, self.cache, context, self.obj_matrix,
- self.bm, mval,
- constrain = self.vector_constrain,
- previous_vert = (self.list_verts[-1] if self.list_verts else None),
- increment = self.incremental
- )
- if self.snap_to_grid and self.type == 'OUT':
- loc = self.location / self.rd
- self.location = Vector((round(loc.x),
- round(loc.y),
- round(loc.z))) * self.rd
-
- if self.keyf8 and self.list_verts_co:
- lloc = self.list_verts_co[-1]
- view_vec, orig = self.sctx.last_ray
- location = intersect_point_line(lloc, orig, (orig + view_vec))
- vec = (location[0] - lloc)
- ax, ay, az = abs(vec.x), abs(vec.y), abs(vec.z)
- vec.x = ax > ay > az or ax > az > ay
- vec.y = ay > ax > az or ay > az > ax
- vec.z = az > ay > ax or az > ax > ay
- if vec == Vector():
- self.vector_constrain = None
- else:
- vc = lloc + vec
- try:
- if vc != self.vector_constrain[1]:
- type = 'X' if vec.x else 'Y' if vec.y else 'Z' if vec.z else 'shift'
- self.vector_constrain = [lloc, vc, type]
- except:
- type = 'X' if vec.x else 'Y' if vec.y else 'Z' if vec.z else 'shift'
- self.vector_constrain = [lloc, vc, type]
-
- if event.value == 'PRESS':
- if self.list_verts_co and (event.ascii in CharMap.ascii or event.type in CharMap.type):
- CharMap.modal(self, context, event)
-
- elif event.type in self.constrain_keys:
- self.bool_update = True
- if self.vector_constrain and self.vector_constrain[2] == event.type:
- self.vector_constrain = ()
-
- else:
- if event.shift:
- if isinstance(self.geom, bmesh.types.BMEdge):
- if self.list_verts:
- loc = self.list_verts_co[-1]
- self.vector_constrain = (loc, loc + self.geom.verts[1].co -
- self.geom.verts[0].co, event.type)
- else:
- self.vector_constrain = [self.obj_matrix * v.co for
- v in self.geom.verts] + [event.type]
- else:
- if self.list_verts:
- loc = self.list_verts_co[-1]
- else:
- loc = self.location
- self.vector_constrain = [loc, loc + self.constrain_keys[event.type]] + [event.type]
-
- elif event.type == 'LEFTMOUSE':
- point = self.obj_matinv * self.location
- # with constraint the intersection can be in a different element of the selected one
- if self.vector_constrain and self.geom:
- geom2 = get_closest_edge(self.bm, point, 0.001)
- else:
- geom2 = self.geom
-
- self.vector_constrain = None
- self.list_verts_co = draw_line(self, self.obj, self.bm, geom2, point)
- bpy.ops.ed.undo_push(message="Undo draw line*")
-
- elif event.type == 'TAB':
- self.keytab = self.keytab is False
- if self.keytab:
- self.sctx.set_snap_mode(False, False, True)
- context.tool_settings.mesh_select_mode = (False, False, True)
- else:
- self.sctx.set_snap_mode(True, True, True)
- context.tool_settings.mesh_select_mode = (True, True, True)
-
- elif event.type == 'F8':
- self.vector_constrain = None
- self.keyf8 = self.keyf8 is False
-
- elif event.value == 'RELEASE':
- if event.type in {'RET', 'NUMPAD_ENTER'}:
- if self.length_entered != "" and self.list_verts_co:
- try:
- text_value = bpy.utils.units.to_value(self.unit_system, 'LENGTH', self.length_entered)
- vector = (self.location - self.list_verts_co[-1]).normalized()
- location = (self.list_verts_co[-1] + (vector * text_value))
- G_location = self.obj_matinv * location
- self.list_verts_co = draw_line(self, self.obj, self.bm, self.geom, G_location)
- self.length_entered = ""
- self.vector_constrain = None
-
- except: # ValueError:
- self.report({'INFO'}, "Operation not supported yet")
-
- elif event.type in {'RIGHTMOUSE', 'ESC'}:
- if self.list_verts_co == [] or event.type == 'ESC':
- del self.bm
- del self.list_edges
- del self.list_verts
- del self.list_verts_co
-
- bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
- context.area.header_text_set("")
- self.sctx.free()
-
- #restore initial state
- context.user_preferences.view.use_rotate_around_active = self.use_rotate_around_active
- context.tool_settings.mesh_select_mode = self.select_mode
- if not self.is_editmode:
- bpy.ops.object.editmode_toggle()
-
- return {'FINISHED'}
- else:
- self.vector_constrain = None
- self.list_edges = []
- self.list_verts = []
- self.list_verts_co = []
-
- a = ""
- if self.list_verts_co:
- if self.length_entered:
- pos = self.line_pos
- a = 'length: ' + self.length_entered[:pos] + '|' + self.length_entered[pos:]
- else:
- length = self.len
- length = convert_distance(length, self.uinfo)
- a = 'length: ' + length
-
- context.area.header_text_set(
- "hit: %.3f %.3f %.3f %s" % (self.location[0],
- self.location[1], self.location[2], a)
- )
-
- return {'RUNNING_MODAL'}
-
- def invoke(self, context, event):
- if context.space_data.type == 'VIEW_3D':
- # print('name', __name__, __package__)
- preferences = context.user_preferences.addons[__name__].preferences
-
- #Store the preferences that will be used in modal
- self.intersect = preferences.intersect
- self.create_face = preferences.create_face
- self.outer_verts = preferences.outer_verts
- self.snap_to_grid = preferences.increments_grid
-
- self.out_color = preferences.out_color
- self.face_color = preferences.face_color
- self.edge_color = preferences.edge_color
- self.vert_color = preferences.vert_color
- self.center_color = preferences.center_color
- self.perpendicular_color = preferences.perpendicular_color
- self.constrain_shift_color = preferences.constrain_shift_color
-
- self.axis_x_color = tuple(context.user_preferences.themes[0].user_interface.axis_x)
- self.axis_y_color = tuple(context.user_preferences.themes[0].user_interface.axis_y)
- self.axis_z_color = tuple(context.user_preferences.themes[0].user_interface.axis_z)
-
- if context.mode == 'OBJECT' and \
- (preferences.create_new_obj or context.object is None or context.object.type != 'MESH'):
-
- mesh = bpy.data.meshes.new("")
- obj = bpy.data.objects.new("", mesh)
- context.scene.objects.link(obj)
- context.scene.objects.active = obj
-
- #Store current state
- self.is_editmode = context.object.data.is_editmode
- self.use_rotate_around_active = context.user_preferences.view.use_rotate_around_active
- self.select_mode = context.tool_settings.mesh_select_mode[:]
-
- #Modify the current state
- bpy.ops.object.mode_set(mode='EDIT')
- bpy.ops.mesh.select_all(action='DESELECT')
- context.user_preferences.view.use_rotate_around_active = True
- context.tool_settings.mesh_select_mode = (True, True, True)
- context.space_data.use_occlude_geometry = True
-
- #Configure the unit of measure
- scale = context.scene.unit_settings.scale_length
- self.unit_system = context.scene.unit_settings.system
- separate_units = context.scene.unit_settings.use_separate
- self.uinfo = get_units_info(scale, self.unit_system, separate_units)
-
- scale /= context.space_data.grid_scale * preferences.relative_scale
- self.rd = bpy.utils.units.to_value(self.unit_system, 'LENGTH', str(1 / scale))
-
- self.incremental = bpy.utils.units.to_value(self.unit_system, 'LENGTH', str(preferences.incremental))
-
- #Store values from 3d view context
- self.rv3d = context.region_data
- self.rotMat = self.rv3d.view_matrix.copy()
- self.obj = bpy.context.active_object
- self.obj_matrix = self.obj.matrix_world.copy()
- self.obj_matinv = self.obj_matrix.inverted()
- # self.obj_glmatrix = bgl.Buffer(bgl.GL_FLOAT, [4, 4], self.obj_matrix.transposed())
- self.bm = bmesh.from_edit_mesh(self.obj.data) #remove at end
- self.cache = SnapCache()
-
- #init these variables to avoid errors
- self.prevloc = Vector()
- self.location = Vector()
- self.list_verts = []
- self.list_edges = []
- self.list_verts_co = []
- self.bool_update = False
- self.vector_constrain = ()
- self.navigation_keys = NavigationKeys(context)
- self.type = 'OUT'
- self.len = 0
- self.length_entered = ""
- self.line_pos = 0
- self.bm_geom_selected = None
-
- #Init event variables
- self.keytab = False
- self.keyf8 = False
-
- #Init Snap Context
- from snap_context import SnapContext
-
- self.sctx = SnapContext(context.region, context.space_data)
- self.sctx.set_pixel_dist(12)
- self.sctx.use_clip_planes(True)
-
- act_base = context.active_base
-
- if self.outer_verts:
- for base in context.visible_bases:
- if base != act_base:
- self.sctx.add_obj(base.object, base.object.matrix_world)
-
- self.snap_obj = self.sctx.add_obj(act_base.object, act_base.object.matrix_world)
-
- self.snap_face = context.space_data.viewport_shade not in {'BOUNDBOX', 'WIREFRAME'}
- self.sctx.set_snap_mode(True, True, self.snap_face)
-
- #modals
- self._handle = bpy.types.SpaceView3D.draw_handler_add(self.draw_callback_px, (context,), 'WINDOW', 'POST_VIEW')
- context.window_manager.modal_handler_add(self)
-
- return {'RUNNING_MODAL'}
- else:
- self.report({'WARNING'}, "Active space must be a View3d")
- return {'CANCELLED'}
-
-
-class PanelSnapUtilities(Panel):
- bl_space_type = "VIEW_3D"
- bl_region_type = "TOOLS"
- bl_category = "Snap Utilities"
- bl_label = "Snap Utilities"
-
- @classmethod
- def poll(cls, context):
- preferences = context.user_preferences.addons[__name__].preferences
- return (context.mode in {'EDIT_MESH', 'OBJECT'} and
- preferences.create_new_obj or
- (context.object is not None and
- context.object.type == 'MESH'))
-
- def draw(self, context):
- layout = self.layout
- TheCol = layout.column(align=True)
- TheCol.operator("mesh.snap_utilities_line", text="Line", icon="GREASEPENCIL")
-
- addon_prefs = context.user_preferences.addons[__name__].preferences
- expand = addon_prefs.expand_snap_settings
- icon = "TRIA_DOWN" if expand else "TRIA_RIGHT"
-
- box = layout.box()
- box.prop(addon_prefs, "expand_snap_settings", icon=icon,
- text="Settings:", emboss=False)
- if expand:
- box.prop(addon_prefs, "outer_verts")
- box.prop(addon_prefs, "incremental")
- box.prop(addon_prefs, "increments_grid")
- if addon_prefs.increments_grid:
- box.prop(addon_prefs, "relative_scale")
- box.label(text="Line Tool:")
- box.prop(addon_prefs, "intersect")
- box.prop(addon_prefs, "create_face")
- box.prop(addon_prefs, "create_new_obj")
-
-
-# Add-ons Preferences Update Panel
-
-# Define Panel classes for updating
-panels = (
- PanelSnapUtilities,
- )
-
-
-def update_panel(self, context):
- message = "Snap Utilities Line: Updating Panel locations has failed"
- addon_prefs = context.user_preferences.addons[__name__].preferences
- try:
- for panel in panels:
- if addon_prefs.category != panel.bl_category:
- if "bl_rna" in panel.__dict__:
- bpy.utils.unregister_class(panel)
-
- panel.bl_category = addon_prefs.category
- bpy.utils.register_class(panel)
-
- except Exception as e:
- print("\n[{}]\n{}\n\nError:\n{}".format(__name__, message, e))
- pass
-
-
-class SnapAddonPreferences(AddonPreferences):
- # this must match the addon name, use '__package__'
- # when defining this in a submodule of a python package.
- bl_idname = __name__
-
- intersect = BoolProperty(
- name="Intersect",
- description="Intersects created line with the existing edges, "
- "even if the lines do not intersect",
- default=True
- )
- create_new_obj = BoolProperty(
- name="Create a new object",
- description="If have not a active object, or the active object "
- "is not in edit mode, it creates a new object",
- default=False
- )
- create_face = BoolProperty(
- name="Create faces",
- description="Create faces defined by enclosed edges",
- default=False
- )
- outer_verts = BoolProperty(
- name="Snap to outer vertices",
- description="The vertices of the objects are not activated also snapped",
- default=True
- )
- expand_snap_settings = BoolProperty(
- name="Expand",
- description="Expand, to display the settings",
- default=False
- )
- expand_color_settings = BoolProperty(
- name="Color Settings",
- description="Expand, to display the color settings",
- default=False
- )
- increments_grid = BoolProperty(
- name="Increments of Grid",
- description="Snap to increments of grid",
- default=False
- )
- category = StringProperty(
- name="Category",
- description="Choose a name for the category of the panel",
- default="Snap Utilities",
- update=update_panel
- )
- incremental = FloatProperty(
- name="Incremental",
- description="Snap in defined increments",
- default=0,
- min=0,
- step=1,
- precision=3
- )
- relative_scale = FloatProperty(
- name="Relative Scale",
- description="Value that divides the global scale",
- default=1,
- min=0,
- step=1,
- precision=3
- )
- out_color = FloatVectorProperty(
- name="OUT",
- default=(0.0, 0.0, 0.0, 0.5),
- size=4,
- subtype="COLOR",
- min=0, max=1
- )
- face_color = FloatVectorProperty(
- name="FACE",
- default=(1.0, 0.8, 0.0, 1.0),
- size=4,
- subtype="COLOR",
- min=0, max=1
- )
- edge_color = FloatVectorProperty(
- name="EDGE",
- default=(0.0, 0.8, 1.0, 1.0),
- size=4,
- subtype="COLOR",
- min=0, max=1
- )
- vert_color = FloatVectorProperty(
- name="VERT",
- default=(1.0, 0.5, 0.0, 1.0),
- size=4,
- subtype="COLOR",
- min=0, max=1
- )
- center_color = FloatVectorProperty(
- name="CENTER",
- default=(1.0, 0.0, 1.0, 1.0),
- size=4,
- subtype="COLOR",
- min=0, max=1
- )
- perpendicular_color = FloatVectorProperty(
- name="PERPENDICULAR",
- default=(0.1, 0.5, 0.5, 1.0),
- size=4,
- subtype="COLOR",
- min=0, max=1
- )
- constrain_shift_color = FloatVectorProperty(
- name="SHIFT CONSTRAIN",
- default=(0.8, 0.5, 0.4, 1.0),
- size=4,
- subtype="COLOR",
- min=0, max=1
- )
-
- def draw(self, context):
- layout = self.layout
- icon = "TRIA_DOWN" if self.expand_color_settings else "TRIA_RIGHT"
-
- box = layout.box()
- box.prop(self, "expand_color_settings", icon=icon, toggle=True, emboss=False)
- if self.expand_color_settings:
- split = box.split()
-
- col = split.column()
- col.prop(self, "out_color")
- col.prop(self, "constrain_shift_color")
- col = split.column()
- col.prop(self, "face_color")
- col.prop(self, "center_color")
- col = split.column()
- col.prop(self, "edge_color")
- col.prop(self, "perpendicular_color")
- col = split.column()
- col.prop(self, "vert_color")
-
- row = layout.row()
-
- col = row.column(align=True)
- box = col.box()
- box.label(text="Snap Items:")
- box.prop(self, "incremental")
- box.prop(self, "outer_verts")
- box.prop(self, "increments_grid")
- if self.increments_grid:
- box.prop(self, "relative_scale")
- else:
- box.separator()
- box.separator()
-
- col = row.column(align=True)
- box = col.box()
- box.label(text="Line Tool:")
- box.prop(self, "intersect")
- box.prop(self, "create_face")
- box.prop(self, "create_new_obj")
- box.separator()
- box.separator()
-
- row = layout.row()
- col = row.column()
- col.label(text="Tab Category:")
- col.prop(self, "category", text="")
-
-
-def register():
- bpy.utils.register_class(SnapAddonPreferences)
- bpy.utils.register_class(SnapUtilitiesLine)
- bpy.utils.register_class(PanelSnapUtilities)
- update_panel(None, bpy.context)
-
-
-def unregister():
- bpy.utils.unregister_class(PanelSnapUtilities)
- bpy.utils.unregister_class(SnapUtilitiesLine)
- bpy.utils.unregister_class(SnapAddonPreferences)
-
-
-if __name__ == "__main__":
- __name__ = "mesh_snap_utilities_line"
- register()
diff --git a/mesh_snap_utilities_line/__init__.py b/mesh_snap_utilities_line/__init__.py
new file mode 100644
index 00000000..c6c5f27c
--- /dev/null
+++ b/mesh_snap_utilities_line/__init__.py
@@ -0,0 +1,112 @@
+### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 3
+# of the License, or (at your option) any later version.
+#
+# This program 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+# Contact for more information about the Addon:
+# Email: germano.costa@ig.com.br
+# Twitter: wii_mano @mano_wii
+
+bl_info = {
+ "name": "Snap_Utilities_Line",
+ "author": "Germano Cavalcante",
+ "version": (5, 8, 24),
+ "blender": (0, 0, 0),
+ "location": "View3D > TOOLS > Make Line",
+ "description": "Extends Blender Snap controls",
+ #"wiki_url" : "http://blenderartists.org/forum/showthread.php?363859-Addon-CAD-Snap-Utilities",
+ "category": "Mesh"}
+
+if "bpy" in locals():
+ import importlib
+ importlib.reload(preferences)
+ importlib.reload(ops_line)
+ importlib.reload(common_classes)
+else:
+ from . import preferences
+ from . import ops_line
+
+import bpy
+from bpy.utils.toolsystem import ToolDef
+
+@ToolDef.from_fn
+def tool_make_line():
+ import os
+ def draw_settings(context, layout, tool):
+ addon_prefs = context.user_preferences.addons["mesh_snap_utilities_line"].preferences
+
+ layout.prop(addon_prefs, "incremental")
+ layout.prop(addon_prefs, "increments_grid")
+ if addon_prefs.increments_grid:
+ layout.prop(addon_prefs, "relative_scale")
+ layout.prop(addon_prefs, "create_face")
+ layout.prop(addon_prefs, "outer_verts")
+ #props = tool.operator_properties("mesh.snap_utilities_line")
+ #layout.prop(props, "radius")
+
+ icons_dir = os.path.join(os.path.dirname(__file__), "icons")
+
+ return dict(
+ text="Make Line",
+ description=(
+ "Make Lines\n"
+ "Connect them to split faces"
+ ),
+ icon=os.path.join(icons_dir, "ops.mesh.make_line"),
+# widget="MESH_GGT_mouse_point",
+ operator="mesh.make_line",
+ keymap=(
+ ("mesh.make_line", dict(type='LEFTMOUSE', value='PRESS'), None),
+ ),
+ draw_settings=draw_settings,
+ )
+
+
+def register():
+ def get_tool_list(space_type, context_mode):
+ from bl_ui.space_toolsystem_common import ToolSelectPanelHelper
+ cls = ToolSelectPanelHelper._tool_class_from_space_type(space_type)
+ return cls._tools[context_mode]
+
+ bpy.utils.register_class(preferences.SnapUtilitiesLinePreferences)
+ bpy.utils.register_class(common_classes.VIEW3D_OT_rotate_custom_pivot)
+ bpy.utils.register_class(common_classes.VIEW3D_OT_zoom_custom_target)
+ bpy.utils.register_class(ops_line.SnapUtilitiesLine)
+# bpy.utils.register_class(common_classes.MousePointWidget)
+# bpy.utils.register_class(common_classes.MousePointWidgetGroup)
+
+ bpy.utils.register_tool('VIEW_3D', 'EDIT_MESH', tool_make_line)
+
+ # Move tool to after 'Add Cube'
+ tools = get_tool_list('VIEW_3D', 'EDIT_MESH')
+ for index, tool in enumerate(tools):
+ if isinstance(tool, ToolDef) and tool.text == "Add Cube":
+ break
+ tools.insert(index + 1, tools.pop(-1))
+
+def unregister():
+ bpy.utils.unregister_tool('VIEW_3D', 'EDIT_MESH', tool_make_line)
+
+# bpy.utils.unregister_class(common_classes.MousePointWidgetGroup)
+# bpy.utils.unregister_class(common_classes.MousePointWidget)
+ bpy.utils.unregister_class(ops_line.SnapUtilitiesLine)
+ bpy.utils.unregister_class(common_classes.VIEW3D_OT_zoom_custom_target)
+ bpy.utils.unregister_class(common_classes.VIEW3D_OT_rotate_custom_pivot)
+ bpy.utils.unregister_class(preferences.SnapUtilitiesLinePreferences)
+
+if __name__ == "__main__":
+ __name__ = "mesh_snap_utilities_line"
+ __package__ = "mesh_snap_utilities_line"
+ register()
diff --git a/mesh_snap_utilities_line/common_classes.py b/mesh_snap_utilities_line/common_classes.py
new file mode 100644
index 00000000..8227f485
--- /dev/null
+++ b/mesh_snap_utilities_line/common_classes.py
@@ -0,0 +1,510 @@
+### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 3
+# of the License, or (at your option) any later version.
+#
+# This program 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+import bpy
+import bgl
+import gpu
+import numpy as np
+
+from .common_utilities import snap_utilities
+
+
+class SnapDrawn():
+ def __init__(self, out_color, face_color,
+ edge_color, vert_color, center_color,
+ perpendicular_color, constrain_shift_color,
+ axis_x_color, axis_y_color, axis_z_color):
+
+ self.out_color = out_color
+ self.face_color = face_color
+ self.edge_color = edge_color
+ self.vert_color = vert_color
+ self.center_color = center_color
+ self.perpendicular_color = perpendicular_color
+ self.constrain_shift_color = constrain_shift_color
+
+ self.axis_x_color = axis_x_color
+ self.axis_y_color = axis_y_color
+ self.axis_z_color = axis_z_color
+
+ self._format_pos = gpu.types.GPUVertFormat()
+ self._format_pos.attr_add(id="pos", comp_type='F32', len=3, fetch_mode='FLOAT')
+
+ self._format_pos_and_color = gpu.types.GPUVertFormat()
+ self._format_pos_and_color.attr_add(id="pos", comp_type='F32', len=3, fetch_mode='FLOAT')
+ self._format_pos_and_color.attr_add(id="color", comp_type='F32', len=4, fetch_mode='FLOAT')
+
+ self._program_unif_col = gpu.shader.from_builtin("3D_UNIFORM_COLOR")
+ self._program_smooth_col = gpu.shader.from_builtin("3D_SMOOTH_COLOR")
+
+ self._batch_point = None
+ self._batch_circle = None
+ self._batch_vector = None
+
+
+ def batch_line_strip_create(self, coords):
+ vbo = gpu.types.GPUVertBuf(self._format_pos, len = len(coords))
+ vbo.attr_fill(0, data = coords)
+ batch_lines = gpu.types.GPUBatch(type = "LINE_STRIP", buf = vbo)
+ return batch_lines
+
+ def batch_lines_smooth_color_create(self, coords, colors):
+ vbo = gpu.types.GPUVertBuf(self._format_pos_and_color, len = len(coords))
+ vbo.attr_fill(0, data = coords)
+ vbo.attr_fill(1, data = colors)
+ batch_lines = gpu.types.GPUBatch(type = "LINES", buf = vbo)
+ return batch_lines
+
+ def batch_triangles_create(self, coords):
+ vbo = gpu.types.GPUVertBuf(self._format_pos, len = len(coords))
+ vbo.attr_fill(0, data = coords)
+ batch_tris = gpu.types.GPUBatch(type = "TRIS", buf = vbo)
+ return batch_tris
+
+ def batch_point_get(self):
+ if self._batch_point is None:
+ vbo = gpu.types.GPUVertBuf(self._format_pos, len = 1)
+ vbo.attr_fill(0, ((0.0, 0.0, 0.0),))
+ self._batch_point = gpu.types.GPUBatch(type = "POINTS", buf = vbo)
+ return self._batch_point
+
+ def draw(self, type, location, list_verts_co, vector_constrain, prevloc):
+ # draw 3d point OpenGL in the 3D View
+ bgl.glEnable(bgl.GL_BLEND)
+ gpu.matrix.push()
+ self._program_unif_col.bind()
+
+ if list_verts_co:
+ # draw 3d line OpenGL in the 3D View
+ bgl.glDepthRange(0, 0.9999)
+ bgl.glLineWidth(3.0)
+
+ batch = self.batch_line_strip_create([v.to_tuple() for v in list_verts_co] + [location.to_tuple()])
+
+ self._program_unif_col.uniform_float("color", (1.0, 0.8, 0.0, 0.5))
+ batch.draw(self._program_unif_col)
+ del batch
+
+ bgl.glDisable(bgl.GL_DEPTH_TEST)
+
+ point_batch = self.batch_point_get()
+ if vector_constrain:
+ if prevloc:
+ bgl.glPointSize(5)
+ gpu.matrix.translate(prevloc)
+ self._program_unif_col.uniform_float("color", (1.0, 1.0, 1.0, 0.5))
+ point_batch.draw(self._program_unif_col)
+ gpu.matrix.translate(-prevloc)
+
+ if vector_constrain[2] == 'X':
+ Color4f = self.axis_x_color
+ elif vector_constrain[2] == 'Y':
+ Color4f = self.axis_y_color
+ elif vector_constrain[2] == 'Z':
+ Color4f = self.axis_z_color
+ else:
+ Color4f = self.constrain_shift_color
+ else:
+ if type == 'OUT':
+ Color4f = self.out_color
+ elif type == 'FACE':
+ Color4f = self.face_color
+ elif type == 'EDGE':
+ Color4f = self.edge_color
+ elif type == 'VERT':
+ Color4f = self.vert_color
+ elif type == 'CENTER':
+ Color4f = self.center_color
+ elif type == 'PERPENDICULAR':
+ Color4f = self.perpendicular_color
+ else: # type == None
+ Color4f = self.out_color
+
+ bgl.glPointSize(10)
+
+ gpu.matrix.translate(location)
+ self._program_unif_col.uniform_float("color", Color4f)
+ point_batch.draw(self._program_unif_col)
+
+ # restore opengl defaults
+ bgl.glDepthRange(0.0, 1.0)
+ bgl.glPointSize(1.0)
+ bgl.glLineWidth(1.0)
+ bgl.glEnable(bgl.GL_DEPTH_TEST)
+ bgl.glDisable(bgl.GL_BLEND)
+
+ gpu.matrix.pop()
+
+ def draw_elem(self, snap_obj, bm, elem):
+ from bmesh.types import(
+ BMVert,
+ BMEdge,
+ BMFace,
+ )
+ # draw 3d point OpenGL in the 3D View
+ bgl.glEnable(bgl.GL_BLEND)
+ bgl.glDisable(bgl.GL_DEPTH_TEST)
+
+ with gpu.matrix.push_pop():
+ gpu.matrix.multiply_matrix(snap_obj.mat)
+
+ if isinstance(elem, BMVert):
+ if elem.link_edges:
+ color = self.vert_color
+ edges = np.empty((len(elem.link_edges), 2), [("pos", "f4", 3), ("color", "f4", 4)])
+ edges["pos"][:, 0] = elem.co
+ edges["pos"][:, 1] = [e.other_vert(elem).co for e in elem.link_edges]
+ edges["color"][:, 0] = color
+ edges["color"][:, 1] = (color[0], color[1], color[2], 0.0)
+ edges.shape = -1
+
+ self._program_smooth_col.bind()
+ bgl.glLineWidth(3.0)
+ batch = self.batch_lines_smooth_color_create(edges["pos"], edges["color"])
+ batch.draw(self._program_smooth_col)
+ bgl.glLineWidth(1.0)
+ else:
+ self._program_unif_col.bind()
+
+ if isinstance(elem, BMEdge):
+ self._program_unif_col.uniform_float("color", self.edge_color)
+
+ bgl.glLineWidth(3.0)
+ batch = self.batch_line_strip_create([v.co for v in elem.verts])
+ batch.draw(self._program_unif_col)
+ bgl.glLineWidth(1.0)
+
+ elif isinstance(elem, BMFace):
+ if len(snap_obj.data) == 2:
+ face_color = self.face_color[0], self.face_color[1], self.face_color[2], self.face_color[3] * 0.2
+ self._program_unif_col.uniform_float("color", face_color)
+
+ tris = snap_obj.data[1].get_loop_tri_co_by_bmface(bm, elem)
+ tris.shape = (-1, 3)
+ batch = self.batch_triangles_create(tris)
+ batch.draw(self._program_unif_col)
+
+ # restore opengl defaults
+ bgl.glEnable(bgl.GL_DEPTH_TEST)
+ bgl.glDisable(bgl.GL_BLEND)
+
+
+class SnapNavigation():
+ @staticmethod
+ def debug_key(key):
+ for member in dir(key):
+ print(member, getattr(key, member))
+
+ @staticmethod
+ def convert_to_flag(shift, ctrl, alt):
+ return (shift << 0) | (ctrl << 1) | (alt << 2)
+
+ def __init__(self, context, use_ndof):
+ # TO DO:
+ # 'View Orbit', 'View Pan', 'NDOF Orbit View', 'NDOF Pan View'
+ self.use_ndof = use_ndof and context.user_preferences.inputs.use_ndof
+
+ self._rotate = set()
+ self._move = set()
+ self._zoom = set()
+
+ if self.use_ndof:
+ self._ndof_all = set()
+ self._ndof_orbit = set()
+ self._ndof_orbit_zoom = set()
+ self._ndof_pan = set()
+
+ for key in context.window_manager.keyconfigs.user.keymaps['3D View'].keymap_items:
+ if key.idname == 'view3d.rotate':
+ self._rotate.add((self.convert_to_flag(key.shift, key.ctrl, key.alt), key.type, key.value))
+ elif key.idname == 'view3d.move':
+ self._move.add((self.convert_to_flag(key.shift, key.ctrl, key.alt), key.type, key.value))
+ elif key.idname == 'view3d.zoom':
+ if key.type == 'WHEELINMOUSE':
+ self._zoom.add((self.convert_to_flag(key.shift, key.ctrl, key.alt), 'WHEELUPMOUSE', key.value, key.properties.delta))
+ elif key.type == 'WHEELOUTMOUSE':
+ self._zoom.add((self.convert_to_flag(key.shift, key.ctrl, key.alt), 'WHEELDOWNMOUSE', key.value, key.properties.delta))
+ else:
+ self._zoom.add((self.convert_to_flag(key.shift, key.ctrl, key.alt), key.type, key.value, key.properties.delta))
+
+ elif self.use_ndof:
+ if key.idname == 'view3d.ndof_all':
+ self._ndof_all.add((self.convert_to_flag(key.shift, key.ctrl, key.alt), key.type))
+ elif key.idname == 'view3d.ndof_orbit':
+ self._ndof_orbit.add((self.convert_to_flag(key.shift, key.ctrl, key.alt), key.type))
+ elif key.idname == 'view3d.ndof_orbit_zoom':
+ self._ndof_orbit_zoom.add((self.convert_to_flag(key.shift, key.ctrl, key.alt), key.type))
+ elif key.idname == 'view3d.ndof_pan':
+ self._ndof_pan.add((self.convert_to_flag(key.shift, key.ctrl, key.alt), key.type))
+
+
+ def run(self, context, event, snap_location):
+ evkey = (self.convert_to_flag(event.shift, event.ctrl, event.alt), event.type, event.value)
+
+ if evkey in self._rotate:
+ if snap_location:
+ bpy.ops.view3d.rotate_custom_pivot('INVOKE_DEFAULT', pivot=snap_location)
+ else:
+ bpy.ops.view3d.rotate('INVOKE_DEFAULT', use_mouse_init=True)
+ return True
+
+ if evkey in self._move:
+ #if event.shift and self.vector_constrain and \
+ # self.vector_constrain[2] in {'RIGHT_SHIFT', 'LEFT_SHIFT', 'shift'}:
+ # self.vector_constrain = None
+ bpy.ops.view3d.move('INVOKE_DEFAULT')
+ return True
+
+ for key in self._zoom:
+ if evkey == key[0:3]:
+ if snap_location and key[3]:
+ bpy.ops.view3d.zoom_custom_target('INVOKE_DEFAULT', delta=key[3], target=snap_location)
+ else:
+ bpy.ops.view3d.zoom('INVOKE_DEFAULT', delta=key[3])
+ return True
+
+ if self.use_ndof:
+ ndofkey = evkey[:2]
+ if ndofkey in self._ndof_all:
+ bpy.ops.view3d.ndof_all('INVOKE_DEFAULT')
+ return True
+ if ndofkey in self._ndof_orbit:
+ bpy.ops.view3d.ndof_orbit('INVOKE_DEFAULT')
+ return True
+ if ndofkey in self._ndof_orbit_zoom:
+ bpy.ops.view3d.ndof_orbit_zoom('INVOKE_DEFAULT')
+ return True
+ if ndofkey in self._ndof_pan:
+ bpy.ops.view3d.ndof_pan('INVOKE_DEFAULT')
+ return True
+
+ return False
+
+
+class CharMap:
+ ascii = {
+ ".", ",", "-", "+", "1", "2", "3",
+ "4", "5", "6", "7", "8", "9", "0",
+ "c", "m", "d", "k", "h", "a",
+ " ", "/", "*", "'", "\""
+ # "="
+ }
+ type = {
+ 'BACK_SPACE', 'DEL',
+ 'LEFT_ARROW', 'RIGHT_ARROW'
+ }
+
+ @staticmethod
+ def modal(self, context, event):
+ c = event.ascii
+ if c:
+ if c == ",":
+ c = "."
+ self.length_entered = self.length_entered[:self.line_pos] + c + self.length_entered[self.line_pos:]
+ self.line_pos += 1
+ if self.length_entered:
+ if event.type == 'BACK_SPACE':
+ self.length_entered = self.length_entered[:self.line_pos - 1] + self.length_entered[self.line_pos:]
+ self.line_pos -= 1
+
+ elif event.type == 'DEL':
+ self.length_entered = self.length_entered[:self.line_pos] + self.length_entered[self.line_pos + 1:]
+
+ elif event.type == 'LEFT_ARROW':
+ self.line_pos = (self.line_pos - 1) % (len(self.length_entered) + 1)
+
+ elif event.type == 'RIGHT_ARROW':
+ self.line_pos = (self.line_pos + 1) % (len(self.length_entered) + 1)
+
+g_snap_widget = [None]
+
+class MousePointWidget(bpy.types.Gizmo):
+ bl_idname = "VIEW3D_GT_mouse_point"
+
+ __slots__ = (
+ "sctx",
+ "bm",
+ "draw_cache",
+ "geom",
+ "incremental",
+ "preferences",
+ "loc",
+ "snap_obj",
+ "snap_to_grid",
+ "type",
+ )
+
+ def test_select(self, context, mval):
+ #print('test_select', mval)
+ self.snap_obj, prev_loc, self.loc, self.type, self.bm, self.geom, len = snap_utilities(
+ self.sctx,
+ None,
+ mval,
+ increment=self.incremental
+ )
+ context.area.tag_redraw()
+ return False
+
+ def draw(self, context):
+ if self.bm:
+ self.draw_cache.draw_elem(self.snap_obj, self.bm, self.geom)
+ self.draw_cache.draw(self.type, self.loc, None, None, None)
+
+ def setup(self):
+ if not hasattr(self, "sctx"):
+ global g_snap_widget
+ g_snap_widget[0] = self
+
+ context = bpy.context
+
+ self.preferences = preferences = context.user_preferences.addons[__package__].preferences
+
+ #Configure the unit of measure
+ self.snap_to_grid = preferences.increments_grid
+ self.incremental = bpy.utils.units.to_value(
+ context.scene.unit_settings.system, 'LENGTH', str(preferences.incremental))
+
+ self.draw_cache = SnapDrawn(
+ preferences.out_color,
+ preferences.face_color,
+ preferences.edge_color,
+ preferences.vert_color,
+ preferences.center_color,
+ preferences.perpendicular_color,
+ preferences.constrain_shift_color,
+ (*context.user_preferences.themes[0].user_interface.axis_x, 1.0),
+ (*context.user_preferences.themes[0].user_interface.axis_y, 1.0),
+ (*context.user_preferences.themes[0].user_interface.axis_z, 1.0)
+ )
+
+ #Init Snap Context
+ from .snap_context_l import SnapContext
+ from mathutils import Vector
+
+ self.sctx = SnapContext(context.region, context.space_data)
+ self.sctx.set_pixel_dist(12)
+ self.sctx.use_clip_planes(True)
+
+ if preferences.outer_verts:
+ for base in context.visible_bases:
+ self.sctx.add_obj(base.object, base.object.matrix_world)
+
+ self.sctx.set_snap_mode(True, True, True)
+ self.bm = None
+ self.type = 'OUT'
+ self.loc = Vector()
+
+ def __del__(self):
+ global g_snap_widget
+ g_snap_widget[0] = None
+
+
+class MousePointWidgetGroup(bpy.types.GizmoGroup):
+ bl_idname = "MESH_GGT_mouse_point"
+ bl_label = "Draw Mouse Point"
+ bl_space_type = 'VIEW_3D'
+ bl_region_type = 'WINDOW'
+ bl_options = {'3D'}
+
+ def setup(self, context):
+ snap_widget = self.gizmos.new(MousePointWidget.bl_idname)
+ props = snap_widget.target_set_operator("mesh.make_line")
+ props.wait_for_input = False
+
+
+class VIEW3D_OT_rotate_custom_pivot(bpy.types.Operator):
+ bl_idname = "view3d.rotate_custom_pivot"
+ bl_label = "Rotate the view"
+ bl_options = {'BLOCKING', 'GRAB_CURSOR'}
+
+ pivot: bpy.props.FloatVectorProperty("Pivot", subtype='XYZ')
+ g_up_axis: bpy.props.FloatVectorProperty("up_axis", default=(0.0, 0.0, 1.0), subtype='XYZ')
+ sensitivity: bpy.props.FloatProperty("sensitivity", default=0.007)
+
+ def modal(self, context, event):
+ from mathutils import Matrix
+ if event.value == 'PRESS' and event.type in {'MOUSEMOVE', 'INBETWEEN_MOUSEMOVE'}:
+ dx = self.init_coord[0] - event.mouse_region_x
+ dy = self.init_coord[1] - event.mouse_region_y
+ rot_ver = Matrix.Rotation(-dx * self.sensitivity, 3, self.g_up_axis)
+ rot_hor = Matrix.Rotation(dy * self.sensitivity, 3, self.view_rot[0])
+ rot_mat = rot_hor @ rot_ver
+ view_matrix = self.view_rot @ rot_mat
+
+ pos = self.pos1 @ rot_mat + self.pivot
+ qua = view_matrix.to_quaternion()
+ qua.invert()
+
+ self.rv3d.view_location = pos
+ self.rv3d.view_rotation = qua
+
+ context.area.tag_redraw()
+ return {'RUNNING_MODAL'}
+
+ return {'FINISHED'}
+
+ def invoke(self, context, event):
+ self.rv3d = context.region_data
+ self.init_coord = event.mouse_region_x, event.mouse_region_y
+ self.pos1 = self.rv3d.view_location - self.pivot
+ self.view_rot = self.rv3d.view_matrix.to_3x3()
+
+ context.window_manager.modal_handler_add(self)
+ return {'RUNNING_MODAL'}
+
+
+class VIEW3D_OT_zoom_custom_target(bpy.types.Operator):
+ bl_idname = "view3d.zoom_custom_target"
+ bl_label = "Zoom the view"
+ bl_options = {'BLOCKING', 'GRAB_CURSOR'}
+
+ target: bpy.props.FloatVectorProperty("target", subtype='XYZ')
+ delta: bpy.props.IntProperty("delta", default=0)
+ step_factor = 0.333
+
+ def modal(self, context, event):
+ if event.value == 'PRESS' and event.type in {'MOUSEMOVE', 'INBETWEEN_MOUSEMOVE'}:
+ if not hasattr(self, "init_mouse_region_y"):
+ self.init_mouse_region_y = event.mouse_region_y
+ self.heigt_up = context.area.height - self.init_mouse_region_y
+ self.rv3d.view_location = self.target
+
+ fac = (event.mouse_region_y - self.init_mouse_region_y) / self.heigt_up
+ ret = 'RUNNING_MODAL'
+ else:
+ fac = self.step_factor * self.delta
+ ret = 'FINISHED'
+
+ self.rv3d.view_location = self.init_loc + (self.target - self.init_loc) * fac
+ self.rv3d.view_distance = self.init_dist - self.init_dist * fac
+
+ context.area.tag_redraw()
+ return {ret}
+
+ def invoke(self, context, event):
+ v3d = context.space_data
+ dist_range = (v3d.clip_start, v3d.clip_end)
+ self.rv3d = context.region_data
+ self.init_dist = self.rv3d.view_distance
+ if ((self.delta <= 0 and self.init_dist < dist_range[1]) or
+ (self.delta > 0 and self.init_dist > dist_range[0])):
+ self.init_loc = self.rv3d.view_location.copy()
+
+ context.window_manager.modal_handler_add(self)
+ return {'RUNNING_MODAL'}
+
+ return {'FINISHED'}
diff --git a/mesh_snap_utilities_line/common_utilities.py b/mesh_snap_utilities_line/common_utilities.py
new file mode 100644
index 00000000..2fc0ce6d
--- /dev/null
+++ b/mesh_snap_utilities_line/common_utilities.py
@@ -0,0 +1,269 @@
+### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 3
+# of the License, or (at your option) any later version.
+#
+# This program 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+#python tip: from-imports don't save memory.
+#They execute and cache the entire module just like a regular import.
+
+import bpy
+import bmesh
+from .snap_context_l import SnapContext
+from mathutils import Vector
+from mathutils.geometry import (
+ intersect_point_line,
+ intersect_line_line,
+ intersect_line_plane,
+ intersect_ray_tri,
+ )
+
+
+def get_units_info(scale, unit_system, separate_units):
+ if unit_system == 'METRIC':
+ scale_steps = ((1000, 'km'), (1, 'm'), (1 / 100, 'cm'),
+ (1 / 1000, 'mm'), (1 / 1000000, '\u00b5m'))
+ elif unit_system == 'IMPERIAL':
+ scale_steps = ((5280, 'mi'), (1, '\''),
+ (1 / 12, '"'), (1 / 12000, 'thou'))
+ scale /= 0.3048 # BU to feet
+ else:
+ scale_steps = ((1, ' BU'),)
+ separate_units = False
+
+ return (scale, scale_steps, separate_units)
+
+
+def convert_distance(val, units_info, precision=5):
+ scale, scale_steps, separate_units = units_info
+ sval = val * scale
+ idx = 0
+ while idx < len(scale_steps) - 1:
+ if sval >= scale_steps[idx][0]:
+ break
+ idx += 1
+ factor, suffix = scale_steps[idx]
+ sval /= factor
+ if not separate_units or idx == len(scale_steps) - 1:
+ dval = str(round(sval, precision)) + suffix
+ else:
+ ival = int(sval)
+ dval = str(round(ival, precision)) + suffix
+ fval = sval - ival
+ idx += 1
+ while idx < len(scale_steps):
+ fval *= scale_steps[idx - 1][0] / scale_steps[idx][0]
+ if fval >= 1:
+ dval += ' ' \
+ + ("%.1f" % fval) \
+ + scale_steps[idx][1]
+ break
+ idx += 1
+
+ return dval
+
+
+def location_3d_to_region_2d(region, rv3d, coord):
+ prj = rv3d.perspective_matrix @ Vector((coord[0], coord[1], coord[2], 1.0))
+ width_half = region.width / 2.0
+ height_half = region.height / 2.0
+ return Vector((width_half + width_half * (prj.x / prj.w),
+ height_half + height_half * (prj.y / prj.w),
+ prj.z / prj.w
+ ))
+
+
+def out_Location(rv3d, orig, vector):
+ view_matrix = rv3d.view_matrix
+ v1 = (int(view_matrix[0][0]*1.5), int(view_matrix[0][1]*1.5), int(view_matrix[0][2]*1.5))
+ v2 = (int(view_matrix[1][0]*1.5), int(view_matrix[1][1]*1.5), int(view_matrix[1][2]*1.5))
+
+ hit = intersect_ray_tri((1,0,0), (0,1,0), (0,0,0), (vector), (orig), False)
+ if hit is None:
+ hit = intersect_ray_tri(v1, v2, (0,0,0), (vector), (orig), False)
+ if hit is None:
+ hit = intersect_ray_tri(v1, v2, (0,0,0), (-vector), (orig), False)
+ if hit is None:
+ hit = Vector()
+ return hit
+
+
+def get_snap_bm_geom(sctx, main_snap_obj, mcursor):
+
+ r_snp_obj, r_loc, r_elem, r_elem_co = sctx.snap_get(mcursor, main_snap_obj)
+ r_view_vector, r_orig = sctx.last_ray
+ r_bm = None
+ r_bm_geom = None
+
+ if r_snp_obj is not None:
+ obj = r_snp_obj.data[0]
+
+ if obj.type == 'MESH' and obj.data.is_editmode:
+ r_bm = bmesh.from_edit_mesh(obj.data)
+ if len(r_elem) == 1:
+ r_bm_geom = r_bm.verts[r_elem[0]]
+
+ elif len(r_elem) == 2:
+ try:
+ v1 = r_bm.verts[r_elem[0]]
+ v2 = r_bm.verts[r_elem[1]]
+ r_bm_geom = r_bm.edges.get([v1, v2])
+ except IndexError:
+ r_bm.verts.ensure_lookup_table()
+
+ elif len(r_elem) == 3:
+ tri = [
+ r_bm.verts[r_elem[0]],
+ r_bm.verts[r_elem[1]],
+ r_bm.verts[r_elem[2]],
+ ]
+
+ faces = set(tri[0].link_faces).intersection(tri[1].link_faces, tri[2].link_faces)
+ if len(faces) == 1:
+ r_bm_geom = faces.pop()
+ else:
+ i = -2
+ edge = None
+ while not edge and i != 1:
+ edge = r_bm.edges.get([tri[i], tri[i + 1]])
+ i += 1
+ if edge:
+ for l in edge.link_loops:
+ if l.link_loop_next.vert == tri[i] or l.link_loop_prev.vert == tri[i - 2]:
+ r_bm_geom = l.face
+ break
+
+ return r_snp_obj, r_loc, r_elem, r_elem_co, r_view_vector, r_orig, r_bm, r_bm_geom
+
+
+class SnapCache:
+ snp_obj = None
+ elem = None
+
+ v0 = None
+ v1 = None
+ vmid = None
+ vperp = None
+
+ v2d0 = None
+ v2d1 = None
+ v2dmid = None
+ v2dperp = None
+
+ is_increment = False
+
+
+def snap_utilities(
+ sctx, main_snap_obj,
+ mcursor,
+ constrain = None,
+ previous_vert = None,
+ increment = 0.0):
+
+ snp_obj, loc, elem, elem_co, view_vector, orig, bm, bm_geom = get_snap_bm_geom(sctx, main_snap_obj, mcursor)
+
+ is_increment = False
+ r_loc = None
+ r_type = None
+ r_len = 0.0
+
+ if not snp_obj:
+ is_increment = True
+ if constrain:
+ end = orig + view_vector
+ t_loc = intersect_line_line(constrain[0], constrain[1], orig, end)
+ if t_loc is None:
+ t_loc = constrain
+ r_loc = t_loc[0]
+ else:
+ r_type = 'OUT'
+ r_loc = out_Location(sctx.rv3d, orig, view_vector)
+
+ elif len(elem) == 1:
+ r_type = 'VERT'
+ if constrain:
+ r_loc = intersect_point_line(loc, constrain[0], constrain[1])[0]
+ else:
+ r_loc = loc
+
+ elif len(elem) == 2:
+ if SnapCache.snp_obj is not snp_obj or not (elem == SnapCache.elem).all():
+ SnapCache.snp_obj = snp_obj
+ SnapCache.elem = elem
+
+ SnapCache.v0 = elem_co[0]
+ SnapCache.v1 = elem_co[1]
+ SnapCache.vmid = 0.5 * (SnapCache.v0 + SnapCache.v1)
+ SnapCache.v2d0 = location_3d_to_region_2d(sctx.region, sctx.rv3d, SnapCache.v0)
+ SnapCache.v2d1 = location_3d_to_region_2d(sctx.region, sctx.rv3d, SnapCache.v1)
+ SnapCache.v2dmid = location_3d_to_region_2d(sctx.region, sctx.rv3d, SnapCache.vmid)
+
+ if previous_vert and (not bm_geom or previous_vert not in bm_geom.verts):
+ pvert_co = main_snap_obj.mat @ previous_vert.co
+ perp_point = intersect_point_line(pvert_co, SnapCache.v0, SnapCache.v1)
+ SnapCache.vperp = perp_point[0]
+ #factor = point_perpendicular[1]
+ SnapCache.v2dperp = location_3d_to_region_2d(sctx.region, sctx.rv3d, perp_point[0])
+ SnapCache.is_increment = False
+ else:
+ SnapCache.is_increment = True
+
+ #else: SnapCache.v2dperp = None
+
+ if constrain:
+ t_loc = intersect_line_line(constrain[0], constrain[1], SnapCache.v0, SnapCache.v1)
+
+ if t_loc is None:
+ is_increment = True
+ end = orig + view_vector
+ t_loc = intersect_line_line(constrain[0], constrain[1], orig, end)
+ r_loc = t_loc[0]
+
+ elif SnapCache.v2dperp and\
+ abs(SnapCache.v2dperp[0] - mcursor[0]) < 10 and abs(SnapCache.v2dperp[1] - mcursor[1]) < 10:
+ r_type = 'PERPENDICULAR'
+ r_loc = SnapCache.vperp
+
+ elif abs(SnapCache.v2dmid[0] - mcursor[0]) < 10 and abs(SnapCache.v2dmid[1] - mcursor[1]) < 10:
+ r_type = 'CENTER'
+ r_loc = SnapCache.vmid
+
+ else:
+ is_increment = SnapCache.is_increment
+
+ r_type = 'EDGE'
+ r_loc = loc
+
+ elif len(elem) == 3:
+ r_type = 'FACE'
+
+ if constrain:
+ is_increment = False
+ r_loc = intersect_point_line(loc, constrain[0], constrain[1])[0]
+ else:
+ is_increment = True
+ r_loc = loc
+
+ if previous_vert:
+ pv_co = main_snap_obj.mat @ previous_vert.co
+ vec = r_loc - pv_co
+ if is_increment and increment:
+ r_len = round((1 / increment) * vec.length) * increment
+ r_loc = r_len * vec.normalized() + pv_co
+ else:
+ r_len = vec.length
+
+ return snp_obj, loc, r_loc, r_type, bm, bm_geom, r_len
+
+snap_utilities.cache = SnapCache
diff --git a/mesh_snap_utilities_line/icons/ops.mesh.make_line.dat b/mesh_snap_utilities_line/icons/ops.mesh.make_line.dat
new file mode 100644
index 00000000..fa738db9
--- /dev/null
+++ b/mesh_snap_utilities_line/icons/ops.mesh.make_line.dat
Binary files differ
diff --git a/mesh_snap_utilities_line/ops_line.py b/mesh_snap_utilities_line/ops_line.py
new file mode 100644
index 00000000..11a40871
--- /dev/null
+++ b/mesh_snap_utilities_line/ops_line.py
@@ -0,0 +1,553 @@
+### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 3
+# of the License, or (at your option) any later version.
+#
+# This program 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+import bpy, bmesh
+
+from bpy.props import FloatProperty
+
+from mathutils import Vector
+
+from mathutils.geometry import intersect_point_line
+
+from .common_classes import (
+ SnapDrawn,
+ CharMap,
+ SnapNavigation,
+ g_snap_widget, #TODO: remove
+ )
+
+from .common_utilities import (
+ get_units_info,
+ convert_distance,
+ snap_utilities,
+ )
+
+if not __package__:
+ __package__ = "mesh_snap_utilities"
+
+
+def get_closest_edge(bm, point, dist):
+ r_edge = None
+ for edge in bm.edges:
+ v1 = edge.verts[0].co
+ v2 = edge.verts[1].co
+ # Test the BVH (AABB) first
+ for i in range(3):
+ if v1[i] <= v2[i]:
+ isect = v1[i] - dist <= point[i] <= v2[i] + dist
+ else:
+ isect = v2[i] - dist <= point[i] <= v1[i] + dist
+
+ if not isect:
+ break
+ else:
+ ret = intersect_point_line(point, v1, v2)
+
+ if ret[1] < 0.0:
+ tmp = v1
+ elif ret[1] > 1.0:
+ tmp = v2
+ else:
+ tmp = ret[0]
+
+ new_dist = (point - tmp).length
+ if new_dist <= dist:
+ dist = new_dist
+ r_edge = edge
+
+ return r_edge
+
+
+def get_loose_linked_edges(bmvert):
+ linked = [e for e in bmvert.link_edges if not e.link_faces]
+ for e in linked:
+ linked += [le for v in e.verts if not v.link_faces for le in v.link_edges if le not in linked]
+ return linked
+
+
+def draw_line(self, bm_geom, location):
+ obj = self.main_snap_obj.data[0]
+ bm = self.main_bm
+ split_faces = set()
+
+ update_edit_mesh = False
+
+ if bm_geom is None:
+ vert = bm.verts.new(location)
+ self.list_verts.append(vert)
+ update_edit_mesh = True
+
+ elif isinstance(bm_geom, bmesh.types.BMVert):
+ if (bm_geom.co - location).length_squared < .001:
+ if self.list_verts == [] or self.list_verts[-1] != bm_geom:
+ self.list_verts.append(bm_geom)
+ else:
+ vert = bm.verts.new(location)
+ self.list_verts.append(vert)
+ update_edit_mesh = True
+
+ elif isinstance(bm_geom, bmesh.types.BMEdge):
+ self.list_edges.append(bm_geom)
+ ret = intersect_point_line(location, bm_geom.verts[0].co, bm_geom.verts[1].co)
+
+ if (ret[0] - location).length_squared < .001:
+ if ret[1] == 0.0:
+ vert = bm_geom.verts[0]
+ elif ret[1] == 1.0:
+ vert = bm_geom.verts[1]
+ else:
+ edge, vert = bmesh.utils.edge_split(bm_geom, bm_geom.verts[0], ret[1])
+ update_edit_mesh = True
+
+ self.list_verts.append(vert)
+ self.geom = vert # hack to highlight in the drawing
+ # self.list_edges.append(edge)
+
+ else: # constrain point is near
+ vert = bm.verts.new(location)
+ self.list_verts.append(vert)
+ update_edit_mesh = True
+
+ elif isinstance(bm_geom, bmesh.types.BMFace):
+ split_faces.add(bm_geom)
+ vert = bm.verts.new(location)
+ self.list_verts.append(vert)
+ update_edit_mesh = True
+
+ # draw, split and create face
+ if len(self.list_verts) >= 2:
+ v1, v2 = self.list_verts[-2:]
+ edge = bm.edges.get([v1, v2])
+ if edge:
+ self.list_edges.append(edge)
+
+ else:
+ if not v2.link_edges:
+ edge = bm.edges.new([v1, v2])
+ self.list_edges.append(edge)
+ else: # split face
+ v1_link_faces = v1.link_faces
+ v2_link_faces = v2.link_faces
+ if v1_link_faces and v2_link_faces:
+ split_faces.update(set(v1_link_faces).intersection(v2_link_faces))
+
+ else:
+ if v1_link_faces:
+ faces = v1_link_faces
+ co2 = v2.co.copy()
+ else:
+ faces = v2_link_faces
+ co2 = v1.co.copy()
+
+ for face in faces:
+ if bmesh.geometry.intersect_face_point(face, co2):
+ co = co2 - face.calc_center_median()
+ if co.dot(face.normal) < 0.001:
+ split_faces.add(face)
+
+ if split_faces:
+ edge = bm.edges.new([v1, v2])
+ self.list_edges.append(edge)
+ ed_list = get_loose_linked_edges(v2)
+ for face in split_faces:
+ facesp = bmesh.utils.face_split_edgenet(face, ed_list)
+ del split_faces
+ else:
+ if self.intersect:
+ facesp = bmesh.ops.connect_vert_pair(bm, verts=[v1, v2], verts_exclude=bm.verts)
+ # print(facesp)
+ if not self.intersect or not facesp['edges']:
+ edge = bm.edges.new([v1, v2])
+ self.list_edges.append(edge)
+ else:
+ for edge in facesp['edges']:
+ self.list_edges.append(edge)
+ update_edit_mesh = True
+
+ # create face
+ if self.create_face:
+ ed_list = set(self.list_edges)
+ for edge in v2.link_edges:
+ for vert in edge.verts:
+ if vert != v2 and vert in self.list_verts:
+ ed_list.add(edge)
+ break
+ else:
+ continue
+ # Inner loop had a break, break the outer
+ break
+
+ ed_list.update(get_loose_linked_edges(v2))
+
+ bmesh.ops.edgenet_fill(bm, edges=list(ed_list))
+ update_edit_mesh = True
+ # print('face created')
+
+ if update_edit_mesh:
+ obj.data.update_gpu_tag()
+ obj.data.update_tag()
+ obj.update_from_editmode()
+ obj.update_tag()
+ bmesh.update_edit_mesh(obj.data)
+ self.sctx.tag_update_drawn_snap_object(self.main_snap_obj)
+ #bm.verts.index_update()
+
+ if not self.wait_for_input:
+ bpy.ops.ed.undo_push(message="Undo draw line*")
+
+ return [obj.matrix_world @ v.co for v in self.list_verts]
+
+
+class SnapUtilitiesLine(bpy.types.Operator):
+ """Make Lines. Connect them to split faces"""
+ bl_idname = "mesh.make_line"
+ bl_label = "Line Tool"
+ bl_options = {'REGISTER'}
+
+ wait_for_input: bpy.props.BoolProperty(name="Wait for Input", default=True)
+
+ constrain_keys = {
+ 'X': Vector((1,0,0)),
+ 'Y': Vector((0,1,0)),
+ 'Z': Vector((0,0,1)),
+ 'RIGHT_SHIFT': 'shift',
+ 'LEFT_SHIFT': 'shift',
+ }
+
+ def _exit(self, context):
+ del self.main_bm #avoids unpredictable crashs
+ del self.bm
+ del self.list_edges
+ del self.list_verts
+ del self.list_verts_co
+
+ bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
+ context.area.header_text_set(None)
+
+ if not self.snap_widget:
+ self.sctx.free()
+ del self.draw_cache
+ else:
+ self.sctx = None
+ self.draw_cache = None
+
+ #Restore initial state
+ context.tool_settings.mesh_select_mode = self.select_mode
+ context.space_data.overlay.show_face_center = self.show_face_center
+
+ def modal(self, context, event):
+ if self.navigation_ops.run(context, event, self.prevloc if self.vector_constrain else self.location):
+ return {'RUNNING_MODAL'}
+
+ context.area.tag_redraw()
+
+ if event.ctrl and event.type == 'Z' and event.value == 'PRESS':
+ if self.bm:
+ self.bm.free()
+ self.bm = None
+ if self.main_bm:
+ self.main_bm.free()
+ bpy.ops.ed.undo()
+ self.vector_constrain = None
+ self.list_verts_co = []
+ self.list_verts = []
+ self.list_edges = []
+ bpy.ops.object.mode_set(mode='EDIT') # just to be sure
+ self.main_bm = bmesh.from_edit_mesh(self.main_snap_obj.data[0].data)
+ self.sctx.tag_update_drawn_snap_object(self.main_snap_obj)
+ return {'RUNNING_MODAL'}
+
+ is_making_lines = bool(self.list_verts_co)
+
+ if event.type == 'MOUSEMOVE' or self.bool_update:
+ if self.rv3d.view_matrix != self.rotMat:
+ self.rotMat = self.rv3d.view_matrix.copy()
+ self.bool_update = True
+ snap_utilities.cache.snp_obj = None # hack for snap edge elemens update
+ else:
+ self.bool_update = False
+
+ mval = Vector((event.mouse_region_x, event.mouse_region_y))
+
+ self.snap_obj, self.prevloc, self.location, self.type, self.bm, self.geom, self.len = snap_utilities(
+ self.sctx,
+ self.main_snap_obj,
+ mval,
+ constrain=self.vector_constrain,
+ previous_vert=(self.list_verts[-1] if self.list_verts else None),
+ increment=self.incremental
+ )
+
+ if self.snap_to_grid and self.type == 'OUT':
+ loc = self.location / self.rd
+ self.location = Vector((round(loc.x),
+ round(loc.y),
+ round(loc.z))) * self.rd
+
+ if self.keyf8 and is_making_lines:
+ lloc = self.list_verts_co[-1]
+ view_vec, orig = self.sctx.last_ray
+ location = intersect_point_line(lloc, orig, (orig + view_vec))
+ vec = (location[0] - lloc)
+ ax, ay, az = abs(vec.x), abs(vec.y), abs(vec.z)
+ vec.x = ax > ay > az or ax > az > ay
+ vec.y = ay > ax > az or ay > az > ax
+ vec.z = az > ay > ax or az > ax > ay
+ if vec == Vector():
+ self.vector_constrain = None
+ else:
+ vc = lloc + vec
+ try:
+ if vc != self.vector_constrain[1]:
+ type = 'X' if vec.x else 'Y' if vec.y else 'Z' if vec.z else 'shift'
+ self.vector_constrain = [lloc, vc, type]
+ except:
+ type = 'X' if vec.x else 'Y' if vec.y else 'Z' if vec.z else 'shift'
+ self.vector_constrain = [lloc, vc, type]
+
+ if event.value == 'PRESS':
+ if is_making_lines and (event.ascii in CharMap.ascii or event.type in CharMap.type):
+ CharMap.modal(self, context, event)
+
+ elif event.type in self.constrain_keys:
+ self.bool_update = True
+ self.keyf8 = False
+
+ if self.vector_constrain and self.vector_constrain[2] == event.type:
+ self.vector_constrain = ()
+
+ else:
+ if event.shift:
+ if isinstance(self.geom, bmesh.types.BMEdge):
+ if is_making_lines:
+ loc = self.list_verts_co[-1]
+ self.vector_constrain = (loc, loc + self.geom.verts[1].co -
+ self.geom.verts[0].co, event.type)
+ else:
+ self.vector_constrain = [self.main_snap_obj.mat @ v.co for
+ v in self.geom.verts] + [event.type]
+ else:
+ if is_making_lines:
+ loc = self.list_verts_co[-1]
+ else:
+ loc = self.location
+ self.vector_constrain = [loc, loc + self.constrain_keys[event.type], event.type]
+
+ elif event.type == 'LEFTMOUSE':
+ if not is_making_lines and self.bm:
+ self.main_snap_obj = self.snap_obj
+ self.main_bm = self.bm
+
+ mat_inv = self.main_snap_obj.mat.inverted_safe()
+ point = mat_inv @ self.location
+ # with constraint the intersection can be in a different element of the selected one
+ geom2 = self.geom
+ if geom2:
+ geom2.select = False
+
+ if self.vector_constrain:
+ geom2 = get_closest_edge(self.main_bm, point, .001)
+
+ self.vector_constrain = None
+ self.list_verts_co = draw_line(self, geom2, point)
+
+ elif event.type == 'F8':
+ self.vector_constrain = None
+ self.keyf8 = self.keyf8 is False
+
+ elif event.value == 'RELEASE':
+ if event.type in {'RET', 'NUMPAD_ENTER'}:
+ if self.length_entered != "" and self.list_verts_co:
+ try:
+ text_value = bpy.utils.units.to_value(self.unit_system, 'LENGTH', self.length_entered)
+ vector = (self.location - self.list_verts_co[-1]).normalized()
+ location = (self.list_verts_co[-1] + (vector * text_value))
+
+ mat_inv = self.main_snap_obj.mat.inverted_safe()
+ self.list_verts_co = draw_line(self, self.geom, mat_inv @ location)
+ self.length_entered = ""
+ self.vector_constrain = None
+
+ except: # ValueError:
+ self.report({'INFO'}, "Operation not supported yet")
+
+ if not self.wait_for_input:
+ self._exit(context)
+ return {'FINISHED'}
+
+ elif event.type in {'RIGHTMOUSE', 'ESC'}:
+ if not self.wait_for_input or not is_making_lines or event.type == 'ESC':
+ self._exit(context)
+ return {'FINISHED'}
+ else:
+ snap_utilities.cache.snp_obj = None # hack for snap edge elemens update
+ self.vector_constrain = None
+ self.list_edges = []
+ self.list_verts = []
+ self.list_verts_co = []
+
+ a = ""
+ if is_making_lines:
+ if self.length_entered:
+ pos = self.line_pos
+ a = 'length: ' + self.length_entered[:pos] + '|' + self.length_entered[pos:]
+ else:
+ length = self.len
+ length = convert_distance(length, self.uinfo)
+ a = 'length: ' + length
+
+ context.area.header_text_set(text = "hit: %.3f %.3f %.3f %s" % (*self.location, a))
+
+ if True or is_making_lines:
+ return {'RUNNING_MODAL'}
+
+ return {'PASS_THROUGH'}
+
+ def draw_callback_px(self):
+ if self.bm:
+ self.draw_cache.draw_elem(self.snap_obj, self.bm, self.geom)
+ self.draw_cache.draw(self.type, self.location, self.list_verts_co, self.vector_constrain, self.prevloc)
+
+ def invoke(self, context, event):
+ if context.space_data.type == 'VIEW_3D':
+ # print('name', __name__, __package__)
+
+ #Store current state
+ self.select_mode = context.tool_settings.mesh_select_mode[:]
+ self.show_face_center = context.space_data.overlay.show_face_center
+
+ #Modify the current state
+ bpy.ops.mesh.select_all(action='DESELECT')
+ context.tool_settings.mesh_select_mode = (True, False, True)
+ context.space_data.overlay.show_face_center = True
+
+ #Store values from 3d view context
+ self.rv3d = context.region_data
+ self.rotMat = self.rv3d.view_matrix.copy()
+ # self.obj_glmatrix = bgl.Buffer(bgl.GL_FLOAT, [4, 4], self.obj_matrix.transposed())
+
+ #Init event variables
+ self.keyf8 = False
+ self.snap_face = True
+
+ self.snap_widget = g_snap_widget[0]
+
+ if self.snap_widget is not None:
+ self.draw_cache = self.snap_widget.draw_cache
+ self.sctx = self.snap_widget.sctx
+
+ preferences = self.snap_widget.preferences
+ else:
+ preferences = context.user_preferences.addons[__package__].preferences
+
+ #Init DrawCache
+ self.draw_cache = SnapDrawn(
+ preferences.out_color,
+ preferences.face_color,
+ preferences.edge_color,
+ preferences.vert_color,
+ preferences.center_color,
+ preferences.perpendicular_color,
+ preferences.constrain_shift_color,
+ tuple(context.user_preferences.themes[0].user_interface.axis_x) + (1.0,),
+ tuple(context.user_preferences.themes[0].user_interface.axis_y) + (1.0,),
+ tuple(context.user_preferences.themes[0].user_interface.axis_z) + (1.0,)
+ )
+
+ #Init Snap Context
+ from .snap_context_l import SnapContext
+
+ self.sctx = SnapContext(context.region, context.space_data)
+ self.sctx.set_pixel_dist(12)
+ self.sctx.use_clip_planes(True)
+
+ if preferences.outer_verts:
+ for base in context.visible_bases:
+ self.sctx.add_obj(base.object, base.object.matrix_world)
+
+ self.sctx.set_snap_mode(True, True, self.snap_face)
+
+ #Configure the unit of measure
+ self.unit_system = context.scene.unit_settings.system
+ scale = context.scene.unit_settings.scale_length
+ separate_units = context.scene.unit_settings.use_separate
+ self.uinfo = get_units_info(scale, self.unit_system, separate_units)
+ scale /= context.space_data.overlay.grid_scale * preferences.relative_scale
+ self.rd = bpy.utils.units.to_value(self.unit_system, 'LENGTH', str(1 / scale))
+
+ self.intersect = preferences.intersect
+ self.create_face = preferences.create_face
+ self.outer_verts = preferences.outer_verts
+ self.snap_to_grid = preferences.increments_grid
+ self.incremental = bpy.utils.units.to_value(self.unit_system, 'LENGTH', str(preferences.incremental))
+
+ if self.snap_widget:
+ self.geom = self.snap_widget.geom
+ self.type = self.snap_widget.type
+ self.location = self.snap_widget.loc
+ if self.snap_widget.snap_obj:
+ context.view_layer.objects.active = self.snap_widget.snap_obj.data[0]
+ else:
+ #init these variables to avoid errors
+ self.geom = None
+ self.type = 'OUT'
+ self.location = Vector()
+
+ self.prevloc = Vector()
+ self.list_verts = []
+ self.list_edges = []
+ self.list_verts_co = []
+ self.bool_update = False
+ self.vector_constrain = ()
+ self.len = 0
+ self.length_entered = ""
+ self.line_pos = 0
+
+ self.navigation_ops = SnapNavigation(context, True)
+
+ active_object = context.active_object
+
+ #Create a new object
+ if active_object is None or active_object.type != 'MESH':
+ mesh = bpy.data.meshes.new("")
+ active_object = bpy.data.objects.new("", mesh)
+ context.scene.objects.link(obj)
+ context.scene.objects.active = active_object
+ else:
+ mesh = active_object.data
+
+ self.main_snap_obj = self.snap_obj = self.sctx._get_snap_obj_by_obj(active_object)
+ self.main_bm = self.bm = bmesh.from_edit_mesh(mesh) #remove at end
+
+ #modals
+ if not self.wait_for_input:
+ self.modal(context, event)
+
+ self._handle = bpy.types.SpaceView3D.draw_handler_add(self.draw_callback_px, (), 'WINDOW', 'POST_VIEW')
+ context.window_manager.modal_handler_add(self)
+
+ return {'RUNNING_MODAL'}
+ else:
+ self.report({'WARNING'}, "Active space must be a View3d")
+ return {'CANCELLED'}
+
+
+def register():
+ bpy.utils.register_class(SnapUtilitiesLine)
+
+if __name__ == "__main__":
+ register()
diff --git a/mesh_snap_utilities_line/preferences.py b/mesh_snap_utilities_line/preferences.py
new file mode 100644
index 00000000..98b89774
--- /dev/null
+++ b/mesh_snap_utilities_line/preferences.py
@@ -0,0 +1,113 @@
+### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 3
+# of the License, or (at your option) any later version.
+#
+# This program 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+import bpy
+from bpy.props import (
+ EnumProperty,
+ StringProperty,
+ BoolProperty,
+ IntProperty,
+ FloatVectorProperty,
+ FloatProperty,
+ )
+
+
+class SnapUtilitiesLinePreferences(bpy.types.AddonPreferences):
+ # this must match the addon name, use '__package__'
+ # when defining this in a submodule of a python package.
+ bl_idname = __package__
+
+ intersect: BoolProperty(
+ name="Intersect",
+ description="intersects created line with the existing edges, even if the lines do not intersect",
+ default=True)
+
+ create_face: BoolProperty(
+ name="Create faces",
+ description="Create faces defined by enclosed edges",
+ default=False)
+
+ outer_verts: BoolProperty(
+ name="Snap to outer vertices",
+ description="The vertices of the objects are not activated also snapped",
+ default=True)
+
+ increments_grid: BoolProperty(
+ name="Increments of Grid",
+ description="Snap to increments of grid",
+ default=False)
+
+ incremental: FloatProperty(
+ name="Incremental",
+ description="Snap in defined increments",
+ default=0,
+ min=0,
+ step=1,
+ precision=3)
+
+ relative_scale: FloatProperty(
+ name="Relative Scale",
+ description="Value that divides the global scale",
+ default=1,
+ min=0,
+ step=1,
+ precision=3)
+
+ out_color: FloatVectorProperty(name="OUT", default=(0.0, 0.0, 0.0, 0.5), size=4, subtype="COLOR", min=0, max=1)
+ face_color: FloatVectorProperty(name="FACE", default=(1.0, 0.8, 0.0, 1.0), size=4, subtype="COLOR", min=0, max=1)
+ edge_color: FloatVectorProperty(name="EDGE", default=(0.0, 0.8, 1.0, 1.0), size=4, subtype="COLOR", min=0, max=1)
+ vert_color: FloatVectorProperty(name="VERT", default=(1.0, 0.5, 0.0, 1.0), size=4, subtype="COLOR", min=0, max=1)
+ center_color: FloatVectorProperty(name="CENTER", default=(1.0, 0.0, 1.0, 1.0), size=4, subtype="COLOR", min=0, max=1)
+ perpendicular_color: FloatVectorProperty(name="PERPENDICULAR", default=(0.1, 0.5, 0.5, 1.0), size=4, subtype="COLOR", min=0, max=1)
+ constrain_shift_color: FloatVectorProperty(name="SHIFT CONSTRAIN", default=(0.8, 0.5, 0.4, 1.0), size=4, subtype="COLOR", min=0, max=1)
+
+ def draw(self, context):
+ layout = self.layout
+
+ layout.label(text="Snap Colors:")
+ split = layout.split()
+
+ col = split.column()
+ col.prop(self, "out_color")
+ col.prop(self, "constrain_shift_color")
+ col = split.column()
+ col.prop(self, "face_color")
+ col = split.column()
+ col.prop(self, "edge_color")
+ col = split.column()
+ col.prop(self, "vert_color")
+ col = split.column()
+ col.prop(self, "center_color")
+ col = split.column()
+ col.prop(self, "perpendicular_color")
+
+ row = layout.row()
+
+ col = row.column()
+ #col.label(text="Snap Items:")
+ col.prop(self, "incremental")
+ col.prop(self, "increments_grid")
+ if self.increments_grid:
+ col.prop(self, "relative_scale")
+
+ col.prop(self, "outer_verts")
+ row.separator()
+
+ col = row.column()
+ col.label(text="Line Tool:")
+ col.prop(self, "intersect")
+ col.prop(self, "create_face")
diff --git a/modules/snap_context/__init__.py b/mesh_snap_utilities_line/snap_context_l/__init__.py
index 77e84726..c4b03156 100644
--- a/modules/snap_context/__init__.py
+++ b/mesh_snap_utilities_line/snap_context_l/__init__.py
@@ -52,6 +52,100 @@ class _SnapObjectData():
self.mat = omat
+class _SnapOffscreen():
+ bound = None
+ def __init__(self, width, height):
+ import ctypes
+
+ self.freed = False
+ self.is_bound = False
+
+ self.width = width
+ self.height = height
+
+ self.fbo = bgl.Buffer(bgl.GL_INT, 1)
+ self.buf_color = bgl.Buffer(bgl.GL_INT, 1)
+ self.buf_depth = bgl.Buffer(bgl.GL_INT, 1)
+
+ self.cur_fbo = bgl.Buffer(bgl.GL_INT, 1)
+ self.cur_viewport = bgl.Buffer(bgl.GL_INT, 4)
+
+ bgl.glGenRenderbuffers(1, self.buf_depth)
+ bgl.glBindRenderbuffer(bgl.GL_RENDERBUFFER, self.buf_depth[0])
+ bgl.glRenderbufferStorage(bgl.GL_RENDERBUFFER, bgl.GL_DEPTH_COMPONENT, width, height)
+
+ bgl.glGenTextures(1, self.buf_color)
+ bgl.glBindTexture(bgl.GL_TEXTURE_2D, self.buf_color[0])
+ NULL = bgl.Buffer(bgl.GL_INT, 1, (ctypes.c_int32 * 1).from_address(0))
+ bgl.glTexImage2D(bgl.GL_TEXTURE_2D, 0, bgl.GL_R32UI, width, height, 0, bgl.GL_RED_INTEGER, bgl.GL_UNSIGNED_INT, NULL)
+ del NULL
+ bgl.glTexParameteri(bgl.GL_TEXTURE_2D, bgl.GL_TEXTURE_MIN_FILTER, bgl.GL_NEAREST)
+ bgl.glTexParameteri(bgl.GL_TEXTURE_2D, bgl.GL_TEXTURE_MAG_FILTER, bgl.GL_NEAREST)
+
+ bgl.glGetIntegerv(bgl.GL_FRAMEBUFFER_BINDING, self.cur_fbo)
+
+ bgl.glGenFramebuffers(1, self.fbo)
+ bgl.glBindFramebuffer(bgl.GL_FRAMEBUFFER, self.fbo[0])
+ bgl.glFramebufferRenderbuffer(bgl.GL_FRAMEBUFFER, bgl.GL_DEPTH_ATTACHMENT, bgl.GL_RENDERBUFFER, self.buf_depth[0])
+ bgl.glFramebufferTexture(bgl.GL_FRAMEBUFFER, bgl.GL_COLOR_ATTACHMENT0, self.buf_color[0], 0)
+
+ bgl.glDrawBuffers(1, bgl.Buffer(bgl.GL_INT, 1, [bgl.GL_COLOR_ATTACHMENT0]))
+
+ status = bgl.glCheckFramebufferStatus(bgl.GL_FRAMEBUFFER)
+ if status != bgl.GL_FRAMEBUFFER_COMPLETE:
+ print("Framebuffer Invalid", status)
+
+ bgl.glBindFramebuffer(bgl.GL_FRAMEBUFFER, self.cur_fbo[0])
+
+ def bind(self):
+ if self is not _SnapOffscreen.bound:
+ if _SnapOffscreen.bound is None:
+ bgl.glGetIntegerv(bgl.GL_FRAMEBUFFER_BINDING, self.cur_fbo)
+ bgl.glGetIntegerv(bgl.GL_VIEWPORT, self.cur_viewport)
+
+ bgl.glBindFramebuffer(bgl.GL_FRAMEBUFFER, self.fbo[0])
+ bgl.glViewport(0, 0, self.width, self.height)
+ _SnapOffscreen.bound = self
+
+ def unbind(self):
+ if self is _SnapOffscreen.bound:
+ bgl.glBindFramebuffer(bgl.GL_FRAMEBUFFER, self.cur_fbo[0])
+ bgl.glViewport(*self.cur_viewport)
+ _SnapOffscreen.bound = None
+
+ def clear(self):
+ is_bound = self is _SnapOffscreen.bound
+ if not is_bound:
+ self.bind()
+
+ bgl.glColorMask(bgl.GL_TRUE, bgl.GL_TRUE, bgl.GL_TRUE, bgl.GL_TRUE)
+ bgl.glClearColor(0.0, 0.0, 0.0, 0.0)
+
+ bgl.glDepthMask(bgl.GL_TRUE)
+ bgl.glClearDepth(1.0);
+
+ bgl.glClear(bgl.GL_COLOR_BUFFER_BIT | bgl.GL_DEPTH_BUFFER_BIT)
+
+ if not is_bound:
+ self.unbind()
+
+ def __del__(self):
+ if not self.freed:
+ bgl.glDeleteFramebuffers(1, self.fbo)
+ bgl.glDeleteRenderbuffers(1, self.buf_depth)
+ bgl.glDeleteTextures(1, self.buf_color)
+ del self.fbo
+ del self.buf_color
+ del self.buf_depth
+
+ del self.cur_fbo
+ del self.cur_viewport
+
+ def free(self):
+ self.__del__()
+ self.freed = True
+
+
class SnapContext():
"""
Initializes the snap context with the region and space where the snap objects will be added.
@@ -66,8 +160,8 @@ class SnapContext():
"""
def __init__(self, region, space):
- import gpu
- import ctypes
+ #print('Render:', bgl.glGetString(bgl.GL_RENDERER))
+ #print('OpenGL Version:', bgl.glGetString(bgl.GL_VERSION))
self.freed = False
self.snap_objects = []
@@ -87,75 +181,74 @@ class SnapContext():
self.set_pixel_dist(12)
- self._offscreen = gpu.offscreen.new(self.region.width, self.region.height)
-
- self._texture = self._offscreen.color_texture
- bgl.glBindTexture(bgl.GL_TEXTURE_2D, self._texture)
-
- NULL = bgl.Buffer(bgl.GL_INT, 1, (ctypes.c_int32 * 1).from_address(0))
- bgl.glTexImage2D(bgl.GL_TEXTURE_2D, 0, bgl.GL_R32UI, self.region.width, self.region.height, 0, bgl.GL_RED_INTEGER, bgl.GL_UNSIGNED_INT, NULL)
- del NULL
-
- bgl.glTexParameteri(bgl.GL_TEXTURE_2D, bgl.GL_TEXTURE_MIN_FILTER, bgl.GL_NEAREST)
- bgl.glTexParameteri(bgl.GL_TEXTURE_2D, bgl.GL_TEXTURE_MAG_FILTER, bgl.GL_NEAREST)
- bgl.glBindTexture(bgl.GL_TEXTURE_2D, 0)
+ self._offscreen = _SnapOffscreen(self.region.width, self.region.height)
self.winsize = Vector((self._offscreen.width, self._offscreen.height))
+ self._offscreen.clear()
+
## PRIVATE ##
def _get_snap_obj_by_index(self, index):
- for snap_obj in self.snap_objects[:self.drawn_count]:
- data = snap_obj.data[1]
- if index < data.first_index + data.get_tot_elems():
- return snap_obj
+ if index:
+ for snap_obj in self.snap_objects[:self.drawn_count]:
+ data = snap_obj.data[1]
+ if index < data.first_index + data.get_tot_elems():
+ return snap_obj
return None
def _get_nearest_index(self):
+ r_snap_obj = None
+ r_value = 0
+
loc = [self._dist_px, self._dist_px]
d = 1
m = self.threshold
- max = 2 * m - 1
- offset = 1
- last_snap_obj = None
- r_value = 0
- while m < max:
+ max_val = 2 * m - 1
+ last_value = -1
+ find_next_index = self._snap_mode & FACE and self._snap_mode & (VERT | EDGE)
+ while m < max_val:
for i in range(2):
while 2 * loc[i] * d < m:
value = int(self._snap_buffer[loc[0]][loc[1]])
loc[i] += d
- if value >= offset:
+ if value != last_value:
r_value = value
- snap_obj = self._get_snap_obj_by_index(r_value)
-
- if self._snap_mode & FACE and self._snap_mode & (VERT | EDGE) and last_snap_obj != snap_obj:
- data = snap_obj.data[1]
- offset = data.first_index + data.num_tris
- last_snap_obj = snap_obj
- continue
- return snap_obj, r_value
+ if find_next_index:
+ last_value = value
+ r_snap_obj = self._get_snap_obj_by_index(value)
+ if (r_snap_obj is None) or value < (r_snap_obj.data[1].first_index + len(r_snap_obj.data[1].tri_verts)):
+ continue
+ find_next_index = False
+ elif (r_snap_obj is None) or\
+ (value < r_snap_obj.data[1].first_index) or\
+ (value >= (r_snap_obj.data[1].first_index + r_snap_obj.data[1].get_tot_elems())):
+ r_snap_obj = self._get_snap_obj_by_index(value)
+ return r_snap_obj, r_value
d = -d
m += 4 * self._dist_px * d + 1
- return last_snap_obj, r_value
+ return r_snap_obj, r_value
def _get_loc(self, snap_obj, index):
index -= snap_obj.data[1].first_index
gpu_data = snap_obj.data[1]
if gpu_data.draw_tris:
- if index < snap_obj.data[1].num_tris:
+ num_tris = len(snap_obj.data[1].tri_verts)
+ if index < num_tris:
tri_verts = gpu_data.get_tri_verts(index)
- tri_co = [snap_obj.mat * Vector(v) for v in gpu_data.get_tri_co(index)]
+ tri_co = [snap_obj.mat @ Vector(v) for v in gpu_data.get_tri_co(index)]
nor = (tri_co[1] - tri_co[0]).cross(tri_co[2] - tri_co[0])
- return _Internal.intersect_line_plane(self.last_ray[1], self.last_ray[1] + self.last_ray[0], tri_co[0], nor), tri_verts
+ return _Internal.intersect_line_plane(self.last_ray[1], self.last_ray[1] + self.last_ray[0], tri_co[0], nor), tri_verts, tri_co
- index -= gpu_data.num_tris
+ index -= num_tris
if gpu_data.draw_edges:
- if index < snap_obj.data[1].num_edges:
+ num_edges = len(snap_obj.data[1].edge_verts)
+ if index < num_edges:
edge_verts = gpu_data.get_edge_verts(index)
- edge_co = [snap_obj.mat * Vector(v) for v in gpu_data.get_edge_co(index)]
+ edge_co = [snap_obj.mat @ Vector(v) for v in gpu_data.get_edge_co(index)]
fac = _Internal.intersect_ray_segment_fac(*edge_co, *self.last_ray)
if (self._snap_mode) & VERT and (fac < 0.25 or fac > 0.75):
@@ -163,7 +256,7 @@ class SnapContext():
proj_co = _Internal.project_co_v3(self, co)
dist = self.mval - proj_co
if abs(dist.x) < self._dist_px and abs(dist.y) < self._dist_px:
- return co, (edge_verts[0] if fac < 0.5 else edge_verts[1],)
+ return co, (edge_verts[0] if fac < 0.5 else edge_verts[1],), co
if fac <= 0.0:
co = edge_co[0]
@@ -172,15 +265,16 @@ class SnapContext():
else:
co = edge_co[0] + fac * (edge_co[1] - edge_co[0])
- return co, edge_verts
+ return co, edge_verts, edge_co
- index -= gpu_data.num_edges
+ index -= num_edges
if gpu_data.draw_verts:
- if index < snap_obj.data[1].num_verts:
- return snap_obj.mat * Vector(gpu_data.get_loosevert_co(index)), (gpu_data.get_loosevert_index(index),)
+ if index < len(snap_obj.data[1].looseverts):
+ co = snap_obj.mat @ Vector(gpu_data.get_loosevert_co(index))
+ return co, (gpu_data.get_loosevert_index(index),), co
- return None, None
+ return None, None, None
def _get_snap_obj_by_obj(self, obj):
@@ -203,17 +297,27 @@ class SnapContext():
def update_all(self):
self.drawn_count = 0
self._offset_cur = 1
+ self._offscreen.clear()
- bgl.glClearColor(0.0, 0.0, 0.0, 0.0)
- bgl.glClear(bgl.GL_COLOR_BUFFER_BIT | bgl.GL_DEPTH_BUFFER_BIT)
-
- def update_drawn_snap_object(self, snap_obj):
+ def tag_update_drawn_snap_object(self, snap_obj):
if len(snap_obj.data) > 1:
del snap_obj.data[1:]
#self.update_all()
# Update on next snap_get call #
self.proj_mat = None
+ def update_drawn_snap_object(self, snap_obj):
+ if len(snap_obj.data) > 1:
+ _Internal.gpu_Indices_enable_state()
+
+ from .mesh_drawing import GPU_Indices_Mesh
+ snap_vert = self._snap_mode & VERT != 0
+ snap_edge = self._snap_mode & EDGE != 0
+ snap_face = self._snap_mode & FACE != 0
+ snap_obj.data[1] = GPU_Indices_Mesh(snap_obj.data[0], snap_face, snap_edge, snap_vert)
+
+ _Internal.gpu_Indices_restore_state()
+
def use_clip_planes(self, value):
_Internal.gpu_Indices_use_clip_planes(self.rv3d, value)
@@ -221,7 +325,7 @@ class SnapContext():
self._dist_px = int(dist_px)
self._dist_px_sq = self._dist_px ** 2
self.threshold = 2 * self._dist_px + 1
- self._snap_buffer = bgl.Buffer(bgl.GL_FLOAT, (self.threshold, self.threshold))
+ self._snap_buffer = bgl.Buffer(bgl.GL_INT, (self.threshold, self.threshold))
def set_snap_mode(self, snap_to_vert, snap_to_edge, snap_to_face):
snap_mode = 0
@@ -250,8 +354,8 @@ class SnapContext():
self.last_ray = _Internal.region_2d_to_orig_and_view_vector(self.region, self.rv3d, mval)
return self.last_ray
- def snap_get(self, mval):
- ret = None, None
+ def snap_get(self, mval, main_snap_obj = None):
+ ret = None, None, None
self.mval[:] = mval
snap_vert = self._snap_mode & VERT != 0
snap_edge = self._snap_mode & EDGE != 0
@@ -274,15 +378,20 @@ class SnapContext():
ray_dir, ray_orig = self.get_ray(mval)
for i, snap_obj in enumerate(self.snap_objects[self.drawn_count:], self.drawn_count):
obj = snap_obj.data[0]
- bbmin = Vector(obj.bound_box[0])
- bbmax = Vector(obj.bound_box[6])
+ try:
+ bbmin = Vector(obj.bound_box[0])
+ bbmax = Vector(obj.bound_box[6])
+ except ReferenceError:
+ self.snap_objects.remove(snap_obj)
+ continue
if bbmin != bbmax:
- MVP = proj_mat * snap_obj.mat
+ MVP = proj_mat @ snap_obj.mat
mat_inv = snap_obj.mat.inverted()
- ray_orig_local = mat_inv * ray_orig
- ray_dir_local = mat_inv.to_3x3() * ray_dir
- in_threshold = _Internal.intersect_boundbox_threshold(self, MVP, ray_orig_local, ray_dir_local, bbmin, bbmax)
+ ray_orig_local = mat_inv @ ray_orig
+ ray_dir_local = mat_inv.to_3x3() @ ray_dir
+ in_threshold = _Internal.intersect_boundbox_threshold(
+ self, MVP, ray_orig_local, ray_dir_local, bbmin, bbmax)
else:
proj_co = _Internal.project_co_v3(self, snap_obj.mat.translation)
dist = self.mval - proj_co
@@ -295,27 +404,38 @@ class SnapContext():
snap_obj.data.append(GPU_Indices_Mesh(obj, snap_face, snap_edge, snap_vert))
snap_obj.data[1].set_draw_mode(snap_face, snap_edge, snap_vert)
snap_obj.data[1].set_ModelViewMatrix(snap_obj.mat)
- snap_obj.data[1].Draw(self._offset_cur)
+
+ if snap_obj == main_snap_obj:
+ snap_obj.data[1].Draw(self._offset_cur, -0.0001)
+ else:
+ snap_obj.data[1].Draw(self._offset_cur)
self._offset_cur += snap_obj.data[1].get_tot_elems()
- self.snap_objects[self.drawn_count], self.snap_objects[i] = self.snap_objects[i], self.snap_objects[self.drawn_count]
+ tmp = self.snap_objects[self.drawn_count]
+ self.snap_objects[self.drawn_count] = self.snap_objects[i]
+ self.snap_objects[i] = tmp
+
self.drawn_count += 1
bgl.glReadBuffer(bgl.GL_COLOR_ATTACHMENT0)
bgl.glReadPixels(
int(self.mval[0]) - self._dist_px, int(self.mval[1]) - self._dist_px,
self.threshold, self.threshold, bgl.GL_RED_INTEGER, bgl.GL_UNSIGNED_INT, self._snap_buffer)
- bgl.glReadBuffer(bgl.GL_BACK)
+ #bgl.glReadBuffer(bgl.GL_BACK)
+ #import numpy as np
+ #a = np.array(self._snap_buffer)
+ #print(a)
snap_obj, index = self._get_nearest_index()
- #print(index)
+ #print("index:", index)
if snap_obj:
ret = self._get_loc(snap_obj, index)
+ bgl.glDisable(bgl.GL_DEPTH_TEST)
self._offscreen.unbind()
_Internal.gpu_Indices_restore_state()
- return snap_obj, ret[0], ret[1]
+ return (snap_obj, *ret)
def free(self):
self.__del__()
diff --git a/mesh_snap_utilities_line/snap_context_l/mesh_drawing.py b/mesh_snap_utilities_line/snap_context_l/mesh_drawing.py
new file mode 100644
index 00000000..6ef0b0a0
--- /dev/null
+++ b/mesh_snap_utilities_line/snap_context_l/mesh_drawing.py
@@ -0,0 +1,412 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 3
+# of the License, or (at your option) any later version.
+#
+# This program 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+
+import bgl
+import bmesh
+import numpy as np
+from mathutils import Matrix
+import gpu
+
+_Hash = {}
+
+def load_shader(shadername):
+ from os import path
+ with open(path.join(path.dirname(__file__), 'shaders', shadername), 'r') as f:
+ return f.read()
+
+def get_mesh_vert_co_array(me):
+ tot_vco = len(me.vertices)
+ if tot_vco:
+ verts_co = np.empty(len(me.vertices) * 3, 'f4')
+ me.vertices.foreach_get("co", verts_co)
+ verts_co.shape = (-1, 3)
+ return verts_co
+ return None
+
+
+def get_bmesh_vert_co_array(bm):
+ tot_vco = len(bm.verts)
+ if tot_vco:
+ return np.array([v.co for v in bm.verts], 'f4')
+ return None
+
+
+def get_mesh_tri_verts_array(me):
+ me.calc_loop_triangles()
+ len_triangles = len(me.loop_triangles)
+ if len_triangles:
+ tris = np.empty(len_triangles * 3, 'i4')
+ me.loop_triangles.foreach_get("vertices", tris)
+ tris.shape = (-1, 3)
+ return tris
+ return None
+
+
+def get_bmesh_tri_verts_array(bm):
+ l_tri_layer = bm.faces.layers.int.get("l_tri")
+ if l_tri_layer is None:
+ l_tri_layer = bm.faces.layers.int.new("l_tri")
+
+ ltris = bm.calc_loop_triangles()
+ tris = np.empty((len(ltris), 3), 'i4')
+ i = 0
+ last_face = bm.faces[-1]
+ for ltri in ltris:
+ face = ltri[0].face
+ if not face.hide:
+ tris[i] = ltri[0].vert.index, ltri[1].vert.index, ltri[2].vert.index
+ if last_face != face:
+ last_face = face
+ face[l_tri_layer] = i
+ i += 1
+ if i:
+ tris.resize((i, 3), refcheck=False)
+ return tris
+ return None
+
+
+def get_mesh_edge_verts_array(me):
+ tot_edges = len(me.edges)
+ if tot_edges:
+ edge_verts = np.empty(tot_edges * 2, 'i4')
+ me.edges.foreach_get("vertices", edge_verts)
+ edge_verts.shape = tot_edges, 2
+ return edge_verts
+ return None
+
+
+def get_bmesh_edge_verts_array(bm):
+ bm.edges.ensure_lookup_table()
+ edges = [[e.verts[0].index, e.verts[1].index] for e in bm.edges if not e.hide]
+ if edges:
+ return np.array(edges, 'i4')
+ return None
+
+
+def get_mesh_loosevert_array(me, edges):
+ verts = np.arange(len(me.vertices))
+
+ mask = np.in1d(verts, edges, invert=True)
+
+ verts = verts[mask]
+ if len(verts):
+ return verts
+ return None
+
+
+def get_bmesh_loosevert_array(bm):
+ looseverts = [v.index for v in bm.verts if not (v.link_edges or v.hide)]
+ if looseverts:
+ return np.array(looseverts, 'i4')
+ return None
+
+
+class _Mesh_Arrays():
+ def __init__(self, obj, create_tris, create_edges, create_looseverts):
+ self.tri_verts = self.edge_verts = self.looseverts = ()
+ if obj.type == 'MESH':
+ me = obj.data
+ if me.is_editmode:
+ bm = bmesh.from_edit_mesh(me)
+ bm.verts.ensure_lookup_table()
+
+ self.verts_co = get_bmesh_vert_co_array(bm)
+
+ if create_tris:
+ self.tri_verts = get_bmesh_tri_verts_array(bm)
+ if create_edges:
+ self.edge_verts = get_bmesh_edge_verts_array(bm)
+ if create_looseverts:
+ self.looseverts = get_bmesh_loosevert_array(bm)
+
+ del bm
+ else:
+ import bpy
+ self.verts_co = get_mesh_vert_co_array(me)
+
+ if create_tris:
+ self.tri_verts = get_mesh_tri_verts_array(me)
+ if create_edges:
+ self.edge_verts = get_mesh_edge_verts_array(me)
+ if create_looseverts:
+ edge_verts = self.edge_verts
+ if edge_verts is None:
+ edge_verts = get_mesh_edge_verts_array(me)
+ self.looseverts = get_mesh_loosevert_array(me, edge_verts)
+ del edge_verts
+
+
+ else: #TODO
+ self.verts_co = np.zeros((1,3), 'f4')
+ self.looseverts = np.zeros(1, 'i4')
+
+ def __del__(self):
+ del self.tri_verts, self.edge_verts, self.looseverts
+ del self.verts_co
+
+
+class GPU_Indices_Mesh():
+ __slots__ = (
+ "obj",
+ "draw_tris",
+ "draw_edges",
+ "draw_verts",
+ "batch_tris",
+ "batch_edges",
+ "batch_lverts",
+ "verts_co",
+ "tri_verts",
+ "edge_verts",
+ "looseverts",
+ "first_index",
+ "users"
+ )
+
+ shader = None
+
+ @classmethod
+ def end_opengl(cls):
+ del cls.shader
+ del cls.P
+
+ del cls
+
+ @staticmethod
+ def init_opengl():
+ cls = GPU_Indices_Mesh
+ # OpenGL was already initialized, nothing to do here.
+ if cls.shader is not None:
+ return
+
+ import atexit
+
+ # Make sure we only registered the callback once.
+ atexit.unregister(cls.end_opengl)
+ atexit.register(cls.end_opengl)
+
+ cls.shader = gpu.types.GPUShader(
+ load_shader("ID_color_vert.glsl"),
+ load_shader("ID_color_frag.glsl"),
+ )
+ #cls.unif_use_clip_planes = cls.shader.uniform_from_name('use_clip_planes')
+ #cls.unif_clip_plane = cls.shader.uniform_from_name('clip_plane')
+ cls.unif_offset = cls.shader.uniform_from_name('offset')
+
+ cls.P = Matrix()
+
+
+ @staticmethod
+ def set_ModelViewMatrix(MV):
+ gpu.matrix.load_matrix(MV)
+
+
+ def __init__(self, obj, draw_tris, draw_edges, draw_verts):
+ self.obj = obj
+
+ if obj.data in _Hash:
+ src = _Hash[obj.data]
+ dst = self
+
+ dst.draw_tris = src.draw_tris
+ dst.draw_edges = src.draw_edges
+ dst.draw_verts = src.draw_verts
+ dst.batch_tris = src.batch_tris
+ dst.batch_edges = src.batch_edges
+ dst.batch_lverts = src.batch_lverts
+ dst.verts_co = src.verts_co
+ dst.tri_verts = src.tri_verts
+ dst.edge_verts = src.edge_verts
+ dst.looseverts = src.looseverts
+ dst.users = src.users
+ dst.users.append(self)
+
+ update = obj.type == 'MESH' and obj.data.is_editmode
+
+ else:
+ _Hash[obj.data] = self
+ self.users = [self]
+ update = True;
+
+ if update:
+ self.draw_tris = draw_tris
+ self.draw_edges = draw_edges
+ self.draw_verts = draw_verts
+
+ GPU_Indices_Mesh.init_opengl()
+
+ ## Init Array ##
+ mesh_arrays = _Mesh_Arrays(obj, draw_tris, draw_edges, draw_verts)
+
+ if mesh_arrays.verts_co is None:
+ self.draw_tris = False
+ self.draw_edges = False
+ self.draw_verts = False
+ self.tri_verts = None
+ self.edge_verts = None
+ self.looseverts = None
+ return
+
+ ## Create VBO for vertices ##
+ self.verts_co = mesh_arrays.verts_co
+ self.tri_verts = mesh_arrays.tri_verts
+ self.edge_verts = mesh_arrays.edge_verts
+ self.looseverts = mesh_arrays.looseverts
+ del mesh_arrays
+
+ format = gpu.types.GPUVertFormat()
+ format.attr_add(id="pos", comp_type='F32', len=3, fetch_mode='FLOAT')
+
+ vbo = gpu.types.GPUVertBuf(format, len = len(self.verts_co))
+
+ vbo.attr_fill(0, data = self.verts_co)
+
+ ## Create Batch for Tris ##
+ if self.tri_verts is not None:
+ ebo = gpu.types.GPUIndexBuf(type = "TRIS", seq = self.tri_verts)
+ self.batch_tris = gpu.types.GPUBatch(type = "TRIS", buf = vbo, elem = ebo)
+ self.batch_tris.program_set(self.shader)
+ else:
+ self.draw_tris = False
+ self.batch_tris = None
+
+ ## Create Batch for Edges ##
+ if self.edge_verts is not None:
+ ebo = gpu.types.GPUIndexBuf(type = "LINES", seq = self.edge_verts)
+ self.batch_edges = gpu.types.GPUBatch(type = "LINES", buf = vbo, elem = ebo)
+ self.batch_edges.program_set(self.shader)
+ else:
+ self.draw_edges = False
+ self.batch_edges = None
+
+ ## Create Batch for Loose Verts ##
+ if self.looseverts is not None:
+ ebo = gpu.types.GPUIndexBuf(type = "POINTS", seq = self.looseverts)
+ self.batch_lverts = gpu.types.GPUBatch(type = "POINTS", buf = vbo, elem = ebo)
+ self.batch_lverts.program_set(self.shader)
+ else:
+ self.draw_verts = False
+ self.batch_lverts = None
+
+
+ def get_tot_elems(self):
+ tot = 0
+ if self.draw_tris:
+ tot += len(self.tri_verts)
+
+ if self.draw_edges:
+ tot += len(self.edge_verts)
+
+ if self.draw_verts:
+ tot += len(self.looseverts)
+
+ return tot
+
+
+ def set_draw_mode(self, draw_tris, draw_edges, draw_verts):
+ self.draw_tris = draw_tris and self.tri_verts is not None
+ self.draw_edges = draw_edges and self.edge_verts is not None
+ self.draw_verts = draw_verts and self.looseverts is not None
+
+
+ def Draw(self, index_offset, depth_offset = -0.00005):
+ self.first_index = index_offset
+ if self.draw_tris:
+ self.shader.uniform_int("offset", (index_offset,))
+ self.batch_tris.draw(self.shader)
+ index_offset += len(self.tri_verts)
+ bgl.glDepthRange(depth_offset, 1 + depth_offset)
+
+ if self.draw_edges:
+ self.shader.uniform_int("offset", (index_offset,))
+ #bgl.glLineWidth(3.0)
+ self.batch_edges.draw(self.shader)
+ #bgl.glLineWidth(1.0)
+ index_offset += len(self.edge_verts)
+
+ if self.draw_verts:
+ self.shader.uniform_int("offset", (index_offset,))
+ self.batch_lverts.draw(self.shader)
+
+ bgl.glDepthRange(0.0, 1.0)
+
+
+ def get_tri_co(self, index):
+ return self.verts_co[self.tri_verts[index]]
+
+ def get_edge_co(self, index):
+ return self.verts_co[self.edge_verts[index]]
+
+ def get_loosevert_co(self, index):
+ return self.verts_co[self.looseverts[index]]
+
+ def get_loop_tri_co_by_bmface(self, bm, bmface):
+ l_tri_layer = bm.faces.layers.int["l_tri"]
+ tri = bmface[l_tri_layer]
+ return self.verts_co[self.tri_verts[tri : tri + len(bmface.verts) - 2]]
+
+
+ def get_tri_verts(self, index):
+ return self.tri_verts[index]
+
+ def get_edge_verts(self, index):
+ return self.edge_verts[index]
+
+ def get_loosevert_index(self, index):
+ return self.looseverts[index]
+
+
+ def __del__(self):
+ if len(self.users) == 1:
+ self.free_gl()
+ _Hash.pop(obj.data)
+
+ self.user.remove(self)
+ #print('mesh_del', self.obj.name)
+
+
+def gpu_Indices_enable_state():
+ GPU_Indices_Mesh.init_opengl()
+ gpu.matrix.push()
+ gpu.matrix.push_projection()
+ gpu.matrix.load_projection_matrix(GPU_Indices_Mesh.P)
+ GPU_Indices_Mesh.shader.bind()
+
+
+def gpu_Indices_restore_state():
+ gpu.matrix.pop()
+ gpu.matrix.pop_projection()
+
+
+def gpu_Indices_use_clip_planes(rv3d, value):
+ pass #TODO
+ #if rv3d.use_clip_planes:
+ #planes = bgl.Buffer(bgl.GL_FLOAT, (6, 4), rv3d.clip_planes)
+
+ #_store_current_shader_state(PreviousGLState)
+ #GPU_Indices_Mesh.init_opengl()
+ #bgl.glUseProgram(GPU_Indices_Mesh.shader.program)
+ #bgl.glUniform1i(GPU_Indices_Mesh.unif_use_clip_planes, value)
+
+ #bgl.glUniform4fv(GPU_Indices_Mesh.unif_clip_plane, 4, planes)
+
+ #_restore_shader_state(PreviousGLState)
+
+
+def gpu_Indices_set_ProjectionMatrix(P):
+ gpu.matrix.load_projection_matrix(P)
+ GPU_Indices_Mesh.P[:] = P
diff --git a/modules/snap_context/resources/primitive_id_frag.glsl b/mesh_snap_utilities_line/snap_context_l/shaders/ID_color_frag.glsl
index f3f7a124..3e01f7b0 100644
--- a/modules/snap_context/resources/primitive_id_frag.glsl
+++ b/mesh_snap_utilities_line/snap_context_l/shaders/ID_color_frag.glsl
@@ -1,14 +1,15 @@
-#version 120
+uniform int offset;
+#ifdef USE_CLIP_PLANES
uniform bool use_clip_planes;
-varying vec4 clip_distance;
+in vec4 clip_distance;
+#endif
-uniform float offset;
-
-varying float primitive_id_var;
+out uint FragColor;
void main()
{
+#ifdef USE_CLIP_PLANES
if (use_clip_planes &&
((clip_distance[0] < 0) ||
(clip_distance[1] < 0) ||
@@ -17,6 +18,7 @@ void main()
{
discard;
}
+#endif
- gl_FragColor = vec4(offset + primitive_id_var, 0, 0, 0);
+ FragColor = uint(gl_PrimitiveID + offset);
}
diff --git a/mesh_snap_utilities_line/snap_context_l/shaders/ID_color_vert.glsl b/mesh_snap_utilities_line/snap_context_l/shaders/ID_color_vert.glsl
new file mode 100644
index 00000000..fa0afec6
--- /dev/null
+++ b/mesh_snap_utilities_line/snap_context_l/shaders/ID_color_vert.glsl
@@ -0,0 +1,25 @@
+uniform mat4 ModelViewProjectionMatrix;
+
+#ifdef USE_CLIP_PLANES
+uniform mat4 ModelViewMatrix;
+uniform bool use_clip_planes;
+uniform vec4 clip_plane[4];
+out vec4 clip_distance;
+#endif
+
+in vec3 pos;
+
+void main()
+{
+#ifdef USE_CLIP_PLANES
+ if (use_clip_planes) {
+ vec4 g_pos = ModelViewMatrix * vec4(pos, 1.0);
+
+ for (int i = 0; i != 4; i++) {
+ clip_distance[i] = dot(clip_plane[i], g_pos);
+ }
+ }
+#endif
+
+ gl_Position = ModelViewProjectionMatrix * vec4(pos, 1.0);
+}
diff --git a/modules/snap_context/utils_projection.py b/mesh_snap_utilities_line/snap_context_l/utils_projection.py
index d3970b46..cc17aa23 100644
--- a/modules/snap_context/utils_projection.py
+++ b/mesh_snap_utilities_line/snap_context_l/utils_projection.py
@@ -26,8 +26,8 @@ def depth_get(co, ray_start, ray_dir):
def region_2d_to_orig_and_view_vector(region, rv3d, coord):
- viewinv = rv3d.view_matrix.inverted()
- persinv = rv3d.perspective_matrix.inverted()
+ viewinv = rv3d.view_matrix.inverted_safe()
+ persinv = rv3d.perspective_matrix.inverted_safe()
dx = (2.0 * coord[0] / region.width) - 1.0
dy = (2.0 * coord[1] / region.height) - 1.0
@@ -39,7 +39,7 @@ def region_2d_to_orig_and_view_vector(region, rv3d, coord):
w = out.dot(persinv[3].xyz) + persinv[3][3]
- view_vector = ((persinv * out) / w) - origin_start
+ view_vector = ((persinv @ out) / w) - origin_start
else:
view_vector = -viewinv.col[2].xyz
@@ -52,8 +52,11 @@ def region_2d_to_orig_and_view_vector(region, rv3d, coord):
def project_co_v3(sctx, co):
- proj_co = sctx.proj_mat * co.to_4d()
- proj_co.xy /= proj_co.w
+ proj_co = sctx.proj_mat @ co.to_4d()
+ try:
+ proj_co.xy /= proj_co.w
+ except Exception as e:
+ print(e)
win_half = sctx.winsize * 0.5
proj_co[0] = (proj_co[0] + 1.0) * win_half[0]
@@ -210,3 +213,4 @@ def intersect_ray_segment_fac(v0, v1, ray_direction, ray_origin):
c = n - t
cray = c.cross(ray_direction)
return cray.dot(n) / nlen
+
diff --git a/mesh_tissue/tessellate_numpy.py b/mesh_tissue/tessellate_numpy.py
index 57b80b5e..d2361cb4 100644
--- a/mesh_tissue/tessellate_numpy.py
+++ b/mesh_tissue/tessellate_numpy.py
@@ -401,7 +401,7 @@ def tassellate(ob0, ob1, offset, zscale, gen_modifiers, com_modifiers, mode,
# vertex group
if bool_vertex_group:
- new_ob.vertex_groups.new("generator_group")
+ new_ob.vertex_groups.new(name="generator_group")
for i in range(len(new_vertex_group_np)):
new_ob.vertex_groups["generator_group"].add([i],
new_vertex_group_np[i],
diff --git a/mesh_tissue/uv_to_mesh.py b/mesh_tissue/uv_to_mesh.py
index 7c64b5f5..f502c147 100644
--- a/mesh_tissue/uv_to_mesh.py
+++ b/mesh_tissue/uv_to_mesh.py
@@ -141,7 +141,7 @@ class uv_to_mesh(Operator):
try:
for group in ob0.vertex_groups:
index = group.index
- ob.vertex_groups.new(group.name)
+ ob.vertex_groups.new(name=group.name)
for p in me0.polygons:
for vert, loop in zip(p.vertices, p.loop_indices):
ob.vertex_groups[index].add([loop], group.weight(vert), "ADD")
diff --git a/modules/rna_manual_reference.py b/modules/rna_manual_reference.py
index 6f7903ef..1ba5501a 100644
--- a/modules/rna_manual_reference.py
+++ b/modules/rna_manual_reference.py
@@ -253,7 +253,6 @@ url_manual_mapping = (
("bpy.types.cyclesrendersettings*", "render/cycles/settings/index.html#bpy-types-cyclesrendersettings"),
("bpy.types.datatransfermodifier*", "modeling/modifiers/modify/data_transfer.html#bpy-types-datatransfermodifier"),
("bpy.types.dynamicpaintmodifier*", "physics/dynamic_paint/index.html#bpy-types-dynamicpaintmodifier"),
- ("bpy.types.expressioncontroller*", "game_engine/logic/controllers/types/expression.html#bpy-types-expressioncontroller"),
("bpy.types.ffmpegsettings.audio*", "render/output/video.html#bpy-types-ffmpegsettings-audio"),
("bpy.types.followpathconstraint*", "rigging/constraints/relationship/follow_path.html#bpy-types-followpathconstraint"),
("bpy.types.gaussianblursequence*", "editors/vse/sequencer/strips/effects/blur.html#bpy-types-gaussianblursequence"),
@@ -333,7 +332,7 @@ url_manual_mapping = (
("bpy.ops.sound.bake_animation*", "data_system/scenes/properties.html#bpy-ops-sound-bake-animation"),
("bpy.ops.transform.edge_slide*", "modeling/meshes/editing/edges.html#bpy-ops-transform-edge-slide"),
("bpy.ops.transform.vert_slide*", "modeling/meshes/editing/vertices.html#bpy-ops-transform-vert-slide"),
- ("bpy.ops.view3d.select_border*", "editors/3dview/object/selecting/tools.html#bpy-ops-view3d-select-border"),
+ ("bpy.ops.view3d.select_box*", "editors/3dview/object/selecting/tools.html#bpy-ops-view3d-select-border"),
("bpy.ops.view3d.select_circle*", "editors/3dview/object/selecting/tools.html#bpy-ops-view3d-select-circle"),
("bpy.types.adjustmentsequence*", "editors/vse/sequencer/strips/effects/adjustment.html#bpy-types-adjustmentsequence"),
("bpy.types.alphaundersequence*", "editors/vse/sequencer/strips/effects/alpha_over_under_overdrop.html#bpy-types-alphaundersequence"),
@@ -344,8 +343,6 @@ url_manual_mapping = (
("bpy.types.compositornodemask*", "compositing/types/input/mask.html#bpy-types-compositornodemask"),
("bpy.types.compositornodemath*", "compositing/types/converter/math.html#bpy-types-compositornodemath"),
("bpy.types.compositornodetime*", "compositing/types/input/time.html#bpy-types-compositornodetime"),
- ("bpy.types.constraintactuator*", "game_engine/logic/actuators/types/constraint.html#bpy-types-constraintactuator"),
- ("bpy.types.editobjectactuator*", "game_engine/logic/actuators/types/edit_object.html#bpy-types-editobjectactuator"),
("bpy.types.fluidfluidsettings*", "physics/fluid/types/fluid_object.html#bpy-types-fluidfluidsettings"),
("bpy.types.fmodifiergenerator*", "editors/graph_editor/fcurves/modifiers.html#bpy-types-fmodifiergenerator"),
("bpy.types.freestylelinestyle*", "render/freestyle/parameter_editor/line_style/index.html#bpy-types-freestylelinestyle"),
@@ -355,7 +352,7 @@ url_manual_mapping = (
("bpy.types.movietrackingtrack*", "editors/movie_clip_editor/tracking/clip/properties/introduction.html#bpy-types-movietrackingtrack"),
("bpy.types.nodeoutputfileslot*", "compositing/types/output/file.html#bpy-types-nodeoutputfileslot"),
("bpy.types.normaleditmodifier*", "modeling/modifiers/modify/normal_edit.html#bpy-types-normaleditmodifier"),
- ("bpy.types.object.dupli_group*", "editors/3dview/object/properties/duplication/dupligroup.html#bpy-types-object-dupli-group"),
+ ("bpy.types.object.instance_collection*", "editors/3dview/object/properties/duplication/dupligroup.html#bpy-types-object-dupli-group"),
("bpy.types.scene.audio_volume*", "data_system/scenes/properties.html#bpy-types-scene-audio-volume"),
("bpy.types.shadernodebsdfhair*", "render/cycles/nodes/types/shaders/hair.html#bpy-types-shadernodebsdfhair"),
("bpy.types.shadernodebsdftoon*", "render/cycles/nodes/types/shaders/toon.html#bpy-types-shadernodebsdftoon"),
@@ -372,7 +369,6 @@ url_manual_mapping = (
("bpy.types.shrinkwrapmodifier*", "modeling/modifiers/deform/shrinkwrap.html#bpy-types-shrinkwrapmodifier"),
("bpy.types.splineikconstraint*", "rigging/constraints/tracking/spline_ik.html#bpy-types-splineikconstraint"),
("bpy.types.texturenodetexture*", "render/blender_render/textures/nodes/types/input/texture.html#bpy-types-texturenodetexture"),
- ("bpy.types.visibilityactuator*", "game_engine/logic/actuators/types/visibility.html#bpy-types-visibilityactuator"),
("bpy.ops.anim.keyframe_clear*", "animation/keyframes/editing.html#bpy-ops-anim-keyframe-clear"),
("bpy.ops.curve.primitive*add*", "modeling/curves/primitives.html#bpy-ops-curve-primitive-add"),
("bpy.ops.mesh.duplicate_move*", "modeling/meshes/editing/duplicating/duplicate.html#bpy-ops-mesh-duplicate-move"),
@@ -386,7 +382,7 @@ url_manual_mapping = (
("bpy.types.alphaoversequence*", "editors/vse/sequencer/strips/effects/alpha_over_under_overdrop.html#bpy-types-alphaoversequence"),
("bpy.types.armatureeditbones*", "rigging/armatures/bones/editing/index.html#bpy-types-armatureeditbones"),
("bpy.types.childofconstraint*", "rigging/constraints/relationship/child_of.html#bpy-types-childofconstraint"),
- ("bpy.types.clamptoconstraint*", "rigging/constraints/tracking/clamp_to.html#bpy-types-clamptoconstraint"),
+ ("bpy.types.clamptoconstraint*", "rigging/constraints/tracking/clight_to.html#bpy-types-clamptoconstraint"),
("bpy.types.collisionmodifier*", "physics/collision.html#bpy-types-collisionmodifier"),
("bpy.types.collisionsettings*", "physics/collision.html#bpy-types-collisionsettings"),
("bpy.types.compositornodergb*", "compositing/types/input/rgb.html#bpy-types-compositornodergb"),
@@ -398,7 +394,7 @@ url_manual_mapping = (
("bpy.types.meshcachemodifier*", "modeling/modifiers/modify/mesh_cache.html#bpy-types-meshcachemodifier"),
("bpy.types.movieclipsequence*", "editors/vse/sequencer/strips/clip_mask.html#bpy-types-movieclipsequence"),
("bpy.types.object.dimensions*", "editors/3dview/object/properties/transforms.html#bpy-types-object-dimensions"),
- ("bpy.types.object.dupli_type*", "editors/3dview/object/properties/duplication/index.html#bpy-types-object-dupli-type"),
+ ("bpy.types.object.instance_type*", "editors/3dview/object/properties/duplication/index.html#bpy-types-object-dupli-type"),
("bpy.types.object.track_axis*", "editors/3dview/object/properties/relations/extras.html#bpy-types-object-track-axis"),
("bpy.types.scene.active_clip*", "data_system/scenes/properties.html#bpy-types-scene-active-clip"),
("bpy.types.shadernodecombine*", "render/cycles/nodes/types/converter/combine_separate.html#bpy-types-shadernodecombine"),
@@ -428,12 +424,10 @@ url_manual_mapping = (
("bpy.ops.transform.tosphere*", "modeling/meshes/editing/transform/to_sphere.html#bpy-ops-transform-tosphere"),
("bpy.types.actionconstraint*", "rigging/constraints/relationship/action.html#bpy-types-actionconstraint"),
("bpy.types.addonpreferences*", "preferences/addons.html#bpy-types-addonpreferences"),
- ("bpy.types.armatureactuator*", "game_engine/logic/actuators/types/armature.html#bpy-types-armatureactuator"),
("bpy.types.armaturemodifier*", "modeling/modifiers/deform/armature.html#bpy-types-armaturemodifier"),
("bpy.types.decimatemodifier*", "modeling/modifiers/generate/decimate.html#bpy-types-decimatemodifier"),
("bpy.types.displacemodifier*", "modeling/modifiers/deform/displace.html#bpy-types-displacemodifier"),
("bpy.types.displaysafeareas*", "render/blender_render/camera/object_data.html#bpy-types-displaysafeareas"),
- ("bpy.types.filter2dactuator*", "game_engine/logic/actuators/types/filter_2d.html#bpy-types-filter2dactuator"),
("bpy.types.fmodifierstepped*", "editors/graph_editor/fcurves/modifiers.html#bpy-types-fmodifierstepped"),
("bpy.types.freestylelineset*", "render/freestyle/parameter_editor/line_set.html#bpy-types-freestylelineset"),
("bpy.types.material.ambient*", "render/blender_render/materials/properties/shading.html#bpy-types-material-ambient"),
@@ -441,12 +435,10 @@ url_manual_mapping = (
("bpy.types.multicamsequence*", "editors/vse/sequencer/strips/effects/multicam.html#bpy-types-multicamsequence"),
("bpy.types.multiplysequence*", "editors/vse/sequencer/strips/effects/multiply.html#bpy-types-multiplysequence"),
("bpy.types.multiresmodifier*", "modeling/modifiers/generate/multiresolution.html#bpy-types-multiresmodifier"),
- ("bpy.types.object.draw_type*", "editors/3dview/object/properties/display.html#bpy-types-object-draw-type"),
+ ("bpy.types.object.display_type*", "editors/3dview/object/properties/display.html#bpy-types-object-draw-type"),
("bpy.types.object.use_extra*", "editors/3dview/object/properties/relations/extras.html#bpy-types-object-use-extra"),
("bpy.types.overdropsequence*", "editors/vse/sequencer/strips/effects/alpha_over_under_overdrop.html#bpy-types-overdropsequence"),
("bpy.types.particlesettings*", "physics/particles/index.html#bpy-types-particlesettings"),
- ("bpy.types.propertyactuator*", "game_engine/logic/actuators/types/property.html#bpy-types-propertyactuator"),
- ("bpy.types.pythoncontroller*", "game_engine/logic/controllers/types/python.html#bpy-types-pythoncontroller"),
("bpy.types.scenerenderlayer*", "render/cycles/settings/scene/render_layers/layers.html#bpy-types-scenerenderlayer"),
("bpy.types.sequencemodifier*", "editors/vse/sequencer/properties/modifiers.html#bpy-types-sequencemodifier"),
("bpy.types.shadernodeinvert*", "render/cycles/nodes/types/color/invert.html#bpy-types-shadernodeinvert"),
@@ -462,7 +454,6 @@ url_manual_mapping = (
("bpy.types.spaceimageeditor*", "editors/uv_image/image/index.html#bpy-types-spaceimageeditor"),
("bpy.types.spacelogiceditor*", "editors/logic_editor.html#bpy-types-spacelogiceditor"),
("bpy.types.sphfluidsettings*", "physics/fluid/index.html#bpy-types-sphfluidsettings"),
- ("bpy.types.steeringactuator*", "game_engine/logic/actuators/types/steering.html#bpy-types-steeringactuator"),
("bpy.types.subtractsequence*", "editors/vse/sequencer/strips/effects/subtract.html#bpy-types-subtractsequence"),
("bpy.types.texturenodegroup*", "render/blender_render/textures/nodes/types/groups.html#bpy-types-texturenodegroup"),
("bpy.types.texturenodeimage*", "render/blender_render/textures/nodes/types/input/image.html#bpy-types-texturenodeimage"),
@@ -481,7 +472,6 @@ url_manual_mapping = (
("bpy.ops.uv.remove_doubles*", "editors/uv_image/uv/editing/layout.html#bpy-ops-uv-remove-doubles"),
("bpy.types.backgroundimage*", "editors/3dview/properties/background_images.html#bpy-types-backgroundimage"),
("bpy.types.booleanmodifier*", "modeling/modifiers/generate/booleans.html#bpy-types-booleanmodifier"),
- ("bpy.types.collisionsensor*", "game_engine/logic/sensors/types/collision.html#bpy-types-collisionsensor"),
("bpy.types.constraint.mute*", "rigging/constraints/interface/header.html#bpy-types-constraint-mute"),
("bpy.types.explodemodifier*", "modeling/modifiers/simulate/explode.html#bpy-types-explodemodifier"),
("bpy.types.fcurvemodifiers*", "editors/graph_editor/fcurves/modifiers.html#bpy-types-fcurvemodifiers"),
@@ -491,7 +481,6 @@ url_manual_mapping = (
("bpy.types.fmodifierlimits*", "editors/graph_editor/fcurves/modifiers.html#bpy-types-fmodifierlimits"),
("bpy.types.gpussaosettings*", "editors/3dview/properties/shading.html#bpy-types-gpussaosettings"),
("bpy.types.latticemodifier*", "modeling/modifiers/deform/lattice.html#bpy-types-latticemodifier"),
- ("bpy.types.messageactuator*", "game_engine/logic/actuators/types/message.html#bpy-types-messageactuator"),
("bpy.types.musgravetexture*", "render/blender_render/textures/types/procedural/musgrave.html#bpy-types-musgravetexture"),
("bpy.types.object.location*", "editors/3dview/object/properties/transforms.html#bpy-types-object-location"),
("bpy.types.object.rotation*", "editors/3dview/object/properties/transforms.html#bpy-types-object-rotation"),
@@ -516,28 +505,18 @@ url_manual_mapping = (
("bpy.ops.transform.resize*", "editors/3dview/object/editing/transform/basics.html#bpy-ops-transform-resize"),
("bpy.ops.transform.rotate*", "editors/3dview/object/editing/transform/basics.html#bpy-ops-transform-rotate"),
("bpy.ops.view3d.localview*", "editors/3dview/navigate/views.html#bpy-ops-view3d-localview"),
- ("bpy.types.actionactuator*", "game_engine/logic/actuators/types/action.html#bpy-types-actionactuator"),
- ("bpy.types.actuatorsensor*", "game_engine/logic/sensors/types/actuator.html#bpy-types-actuatorsensor"),
- ("bpy.types.armaturesensor*", "game_engine/logic/sensors/types/armature.html#bpy-types-armaturesensor"),
- ("bpy.types.cameraactuator*", "game_engine/logic/actuators/types/camera.html#bpy-types-cameraactuator"),
("bpy.types.curvesmodifier*", "editors/vse/sequencer/properties/modifiers.html#bpy-types-curvesmodifier"),
("bpy.types.effectsequence*", "editors/vse/sequencer/properties/filter.html#bpy-types-effectsequence"),
("bpy.types.ffmpegsettings*", "render/output/video.html#bpy-types-ffmpegsettings"),
("bpy.types.fmodifiernoise*", "editors/graph_editor/fcurves/modifiers.html#bpy-types-fmodifiernoise"),
("bpy.types.gpudofsettings*", "editors/3dview/properties/shading.html#bpy-types-gpudofsettings"),
- ("bpy.types.joysticksensor*", "game_engine/logic/sensors/types/joystick.html#bpy-types-joysticksensor"),
- ("bpy.types.keyboardsensor*", "game_engine/logic/sensors/types/keyboard.html#bpy-types-keyboardsensor"),
("bpy.types.materialstrand*", "render/blender_render/materials/properties/strands.html#bpy-types-materialstrand"),
("bpy.types.materialvolume*", "render/blender_render/materials/special_effects/volume.html#bpy-types-materialvolume"),
("bpy.types.mirrormodifier*", "modeling/modifiers/generate/mirror.html#bpy-types-mirrormodifier"),
("bpy.types.movieclipproxy*", "editors/movie_clip_editor/properties/proxy.html#bpy-types-movieclipproxy"),
("bpy.types.object.up_axis*", "editors/3dview/object/properties/relations/extras.html#bpy-types-object-up-axis"),
- ("bpy.types.objectactuator*", "game_engine/logic/actuators/types/motion.html#bpy-types-objectactuator"),
- ("bpy.types.parentactuator*", "game_engine/logic/actuators/types/parent.html#bpy-types-parentactuator"),
("bpy.types.particlesystem*", "physics/particles/index.html#bpy-types-particlesystem"),
("bpy.types.particletarget*", "physics/particles/emitter/physics/keyed.html#bpy-types-particletarget"),
- ("bpy.types.propertysensor*", "game_engine/logic/sensors/types/property.html#bpy-types-propertysensor"),
- ("bpy.types.randomactuator*", "game_engine/logic/actuators/types/random.html#bpy-types-randomactuator"),
("bpy.types.remeshmodifier*", "modeling/modifiers/generate/remesh.html#bpy-types-remeshmodifier"),
("bpy.types.rendersettings*", "render/index.html#bpy-types-rendersettings"),
("bpy.types.rigidbodyworld*", "physics/rigid_body/world.html#bpy-types-rigidbodyworld"),
@@ -571,9 +550,7 @@ url_manual_mapping = (
("bpy.types.gpufxsettings*", "editors/3dview/properties/shading.html#bpy-types-gpufxsettings"),
("bpy.types.imagesequence*", "editors/vse/sequencer/strips/movie_image.html#bpy-types-imagesequence"),
("bpy.types.marbletexture*", "render/blender_render/textures/types/procedural/marble.html#bpy-types-marbletexture"),
- ("bpy.types.messagesensor*", "game_engine/logic/sensors/types/message.html#bpy-types-messagesensor"),
("bpy.types.modifier.show*", "modeling/modifiers/introduction.html#bpy-types-modifier-show"),
- ("bpy.types.mouseactuator*", "game_engine/logic/actuators/types/mouse.html#bpy-types-mouseactuator"),
("bpy.types.moviesequence*", "editors/vse/sequencer/strips/movie_image.html#bpy-types-moviesequence"),
("bpy.types.movietracking*", "editors/movie_clip_editor/tracking/index.html#bpy-types-movietracking"),
("bpy.types.object.layers*", "editors/3dview/object/properties/relations/layers.html#bpy-types-object-layers"),
@@ -581,18 +558,15 @@ url_manual_mapping = (
("bpy.types.oceanmodifier*", "modeling/modifiers/simulate/ocean.html#bpy-types-oceanmodifier"),
("bpy.types.particlebrush*", "physics/particles/mode.html#bpy-types-particlebrush"),
("bpy.types.scene.gravity*", "physics/gravity.html#bpy-types-scene-gravity"),
- ("bpy.types.sceneactuator*", "game_engine/logic/actuators/types/scene.html#bpy-types-sceneactuator"),
("bpy.types.scenesequence*", "editors/vse/sequencer/strips/scene.html#bpy-types-scenesequence"),
("bpy.types.screwmodifier*", "modeling/modifiers/generate/screw.html#bpy-types-screwmodifier"),
("bpy.types.sequenceproxy*", "editors/vse/sequencer/properties/proxy_timecode.html#bpy-types-sequenceproxy"),
("bpy.types.shadernodergb*", "render/cycles/nodes/types/input/rgb.html#bpy-types-shadernodergb"),
("bpy.types.smokemodifier*", "physics/smoke/index.html#bpy-types-smokemodifier"),
- ("bpy.types.soundactuator*", "game_engine/logic/actuators/types/sound.html#bpy-types-soundactuator"),
("bpy.types.soundsequence*", "editors/vse/sequencer/strips/sound.html#bpy-types-soundsequence"),
("bpy.types.spaceoutliner*", "editors/outliner.html#bpy-types-spaceoutliner"),
("bpy.types.spacetimeline*", "editors/timeline.html#bpy-types-spacetimeline"),
("bpy.types.spaceuveditor*", "editors/uv_image/uv/index.html#bpy-types-spaceuveditor"),
- ("bpy.types.stateactuator*", "game_engine/logic/actuators/types/state.html#bpy-types-stateactuator"),
("bpy.types.stuccitexture*", "render/blender_render/textures/types/procedural/stucci.html#bpy-types-stuccitexture"),
("bpy.types.windowmanager*", "interface/index.html#bpy-types-windowmanager"),
("bpy.types.worldlighting*", "render/blender_render/world/ambient_occlusion.html#bpy-types-worldlighting"),
@@ -600,12 +574,10 @@ url_manual_mapping = (
("bpy.ops.object.convert*", "editors/3dview/object/editing/introduction.html#bpy-ops-object-convert"),
("bpy.ops.object.speaker*", "render/audio/speaker.html#bpy-ops-object-speaker"),
("bpy.ops.transform.bend*", "modeling/meshes/editing/transform/bend.html#bpy-ops-transform-bend"),
- ("bpy.types.alwayssensor*", "game_engine/logic/sensors/types/always.html#bpy-types-alwayssensor"),
("bpy.types.bakesettings*", "render/cycles/baking.html#bpy-types-bakesettings"),
("bpy.types.blendtexture*", "render/blender_render/textures/types/procedural/blend.html#bpy-types-blendtexture"),
("bpy.types.castmodifier*", "modeling/modifiers/deform/cast.html#bpy-types-castmodifier"),
("bpy.types.colormanaged*", "render/post_process/color_management.html#bpy-types-colormanaged"),
- ("bpy.types.gameactuator*", "game_engine/logic/actuators/types/game.html#bpy-types-gameactuator"),
("bpy.types.glowsequence*", "editors/vse/sequencer/strips/effects/glow.html#bpy-types-glowsequence"),
("bpy.types.gpencilbrush*", "interface/grease_pencil/drawing/brushes.html#bpy-types-gpencilbrush"),
("bpy.types.greasepencil*", "interface/grease_pencil/index.html#bpy-types-greasepencil"),
@@ -623,7 +595,6 @@ url_manual_mapping = (
("bpy.types.object.scale*", "editors/3dview/object/properties/transforms.html#bpy-types-object-scale"),
("bpy.types.oceantexture*", "render/blender_render/textures/types/procedural/ocean.html#bpy-types-oceantexture"),
("bpy.types.particleedit*", "physics/particles/mode.html#bpy-types-particleedit"),
- ("bpy.types.randomsensor*", "game_engine/logic/sensors/types/random.html#bpy-types-randomsensor"),
("bpy.types.scene.camera*", "data_system/scenes/properties.html#bpy-types-scene-camera"),
("bpy.types.sequencecrop*", "editors/vse/sequencer/properties/input.html#bpy-types-sequencecrop"),
("bpy.types.skinmodifier*", "modeling/modifiers/generate/skin.html#bpy-types-skinmodifier"),
@@ -641,14 +612,11 @@ url_manual_mapping = (
("bpy.ops.wm.properties*", "data_system/custom_properties.html#bpy-ops-wm-properties"),
("bpy.types.addsequence*", "editors/vse/sequencer/strips/effects/add.html#bpy-types-addsequence"),
("bpy.types.consoleline*", "editors/python_console.html#bpy-types-consoleline"),
- ("bpy.types.delaysensor*", "game_engine/logic/sensors/types/delay.html#bpy-types-delaysensor"),
("bpy.types.meshstatvis*", "modeling/meshes/mesh_analysis.html#bpy-types-meshstatvis"),
- ("bpy.types.mousesensor*", "game_engine/logic/sensors/types/mouse.html#bpy-types-mousesensor"),
("bpy.types.nodesetting*", "editors/node_editor/nodes/parts.html#bpy-types-nodesetting"),
("bpy.types.object.lock*", "editors/3dview/object/properties/transforms.html#bpy-types-object-lock"),
("bpy.types.object.show*", "editors/3dview/object/properties/display.html#bpy-types-object-show"),
("bpy.types.particlekey*", "physics/particles/emitter/physics/keyed.html#bpy-types-particlekey"),
- ("bpy.types.radarsensor*", "game_engine/logic/sensors/types/radar.html#bpy-types-radarsensor"),
("bpy.types.renderlayer*", "render/post_process/layers.html#bpy-types-renderlayer"),
("bpy.types.spaceview3d*", "editors/3dview/index.html#bpy-types-spaceview3d"),
("bpy.types.uipopupmenu*", "interface/controls/buttons/menus.html#bpy-types-uipopupmenu"),
@@ -665,8 +633,6 @@ url_manual_mapping = (
("bpy.types.bpy_struct*", "data_system/custom_properties.html#bpy-types-bpy-struct"),
("bpy.types.compositor*", "compositing/index.html#bpy-types-compositor"),
("bpy.types.constraint*", "rigging/constraints/index.html#bpy-types-constraint"),
- ("bpy.types.controller*", "game_engine/logic/controllers/index.html#bpy-types-controller"),
- ("bpy.types.nearsensor*", "game_engine/logic/sensors/types/near.html#bpy-types-nearsensor"),
("bpy.types.nodesocket*", "editors/node_editor/nodes/parts.html#bpy-types-nodesocket"),
("bpy.types.pointcache*", "physics/baking.html#bpy-types-pointcache"),
("bpy.types.renderpass*", "render/blender_render/settings/passes.html#bpy-types-renderpass"),
@@ -687,7 +653,6 @@ url_manual_mapping = (
("bpy.types.nodeframe*", "editors/node_editor/nodes/frame.html#bpy-types-nodeframe"),
("bpy.types.nodegroup*", "editors/node_editor/nodes/groups.html#bpy-types-nodegroup"),
("bpy.types.pointlamp*", "render/blender_render/lighting/lamps/point.html#bpy-types-pointlamp"),
- ("bpy.types.raysensor*", "game_engine/logic/sensors/types/ray.html#bpy-types-raysensor"),
("bpy.types.spaceinfo*", "editors/info/index.html#bpy-types-spaceinfo"),
("bpy.types.textcurve*", "modeling/texts/index.html#bpy-types-textcurve"),
("bpy.types.uipiemenu*", "interface/controls/buttons/menus.html#bpy-types-uipiemenu"),
@@ -697,14 +662,12 @@ url_manual_mapping = (
("bpy.ops.mesh.noise*", "modeling/meshes/editing/transform/noise.html#bpy-ops-mesh-noise"),
("bpy.ops.mesh.screw*", "modeling/meshes/editing/duplicating/screw.html#bpy-ops-mesh-screw"),
("bpy.ops.safe_areas*", "render/blender_render/camera/object_data.html#bpy-ops-safe-areas"),
- ("bpy.types.actuator*", "game_engine/logic/actuators/index.html#bpy-types-actuator"),
("bpy.types.arealamp*", "render/blender_render/lighting/lamps/area/index.html#bpy-types-arealamp"),
("bpy.types.armature*", "rigging/armatures/index.html#bpy-types-armature"),
("bpy.types.editbone*", "rigging/armatures/bones/editing/index.html#bpy-types-editbone"),
("bpy.types.hemilamp*", "render/blender_render/lighting/lamps/hemi.html#bpy-types-hemilamp"),
("bpy.types.keyframe*", "animation/keyframes/index.html#bpy-types-keyframe"),
("bpy.types.linesets*", "render/freestyle/parameter_editor/line_set.html#bpy-types-linesets"),
- ("bpy.types.lodlevel*", "game_engine/settings/object.html#bpy-types-lodlevel"),
("bpy.types.material*", "render/blender_render/materials/index.html#bpy-types-material"),
("bpy.types.metaball*", "modeling/metas/index.html#bpy-types-metaball"),
("bpy.types.modifier*", "modeling/modifiers/index.html#bpy-types-modifier"),
@@ -748,7 +711,6 @@ url_manual_mapping = (
("bpy.types.render*", "render/index.html#bpy-types-render"),
("bpy.types.screen*", "interface/window_system/screens.html#bpy-types-screen"),
("bpy.types.sculpt*", "sculpt_paint/sculpting/index.html#bpy-types-sculpt"),
- ("bpy.types.sensor*", "game_engine/logic/sensors/index.html#bpy-types-sensor"),
("bpy.types.shader*", "render/cycles/nodes/types/shaders/index.html#bpy-types-shader"),
("bpy.types.window*", "interface/index.html#bpy-types-window"),
("bpy.ops.buttons*", "interface/index.html#bpy-ops-buttons"),
@@ -765,7 +727,7 @@ url_manual_mapping = (
("bpy.types.addon*", "preferences/addons.html#bpy-types-addon"),
("bpy.types.brush*", "sculpt_paint/brush.html#bpy-types-brush"),
("bpy.types.curve*", "modeling/curves/index.html#bpy-types-curve"),
- ("bpy.types.group*", "editors/3dview/object/properties/relations/groups.html#bpy-types-group"),
+ ("bpy.types.collection*", "editors/3dview/object/properties/relations/groups.html#bpy-types-group"),
("bpy.types.image*", "data_system/files/media/image_formats.html#bpy-types-image"),
("bpy.types.nodes*", "editors/node_editor/nodes/index.html#bpy-types-nodes"),
("bpy.types.panel*", "interface/window_system/tabs_panels.html#bpy-types-panel"),
@@ -792,7 +754,7 @@ url_manual_mapping = (
("bpy.types.area*", "interface/window_system/areas.html#bpy-types-area"),
("bpy.types.boid*", "physics/particles/emitter/physics/boids.html#bpy-types-boid"),
("bpy.types.bone*", "rigging/armatures/bones/index.html#bpy-types-bone"),
- ("bpy.types.lamp*", "render/blender_render/lighting/index.html#bpy-types-lamp"),
+ ("bpy.types.light*", "render/blender_render/lighting/index.html#bpy-types-lamp"),
("bpy.types.mask*", "editors/movie_clip_editor/masking/index.html#bpy-types-mask"),
("bpy.types.menu*", "interface/controls/buttons/menus.html#bpy-types-menu"),
("bpy.types.mesh*", "modeling/meshes/index.html#bpy-types-mesh"),
@@ -803,9 +765,8 @@ url_manual_mapping = (
("bpy.ops.curve*", "modeling/curves/index.html#bpy-ops-curve"),
("bpy.ops.fluid*", "physics/fluid/index.html#bpy-ops-fluid"),
("bpy.ops.graph*", "editors/graph_editor/index.html#bpy-ops-graph"),
- ("bpy.ops.group*", "editors/3dview/object/properties/relations/groups.html#bpy-ops-group"),
+ ("bpy.ops.collection*", "editors/3dview/object/properties/relations/groups.html#bpy-ops-group"),
("bpy.ops.image*", "data_system/files/media/image_formats.html#bpy-ops-image"),
- ("bpy.ops.logic*", "game_engine/logic/index.html#bpy-ops-logic"),
("bpy.ops.mball*", "modeling/metas/index.html#bpy-ops-mball"),
("bpy.ops.paint*", "sculpt_paint/index.html#bpy-ops-paint"),
("bpy.ops.scene*", "data_system/scenes/index.html#bpy-ops-scene"),
@@ -818,7 +779,7 @@ url_manual_mapping = (
("bpy.ops.file*", "editors/file_browser/index.html#bpy-ops-file"),
("bpy.ops.font*", "modeling/texts/index.html#bpy-ops-font"),
("bpy.ops.info*", "editors/info/index.html#bpy-ops-info"),
- ("bpy.ops.lamp*", "render/blender_render/lighting/index.html#bpy-ops-lamp"),
+ ("bpy.ops.light*", "render/blender_render/lighting/index.html#bpy-ops-lamp"),
("bpy.ops.mask*", "editors/movie_clip_editor/masking/index.html#bpy-ops-mask"),
("bpy.ops.mesh*", "modeling/meshes/index.html#bpy-ops-mesh"),
("bpy.ops.node*", "editors/node_editor/nodes/index.html#bpy-ops-node"),
diff --git a/modules/snap_context/mesh_drawing.py b/modules/snap_context/mesh_drawing.py
deleted file mode 100644
index bdfca4d3..00000000
--- a/modules/snap_context/mesh_drawing.py
+++ /dev/null
@@ -1,518 +0,0 @@
-# ##### BEGIN GPL LICENSE BLOCK #####
-#
-# This program is free software; you can redistribute it and/or
-# modify it under the terms of the GNU General Public License
-# as published by the Free Software Foundation; either version 3
-# of the License, or (at your option) any later version.
-#
-# This program 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 General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-#
-# ##### END GPL LICENSE BLOCK #####
-
-
-import bgl
-import bmesh
-import numpy as np
-from mathutils import Matrix
-
-from .utils_shader import Shader
-
-
-def load_shader(shadername):
- from os import path
- with open(path.join(path.dirname(__file__), 'resources', shadername), 'r') as f:
- return f.read()
-
-def gl_buffer_void_as_long(value):
- import ctypes
- a = (ctypes.c_byte * 1).from_address(value)
- return bgl.Buffer(bgl.GL_BYTE, 1, a)
-
-def get_mesh_vert_co_array(me):
- tot_vco = len(me.vertices)
- if tot_vco:
- verts_co = np.empty(len(me.vertices) * 3, 'f4')
- me.vertices.foreach_get("co", verts_co)
- verts_co.shape = (-1, 3)
- return verts_co
- return None
-
-
-def get_bmesh_vert_co_array(bm):
- tot_vco = len(bm.verts)
- if tot_vco:
- return np.array([v.co for v in bm.verts], 'f4')
- return None
-
-
-def get_mesh_tri_verts_array(me):
- me.calc_tessface()
- len_tessfaces = len(me.tessfaces)
- if len_tessfaces:
- tessfaces = np.empty(len_tessfaces * 4, 'i4')
- me.tessfaces.foreach_get("vertices_raw", tessfaces)
- tessfaces.shape = (-1, 4)
-
- quad_indices = tessfaces[:, 3].nonzero()[0]
- tris = np.empty(((len_tessfaces + len(quad_indices)), 3), 'i4')
-
- tris[:len_tessfaces] = tessfaces[:, :3]
- tris[len_tessfaces:] = tessfaces[quad_indices][:, (0, 2, 3)]
-
- del tessfaces
- return tris
- return None
-
-
-def get_bmesh_tri_verts_array(bm):
- ltris = bm.calc_tessface()
- tris = [[ltri[0].vert.index, ltri[1].vert.index, ltri[2].vert.index] for ltri in ltris if not ltri[0].face.hide]
- if tris:
- return np.array(tris, 'i4')
- return None
-
-
-def get_mesh_edge_verts_array(me):
- tot_edges = len(me.edges)
- if tot_edges:
- edge_verts = np.empty(tot_edges * 2, 'i4')
- me.edges.foreach_get("vertices", edge_verts)
- edge_verts.shape = tot_edges, 2
- return edge_verts
- return None
-
-
-def get_bmesh_edge_verts_array(bm):
- bm.edges.ensure_lookup_table()
- edges = [[e.verts[0].index, e.verts[1].index] for e in bm.edges if not e.hide]
- if edges:
- return np.array(edges, 'i4')
- return None
-
-
-def get_mesh_loosevert_array(me, edges):
- verts = np.arange(len(me.vertices))
-
- mask = np.in1d(verts, edges, invert=True)
-
- verts = verts[mask]
- if len(verts):
- return verts
- return None
-
-
-def get_bmesh_loosevert_array(bm):
- looseverts = [v.index for v in bm.verts if not (v.link_edges or v.hide)]
- if looseverts:
- return np.array(looseverts, 'i4')
- return None
-
-
-class _Mesh_Arrays():
- def __init__(self, obj, create_tris, create_edges, create_looseverts):
- self.tri_verts = self.edge_verts = self.looseverts = None
- self.tris_co = self.edges_co = self.looseverts_co = None
- if obj.type == 'MESH':
- me = obj.data
- if me.is_editmode:
- bm = bmesh.from_edit_mesh(me)
- bm.verts.ensure_lookup_table()
-
- self.verts_co = get_bmesh_vert_co_array(bm)
-
- if create_tris:
- self.tri_verts = get_bmesh_tri_verts_array(bm)
- if create_edges:
- self.edge_verts = get_bmesh_edge_verts_array(bm)
- if create_looseverts:
- self.looseverts = get_bmesh_loosevert_array(bm)
- else:
- self.verts_co = get_mesh_vert_co_array(me)
-
- if create_tris:
- self.tri_verts = get_mesh_tri_verts_array(me)
- if create_edges:
- self.edge_verts = get_mesh_edge_verts_array(me)
- if create_looseverts:
- edge_verts = self.edge_verts
- if edge_verts is None:
- edge_verts = get_mesh_edge_verts_array(me)
- self.looseverts = get_mesh_loosevert_array(me, edge_verts)
- del edge_verts
-
- else: #TODO
- self.verts_co = np.zeros((1,3), 'f4')
- self.looseverts = np.zeros(1, 'i4')
-
- def __del__(self):
- del self.tri_verts, self.edge_verts, self.looseverts
- del self.verts_co
-
-
-class GPU_Indices_Mesh():
- shader = None
-
- @classmethod
- def end_opengl(cls):
- del cls.shader
- del cls._NULL
- del cls.P
- del cls.MV
- del cls.MVP
- del cls.vert_index
- del cls.tri_co
- del cls.edge_co
- del cls.vert_co
-
- del cls
-
- @classmethod
- def init_opengl(cls):
- # OpenGL was already initialized, nothing to do here.
- if cls.shader is not None:
- return
-
- import atexit
-
- # Make sure we only registered the callback once.
- atexit.unregister(cls.end_opengl)
- atexit.register(cls.end_opengl)
-
- cls.shader = Shader(
- load_shader('3D_vert.glsl'),
- None,
- load_shader('primitive_id_frag.glsl'),
- )
-
- cls.unif_use_clip_planes = bgl.glGetUniformLocation(cls.shader.program, 'use_clip_planes')
- cls.unif_clip_plane = bgl.glGetUniformLocation(cls.shader.program, 'clip_plane')
-
- cls._NULL = gl_buffer_void_as_long(0)
-
- cls.unif_MVP = bgl.glGetUniformLocation(cls.shader.program, 'MVP')
- cls.unif_MV = bgl.glGetUniformLocation(cls.shader.program, 'MV')
- cls.unif_offset = bgl.glGetUniformLocation(cls.shader.program, 'offset')
-
- cls.attr_pos = bgl.glGetAttribLocation(cls.shader.program, 'pos')
- cls.attr_primitive_id = bgl.glGetAttribLocation(cls.shader.program, 'primitive_id')
-
- cls.P = bgl.Buffer(bgl.GL_FLOAT, (4, 4))
- cls.MV = bgl.Buffer(bgl.GL_FLOAT, (4, 4))
- cls.MVP = bgl.Buffer(bgl.GL_FLOAT, (4, 4))
-
- # returns of public API #
- cls.vert_index = bgl.Buffer(bgl.GL_INT, 1)
-
- cls.tri_co = bgl.Buffer(bgl.GL_FLOAT, (3, 3))
- cls.edge_co = bgl.Buffer(bgl.GL_FLOAT, (2, 3))
- cls.vert_co = bgl.Buffer(bgl.GL_FLOAT, 3)
-
- def __init__(self, obj, draw_tris, draw_edges, draw_verts):
- GPU_Indices_Mesh.init_opengl()
-
- self.obj = obj
- self.draw_tris = draw_tris
- self.draw_edges = draw_edges
- self.draw_verts = draw_verts
-
- self.vbo = None
- self.vbo_tris = None
- self.vbo_edges = None
- self.vbo_verts = None
-
- ## Create VAO ##
- self.vao = bgl.Buffer(bgl.GL_INT, 1)
- bgl.glGenVertexArrays(1, self.vao)
- bgl.glBindVertexArray(self.vao[0])
-
- ## Init Array ##
- mesh_arrays = _Mesh_Arrays(obj, draw_tris, draw_edges, draw_verts)
-
- ## Create VBO for vertices ##
- if mesh_arrays.verts_co is None:
- self.draw_tris = False
- self.draw_edges = False
- self.draw_verts = False
- return
-
- if False: # Blender 2.8
- self.vbo_len = len(mesh_arrays.verts_co)
-
- self.vbo = bgl.Buffer(bgl.GL_INT, 1)
- bgl.glGenBuffers(1, self.vbo)
- bgl.glBindBuffer(bgl.GL_ARRAY_BUFFER, self.vbo[0])
- verts_co = bgl.Buffer(bgl.GL_FLOAT, mesh_arrays.verts_co.shape, mesh_arrays.verts_co)
- bgl.glBufferData(bgl.GL_ARRAY_BUFFER, self.vbo_len * 12, verts_co, bgl.GL_STATIC_DRAW)
-
- ## Create VBO for Tris ##
- if mesh_arrays.tri_verts is not None:
- self.tri_verts = mesh_arrays.tri_verts
- self.num_tris = len(self.tri_verts)
-
- np_tris_co = mesh_arrays.verts_co[mesh_arrays.tri_verts]
- np_tris_co = bgl.Buffer(bgl.GL_FLOAT, np_tris_co.shape, np_tris_co)
- self.vbo_tris = bgl.Buffer(bgl.GL_INT, 1)
- bgl.glGenBuffers(1, self.vbo_tris)
- bgl.glBindBuffer(bgl.GL_ARRAY_BUFFER, self.vbo_tris[0])
- bgl.glBufferData(bgl.GL_ARRAY_BUFFER, self.num_tris * 36, np_tris_co, bgl.GL_STATIC_DRAW)
- del np_tris_co
-
- tri_indices = np.repeat(np.arange(self.num_tris, dtype = 'f4'), 3)
- tri_indices = bgl.Buffer(bgl.GL_FLOAT, tri_indices.shape, tri_indices)
- self.vbo_tri_indices = bgl.Buffer(bgl.GL_INT, 1)
- bgl.glGenBuffers(1, self.vbo_tri_indices)
- bgl.glBindBuffer(bgl.GL_ARRAY_BUFFER, self.vbo_tri_indices[0])
- bgl.glBufferData(bgl.GL_ARRAY_BUFFER, self.num_tris * 12, tri_indices, bgl.GL_STATIC_DRAW)
- del tri_indices
-
- else:
- self.num_tris = 0
- self.draw_tris = False
-
- ## Create VBO for Edges ##
- if mesh_arrays.edge_verts is not None:
- self.edge_verts = mesh_arrays.edge_verts
- self.num_edges = len(self.edge_verts)
-
- np_edges_co = mesh_arrays.verts_co[mesh_arrays.edge_verts]
- np_edges_co = bgl.Buffer(bgl.GL_FLOAT, np_edges_co.shape, np_edges_co)
- self.vbo_edges = bgl.Buffer(bgl.GL_INT, 1)
- bgl.glGenBuffers(1, self.vbo_edges)
- bgl.glBindBuffer(bgl.GL_ARRAY_BUFFER, self.vbo_edges[0])
- bgl.glBufferData(bgl.GL_ARRAY_BUFFER, self.num_edges * 24, np_edges_co, bgl.GL_STATIC_DRAW)
- del np_edges_co
-
- edge_indices = np.repeat(np.arange(self.num_edges, dtype = 'f4'), 2)
- edge_indices = bgl.Buffer(bgl.GL_FLOAT, edge_indices.shape, edge_indices)
- self.vbo_edge_indices = bgl.Buffer(bgl.GL_INT, 1)
- bgl.glGenBuffers(1, self.vbo_edge_indices)
- bgl.glBindBuffer(bgl.GL_ARRAY_BUFFER, self.vbo_edge_indices[0])
- bgl.glBufferData(bgl.GL_ARRAY_BUFFER, self.num_edges * 8, edge_indices, bgl.GL_STATIC_DRAW)
- del edge_indices
- else:
- self.num_edges = 0
- self.draw_edges = False
-
- ## Create EBO for Loose Verts ##
- if mesh_arrays.looseverts is not None:
- self.looseverts = mesh_arrays.looseverts
- self.num_verts = len(mesh_arrays.looseverts)
-
- np_lverts_co = mesh_arrays.verts_co[mesh_arrays.looseverts]
- np_lverts_co = bgl.Buffer(bgl.GL_FLOAT, np_lverts_co.shape, np_lverts_co)
- self.vbo_verts = bgl.Buffer(bgl.GL_INT, 1)
- bgl.glGenBuffers(1, self.vbo_verts)
- bgl.glBindBuffer(bgl.GL_ARRAY_BUFFER, self.vbo_verts[0])
- bgl.glBufferData(bgl.GL_ARRAY_BUFFER, self.num_verts * 12, np_lverts_co, bgl.GL_STATIC_DRAW)
- del np_lverts_co
-
- looseverts_indices = np.arange(self.num_verts, dtype = 'f4')
- looseverts_indices = bgl.Buffer(bgl.GL_FLOAT, looseverts_indices.shape, looseverts_indices)
- self.vbo_looseverts_indices = bgl.Buffer(bgl.GL_INT, 1)
- bgl.glGenBuffers(1, self.vbo_looseverts_indices)
- bgl.glBindBuffer(bgl.GL_ARRAY_BUFFER, self.vbo_looseverts_indices[0])
- bgl.glBufferData(bgl.GL_ARRAY_BUFFER, self.num_verts * 4, looseverts_indices, bgl.GL_STATIC_DRAW)
- del looseverts_indices
- else:
- self.num_verts = 0
- self.draw_verts = False
-
- del mesh_arrays
-
- bgl.glBindVertexArray(0)
-
-
- def get_tot_elems(self):
- tot = 0
-
- if self.draw_tris:
- tot += self.num_tris
-
- if self.draw_edges:
- tot += self.num_edges
-
- if self.draw_verts:
- tot += self.num_verts
-
- return tot
-
-
- def set_draw_mode(self, draw_tris, draw_edges, draw_verts):
- self.draw_tris = draw_tris and self.vbo_tris
- self.draw_edges = draw_edges and self.vbo_edges
- self.draw_verts = draw_verts and self.vbo_verts
-
-
- def set_ModelViewMatrix(self, MV):
- self.MV[:] = MV[:]
- self.MVP[:] = Matrix(self.P) * MV
-
-
- def Draw(self, index_offset):
- self.first_index = index_offset
- bgl.glUseProgram(self.shader.program)
- bgl.glBindVertexArray(self.vao[0])
-
- bgl.glUniformMatrix4fv(self.unif_MV, 1, bgl.GL_TRUE, self.MV)
- bgl.glUniformMatrix4fv(self.unif_MVP, 1, bgl.GL_TRUE, self.MVP)
-
- if self.draw_tris:
- bgl.glUniform1f(self.unif_offset, float(index_offset)) # bgl has no glUniform1ui :\
-
- bgl.glBindBuffer(bgl.GL_ARRAY_BUFFER, self.vbo_tris[0])
- bgl.glEnableVertexAttribArray(self.attr_pos)
- bgl.glVertexAttribPointer(self.attr_pos, 3, bgl.GL_FLOAT, bgl.GL_FALSE, 0, self._NULL)
-
- bgl.glBindBuffer(bgl.GL_ARRAY_BUFFER, self.vbo_tri_indices[0])
- bgl.glEnableVertexAttribArray(self.attr_primitive_id)
- bgl.glVertexAttribPointer(self.attr_primitive_id, 1, bgl.GL_FLOAT, bgl.GL_FALSE, 0, self._NULL)
-
- bgl.glDrawArrays(bgl.GL_TRIANGLES, 0, self.num_tris * 3)
-
- index_offset += self.num_tris
- bgl.glDepthRange(-0.00005, 0.99995)
-
- if self.draw_edges:
- bgl.glUniform1f(self.unif_offset, float(index_offset)) #TODO: use glUniform1ui
-
- bgl.glBindBuffer(bgl.GL_ARRAY_BUFFER, self.vbo_edges[0])
- bgl.glVertexAttribPointer(self.attr_pos, 3, bgl.GL_FLOAT, bgl.GL_FALSE, 0, self._NULL)
- bgl.glEnableVertexAttribArray(self.attr_pos)
-
- bgl.glBindBuffer(bgl.GL_ARRAY_BUFFER, self.vbo_edge_indices[0])
- bgl.glVertexAttribPointer(self.attr_primitive_id, 1, bgl.GL_FLOAT, bgl.GL_FALSE, 0, self._NULL)
- bgl.glEnableVertexAttribArray(self.attr_primitive_id)
-
- bgl.glDrawArrays(bgl.GL_LINES, 0, self.num_edges * 2)
-
- index_offset += self.num_edges
-
- if self.draw_verts:
- bgl.glUniform1f(self.unif_offset, float(index_offset)) #TODO: use glUniform1ui
-
- bgl.glBindBuffer(bgl.GL_ARRAY_BUFFER, self.vbo_verts[0])
- bgl.glVertexAttribPointer(self.attr_pos, 3, bgl.GL_FLOAT, bgl.GL_FALSE, 0, self._NULL)
- bgl.glEnableVertexAttribArray(self.attr_pos)
-
- bgl.glBindBuffer(bgl.GL_ARRAY_BUFFER, self.vbo_looseverts_indices[0])
- bgl.glVertexAttribPointer(self.attr_primitive_id, 1, bgl.GL_FLOAT, bgl.GL_FALSE, 0, self._NULL)
- bgl.glEnableVertexAttribArray(self.attr_primitive_id)
-
- bgl.glDrawArrays(bgl.GL_POINTS, 0, self.num_verts)
-
- bgl.glDepthRange(0.0, 1.0)
-
-
- def get_tri_co(self, index):
- bgl.glBindVertexArray(self.vao[0])
- bgl.glBindBuffer(bgl.GL_ARRAY_BUFFER, self.vbo_tris[0])
- bgl.glGetBufferSubData(bgl.GL_ARRAY_BUFFER, index * 36, 36, self.tri_co)
- bgl.glBindVertexArray(0)
- return self.tri_co
-
-
- def get_edge_co(self, index):
- bgl.glBindVertexArray(self.vao[0])
- bgl.glBindBuffer(bgl.GL_ARRAY_BUFFER, self.vbo_edges[0])
- bgl.glGetBufferSubData(bgl.GL_ARRAY_BUFFER, index * 24, 24, self.edge_co)
- bgl.glBindVertexArray(0)
- return self.edge_co
-
-
- def get_loosevert_co(self, index):
- bgl.glBindVertexArray(self.vao[0])
- bgl.glBindBuffer(bgl.GL_ARRAY_BUFFER, self.vbo_verts[0])
- bgl.glGetBufferSubData(bgl.GL_ARRAY_BUFFER, index * 12, 12, self.vert_co)
- bgl.glBindVertexArray(0)
- return self.vert_co
-
-
- def get_tri_verts(self, index):
- return self.tri_verts[index]
-
-
- def get_edge_verts(self, index):
- return self.edge_verts[index]
-
-
- def get_loosevert_index(self, index):
- return self.looseverts[index]
-
-
- def __del__(self):
- if self.vbo_tris:
- bgl.glDeleteBuffers(1, self.vbo_tris)
- bgl.glDeleteBuffers(1, self.vbo_tri_indices)
- del self.tri_verts
-
- if self.vbo_edges:
- bgl.glDeleteBuffers(1, self.vbo_edges)
- bgl.glDeleteBuffers(1, self.vbo_edge_indices)
- del self.edge_verts
-
- if self.vbo_verts:
- bgl.glDeleteBuffers(1, self.vbo_verts)
- bgl.glDeleteBuffers(1, self.vbo_looseverts_indices)
- del self.looseverts
-
- bgl.glDeleteVertexArrays(1, self.vao)
- #print('mesh_del', self.obj.name)
-
-
-class PreviousGLState:
- buf = bgl.Buffer(bgl.GL_INT, (4, 1))
- cur_program = buf[0]
- cur_vao = buf[1]
- cur_vbo = buf[2]
- cur_ebo = buf[3]
-
-
-def _store_current_shader_state(cls):
- bgl.glGetIntegerv(bgl.GL_CURRENT_PROGRAM, cls.cur_program)
- bgl.glGetIntegerv(bgl.GL_VERTEX_ARRAY_BINDING, cls.cur_vao)
- bgl.glGetIntegerv(bgl.GL_ARRAY_BUFFER_BINDING, cls.cur_vbo)
- bgl.glGetIntegerv(bgl.GL_ELEMENT_ARRAY_BUFFER_BINDING, cls.cur_ebo)
-
-
-def _restore_shader_state(cls):
- bgl.glUseProgram(cls.cur_program[0])
- bgl.glBindVertexArray(cls.cur_vao[0])
- bgl.glBindBuffer(bgl.GL_ARRAY_BUFFER, cls.cur_vbo[0])
- bgl.glBindBuffer(bgl.GL_ELEMENT_ARRAY_BUFFER, cls.cur_ebo[0])
-
-
-def gpu_Indices_enable_state():
- _store_current_shader_state(PreviousGLState)
-
- GPU_Indices_Mesh.init_opengl()
- bgl.glUseProgram(GPU_Indices_Mesh.shader.program)
- #bgl.glBindVertexArray(GPU_Indices_Mesh.vao[0])
-
-
-def gpu_Indices_restore_state():
- bgl.glBindVertexArray(0)
- _restore_shader_state(PreviousGLState)
-
-
-def gpu_Indices_use_clip_planes(rv3d, value):
- if rv3d.use_clip_planes:
- planes = bgl.Buffer(bgl.GL_FLOAT, (6, 4), rv3d.clip_planes)
-
- _store_current_shader_state(PreviousGLState)
- GPU_Indices_Mesh.init_opengl()
- bgl.glUseProgram(GPU_Indices_Mesh.shader.program)
- bgl.glUniform1i(GPU_Indices_Mesh.unif_use_clip_planes, value)
-
- bgl.glUniform4fv(GPU_Indices_Mesh.unif_clip_plane, 4, planes)
-
- _restore_shader_state(PreviousGLState)
-
-
-def gpu_Indices_set_ProjectionMatrix(P):
- GPU_Indices_Mesh.P[:] = P
diff --git a/modules/snap_context/resources/3D_vert.glsl b/modules/snap_context/resources/3D_vert.glsl
deleted file mode 100644
index c97df2bf..00000000
--- a/modules/snap_context/resources/3D_vert.glsl
+++ /dev/null
@@ -1,26 +0,0 @@
-#version 120
-
-uniform bool use_clip_planes;
-uniform vec4 clip_plane[4];
-varying vec4 clip_distance;
-
-uniform mat4 MV;
-uniform mat4 MVP;
-
-attribute vec3 pos;
-attribute float primitive_id;
-varying float primitive_id_var;
-
-void main()
-{
- if (use_clip_planes) {
- vec4 g_pos = MV * vec4(pos, 1.0);
-
- for (int i = 0; i != 4; i++) {
- clip_distance[i] = dot(clip_plane[i], g_pos);
- }
- }
-
- primitive_id_var = primitive_id;
- gl_Position = MVP * vec4(pos, 1.0);
-}
diff --git a/modules/snap_context/utils_shader.py b/modules/snap_context/utils_shader.py
deleted file mode 100644
index 1758585a..00000000
--- a/modules/snap_context/utils_shader.py
+++ /dev/null
@@ -1,85 +0,0 @@
-# ##### BEGIN GPL LICENSE BLOCK #####
-#
-# This program is free software; you can redistribute it and/or
-# modify it under the terms of the GNU General Public License
-# as published by the Free Software Foundation; either version 3
-# of the License, or (at your option) any later version.
-#
-# This program 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 General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-#
-# ##### END GPL LICENSE BLOCK #####
-
-
-import bgl
-
-def check_shaderError(shader, flag, isProgram, errorMessage):
- success = bgl.Buffer(bgl.GL_INT, 1)
-
- if isProgram:
- bgl.glGetProgramiv(shader, flag, success)
- else:
- bgl.glGetShaderiv(shader, flag, success)
-
- if success[0] == bgl.GL_FALSE:
- import numpy as np
- import ctypes
-
- offset = bgl.Buffer(bgl.GL_INT, 1, (ctypes.c_int32 * 1).from_address(0))
- error = bgl.Buffer(bgl.GL_BYTE, 1024)
- if isProgram:
- bgl.glGetProgramInfoLog(shader, 1024, offset, error)
- print(errorMessage, np.bytes_(error).decode("utf-8"))
- else:
- bgl.glGetShaderInfoLog(shader, 1024, offset, error)
- print(errorMessage, np.bytes_(error).decode("utf-8"))
-
- del offset
- raise #RuntimeError(errorMessage, bgl.glGetShaderInfoLog(shader))
-
-
-def create_shader(source, shaderType):
- shader = bgl.glCreateShader(shaderType)
-
- if shader == 0:
- raise RuntimeError("Error: Shader creation failed!")
-
- bgl.glShaderSource(shader, source)
- bgl.glCompileShader(shader)
-
- check_shaderError(shader, bgl.GL_COMPILE_STATUS, False, "Error: Shader compilation failed:")
-
- return shader
-
-
-class Shader():
- def __init__(self, vertexcode, geomcode, fragcode):
- self.program = bgl.glCreateProgram()
- self.shaders = []
-
- if vertexcode:
- self.shaders.append(create_shader(vertexcode, bgl.GL_VERTEX_SHADER))
- if geomcode:
- self.shaders.append(create_shader(geomcode, bgl.GL_GEOMETRY_SHADER))
- if fragcode:
- self.shaders.append(create_shader(fragcode, bgl.GL_FRAGMENT_SHADER))
-
- for shad in self.shaders:
- bgl.glAttachShader(self.program, shad)
-
- bgl.glLinkProgram(self.program)
- check_shaderError(self.program, bgl.GL_LINK_STATUS, True, "Error: Program linking failed:")
- bgl.glValidateProgram(self.program)
- check_shaderError(self.program, bgl.GL_VALIDATE_STATUS, True, "Error: Program is invalid:")
-
- def __del__(self):
- for shad in self.shaders:
- bgl.glDetachShader(self.program, shad)
- bgl.glDeleteShader(shad)
- bgl.glDeleteProgram(self.program)
- #print('shader_del')
diff --git a/netrender/ui.py b/netrender/ui.py
index c5c22329..bcd83197 100644
--- a/netrender/ui.py
+++ b/netrender/ui.py
@@ -35,7 +35,7 @@ ADDRESS_TEST_TIMEOUT = 30
def base_poll(cls, context):
rd = context.scene.render
- return (rd.use_game_engine==False) and (rd.engine in cls.COMPAT_ENGINES)
+ return (rd.engine in cls.COMPAT_ENGINES)
def init_file():
@@ -96,7 +96,7 @@ class NetRenderButtonsPanel():
@classmethod
def poll(cls, context):
rd = context.scene.render
- return rd.engine == 'NET_RENDER' and rd.use_game_engine == False
+ return rd.engine == 'NET_RENDER'
# Setting panel, use in the scene for now.
class RENDER_PT_network_settings(NetRenderButtonsPanel, bpy.types.Panel):
diff --git a/node_wrangler.py b/node_wrangler.py
index b535a580..d3fd8448 100644
--- a/node_wrangler.py
+++ b/node_wrangler.py
@@ -20,7 +20,7 @@ bl_info = {
"name": "Node Wrangler",
"author": "Bartek Skorupa, Greg Zaal, Sebastian Koenig, Christian Brinkmann, Florian Meyer",
"version": (3, 35),
- "blender": (2, 78, 0),
+ "blender": (2, 80, 0),
"location": "Node Editor Toolbar or Ctrl-Space",
"description": "Various tools to enhance and speed up node-based workflow",
"warning": "",
@@ -115,7 +115,7 @@ shaders_input_nodes_props = (
# Keeping mixed case to avoid having to translate entries when adding new nodes in operators.
shaders_output_nodes_props = (
('ShaderNodeOutputMaterial', 'OUTPUT_MATERIAL', 'Material Output'),
- ('ShaderNodeOutputLamp', 'OUTPUT_LAMP', 'Lamp Output'),
+ ('ShaderNodeOutputLight', 'OUTPUT_LIGHT', 'Light Output'),
('ShaderNodeOutputWorld', 'OUTPUT_WORLD', 'World Output'),
)
# (rna_type.identifier, type, rna_type.name)
@@ -332,7 +332,7 @@ compo_layout_nodes_props = (
blender_mat_input_nodes_props = (
('ShaderNodeMaterial', 'MATERIAL', 'Material'),
('ShaderNodeCameraData', 'CAMERA', 'Camera Data'),
- ('ShaderNodeLampData', 'LAMP', 'Lamp Data'),
+ ('ShaderNodeLightData', 'LIGHT', 'Light Data'),
('ShaderNodeValue', 'VALUE', 'Value'),
('ShaderNodeRGB', 'RGB', 'RGB'),
('ShaderNodeTexture', 'TEXTURE', 'Texture'),
@@ -547,13 +547,16 @@ draw_color_sets = {
}
+def is_cycles_or_eevee(context):
+ return context.scene.render.engine in {'CYCLES', 'BLENDER_EEVEE'}
+
+
def nice_hotkey_name(punc):
# convert the ugly string name into the actual character
pairs = (
('LEFTMOUSE', "LMB"),
('MIDDLEMOUSE', "MMB"),
('RIGHTMOUSE', "RMB"),
- ('SELECTMOUSE', "Select"),
('WHEELUPMOUSE', "Wheel Up"),
('WHEELDOWNMOUSE', "Wheel Down"),
('WHEELINMOUSE', "Wheel In"),
@@ -1000,39 +1003,39 @@ def get_nodes_links(context):
# Principled prefs
class NWPrincipledPreferences(bpy.types.PropertyGroup):
- base_color = StringProperty(
+ base_color: StringProperty(
name='Base Color',
default='diffuse diff albedo base col color',
description='Naming Components for Base Color maps')
- sss_color = StringProperty(
+ sss_color: StringProperty(
name='Subsurface Color',
default='sss subsurface',
description='Naming Components for Subsurface Color maps')
- metallic = StringProperty(
+ metallic: StringProperty(
name='Metallic',
default='metallic metalness metal mtl',
description='Naming Components for metallness maps')
- specular = StringProperty(
+ specular: StringProperty(
name='Specular',
default='specularity specular spec spc',
description='Naming Components for Specular maps')
- normal = StringProperty(
+ normal: StringProperty(
name='Normal',
default='normal nor nrm nrml norm',
description='Naming Components for Normal maps')
- bump = StringProperty(
+ bump: StringProperty(
name='Bump',
default='bump bmp',
description='Naming Components for bump maps')
- rough = StringProperty(
+ rough: StringProperty(
name='Roughness',
default='roughness rough rgh',
description='Naming Components for roughness maps')
- gloss = StringProperty(
+ gloss: StringProperty(
name='Gloss',
default='gloss glossy glossyness',
description='Naming Components for glossy maps')
- displacement = StringProperty(
+ displacement: StringProperty(
name='Displacement',
default='displacement displace disp dsp height heightmap',
description='Naming Components for displacement maps')
@@ -1041,7 +1044,7 @@ class NWPrincipledPreferences(bpy.types.PropertyGroup):
class NWNodeWrangler(bpy.types.AddonPreferences):
bl_idname = __name__
- merge_hide = EnumProperty(
+ merge_hide: EnumProperty(
name="Hide Mix nodes",
items=(
("ALWAYS", "Always", "Always collapse the new merge nodes"),
@@ -1050,7 +1053,7 @@ class NWNodeWrangler(bpy.types.AddonPreferences):
),
default='NON_SHADER',
description="When merging nodes with the Ctrl+Numpad0 hotkey (and similar) specify whether to collapse them or show the full node with options expanded")
- merge_position = EnumProperty(
+ merge_position: EnumProperty(
name="Mix Node Position",
items=(
("CENTER", "Center", "Place the Mix node between the two nodes"),
@@ -1059,22 +1062,22 @@ class NWNodeWrangler(bpy.types.AddonPreferences):
default='CENTER',
description="When merging nodes with the Ctrl+Numpad0 hotkey (and similar) specify the position of the new nodes")
- show_hotkey_list = BoolProperty(
+ show_hotkey_list: BoolProperty(
name="Show Hotkey List",
default=False,
description="Expand this box into a list of all the hotkeys for functions in this addon"
)
- hotkey_list_filter = StringProperty(
+ hotkey_list_filter: StringProperty(
name=" Filter by Name",
default="",
description="Show only hotkeys that have this text in their name"
)
- show_principled_lists = BoolProperty(
+ show_principled_lists: BoolProperty(
name="Show Principled naming tags",
default=False,
description="Expand this box into a list of all naming tags for principled texture setup"
)
- principled_tags = bpy.props.PointerProperty(type=NWPrincipledPreferences)
+ principled_tags: bpy.props.PointerProperty(type=NWPrincipledPreferences)
def draw(self, context):
layout = self.layout
@@ -1113,7 +1116,7 @@ class NWNodeWrangler(bpy.types.AddonPreferences):
if self.hotkey_list_filter.lower() in hotkey_name.lower():
row = col.row(align=True)
- row.label(hotkey_name)
+ row.label(text=hotkey_name)
keystr = nice_hotkey_name(hotkey[1])
if hotkey[4]:
keystr = "Shift " + keystr
@@ -1121,7 +1124,7 @@ class NWNodeWrangler(bpy.types.AddonPreferences):
keystr = "Alt " + keystr
if hotkey[3]:
keystr = "Ctrl " + keystr
- row.label(keystr)
+ row.label(text=keystr)
@@ -1223,7 +1226,7 @@ class NWLazyConnect(Operator, NWBase):
bl_idname = "node.nw_lazy_connect"
bl_label = "Lazy Connect"
bl_options = {'REGISTER', 'UNDO'}
- with_menu = BoolProperty()
+ with_menu: BoolProperty()
def modal(self, context, event):
context.area.tag_redraw()
@@ -1329,12 +1332,12 @@ class NWDeleteUnused(Operator, NWBase):
bl_label = 'Delete Unused Nodes'
bl_options = {'REGISTER', 'UNDO'}
- delete_muted = BoolProperty(name="Delete Muted", description="Delete (but reconnect, like Ctrl-X) all muted nodes", default=True)
- delete_frames = BoolProperty(name="Delete Empty Frames", description="Delete all frames that have no nodes inside them", default=True)
+ delete_muted: BoolProperty(name="Delete Muted", description="Delete (but reconnect, like Ctrl-X) all muted nodes", default=True)
+ delete_frames: BoolProperty(name="Delete Empty Frames", description="Delete all frames that have no nodes inside them", default=True)
def is_unused_node(self, node):
end_types = ['OUTPUT_MATERIAL', 'OUTPUT', 'VIEWER', 'COMPOSITE', \
- 'SPLITVIEWER', 'OUTPUT_FILE', 'LEVELS', 'OUTPUT_LAMP', \
+ 'SPLITVIEWER', 'OUTPUT_FILE', 'LEVELS', 'OUTPUT_LIGHT', \
'OUTPUT_WORLD', 'GROUP_INPUT', 'GROUP_OUTPUT', 'FRAME']
if node.type in end_types:
return False
@@ -1558,8 +1561,8 @@ class NWResetBG(Operator, NWBase):
def execute(self, context):
context.space_data.backdrop_zoom = 1
- context.space_data.backdrop_x = 0
- context.space_data.backdrop_y = 0
+ context.space_data.backdrop_offset[0] = 0
+ context.space_data.backdrop_offset[1] = 0
return {'FINISHED'}
@@ -1567,9 +1570,10 @@ class NWAddAttrNode(Operator, NWBase):
"""Add an Attribute node with this name"""
bl_idname = 'node.nw_add_attr_node'
bl_label = 'Add UV map'
- attr_name = StringProperty()
bl_options = {'REGISTER', 'UNDO'}
+ attr_name: StringProperty()
+
def execute(self, context):
bpy.ops.node.add_node('INVOKE_DEFAULT', use_transform=True, type="ShaderNodeAttribute")
nodes, links = get_nodes_links(context)
@@ -1585,7 +1589,7 @@ class NWEmissionViewer(Operator, NWBase):
@classmethod
def poll(cls, context):
- is_cycles = context.scene.render.engine == 'CYCLES'
+ is_cycles = is_cycles_or_eevee(context)
if nw_check(context):
space = context.space_data
if space.tree_type == 'ShaderNodeTree' and is_cycles:
@@ -1600,13 +1604,13 @@ class NWEmissionViewer(Operator, NWBase):
space = context.space_data
shader_type = space.shader_type
if shader_type == 'OBJECT':
- if space.id not in [lamp for lamp in bpy.data.lamps]: # cannot use bpy.data.lamps directly as iterable
+ if space.id not in [light for light in bpy.data.lights]: # cannot use bpy.data.lights directly as iterable
shader_output_type = "OUTPUT_MATERIAL"
shader_output_ident = "ShaderNodeOutputMaterial"
shader_viewer_ident = "ShaderNodeEmission"
else:
- shader_output_type = "OUTPUT_LAMP"
- shader_output_ident = "ShaderNodeOutputLamp"
+ shader_output_type = "OUTPUT_LIGHT"
+ shader_output_ident = "ShaderNodeOutputLight"
shader_viewer_ident = "ShaderNodeEmission"
elif shader_type == 'WORLD':
@@ -1734,9 +1738,19 @@ class NWFrameSelected(Operator, NWBase):
bl_label = "Frame Selected"
bl_description = "Add a frame node and parent the selected nodes to it"
bl_options = {'REGISTER', 'UNDO'}
- label_prop = StringProperty(name='Label', default=' ', description='The visual name of the frame node')
- color_prop = FloatVectorProperty(name="Color", description="The color of the frame node", default=(0.6, 0.6, 0.6),
- min=0, max=1, step=1, precision=3, subtype='COLOR_GAMMA', size=3)
+
+ label_prop: StringProperty(
+ name='Label',
+ description='The visual name of the frame node',
+ default=' '
+ )
+ color_prop: FloatVectorProperty(
+ name="Color",
+ description="The color of the frame node",
+ default=(0.6, 0.6, 0.6),
+ min=0, max=1, step=1, precision=3,
+ subtype='COLOR_GAMMA', size=3
+ )
def execute(self, context):
nodes, links = get_nodes_links(context)
@@ -1795,7 +1809,7 @@ class NWSwitchNodeType(Operator, NWBase):
bl_label = "Switch Node Type"
bl_options = {'REGISTER', 'UNDO'}
- to_type = EnumProperty(
+ to_type: EnumProperty(
name="Switch to type",
items=list(shaders_input_nodes_props) +
list(shaders_output_nodes_props) +
@@ -2009,12 +2023,12 @@ class NWMergeNodes(Operator, NWBase):
bl_description = "Merge Selected Nodes"
bl_options = {'REGISTER', 'UNDO'}
- mode = EnumProperty(
+ mode: EnumProperty(
name="mode",
description="All possible blend types and math operations",
items=blend_types + [op for op in operations if op not in blend_types],
)
- merge_type = EnumProperty(
+ merge_type: EnumProperty(
name="merge type",
description="Type of Merge to be used",
items=(
@@ -2260,11 +2274,11 @@ class NWBatchChangeNodes(Operator, NWBase):
bl_description = "Batch Change Blend Type and Math Operation"
bl_options = {'REGISTER', 'UNDO'}
- blend_type = EnumProperty(
+ blend_type: EnumProperty(
name="Blend Type",
items=blend_types + navs,
)
- operation = EnumProperty(
+ operation: EnumProperty(
name="Operation",
items=operations + navs,
)
@@ -2326,7 +2340,7 @@ class NWChangeMixFactor(Operator, NWBase):
# option: Change factor.
# If option is 1.0 or 0.0 - set to 1.0 or 0.0
# Else - change factor by option value.
- option = FloatProperty()
+ option: FloatProperty()
def execute(self, context):
nodes, links = get_nodes_links(context)
@@ -2465,7 +2479,7 @@ class NWCopyLabel(Operator, NWBase):
bl_label = "Copy Label"
bl_options = {'REGISTER', 'UNDO'}
- option = EnumProperty(
+ option: EnumProperty(
name="option",
description="Source of name of label",
items=(
@@ -2509,7 +2523,7 @@ class NWClearLabel(Operator, NWBase):
bl_label = "Clear Label"
bl_options = {'REGISTER', 'UNDO'}
- option = BoolProperty()
+ option: BoolProperty()
def execute(self, context):
nodes, links = get_nodes_links(context)
@@ -2531,16 +2545,16 @@ class NWModifyLabels(Operator, NWBase):
bl_label = "Modify Labels"
bl_options = {'REGISTER', 'UNDO'}
- prepend = StringProperty(
+ prepend: StringProperty(
name="Add to Beginning"
)
- append = StringProperty(
+ append: StringProperty(
name="Add to End"
)
- replace_from = StringProperty(
+ replace_from: StringProperty(
name="Text to Replace"
)
- replace_to = StringProperty(
+ replace_to: StringProperty(
name="Replace with"
)
@@ -2564,14 +2578,14 @@ class NWAddTextureSetup(Operator, NWBase):
bl_description = "Add Texture Node Setup to Selected Shaders"
bl_options = {'REGISTER', 'UNDO'}
- add_mapping = BoolProperty(name="Add Mapping Nodes", description="Create coordinate and mapping nodes for the texture (ignored for selected texture nodes)", default=True)
+ add_mapping: BoolProperty(name="Add Mapping Nodes", description="Create coordinate and mapping nodes for the texture (ignored for selected texture nodes)", default=True)
@classmethod
def poll(cls, context):
valid = False
if nw_check(context):
space = context.space_data
- if space.tree_type == 'ShaderNodeTree' and context.scene.render.engine == 'CYCLES':
+ if space.tree_type == 'ShaderNodeTree' and is_cycles_or_eevee(context):
valid = True
return valid
@@ -2642,26 +2656,28 @@ class NWAddPrincipledSetup(Operator, NWBase, ImportHelper):
bl_description = "Add Texture Node Setup for Principled BSDF"
bl_options = {'REGISTER', 'UNDO'}
- directory = StringProperty(
- name='Directory',
- subtype='DIR_PATH',
- default='',
- description='Folder to search in for image files')
- files = CollectionProperty(
- type=bpy.types.OperatorFileListElement,
- options={'HIDDEN', 'SKIP_SAVE'})
+ directory: StringProperty(
+ name='Directory',
+ subtype='DIR_PATH',
+ default='',
+ description='Folder to search in for image files'
+ )
+ files: CollectionProperty(
+ type=bpy.types.OperatorFileListElement,
+ options={'HIDDEN', 'SKIP_SAVE'}
+ )
order = [
"filepath",
"files",
- ]
+ ]
@classmethod
def poll(cls, context):
valid = False
if nw_check(context):
space = context.space_data
- if space.tree_type == 'ShaderNodeTree' and context.scene.render.engine == 'CYCLES':
+ if space.tree_type == 'ShaderNodeTree' and is_cycles_or_eevee(context):
valid = True
return valid
@@ -2755,35 +2771,18 @@ class NWAddPrincipledSetup(Operator, NWBase, ImportHelper):
disp_texture.color_space = 'NONE'
# Add displacement offset nodes
- math_sub = nodes.new(type='ShaderNodeMath')
- math_sub.operation = 'SUBTRACT'
- math_sub.label = 'Offset'
- math_sub.location = active_node.location + Vector((0, -560))
- math_mul = nodes.new(type='ShaderNodeMath')
- math_mul.operation = 'MULTIPLY'
- math_mul.label = 'Strength'
- math_mul.location = math_sub.location + Vector((200, 0))
- link = links.new(math_mul.inputs[0], math_sub.outputs[0])
- link = links.new(math_sub.inputs[0], disp_texture.outputs[0])
-
- # Turn on true displacement in the material
+ disp_node = nodes.new(type='ShaderNodeDisplacement')
+ disp_node.location = active_node.location + Vector((0, -560))
+ link = links.new(disp_node.inputs[0], disp_texture.outputs[0])
+
+ # TODO Turn on true displacement in the material
# Too complicated for now
- '''
- # Frame. Does not update immediately
- # Seems to need an editor redraw
- frame = nodes.new(type='NodeFrame')
- frame.label = 'Displacement'
- math_sub.parent = frame
- math_mul.parent = frame
- frame.update()
- '''
-
- #find output node
+ # Find output node
output_node = [n for n in nodes if n.bl_idname == 'ShaderNodeOutputMaterial']
if output_node:
if not output_node[0].inputs[2].is_linked:
- link = links.new(output_node[0].inputs[2], math_mul.outputs[0])
+ link = links.new(output_node[0].inputs[2], disp_node.outputs[0])
continue
@@ -2850,23 +2849,24 @@ class NWAddPrincipledSetup(Operator, NWBase, ImportHelper):
# Alignment
for i, texture_node in enumerate(texture_nodes):
- offset = Vector((-400, (i * -260) + 200))
+ offset = Vector((-550, (i * -280) + 200))
texture_node.location = active_node.location + offset
if normal_node:
# Extra alignment if normal node was added
- normal_node.location = normal_node_texture.location + Vector((200, 0))
+ normal_node.location = normal_node_texture.location + Vector((300, 0))
if roughness_node:
# Alignment of invert node if glossy map
- invert_node.location = roughness_node.location + Vector((200, 0))
+ invert_node.location = roughness_node.location + Vector((300, 0))
# Add texture input + mapping
mapping = nodes.new(type='ShaderNodeMapping')
- mapping.location = active_node.location + Vector((-900, 0))
+ mapping.location = active_node.location + Vector((-1050, 0))
if len(texture_nodes) > 1:
# If more than one texture add reroute node in between
reroute = nodes.new(type='NodeReroute')
+ texture_nodes.append(reroute)
tex_coords = Vector((texture_nodes[0].location.x, sum(n.location.y for n in texture_nodes)/len(texture_nodes)))
reroute.location = tex_coords + Vector((-50, -120))
for texture_node in texture_nodes:
@@ -2880,6 +2880,20 @@ class NWAddPrincipledSetup(Operator, NWBase, ImportHelper):
texture_input.location = mapping.location + Vector((-200, 0))
link = links.new(mapping.inputs[0], texture_input.outputs[2])
+ # Create frame around tex coords and mapping
+ frame = nodes.new(type='NodeFrame')
+ frame.label = 'Mapping'
+ mapping.parent = frame
+ texture_input.parent = frame
+ frame.update()
+
+ # Create frame around texture nodes
+ frame = nodes.new(type='NodeFrame')
+ frame.label = 'Textures'
+ for tnode in texture_nodes:
+ tnode.parent = frame
+ frame.update()
+
# Just to be sure
active_node.select = False
nodes.update()
@@ -2895,7 +2909,7 @@ class NWAddReroutes(Operator, NWBase):
bl_description = "Add Reroutes to Outputs"
bl_options = {'REGISTER', 'UNDO'}
- option = EnumProperty(
+ option: EnumProperty(
name="option",
items=[
('ALL', 'to all', 'Add to all outputs'),
@@ -2995,9 +3009,9 @@ class NWLinkActiveToSelected(Operator, NWBase):
bl_label = "Link Active Node to Selected"
bl_options = {'REGISTER', 'UNDO'}
- replace = BoolProperty()
- use_node_name = BoolProperty()
- use_outputs_names = BoolProperty()
+ replace: BoolProperty()
+ use_node_name: BoolProperty()
+ use_outputs_names: BoolProperty()
@classmethod
def poll(cls, context):
@@ -3075,7 +3089,7 @@ class NWAlignNodes(Operator, NWBase):
bl_idname = "node.nw_align_nodes"
bl_label = "Align Nodes"
bl_options = {'REGISTER', 'UNDO'}
- margin = IntProperty(name='Margin', default=50, description='The amount of space between nodes')
+ margin: IntProperty(name='Margin', default=50, description='The amount of space between nodes')
def execute(self, context):
nodes, links = get_nodes_links(context)
@@ -3145,7 +3159,7 @@ class NWSelectParentChildren(Operator, NWBase):
bl_label = "Select Parent or Children"
bl_options = {'REGISTER', 'UNDO'}
- option = EnumProperty(
+ option: EnumProperty(
name="option",
items=(
('PARENT', 'Select Parent', 'Select Parent Frame'),
@@ -3228,7 +3242,7 @@ class NWLinkToOutputNode(Operator, NWBase):
if not output_node:
bpy.ops.node.select_all(action="DESELECT")
if tree_type == 'ShaderNodeTree':
- if context.scene.render.engine == 'CYCLES':
+ if is_cycles_or_eevee(context):
output_node = nodes.new('ShaderNodeOutputMaterial')
else:
output_node = nodes.new('ShaderNodeOutput')
@@ -3249,7 +3263,7 @@ class NWLinkToOutputNode(Operator, NWBase):
break
out_input_index = 0
- if tree_type == 'ShaderNodeTree' and context.scene.render.engine == 'CYCLES':
+ if tree_type == 'ShaderNodeTree' and is_cycles_or_eevee(context):
if active.outputs[output_index].name == 'Volume':
out_input_index = 1
elif active.outputs[output_index].type != 'SHADER': # connect to displacement if not a shader
@@ -3266,8 +3280,8 @@ class NWMakeLink(Operator, NWBase):
bl_idname = 'node.nw_make_link'
bl_label = 'Make Link'
bl_options = {'REGISTER', 'UNDO'}
- from_socket = IntProperty()
- to_socket = IntProperty()
+ from_socket: IntProperty()
+ to_socket: IntProperty()
def execute(self, context):
nodes, links = get_nodes_links(context)
@@ -3287,7 +3301,7 @@ class NWCallInputsMenu(Operator, NWBase):
bl_idname = 'node.nw_call_inputs_menu'
bl_label = 'Make Link'
bl_options = {'REGISTER', 'UNDO'}
- from_socket = IntProperty()
+ from_socket: IntProperty()
def execute(self, context):
nodes, links = get_nodes_links(context)
@@ -3308,9 +3322,17 @@ class NWAddSequence(Operator, ImportHelper):
bl_idname = 'node.nw_add_sequence'
bl_label = 'Import Image Sequence'
bl_options = {'REGISTER', 'UNDO'}
- directory = StringProperty(subtype="DIR_PATH")
- filename = StringProperty(subtype="FILE_NAME")
- files = CollectionProperty(type=bpy.types.OperatorFileListElement, options={'HIDDEN', 'SKIP_SAVE'})
+
+ directory: StringProperty(
+ subtype="DIR_PATH"
+ )
+ filename: StringProperty(
+ subtype="FILE_NAME"
+ )
+ files: CollectionProperty(
+ type=bpy.types.OperatorFileListElement,
+ options={'HIDDEN', 'SKIP_SAVE'}
+ )
def execute(self, context):
nodes, links = get_nodes_links(context)
@@ -3402,8 +3424,13 @@ class NWAddMultipleImages(Operator, ImportHelper):
bl_idname = 'node.nw_add_multiple_images'
bl_label = 'Open Selected Images'
bl_options = {'REGISTER', 'UNDO'}
- directory = StringProperty(subtype="DIR_PATH")
- files = CollectionProperty(type=bpy.types.OperatorFileListElement, options={'HIDDEN', 'SKIP_SAVE'})
+ directory: StringProperty(
+ subtype="DIR_PATH"
+ )
+ files: CollectionProperty(
+ type=bpy.types.OperatorFileListElement,
+ options={'HIDDEN', 'SKIP_SAVE'}
+ )
def execute(self, context):
nodes, links = get_nodes_links(context)
@@ -3450,8 +3477,8 @@ class NWViewerFocus(bpy.types.Operator):
bl_idname = "node.nw_viewer_focus"
bl_label = "Viewer Focus"
- x = bpy.props.IntProperty()
- y = bpy.props.IntProperty()
+ x: bpy.props.IntProperty()
+ y: bpy.props.IntProperty()
@classmethod
def poll(cls, context):
@@ -3505,8 +3532,8 @@ class NWSaveViewer(bpy.types.Operator, ExportHelper):
"""Save the current viewer node to an image file"""
bl_idname = "node.nw_save_viewer"
bl_label = "Save This Image"
- filepath = StringProperty(subtype="FILE_PATH")
- filename_ext = EnumProperty(
+ filepath: StringProperty(subtype="FILE_PATH")
+ filename_ext: EnumProperty(
name="Format",
description="Choose the file format to save to",
items=(('.bmp', "PNG", ""),
@@ -3684,7 +3711,7 @@ def drawlayout(context, layout, mode='non-panel'):
col.menu(NWSwitchNodeTypeMenu.bl_idname, text="Switch Node Type")
col.separator()
- if tree_type == 'ShaderNodeTree' and context.scene.render.engine == 'CYCLES':
+ if tree_type == 'ShaderNodeTree' and is_cycles_or_eevee(context):
col = layout.column(align=True)
col.operator(NWAddTextureSetup.bl_idname, text="Add Texture Setup", icon='NODE_SEL')
col.operator(NWAddPrincipledSetup.bl_idname, text="Add Principled Setup", icon='NODE_SEL')
@@ -3725,7 +3752,7 @@ def drawlayout(context, layout, mode='non-panel'):
col.separator()
col = layout.column(align=True)
- col.operator(NWAlignNodes.bl_idname, icon='ALIGN')
+ col.operator(NWAlignNodes.bl_idname, icon='CENTER_ONLY')
col.separator()
col = layout.column(align=True)
@@ -3737,14 +3764,14 @@ class NodeWranglerPanel(Panel, NWBase):
bl_idname = "NODE_PT_nw_node_wrangler"
bl_space_type = 'NODE_EDITOR'
bl_label = "Node Wrangler"
- bl_region_type = "TOOLS"
+ bl_region_type = "UI"
bl_category = "Node Wrangler"
- prepend = StringProperty(
+ prepend: StringProperty(
name='prepend',
)
- append = StringProperty()
- remove = StringProperty()
+ append: StringProperty()
+ remove: StringProperty()
def draw(self, context):
self.layout.label(text="(Quick access: Ctrl+Space)")
@@ -3769,7 +3796,7 @@ class NWMergeNodesMenu(Menu, NWBase):
def draw(self, context):
type = context.space_data.tree_type
layout = self.layout
- if type == 'ShaderNodeTree' and context.scene.render.engine == 'CYCLES':
+ if type == 'ShaderNodeTree' and is_cycles_or_eevee(context):
layout.menu(NWMergeShadersMenu.bl_idname, text="Use Shaders")
layout.menu(NWMergeMixMenu.bl_idname, text="Use Mix Nodes")
layout.menu(NWMergeMathMenu.bl_idname, text="Use Math Nodes")
@@ -3993,7 +4020,7 @@ class NWVertColMenu(bpy.types.Menu):
valid = False
if nw_check(context):
snode = context.space_data
- valid = snode.tree_type == 'ShaderNodeTree' and context.scene.render.engine == 'CYCLES'
+ valid = snode.tree_type == 'ShaderNodeTree' and is_cycles_or_eevee(context)
return valid
def draw(self, context):
@@ -4028,7 +4055,7 @@ class NWSwitchNodeTypeMenu(Menu, NWBase):
layout = self.layout
tree = context.space_data.node_tree
if tree.type == 'SHADER':
- if context.scene.render.engine == 'CYCLES':
+ if is_cycles_or_eevee(context):
layout.menu(NWSwitchShadersInputSubmenu.bl_idname)
layout.menu(NWSwitchShadersOutputSubmenu.bl_idname)
layout.menu(NWSwitchShadersShaderSubmenu.bl_idname)
@@ -4037,7 +4064,7 @@ class NWSwitchNodeTypeMenu(Menu, NWBase):
layout.menu(NWSwitchShadersVectorSubmenu.bl_idname)
layout.menu(NWSwitchShadersConverterSubmenu.bl_idname)
layout.menu(NWSwitchShadersLayoutSubmenu.bl_idname)
- if context.scene.render.engine != 'CYCLES':
+ else:
layout.menu(NWSwitchMatInputSubmenu.bl_idname)
layout.menu(NWSwitchMatOutputSubmenu.bl_idname)
layout.menu(NWSwitchMatColorSubmenu.bl_idname)
@@ -4645,7 +4672,7 @@ kmi_defs = (
# Reset Nodes (Back Space)
(NWResetNodes.bl_idname, 'BACK_SPACE', 'PRESS', False, False, False, None, "Revert node back to default state, but keep connections"),
# MENUS
- ('wm.call_menu', 'SPACE', 'PRESS', True, False, False, (('name', NodeWranglerMenu.bl_idname),), "Node Wranger menu"),
+ ('wm.call_menu', 'SPACE', 'PRESS', True, True, False, (('name', NodeWranglerMenu.bl_idname),), "Node Wranger menu"),
('wm.call_menu', 'SLASH', 'PRESS', False, False, False, (('name', NWAddReroutesMenu.bl_idname),), "Add Reroutes menu"),
('wm.call_menu', 'NUMPAD_SLASH', 'PRESS', False, False, False, (('name', NWAddReroutesMenu.bl_idname),), "Add Reroutes menu"),
('wm.call_menu', 'BACK_SLASH', 'PRESS', False, False, False, (('name', NWLinkActiveToSelectedMenu.bl_idname),), "Link active to selected (menu)"),
@@ -4781,13 +4808,10 @@ def register():
# menu items
bpy.types.NODE_MT_select.append(select_parent_children_buttons)
bpy.types.NODE_MT_category_SH_NEW_INPUT.prepend(attr_nodes_menu_func)
- bpy.types.NODE_PT_category_SH_NEW_INPUT.prepend(attr_nodes_menu_func)
bpy.types.NODE_PT_backdrop.append(bgreset_menu_func)
bpy.types.NODE_PT_active_node_generic.append(save_viewer_menu_func)
bpy.types.NODE_MT_category_SH_NEW_TEXTURE.prepend(multipleimages_menu_func)
- bpy.types.NODE_PT_category_SH_NEW_TEXTURE.prepend(multipleimages_menu_func)
bpy.types.NODE_MT_category_CMP_INPUT.prepend(multipleimages_menu_func)
- bpy.types.NODE_PT_category_CMP_INPUT.prepend(multipleimages_menu_func)
bpy.types.NODE_PT_active_node_generic.prepend(reset_nodes_button)
bpy.types.NODE_MT_node.prepend(reset_nodes_button)
@@ -4809,13 +4833,10 @@ def unregister():
# menuitems
bpy.types.NODE_MT_select.remove(select_parent_children_buttons)
bpy.types.NODE_MT_category_SH_NEW_INPUT.remove(attr_nodes_menu_func)
- bpy.types.NODE_PT_category_SH_NEW_INPUT.remove(attr_nodes_menu_func)
bpy.types.NODE_PT_backdrop.remove(bgreset_menu_func)
bpy.types.NODE_PT_active_node_generic.remove(save_viewer_menu_func)
bpy.types.NODE_MT_category_SH_NEW_TEXTURE.remove(multipleimages_menu_func)
- bpy.types.NODE_PT_category_SH_NEW_TEXTURE.remove(multipleimages_menu_func)
bpy.types.NODE_MT_category_CMP_INPUT.remove(multipleimages_menu_func)
- bpy.types.NODE_PT_category_CMP_INPUT.remove(multipleimages_menu_func)
bpy.types.NODE_PT_active_node_generic.remove(reset_nodes_button)
bpy.types.NODE_MT_node.remove(reset_nodes_button)
diff --git a/object_boolean_tools.py b/object_boolean_tools.py
index 4c7e1bc1..702fbb8e 100644
--- a/object_boolean_tools.py
+++ b/object_boolean_tools.py
@@ -159,9 +159,9 @@ def Operation(context, _operation):
obj.modifiers.remove(mod)
"""
if useWire:
- selObj.draw_type = "WIRE"
+ selObj.display_type = "WIRE"
else:
- selObj.draw_type = "BOUNDS"
+ selObj.display_type = "BOUNDS"
cyclesVis.camera = False
cyclesVis.diffuse = False
@@ -169,7 +169,7 @@ def Operation(context, _operation):
cyclesVis.shadow = False
cyclesVis.transmission = False
if _operation == "SLICE":
- # copies dupli_group property(empty), but group property is empty (users_group = None)
+ # copies instance_collection property(empty), but group property is empty (users_group = None)
clone = context.active_object.copy()
# clone.select = True
context.scene.objects.link(clone)
@@ -200,7 +200,7 @@ def Remove(context, thisObj_name, Prop):
# if it's the brush object
if obj.name == _thisObj_name:
cyclesVis = obj.cycles_visibility
- obj.draw_type = "TEXTURED"
+ obj.display_type = "TEXTURED"
del obj["BoolToolBrush"]
del obj["BoolTool_FTransform"]
cyclesVis.camera = True
@@ -229,7 +229,7 @@ def Remove(context, thisObj_name, Prop):
if (actObj.name in mod.name):
Canvas.modifiers.remove(mod)
cyclesVis = actObj.cycles_visibility
- actObj.draw_type = "TEXTURED"
+ actObj.display_type = "TEXTURED"
del actObj["BoolToolBrush"]
del actObj["BoolTool_FTransform"]
cyclesVis.camera = True
@@ -509,9 +509,9 @@ class BTool_FastTransform(Operator):
if isBrush(actObj) and actObj["BoolTool_FTransform"] == "True":
EnableThisBrush(bpy.context, "False")
if useWire:
- actObj.draw_type = "WIRE"
+ actObj.display_type = "WIRE"
else:
- actObj.draw_type = "BOUNDS"
+ actObj.display_type = "BOUNDS"
if self.operator == "Translate":
bpy.ops.transform.translate('INVOKE_DEFAULT')
@@ -523,13 +523,13 @@ class BTool_FastTransform(Operator):
if event.type == 'LEFTMOUSE':
if isBrush(actObj):
EnableThisBrush(bpy.context, "True")
- actObj.draw_type = "WIRE"
+ actObj.display_type = "WIRE"
return {'FINISHED'}
if event.type in {'RIGHTMOUSE', 'ESC'}:
if isBrush(actObj):
EnableThisBrush(bpy.context, "True")
- actObj.draw_type = "WIRE"
+ actObj.display_type = "WIRE"
return {'CANCELLED'}
return {'RUNNING_MODAL'}
@@ -914,18 +914,18 @@ class VIEW3D_MT_booltool_menu(Menu):
layout = self.layout
layout.label("Auto Boolean:")
- layout.operator(OBJECT_OT_BoolTool_Auto_Difference.bl_idname, text='Difference', icon="ROTACTIVE")
- layout.operator(OBJECT_OT_BoolTool_Auto_Union.bl_idname, text='Union', icon="ROTATECOLLECTION")
- layout.operator(OBJECT_OT_BoolTool_Auto_Intersect.bl_idname, text='Intersect', icon="ROTATECENTER")
- layout.operator(OBJECT_OT_BoolTool_Auto_Slice.bl_idname, text='Slice', icon="ROTATECENTER")
- layout.operator(OBJECT_OT_BoolTool_Auto_Subtract.bl_idname, text='Subtract', icon="ROTACTIVE")
+ layout.operator(OBJECT_OT_BoolTool_Auto_Difference.bl_idname, text='Difference', icon='PIVOT_ACTIVE')
+ layout.operator(OBJECT_OT_BoolTool_Auto_Union.bl_idname, text='Union', icon='PIVOT_INDIVIDUAL')
+ layout.operator(OBJECT_OT_BoolTool_Auto_Intersect.bl_idname, text='Intersect', icon='PIVOT_MEDIAN')
+ layout.operator(OBJECT_OT_BoolTool_Auto_Slice.bl_idname, text='Slice', icon='PIVOT_MEDIAN')
+ layout.operator(OBJECT_OT_BoolTool_Auto_Subtract.bl_idname, text='Subtract', icon='PIVOT_ACTIVE')
layout.separator()
layout.label("Brush Boolean:")
- layout.operator(BTool_Diff.bl_idname, icon="ROTACTIVE")
- layout.operator(BTool_Union.bl_idname, icon="ROTATECOLLECTION")
- layout.operator(BTool_Inters.bl_idname, icon="ROTATECENTER")
- layout.operator(BTool_Slice.bl_idname, icon="ROTATECENTER")
+ layout.operator(BTool_Diff.bl_idname, icon='PIVOT_ACTIVE')
+ layout.operator(BTool_Union.bl_idname, icon='PIVOT_INDIVIDUAL')
+ layout.operator(BTool_Inters.bl_idname, icon='PIVOT_MEDIAN')
+ layout.operator(BTool_Slice.bl_idname, icon='PIVOT_MEDIAN')
if (isCanvas(context.active_object)):
layout.separator()
@@ -977,16 +977,16 @@ class VIEW3D_PT_booltool_tools(Panel):
col.enabled = obs_len > 1
col.label("Auto Boolean:", icon="MODIFIER")
col.separator()
- col.operator(OBJECT_OT_BoolTool_Auto_Difference.bl_idname, text='Difference', icon="ROTACTIVE")
- col.operator(OBJECT_OT_BoolTool_Auto_Union.bl_idname, text='Union', icon="ROTATECOLLECTION")
- col.operator(OBJECT_OT_BoolTool_Auto_Intersect.bl_idname, text='Intersect', icon="ROTATECENTER")
+ col.operator(OBJECT_OT_BoolTool_Auto_Difference.bl_idname, text='Difference', icon='PIVOT_ACTIVE')
+ col.operator(OBJECT_OT_BoolTool_Auto_Union.bl_idname, text='Union', icon='PIVOT_INDIVIDUAL')
+ col.operator(OBJECT_OT_BoolTool_Auto_Intersect.bl_idname, text='Intersect', icon='PIVOT_MEDIAN')
main.separator()
col = main.column(align=True)
col.enabled = obs_len == 2
- col.operator(OBJECT_OT_BoolTool_Auto_Slice.bl_idname, text='Slice', icon="ROTATECENTER")
- col.operator(OBJECT_OT_BoolTool_Auto_Subtract.bl_idname, text='Subtract', icon="ROTACTIVE")
+ col.operator(OBJECT_OT_BoolTool_Auto_Slice.bl_idname, text='Slice', icon='PIVOT_MEDIAN')
+ col.operator(OBJECT_OT_BoolTool_Auto_Subtract.bl_idname, text='Subtract', icon='PIVOT_ACTIVE')
main.separator()
@@ -994,10 +994,10 @@ class VIEW3D_PT_booltool_tools(Panel):
col.enabled = obs_len > 1
col.label("Brush Boolean:", icon="MODIFIER")
col.separator()
- col.operator(BTool_Diff.bl_idname, text="Difference", icon="ROTACTIVE")
- col.operator(BTool_Union.bl_idname, text="Union", icon="ROTATECOLLECTION")
- col.operator(BTool_Inters.bl_idname, text="Intersect", icon="ROTATECENTER")
- col.operator(BTool_Slice.bl_idname, text="Slice", icon="ROTATECENTER")
+ col.operator(BTool_Diff.bl_idname, text="Difference", icon='PIVOT_ACTIVE')
+ col.operator(BTool_Union.bl_idname, text="Union", icon='PIVOT_INDIVIDUAL')
+ col.operator(BTool_Inters.bl_idname, text="Intersect", icon='PIVOT_MEDIAN')
+ col.operator(BTool_Slice.bl_idname, text="Slice", icon='PIVOT_MEDIAN')
main.separator()
diff --git a/object_cloud_gen.py b/object_cloud_gen.py
index 008763fc..6601b8f1 100644
--- a/object_cloud_gen.py
+++ b/object_cloud_gen.py
@@ -563,7 +563,7 @@ class GenerateCloud(Operator):
# Select all of the left over boxes so people can immediately
# press generate again if they want
for eachMember in definitionObjects:
- eachMember.draw_type = 'SOLID'
+ eachMember.display_type = 'SOLID'
eachMember.select = True
eachMember.hide_render = False
@@ -576,7 +576,7 @@ class GenerateCloud(Operator):
# Create a new object cloudPnts
cloudPnts = addNewObject(scene, "CloudPoints", bounds)
cloudPnts["CloudMember"] = "CreatedObj"
- cloudPnts.draw_type = 'WIRE'
+ cloudPnts.display_type = 'WIRE'
cloudPnts.hide_render = True
makeParent(bounds, cloudPnts, scene)
@@ -606,7 +606,7 @@ class GenerateCloud(Operator):
selectedObjects[0]
)
- bounds.draw_type = 'BOUNDS'
+ bounds.display_type = 'BOUNDS'
bounds.hide_render = False
# Just add a Definition Property designating this
@@ -630,7 +630,7 @@ class GenerateCloud(Operator):
for selObj in selectedObjects:
selObj["CloudMember"] = "DefinitionObj"
selObj.name = "DefinitionObj"
- selObj.draw_type = 'WIRE'
+ selObj.display_type = 'WIRE'
selObj.hide_render = True
selObj.hide = True
makeParent(bounds, selObj, scene)
@@ -638,7 +638,7 @@ class GenerateCloud(Operator):
# Do the same to the 1. object since it is no longer in list.
firstObject["CloudMember"] = "DefinitionObj"
firstObject.name = "DefinitionObj"
- firstObject.draw_type = 'WIRE'
+ firstObject.display_type = 'WIRE'
firstObject.hide_render = True
makeParent(bounds, firstObject, scene)
@@ -646,7 +646,7 @@ class GenerateCloud(Operator):
# Create a new object cloud.
cloud = addNewObject(scene, "CloudMesh", bounds)
cloud["CloudMember"] = "CreatedObj"
- cloud.draw_type = 'WIRE'
+ cloud.display_type = 'WIRE'
cloud.hide_render = True
makeParent(bounds, cloud, scene)
@@ -677,7 +677,7 @@ class GenerateCloud(Operator):
cloudParticles.settings.frame_end = 0
cloudParticles.settings.emit_from = 'VOLUME'
cloudParticles.settings.lifetime = scene.frame_end
- cloudParticles.settings.draw_method = 'DOT'
+ cloudParticles.settings.display_method = 'DOT'
cloudParticles.settings.render_type = 'NONE'
cloudParticles.settings.distribution = 'RAND'
cloudParticles.settings.physics_type = 'NEWTON'
@@ -836,7 +836,7 @@ class GenerateCloud(Operator):
# Create a new object cloudPnts
cloudPnts = addNewObject(scene, "CloudPoints", bounds)
cloudPnts["CloudMember"] = "CreatedObj"
- cloudPnts.draw_type = 'WIRE'
+ cloudPnts.display_type = 'WIRE'
cloudPnts.hide_render = True
makeParent(bounds, cloudPnts, scene)
diff --git a/object_edit_linked.py b/object_edit_linked.py
index 85359a51..0077fb50 100644
--- a/object_edit_linked.py
+++ b/object_edit_linked.py
@@ -19,21 +19,24 @@
bl_info = {
"name": "Edit Linked Library",
- "author": "Jason van Gumster (Fweeb), Bassam Kurdali, Pablo Vazquez",
- "version": (0, 8, 1),
- "blender": (2, 74, 0),
- "location": "View3D > Toolshelf > Edit Linked Library",
+ "author": "Jason van Gumster (Fweeb), Bassam Kurdali, Pablo Vazquez, Rainer Trummer",
+ "version": (0, 9, 1),
+ "blender": (2, 80, 0),
+ "location": "File > External Data > Edit Linked Library",
"description": "Allows editing of objects linked from a .blend library.",
"wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/"
"Scripts/Object/Edit_Linked_Library",
"category": "Object",
}
-
import bpy
-from bpy.app.handlers import persistent
+import logging
import os
+from bpy.app.handlers import persistent
+
+logger = logging.getLogger('object_edit_linked')
+
settings = {
"original_file": "",
"linked_file": "",
@@ -42,15 +45,15 @@ settings = {
@persistent
-def linked_file_check(context):
+def linked_file_check(context: bpy.context):
if settings["linked_file"] != "":
if os.path.samefile(settings["linked_file"], bpy.data.filepath):
- print("Editing a linked library.")
+ logger.info("Editing a linked library.")
bpy.ops.object.select_all(action='DESELECT')
for ob_name in settings["linked_objects"]:
- bpy.data.objects[ob_name].select = True # XXX Assumes selected object is in the active scene
+ bpy.data.objects[ob_name].select_set(True) # XXX Assumes selected object is in the active scene
if len(settings["linked_objects"]) == 1:
- bpy.context.scene.objects.active = bpy.data.objects[settings["linked_objects"][0]]
+ context.view_layer.objects.active = bpy.data.objects[settings["linked_objects"][0]]
else:
# For some reason, the linked editing session ended
# (failed to find a file or opened a different file
@@ -59,37 +62,35 @@ def linked_file_check(context):
settings["linked_file"] = ""
-class EditLinked(bpy.types.Operator):
+class OBJECT_OT_EditLinked(bpy.types.Operator):
"""Edit Linked Library"""
bl_idname = "object.edit_linked"
bl_label = "Edit Linked Library"
- use_autosave = bpy.props.BoolProperty(
+ use_autosave: bpy.props.BoolProperty(
name="Autosave",
description="Save the current file before opening the linked library",
default=True)
- use_instance = bpy.props.BoolProperty(
+ use_instance: bpy.props.BoolProperty(
name="New Blender Instance",
description="Open in a new Blender instance",
default=False)
@classmethod
- def poll(cls, context):
+ def poll(cls, context: bpy.context):
return settings["original_file"] == "" and context.active_object is not None and (
- (context.active_object.dupli_group and
- context.active_object.dupli_group.library is not None) or
+ (context.active_object.instance_collection and
+ context.active_object.instance_collection.library is not None) or
(context.active_object.proxy and
- context.active_object.proxy.library is not None) or
- context.active_object.library is not None)
- #return context.active_object is not None
+ context.active_object.proxy.library is not None) or
+ context.active_object.library is not None)
- def execute(self, context):
- #print(bpy.context.active_object.library)
+ def execute(self, context: bpy.context):
target = context.active_object
- if target.dupli_group and target.dupli_group.library:
- targetpath = target.dupli_group.library.filepath
- settings["linked_objects"].extend({ob.name for ob in target.dupli_group.objects})
+ if target.instance_collection and target.instance_collection.library:
+ targetpath = target.instance_collection.library.filepath
+ settings["linked_objects"].extend({ob.name for ob in target.instance_collection.objects})
elif target.library:
targetpath = target.library.filepath
settings["linked_objects"].append(target.name)
@@ -99,7 +100,7 @@ class EditLinked(bpy.types.Operator):
settings["linked_objects"].append(target.name)
if targetpath:
- print(target.name + " is linked to " + targetpath)
+ logger.debug(target.name + " is linked to " + targetpath)
if self.use_autosave:
if not bpy.data.filepath:
@@ -116,35 +117,35 @@ class EditLinked(bpy.types.Operator):
try:
subprocess.Popen([bpy.app.binary_path, settings["linked_file"]])
except:
- print("Error on the new Blender instance")
+ logger.error("Error on the new Blender instance")
import traceback
- traceback.print_exc()
+ logger.error(traceback.print_exc())
else:
bpy.ops.wm.open_mainfile(filepath=settings["linked_file"])
- print("Opened linked file!")
+ logger.info("Opened linked file!")
else:
self.report({'WARNING'}, target.name + " is not linked")
- print(target.name + " is not linked")
+ logger.warning(target.name + " is not linked")
return {'FINISHED'}
-class ReturnToOriginal(bpy.types.Operator):
+class WM_OT_ReturnToOriginal(bpy.types.Operator):
"""Load the original file"""
bl_idname = "wm.return_to_original"
bl_label = "Return to Original File"
- use_autosave = bpy.props.BoolProperty(
+ use_autosave: bpy.props.BoolProperty(
name="Autosave",
description="Save the current file before opening original file",
default=True)
@classmethod
- def poll(cls, context):
+ def poll(cls, context: bpy.context):
return (settings["original_file"] != "")
- def execute(self, context):
+ def execute(self, context: bpy.context):
if self.use_autosave:
bpy.ops.wm.save_mainfile()
@@ -152,25 +153,29 @@ class ReturnToOriginal(bpy.types.Operator):
settings["original_file"] = ""
settings["linked_objects"] = []
- print("Back to the original!")
+ logger.info("Back to the original!")
return {'FINISHED'}
-# UI
-# TODO:Add operators to the File menu?
-# Hide the entire panel for non-linked objects?
-class PanelLinkedEdit(bpy.types.Panel):
+class VIEW3D_PT_PanelLinkedEdit(bpy.types.Panel):
bl_label = "Edit Linked Library"
bl_space_type = "VIEW_3D"
- bl_region_type = "TOOLS"
- bl_category = "Relations"
- bl_context = "objectmode"
+ bl_region_type = 'UI'
+ bl_category = "View"
+ bl_context = 'objectmode'
@classmethod
- def poll(cls, context):
+ def poll(cls, context: bpy.context):
return (context.active_object is not None) or (settings["original_file"] != "")
- def draw(self, context):
+ def draw_common(self, scene, layout, props):
+ props.use_autosave = scene.use_autosave
+ props.use_instance = scene.use_instance
+
+ layout.prop(scene, "use_autosave")
+ layout.prop(scene, "use_instance")
+
+ def draw(self, context: bpy.context):
layout = self.layout
scene = context.scene
icon = "OUTLINER_DATA_" + context.active_object.type
@@ -180,11 +185,11 @@ class PanelLinkedEdit(bpy.types.Panel):
if context.active_object.proxy:
target = context.active_object.proxy
else:
- target = context.active_object.dupli_group
+ target = context.active_object.instance_collection
if settings["original_file"] == "" and (
(target and
- target.library is not None) or
+ target.library is not None) or
context.active_object.library is not None):
if (target is not None):
@@ -193,18 +198,15 @@ class PanelLinkedEdit(bpy.types.Panel):
else:
props = layout.operator("object.edit_linked", icon="LINK_BLEND",
text="Edit Library: %s" % context.active_object.name)
- props.use_autosave = scene.use_autosave
- props.use_instance = scene.use_instance
- layout.prop(scene, "use_autosave")
- layout.prop(scene, "use_instance")
+ self.draw_common(scene, layout, props)
if (target is not None):
layout.label(text="Path: %s" %
- target.library.filepath)
+ target.library.filepath)
else:
layout.label(text="Path: %s" %
- context.active_object.library.filepath)
+ context.active_object.library.filepath)
elif settings["original_file"] != "":
@@ -215,19 +217,17 @@ class PanelLinkedEdit(bpy.types.Panel):
layout.separator()
- #XXX - This is for nested linked assets... but it only works
- # when launching a new Blender instance. Nested links don't
- # currently work when using a single instance of Blender.
+ # XXX - This is for nested linked assets... but it only works
+ # when launching a new Blender instance. Nested links don't
+ # currently work when using a single instance of Blender.
props = layout.operator("object.edit_linked",
- text="Edit Library: %s" % context.active_object.dupli_group.name,
+ text="Edit Library: %s" % context.active_object.instance_collection.name,
icon="LINK_BLEND")
- props.use_autosave = scene.use_autosave
- props.use_instance = scene.use_instance
- layout.prop(scene, "use_autosave")
- layout.prop(scene, "use_instance")
+
+ self.draw_common(scene, layout, props)
layout.label(text="Path: %s" %
- context.active_object.dupli_group.library.filepath)
+ context.active_object.instance_collection.library.filepath)
else:
props = layout.operator("wm.return_to_original", icon="LOOP_BACK")
@@ -237,31 +237,50 @@ class PanelLinkedEdit(bpy.types.Panel):
else:
layout.label(text="%s is not linked" % context.active_object.name,
- icon=icon)
+ icon=icon)
+
+
+class TOPBAR_MT_edit_linked_submenu(bpy.types.Menu):
+ bl_label = 'Edit Linked Library'
+ bl_idname = 'view3d.TOPBAR_MT_edit_linked_submenu'
+
+ def draw(self, context):
+ self.layout.separator()
+ self.layout.operator(OBJECT_OT_EditLinked.bl_idname)
+ self.layout.operator(WM_OT_ReturnToOriginal.bl_idname)
addon_keymaps = []
+classes = (
+ OBJECT_OT_EditLinked,
+ WM_OT_ReturnToOriginal,
+ VIEW3D_PT_PanelLinkedEdit,
+ TOPBAR_MT_edit_linked_submenu
+ )
def register():
- bpy.app.handlers.load_post.append(linked_file_check)
- bpy.utils.register_class(EditLinked)
- bpy.utils.register_class(ReturnToOriginal)
- bpy.utils.register_class(PanelLinkedEdit)
+ bpy.app.handlers.load_post.append(linked_file_check)
+
+ for c in classes:
+ bpy.utils.register_class(c)
- # Is there a better place to store this properties?
bpy.types.Scene.use_autosave = bpy.props.BoolProperty(
name="Autosave",
description="Save the current file before opening a linked file",
default=True)
+
bpy.types.Scene.use_instance = bpy.props.BoolProperty(
name="New Blender Instance",
description="Open in a new Blender instance",
default=False)
+ # add the function to the file menu
+ bpy.types.TOPBAR_MT_file_external_data.append(TOPBAR_MT_edit_linked_submenu.draw)
+
# Keymapping (deactivated by default; activated when a library object is selected)
- kc = bpy.context.window_manager.keyconfigs.addon
- if kc: # don't register keymaps from command line
+ kc = bpy.context.window_manager.keyconfigs.addon
+ if kc: # don't register keymaps from command line
km = kc.keymaps.new(name="3D View", space_type='VIEW_3D')
kmi = km.keymap_items.new("object.edit_linked", 'NUMPAD_SLASH', 'PRESS', shift=True)
kmi.active = True
@@ -272,10 +291,9 @@ def register():
def unregister():
- bpy.utils.unregister_class(EditLinked)
- bpy.utils.unregister_class(ReturnToOriginal)
- bpy.utils.unregister_class(PanelLinkedEdit)
- bpy.app.handlers.load_post.remove(linked_file_check)
+
+ bpy.app.handlers.load_post.remove(linked_file_check)
+ bpy.types.TOPBAR_MT_file_external_data.remove(TOPBAR_MT_edit_linked_submenu)
del bpy.types.Scene.use_autosave
del bpy.types.Scene.use_instance
@@ -285,6 +303,9 @@ def unregister():
km.keymap_items.remove(kmi)
addon_keymaps.clear()
+ for c in reversed(classes):
+ bpy.utils.unregister_class(c)
+
if __name__ == "__main__":
register()
diff --git a/object_fracture/__init__.py b/object_fracture/__init__.py
index 0ac9f403..5dad8801 100644
--- a/object_fracture/__init__.py
+++ b/object_fracture/__init__.py
@@ -40,8 +40,8 @@ else:
import bpy
-class INFO_MT_add_fracture_objects(bpy.types.Menu):
- bl_idname = "INFO_MT_add_fracture_objects"
+class VIEW3D_MT_add_fracture_objects(bpy.types.Menu):
+ bl_idname = "VIEW3D_MT_add_fracture_objects"
bl_label = "Fracture Helper Objects"
def draw(self, context):
@@ -57,21 +57,21 @@ class INFO_MT_add_fracture_objects(bpy.types.Menu):
def menu_func(self, context):
- self.layout.menu("INFO_MT_add_fracture_objects")
+ self.layout.menu("VIEW3D_MT_add_fracture_objects")
def register():
bpy.utils.register_module(__name__)
# Add the "add fracture objects" menu to the "Add" menu
- bpy.types.INFO_MT_add.append(menu_func)
+ bpy.types.VIEW3D_MT_add.append(menu_func)
def unregister():
bpy.utils.unregister_module(__name__)
# Remove "add fracture objects" menu from the "Add" menu.
- bpy.types.INFO_MT_add.remove(menu_func)
+ bpy.types.VIEW3D_MT_add.remove(menu_func)
if __name__ == "__main__":
diff --git a/object_fracture/fracture_ops.py b/object_fracture/fracture_ops.py
index f5687e3a..d2f88159 100644
--- a/object_fracture/fracture_ops.py
+++ b/object_fracture/fracture_ops.py
@@ -317,7 +317,7 @@ def fracture_group(context, group):
and (len(ob.users_group) == 0 or ob.users_group[0].name != group)):
tobesplit.append(ob)
- cutters = bpy.data.groups[group].objects
+ cutters = bpy.data.collections[group].objects
# @todo This can be optimized.
# Avoid booleans on obs where bbox doesn't intersect.
@@ -407,7 +407,7 @@ class FractureGroup(bpy.types.Operator):
description="Specify the group used for fracturing")
# e = []
-# for i, g in enumerate(bpy.data.groups):
+# for i, g in enumerate(bpy.data.collections):
# e.append((g.name, g.name, ''))
# group = EnumProperty(name='Group (hit F8 to refresh list)',
# items=e,
@@ -431,7 +431,7 @@ class FractureGroup(bpy.types.Operator):
def draw(self, context):
layout = self.layout
layout.prop(self, "exe")
- layout.prop_search(self, "group", bpy.data, "groups")
+ layout.prop_search(self, "group", bpy.data, "collections")
#####################################################################
# Import Functions
diff --git a/object_fracture_cell/__init__.py b/object_fracture_cell/__init__.py
index ad4cdf39..f845ccf1 100644
--- a/object_fracture_cell/__init__.py
+++ b/object_fracture_cell/__init__.py
@@ -75,8 +75,8 @@ def main_object(scene, obj, level, **kw):
obj.select = False
if kw_copy["use_debug_redraw"]:
- obj_draw_type_prev = obj.draw_type
- obj.draw_type = 'WIRE'
+ obj_display_type_prev = obj.display_type
+ obj.display_type = 'WIRE'
objects = fracture_cell_setup.cell_fracture_objects(scene, obj, **kw_copy)
objects = fracture_cell_setup.cell_fracture_boolean(scene, obj, objects,
@@ -163,16 +163,16 @@ def main_object(scene, obj, level, **kw):
# group
if group_name:
- group = bpy.data.groups.get(group_name)
+ group = bpy.data.collections.get(group_name)
if group is None:
- group = bpy.data.groups.new(group_name)
+ group = bpy.data.collections.new(group_name)
group_objects = group.objects[:]
for obj_cell in objects:
if obj_cell not in group_objects:
group.objects.link(obj_cell)
if kw_copy["use_debug_redraw"]:
- obj.draw_type = obj_draw_type_prev
+ obj.display_type = obj_display_type_prev
# testing only!
# obj.hide = True
diff --git a/object_grease_scatter.py b/object_grease_scatter.py
deleted file mode 100644
index 105b40ea..00000000
--- a/object_grease_scatter.py
+++ /dev/null
@@ -1,401 +0,0 @@
-# ##### BEGIN GPL LICENSE BLOCK #####
-#
-# This program is free software; you can redistribute it and/or
-# modify it under the terms of the GNU General Public License
-# as published by the Free Software Foundation; either version 2
-# of the License, or (at your option) any later version.
-#
-# This program 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 General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software Foundation,
-# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-#
-# ##### END GPL LICENSE BLOCK #####
-
-# <pep8-80 compliant>
-
-# Script copyright (C) Campbell Barton
-
-bl_info = {
- "name": "Grease Scatter Objects",
- "author": "Campbell Barton",
- "version": (0, 1),
- "blender": (2, 58, 0),
- "location": "3D View, Add Mesh",
- "description": "Scatter a group of objects onto the active mesh using "
- "the grease pencil lines",
- "warning": "",
- "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/"
- "Scripts/Object/Grease_Scatter",
- "support": 'OFFICIAL',
- "category": "Object",
-}
-
-from mathutils import Vector, Matrix, Quaternion
-from random import uniform, shuffle
-import bpy
-
-
-def _main(self,
- obj,
- group,
- DENSITY=1.0,
- SCALE=0.6,
- RAND_LOC=0.8,
- RAND_ALIGN=0.75,
- ):
-
- from math import radians, pi
-
- # OFS = 0.2
- SEEK = 2.0 # distance for ray to seek
- BAD_NORMAL = Vector((0.0, 0.0, -1.0))
- WALL_LIMIT = radians(45.0)
-
- mats = (Matrix.Rotation(radians(-45), 3, 'X'),
- Matrix.Rotation(radians(+45), 3, 'X'),
- Matrix.Rotation(radians(-45), 3, 'Y'),
- Matrix.Rotation(radians(+45), 3, 'Y'),
- Matrix.Rotation(radians(-45), 3, 'Z'),
- Matrix.Rotation(radians(+45), 3, 'Z'),
- )
-
- Z_UP = Vector((0.0, 0.0, 1.0))
- Y_UP = Vector((0.0, 1.0, 0.0))
-
- if not group:
- self.report({'WARNING'}, "Group '%s' not found" % obj.name)
- return
-
- def debug_edge(v1, v2):
- mesh = bpy.data.meshes.new("Retopo")
- mesh.from_pydata([v1, v2], [(0.0, 1.0)], [])
-
- scene = bpy.context.scene
- mesh.update()
- obj_new = bpy.data.objects.new("Torus", mesh)
- scene.objects.link(obj_new)
-
- ray = obj.ray_cast
- closest_point_on_mesh = obj.closest_point_on_mesh
-
- obj_mat = obj.matrix_world.copy()
- obj_mat_inv = obj_mat.inverted()
- # obj_quat = obj_mat.to_quaternion()
- # obj_quat_inv = obj_mat_inv.to_quaternion()
-
- DEBUG = False
-
- def fix_point(p):
- ok, hit, no, ind = closest_point_on_mesh(obj_mat_inv * p)
- if ok:
- if DEBUG:
- return [p, no, None]
- else:
- # print("good", hit, no)
- return [hit, no, None]
-
- # worry!
- print("bad!", p, BAD_NORMAL)
-
- return [p, BAD_NORMAL, None]
-
- def get_points(stroke):
- return [fix_point(point.co) for point in stroke.points]
-
- def get_splines(gp):
- if gp.layers.active:
- frame = gp.layers.active.active_frame
- return [get_points(stroke) for stroke in frame.strokes]
- else:
- return []
-
- def main():
- scene = bpy.context.scene
- obj = bpy.context.object
-
- gp = None
-
- if obj:
- gp = obj.grease_pencil
-
- if not gp:
- gp = scene.grease_pencil
-
- if not gp:
- self.report({'WARNING'}, "No grease pencil layer found")
- return
-
- splines = get_splines(gp)
-
- for s in splines:
- for pt in s:
- p = pt[0]
- n = pt[1]
- # print(p, n)
- if n is BAD_NORMAL:
- continue
-
- # # dont self intersect
- best_nor = None
- #best_hit = None
- best_dist = 10000000.0
- pofs = p + n * 0.01
-
- n_seek = n * SEEK
- m_alt_1 = Matrix.Rotation(radians(22.5), 3, n)
- m_alt_2 = Matrix.Rotation(radians(-22.5), 3, n)
- for _m in mats:
- for m in (_m, m_alt_1 * _m, m_alt_2 * _m):
- pdir = m * n_seek
- ok, hit, nor, ind = ray(pofs, pdir, best_dist)
- if ok:
- best_dist = (pofs - hit).length
- best_nor = nor
- # best_hit = hit
-
- if best_nor:
- pt[1].length = best_dist
- best_nor.negate()
- pt[2] = best_nor
-
- #scene.cursor_location[:] = best_hitnyway
- # bpy.ops.wm.redraw_timer(type='DRAW_WIN_SWAP',
- # iterations=1)
- # debug_edge(p, best_hit)
- # p[:] = best_hit
-
- # Now we need to do scattering.
- # first corners
- hits = []
- nors = []
- oris = []
- for s in splines:
- # point, normal, n_other the closest hit normal
- for p, n, n_other in s:
- if n is BAD_NORMAL:
- continue
- if n_other:
- # cast vectors twice as long as the distance
- # needed just in case.
- n_down = (n * -SEEK)
- l = n_down.length
- n_other.length = l
-
- vantage = p + n
- if DEBUG:
- p[:] = vantage
-
- # We should cast rays between n_down and n_other
- #for f in (0.0, 0.2, 0.4, 0.6, 0.8, 1.0):
- TOT = int(10 * DENSITY)
- #for i in list(range(TOT)):
- for i in list(range(TOT))[int(TOT / 1.5):]: # second half
- f = i / (TOT - 1)
-
- # focus on the center
- '''
- f -= 0.5
- f = f*f
- f += 0.5
- '''
-
- ntmp = f * n_down + (1.0 - f) * n_other
- # randomize
- ntmp.x += uniform(-l, l) * RAND_LOC
- ntmp.y += uniform(-l, l) * RAND_LOC
- ntmp.z += uniform(-l, l) * RAND_LOC
-
- ok, hit, hit_no, ind = ray(vantage, ntmp, ntmp.length)
- # print(hit, hit_no)
- if ok:
- if hit_no.angle(Z_UP) < WALL_LIMIT:
- hits.append(hit)
- nors.append(hit_no)
- oris.append(n_other.cross(hit_no))
- #oris.append(n_other)
-
- if 0:
- mesh = bpy.data.meshes.new("ScatterDupliFace")
- mesh.from_pydata(hits, [], [])
-
- scene = bpy.context.scene
- mesh.update()
- obj_new = bpy.data.objects.new("ScatterPar", mesh)
- scene.objects.link(obj_new)
- obj_new.layers[:] = obj.layers
-
- # Now setup dupli-faces
- obj_new.dupli_type = 'VERTS'
- ob_child = bpy.data.objects["trash"]
- ob_child.location = obj_new.location
- ob_child.parent = obj_new
- else:
-
- def apply_faces(triples):
- # first randomize the faces
- shuffle(triples)
-
- obs = group.objects[:]
- tot = len(obs)
- tot_div = int(len(triples) / tot)
-
- for inst_ob in obs:
- triple_sub = triples[0:tot_div]
- triples[0:tot_div] = []
-
- vv = [tuple(v) for f in triple_sub for v in f]
-
- mesh = bpy.data.meshes.new("ScatterDupliFace")
- mesh.from_pydata(vv, [], [(i * 3, i * 3 + 1, i * 3 + 2)
- for i in range(len(triple_sub))])
-
- scene = bpy.context.scene
- mesh.update()
- obj_new = bpy.data.objects.new("ScatterPar", mesh)
-
- scene.objects.link(obj_new)
- obj_new.layers[:] = obj.layers
-
- # Now setup dupli-faces
- obj_new.dupli_type = 'FACES'
- obj_new.use_dupli_faces_scale = True
- obj_new.dupli_faces_scale = 100.0
-
- inst_ob.location = 0.0, 0.0, 0.0
- inst_ob.parent = obj_new
-
- # align the object with worldspace
- obj_new.matrix_world = obj_mat
-
- # BGE settings for testing
- '''
- inst_ob.game.physics_type = 'RIGID_BODY'
- inst_ob.game.use_collision_bounds = True
- inst_ob.game.collision_bounds = 'TRIANGLE_MESH'
- inst_ob.game.collision_margin = 0.1
- obj_new.select = True
- '''
-
- # build faces from vert/normals
- tri = (Vector((0.0, 0.0, 0.01)),
- Vector((0.0, 0.0, 0.0)),
- Vector((0.0, 0.01, 0.01)))
-
- coords = []
- # face_ind = []
- for i in range(len(hits)):
- co = hits[i]
- no = nors[i]
- ori = oris[i]
- quat = no.to_track_quat('X', 'Z')
-
- # make 2 angles and blend
- angle = uniform(-pi, pi)
- angle_aligned = -(ori.angle(quat * Y_UP, pi))
-
- quat = Quaternion(no,
- (angle * (1.0 - RAND_ALIGN)) +
- (angle_aligned * RAND_ALIGN)
- ).cross(quat)
-
- f = uniform(0.1, 1.2) * SCALE
-
- coords.append([co + (quat * (tri[0] * f)),
- co + (quat * (tri[1] * f)),
- co + (quat * (tri[2] * f)),
- ])
-
- apply_faces(coords)
-
- main()
-
-
-from bpy.props import FloatProperty, StringProperty
-
-
-class Scatter(bpy.types.Operator):
- """"""
- bl_idname = "object.scatter"
- bl_label = "Grease Pencil Scatter"
-
- density = FloatProperty(
- name="Density",
- description="Multiplier for the density of items",
- default=1.0, min=0.01, max=10.0,
- )
- scale = FloatProperty(
- name="Scale",
- description="Size multiplier for duplifaces",
- default=1.0, min=0.01, max=10.0,
- )
- rand_align = FloatProperty(
- name="Random Align",
- description="Randomize alignment with the walls",
- default=0.75, min=0.0, max=1.0,
- )
- rand_loc = FloatProperty(
- name="Random Loc",
- description="Randomize placement",
- default=0.75, min=0.0, max=1.0,
- )
- # XXX, should not be a string - TODO, add a way for scritps to select ID's
- group = StringProperty(
- name="Group",
- description=("Group name to use for object placement, "
- "defaults to object name when that matches a group"))
-
- def execute(self, context):
- obj = bpy.context.object
- group = bpy.data.groups.get(self.group)
-
- if not group:
- self.report({'ERROR'}, "Group %r not found" % self.group)
- return {'CANCELLED'}
-
- _main(self,
- obj,
- group,
- DENSITY=self.density,
- SCALE=self.scale,
- RAND_LOC=self.rand_loc,
- RAND_ALIGN=self.rand_align,
- )
- return {'FINISHED'}
-
- def check(self, context):
- if self.group not in bpy.data.groups:
- self.group = ""
- return True
- return False
-
- def invoke(self, context, event):
-
- # useful to initialize, take a guess
- if not self.group and context.object.name in bpy.data.groups:
- self.group = context.object.name
-
- wm = context.window_manager
- wm.invoke_props_dialog(self, width=180)
- return {'RUNNING_MODAL'}
-
-
-def menu_func(self, context):
- self.layout.operator(Scatter.bl_idname, icon='AUTO')
-
-
-def register():
- bpy.utils.register_class(Scatter)
- bpy.types.INFO_MT_mesh_add.append(menu_func)
-
-
-def unregister():
- bpy.utils.unregister_class(Scatter)
- bpy.types.INFO_MT_mesh_add.remove(menu_func)
-
-#if __name__ == "__main__":
-# _main()
diff --git a/object_print3d_utils/__init__.py b/object_print3d_utils/__init__.py
index 5ae1d3d6..6bf2a8a7 100644
--- a/object_print3d_utils/__init__.py
+++ b/object_print3d_utils/__init__.py
@@ -21,7 +21,7 @@
bl_info = {
"name": "3D Print Toolbox",
"author": "Campbell Barton",
- "blender": (2, 79, 0),
+ "blender": (2, 80, 0),
"location": "3D View > Toolbox",
"description": "Utilities for 3D printing",
"wiki_url": "https://wiki.blender.org/index.php/Extensions:2.6/Py/"
@@ -59,101 +59,66 @@ else:
class Print3D_Scene_Props(PropertyGroup):
- export_format = EnumProperty(
- name="Format",
- description="Format type to export to",
- items=(('STL', "STL", ""),
- ('PLY', "PLY", ""),
- ('WRL', "VRML2", ""),
- ('X3D', "X3D", ""),
- ('OBJ', "OBJ", "")),
- default='STL',
- )
- use_export_texture = BoolProperty(
- name="Copy Textures",
- description="Copy textures on export to the output path",
- default=False,
- )
- use_apply_scale = BoolProperty(
- name="Apply Scale",
- description="Apply scene scale setting on export",
- default=False,
- )
- export_path = StringProperty(
- name="Export Directory",
- description="Path to directory where the files are created",
- default="//", maxlen=1024, subtype="DIR_PATH",
- )
- thickness_min = FloatProperty(
- name="Thickness",
- description="Minimum thickness",
- subtype='DISTANCE',
- default=0.001, # 1mm
- min=0.0, max=10.0,
- )
- threshold_zero = FloatProperty(
- name="Threshold",
- description="Limit for checking zero area/length",
- default=0.0001,
- precision=5,
- min=0.0, max=0.2,
- )
- angle_distort = FloatProperty(
- name="Angle",
- description="Limit for checking distorted faces",
- subtype='ANGLE',
- default=math.radians(45.0),
- min=0.0, max=math.radians(180.0),
- )
- angle_sharp = FloatProperty(
- name="Angle",
- subtype='ANGLE',
- default=math.radians(160.0),
- min=0.0, max=math.radians(180.0),
- )
- angle_overhang = FloatProperty(
- name="Angle",
- subtype='ANGLE',
- default=math.radians(45.0),
- min=0.0, max=math.radians(90.0),
- )
-
-
-# Update panel category name
-panels = (
- ui.VIEW3D_PT_Print3D_Object,
- ui.VIEW3D_PT_Print3D_Mesh,
+ export_format: EnumProperty(
+ name="Format",
+ description="Format type to export to",
+ items=(
+ ('STL', "STL", ""),
+ ('PLY', "PLY", ""),
+ ('WRL', "VRML2", ""),
+ ('X3D', "X3D", ""),
+ ('OBJ', "OBJ", ""),
+ ),
+ default='STL',
+ )
+ use_export_texture: BoolProperty(
+ name="Copy Textures",
+ description="Copy textures on export to the output path",
+ default=False,
+ )
+ use_apply_scale: BoolProperty(
+ name="Apply Scale",
+ description="Apply scene scale setting on export",
+ default=False,
+ )
+ export_path: StringProperty(
+ name="Export Directory",
+ description="Path to directory where the files are created",
+ default="//", maxlen=1024, subtype="DIR_PATH",
+ )
+ thickness_min: FloatProperty(
+ name="Thickness",
+ description="Minimum thickness",
+ subtype='DISTANCE',
+ default=0.001, # 1mm
+ min=0.0, max=10.0,
+ )
+ threshold_zero: FloatProperty(
+ name="Threshold",
+ description="Limit for checking zero area/length",
+ default=0.0001,
+ precision=5,
+ min=0.0, max=0.2,
+ )
+ angle_distort: FloatProperty(
+ name="Angle",
+ description="Limit for checking distorted faces",
+ subtype='ANGLE',
+ default=math.radians(45.0),
+ min=0.0, max=math.radians(180.0),
+ )
+ angle_sharp: FloatProperty(
+ name="Angle",
+ subtype='ANGLE',
+ default=math.radians(160.0),
+ min=0.0, max=math.radians(180.0),
+ )
+ angle_overhang: FloatProperty(
+ name="Angle",
+ subtype='ANGLE',
+ default=math.radians(45.0),
+ min=0.0, max=math.radians(90.0),
)
-
-
-def update_panels(self, context):
- try:
- for panel in panels:
- if "bl_rna" in panel.__dict__:
- bpy.utils.unregister_class(panel)
-
- for panel in panels:
- panel.bl_category = context.user_preferences.addons[__name__].preferences.category
- bpy.utils.register_class(panel)
-
- except Exception as e:
- message = "3D Print Toolbox: Updating Panel locations has failed"
- print("\n[{}]\n{}\n\nError:\n{}".format(__name__, message, e))
-
-
-class Print3D_Preferences(AddonPreferences):
- bl_idname = __name__
-
- category = StringProperty(
- name="Tab Category",
- description="Choose a name for the category of the panel",
- default="3D Printing",
- update=update_panels,
- )
-
- def draw(self, context):
- layout = self.layout
- layout.prop(self, "category")
classes = (
@@ -185,8 +150,7 @@ classes = (
operators.MESH_OT_Print3D_Export,
Print3D_Scene_Props,
- Print3D_Preferences,
- )
+)
def register():
@@ -195,8 +159,6 @@ def register():
bpy.types.Scene.print_3d = PointerProperty(type=Print3D_Scene_Props)
- update_panels(None, bpy.context)
-
def unregister():
for cls in classes:
diff --git a/object_print3d_utils/export.py b/object_print3d_utils/export.py
index cbb607d2..00277958 100644
--- a/object_print3d_utils/export.py
+++ b/object_print3d_utils/export.py
@@ -53,11 +53,12 @@ def image_copy_guess(filepath, objects):
def write_mesh(context, info, report_cb):
scene = context.scene
+ layer = context.view_layer
unit = scene.unit_settings
print_3d = scene.print_3d
- obj_base = scene.object_bases.active
- obj = obj_base.object
+ # obj_base = layer.object_bases.active
+ obj = layer.objects.active
export_format = print_3d.export_format
global_scale = unit.scale_length if (unit.system != 'NONE' and print_3d.use_apply_scale) else 1.0
@@ -73,13 +74,16 @@ def write_mesh(context, info, report_cb):
bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
from . import mesh_helpers
- obj_base_tmp = mesh_helpers.object_merge(context, context_override["selected_objects"])
- context_override["active_object"] = obj_base_tmp.object
- context_override["selected_bases"] = [obj_base_tmp]
- context_override["selected_objects"] = [obj_base_tmp.object]
+ obj_tmp = mesh_helpers.object_merge(context, context_override["selected_objects"])
+ context_override["active_object"] = obj_tmp
+ # context_override["selected_bases"] = [obj_base_tmp]
+ context_override["selected_objects"] = [obj_tmp]
else:
+ # XXX28
+ '''
if obj_base not in context_override["selected_bases"]:
context_override["selected_bases"].append(obj_base)
+ '''
if obj not in context_override["selected_objects"]:
context_override["selected_objects"].append(obj)
diff --git a/object_print3d_utils/mesh_helpers.py b/object_print3d_utils/mesh_helpers.py
index a5d5f74f..56dbfc65 100644
--- a/object_print3d_utils/mesh_helpers.py
+++ b/object_print3d_utils/mesh_helpers.py
@@ -32,7 +32,7 @@ def bmesh_copy_from_object(obj, transform=True, triangulate=True, apply_modifier
if apply_modifiers and obj.modifiers:
import bpy
- me = obj.to_mesh(bpy.context.scene, True, 'PREVIEW', calc_tessface=False)
+ me = obj.to_mesh(depsgraph=bpy.context.depsgraph, apply_modifiers=True)
bm = bmesh.new()
bm.from_mesh(me)
bpy.data.meshes.remove(me)
@@ -157,19 +157,27 @@ def bmesh_check_thick_object(obj, thickness):
# Create a real mesh (lame!)
context = bpy.context
scene = context.scene
+ layer = context.view_layer
+ layer_collection = context.layer_collection or layer.active_layer_collection
+ scene_collection = layer_collection.collection
+
me_tmp = bpy.data.meshes.new(name="~temp~")
bm.to_mesh(me_tmp)
# bm.free() # delay free
obj_tmp = bpy.data.objects.new(name=me_tmp.name, object_data=me_tmp)
- base = scene.objects.link(obj_tmp)
+ # base = scene.objects.link(obj_tmp)
+ scene_collection.objects.link(obj_tmp)
# Add new object to local view layer
+ # XXX28
+ '''
v3d = None
if context.space_data and context.space_data.type == 'VIEW_3D':
v3d = context.space_data
if v3d and v3d.local_view:
base.layers_from_view(context.space_data)
+ '''
scene.update()
ray_cast = obj_tmp.ray_cast
@@ -190,7 +198,7 @@ def bmesh_check_thick_object(obj, thickness):
p_b = p - no_end
p_dir = p_b - p_a
- ok, co, no, index = ray_cast(p_a, p_dir, p_dir.length)
+ ok, co, no, index = ray_cast(p_a, p_dir, distance=p_dir.length)
if ok:
# Add the face we hit
@@ -203,7 +211,7 @@ def bmesh_check_thick_object(obj, thickness):
# finished with bm
bm.free()
- scene.objects.unlink(obj_tmp)
+ scene_collection.objects.unlink(obj_tmp)
bpy.data.objects.remove(obj_tmp)
bpy.data.meshes.remove(me_tmp)
@@ -228,17 +236,20 @@ def object_merge(context, objects):
seq.remove(seq[i])
scene = context.scene
+ layer = context.view_layer
+ layer_collection = context.layer_collection or layer.active_layer_collection
+ scene_collection = layer_collection.collection
# deselect all
for obj in scene.objects:
- obj.select = False
+ obj.select_set(False)
# add empty object
mesh_base = bpy.data.meshes.new(name="~tmp~")
obj_base = bpy.data.objects.new(name="~tmp~", object_data=mesh_base)
- base_base = scene.objects.link(obj_base)
- scene.objects.active = obj_base
- obj_base.select = True
+ scene_collection.objects.link(obj_base)
+ layer.objects.active = obj_base
+ obj_base.select_set(True)
# loop over all meshes
for obj in objects:
@@ -246,29 +257,29 @@ def object_merge(context, objects):
continue
# convert each to a mesh
- mesh_new = obj.to_mesh(scene=scene,
- apply_modifiers=True,
- settings='PREVIEW',
- calc_tessface=False)
+ mesh_new = obj.to_mesh(
+ depsgraph=context.depsgraph,
+ apply_modifiers=True,
+ )
# remove non-active uvs/vcols
cd_remove_all_but_active(mesh_new.vertex_colors)
- cd_remove_all_but_active(mesh_new.uv_textures)
+ cd_remove_all_but_active(mesh_new.uv_layers)
# join into base mesh
obj_new = bpy.data.objects.new(name="~tmp-new~", object_data=mesh_new)
- base_new = scene.objects.link(obj_new)
+ base_new = scene_collection.objects.link(obj_new)
obj_new.matrix_world = obj.matrix_world
fake_context = context.copy()
fake_context["active_object"] = obj_base
- fake_context["selected_editable_bases"] = [base_base, base_new]
+ fake_context["selected_editable_objects"] = [obj_base, obj_new]
bpy.ops.object.join(fake_context)
del base_new, obj_new
# remove object and its mesh, join does this
- # scene.objects.unlink(obj_new)
+ # scene_collection.objects.unlink(obj_new)
# bpy.data.objects.remove(obj_new)
bpy.data.meshes.remove(mesh_new)
@@ -276,7 +287,8 @@ def object_merge(context, objects):
scene.update()
# return new object
- return base_base
+ return obj_base
+
def face_is_distorted(ele, angle_distort):
diff --git a/object_print3d_utils/operators.py b/object_print3d_utils/operators.py
index e7ff4713..ebc1a730 100644
--- a/object_print3d_utils/operators.py
+++ b/object_print3d_utils/operators.py
@@ -23,15 +23,15 @@
import bpy
from bpy.types import Operator
from bpy.props import (
- IntProperty,
- FloatProperty,
- )
+ IntProperty,
+ FloatProperty,
+)
import bmesh
from . import (
- mesh_helpers,
- report,
- )
+ mesh_helpers,
+ report,
+)
def clean_float(text):
@@ -430,16 +430,16 @@ class MESH_OT_Print3D_Clean_Non_Manifold(Operator):
bl_label = "Print3D Clean Non-Manifold and Inverted"
bl_options = {'REGISTER', 'UNDO'}
- threshold = bpy.props.FloatProperty(
- name="threshold",
- description="Minimum distance between elements to merge",
- default=0.0001,
- )
- sides = bpy.props.IntProperty(
- name="sides",
- description="Number of sides in hole required to fill",
- default=4,
- )
+ threshold: bpy.props.FloatProperty(
+ name="threshold",
+ description="Minimum distance between elements to merge",
+ default=0.0001,
+ )
+ sides: bpy.props.IntProperty(
+ name="sides",
+ description="Number of sides in hole required to fill",
+ default=4,
+ )
def execute(self, context):
self.context = context
@@ -600,7 +600,7 @@ class MESH_OT_Print3D_Select_Report(Operator):
bl_label = "Print3D Select Report"
bl_options = {'INTERNAL'}
- index = IntProperty()
+ index: IntProperty()
_type_to_mode = {
bmesh.types.BMVert: 'VERT',
@@ -659,14 +659,14 @@ class MESH_OT_Print3D_Scale_To_Volume(Operator):
bl_label = "Scale to Volume"
bl_options = {'REGISTER', 'UNDO'}
- volume_init = FloatProperty(
- options={'HIDDEN'},
- )
- volume = FloatProperty(
- name="Volume",
- unit='VOLUME',
- min=0.0, max=100000.0,
- )
+ volume_init: FloatProperty(
+ options={'HIDDEN'},
+ )
+ volume: FloatProperty(
+ name="Volume",
+ unit='VOLUME',
+ min=0.0, max=100000.0,
+ )
def execute(self, context):
import math
@@ -705,17 +705,17 @@ class MESH_OT_Print3D_Scale_To_Bounds(Operator):
bl_label = "Scale to Bounds"
bl_options = {'REGISTER', 'UNDO'}
- length_init = FloatProperty(
- options={'HIDDEN'},
- )
- axis_init = IntProperty(
- options={'HIDDEN'},
- )
- length = FloatProperty(
- name="Length Limit",
- unit='LENGTH',
- min=0.0, max=100000.0,
- )
+ length_init: FloatProperty(
+ options={'HIDDEN'},
+ )
+ axis_init: IntProperty(
+ options={'HIDDEN'},
+ )
+ length: FloatProperty(
+ name="Length Limit",
+ unit='LENGTH',
+ min=0.0, max=100000.0,
+ )
def execute(self, context):
scale = self.length / self.length_init
@@ -731,11 +731,11 @@ class MESH_OT_Print3D_Scale_To_Bounds(Operator):
return max(((max(v[i] for v in vecs) - min(v[i] for v in vecs)), i) for i in range(3))
if context.mode == 'EDIT_MESH':
- length, axis = calc_length([Vector(v) * obj.matrix_world
+ length, axis = calc_length([Vector(v) @ obj.matrix_world
for obj in [context.edit_object]
for v in obj.bound_box])
else:
- length, axis = calc_length([Vector(v) * obj.matrix_world
+ length, axis = calc_length([Vector(v) @ obj.matrix_world
for obj in context.selected_editable_objects
if obj.type == 'MESH' for v in obj.bound_box])
diff --git a/object_print3d_utils/ui.py b/object_print3d_utils/ui.py
index 6e5c3284..9f950c09 100644
--- a/object_print3d_utils/ui.py
+++ b/object_print3d_utils/ui.py
@@ -29,7 +29,7 @@ from . import report
class Print3D_ToolBar:
bl_label = "Print3D"
bl_space_type = 'VIEW_3D'
- bl_region_type = 'TOOLS'
+ bl_region_type = 'UI'
_type_to_icon = {
bmesh.types.BMVert: 'VERTEXSEL',
@@ -49,7 +49,7 @@ class Print3D_ToolBar:
if info:
obj = context.edit_object
- layout.label("Output:")
+ layout.label(text="Output:")
box = layout.box()
col = box.column(align=False)
# box.alert = True
@@ -61,7 +61,7 @@ class Print3D_ToolBar:
icon=Print3D_ToolBar._type_to_icon[bm_type]).index = i
layout.operator("mesh.select_non_manifold", text='Non Manifold Extended')
else:
- col.label(text)
+ col.label(text=text)
def draw(self, context):
@@ -73,13 +73,13 @@ class Print3D_ToolBar:
# TODO, presets
row = layout.row()
- row.label("Statistics:")
+ row.label(text="Statistics:")
rowsub = layout.row(align=True)
rowsub.operator("mesh.print3d_info_volume", text="Volume")
rowsub.operator("mesh.print3d_info_area", text="Area")
row = layout.row()
- row.label("Checks:")
+ row.label(text="Checks:")
col = layout.column(align=True)
col.operator("mesh.print3d_check_solid", text="Solid")
col.operator("mesh.print3d_check_intersect", text="Intersections")
@@ -102,7 +102,7 @@ class Print3D_ToolBar:
col.operator("mesh.print3d_check_all", text="Check All")
row = layout.row()
- row.label("Cleanup:")
+ row.label(text="Cleanup:")
col = layout.column(align=True)
col.operator("mesh.print3d_clean_isolated", text="Isolated")
rowsub = col.row(align=True)
@@ -114,15 +114,15 @@ class Print3D_ToolBar:
# col.operator("mesh.print3d_clean_thin", text="Wall Thickness")
row = layout.row()
- row.label("Scale To:")
+ row.label(text="Scale To:")
rowsub = layout.row(align=True)
rowsub.operator("mesh.print3d_scale_to_volume", text="Volume")
rowsub.operator("mesh.print3d_scale_to_bounds", text="Bounds")
col = layout.column()
rowsub = col.row(align=True)
- rowsub.label("Export Path:")
- rowsub.prop(print_3d, "use_apply_scale", text="", icon='MAN_SCALE')
+ rowsub.label(text="Export Path:")
+ rowsub.prop(print_3d, "use_apply_scale", text="", icon='ORIENTATION_GLOBAL')
rowsub.prop(print_3d, "use_export_texture", text="", icon='FILE_IMAGE')
rowsub = col.row()
rowsub.prop(print_3d, "export_path", text="")
diff --git a/object_scatter/__init__.py b/object_scatter/__init__.py
new file mode 100644
index 00000000..0d929b44
--- /dev/null
+++ b/object_scatter/__init__.py
@@ -0,0 +1,41 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+bl_info = {
+ "name": "Scatter Objects",
+ "author": "Jacques Lucke",
+ "version": (0, 1),
+ "blender": (2, 80, 0),
+ "location": "3D View",
+ "description": "Distribute object instances on another object.",
+ "warning": "",
+ "wiki_url": "",
+ "support": 'OFFICIAL',
+ "category": "Object",
+}
+
+from . import ui
+from . import operator
+
+def register():
+ ui.register()
+ operator.register()
+
+def unregister():
+ ui.unregister()
+ operator.register()
diff --git a/object_scatter/operator.py b/object_scatter/operator.py
new file mode 100644
index 00000000..1c0c133a
--- /dev/null
+++ b/object_scatter/operator.py
@@ -0,0 +1,508 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+import bpy
+import gpu
+import bgl
+import blf
+import math
+import enum
+import random
+
+from itertools import islice
+from mathutils.bvhtree import BVHTree
+from mathutils import Vector, Matrix, Euler
+from gpu_extras.batch import batch_for_shader
+
+from bpy_extras.view3d_utils import (
+ region_2d_to_vector_3d,
+ region_2d_to_origin_3d
+)
+
+uniform_color_shader = gpu.shader.from_builtin('3D_UNIFORM_COLOR')
+
+
+# Modal Operator
+################################################################
+
+class ScatterObjects(bpy.types.Operator):
+ bl_idname = "object.scatter"
+ bl_label = "Scatter Objects"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ @classmethod
+ def poll(cls, context):
+ return (
+ currently_in_3d_view(context)
+ and context.active_object is not None
+ and context.active_object.mode == 'OBJECT')
+
+ def invoke(self, context, event):
+ self.target_object = context.active_object
+ self.objects_to_scatter = get_selected_non_active_objects(context)
+
+ if self.target_object is None or len(self.objects_to_scatter) == 0:
+ self.report({'ERROR'}, "Select objects to scatter and a target object.")
+ return {'CANCELLED'}
+
+ self.base_scale = get_max_object_side_length(self.objects_to_scatter)
+
+ self.targets = []
+ self.active_target = None
+ self.target_cache = {}
+
+ self.enable_draw_callback()
+ context.window_manager.modal_handler_add(self)
+ return {'RUNNING_MODAL'}
+
+ def modal(self, context, event):
+ context.area.tag_redraw()
+
+ if not event_is_in_region(event, context.region) and self.active_target is None:
+ return {'PASS_THROUGH'}
+
+ if event.type == 'ESC':
+ return self.finish('CANCELLED')
+
+ if event.type == 'RET' and event.value == 'PRESS':
+ self.create_scatter_object()
+ return self.finish('FINISHED')
+
+ event_used = self.handle_non_exit_event(event)
+ if event_used:
+ return {'RUNNING_MODAL'}
+ else:
+ return {'PASS_THROUGH'}
+
+ def handle_non_exit_event(self, event):
+ if self.active_target is None:
+ if event.type == 'LEFTMOUSE' and event.value == 'PRESS':
+ self.active_target = StrokeTarget()
+ self.active_target.start_build(self.target_object)
+ return True
+ else:
+ build_state = self.active_target.continue_build(event)
+ if build_state == BuildState.FINISHED:
+ self.targets.append(self.active_target)
+ self.active_target = None
+ self.remove_target_from_cache(self.active_target)
+ return True
+
+ return False
+
+ def enable_draw_callback(self):
+ self._draw_callback_view = bpy.types.SpaceView3D.draw_handler_add(self.draw_view, (), 'WINDOW', 'POST_VIEW')
+ self._draw_callback_px = bpy.types.SpaceView3D.draw_handler_add(self.draw_px, (), 'WINDOW', 'POST_PIXEL')
+
+ def disable_draw_callback(self):
+ bpy.types.SpaceView3D.draw_handler_remove(self._draw_callback_view, 'WINDOW')
+ bpy.types.SpaceView3D.draw_handler_remove(self._draw_callback_px, 'WINDOW')
+
+ def draw_view(self):
+ for target in self.iter_targets():
+ target.draw()
+
+ draw_matrices_batches(list(self.iter_matrix_batches()))
+
+ def draw_px(self):
+ draw_text((20, 20, 0), "Instances: " + str(len(self.get_all_matrices())))
+
+ def finish(self, return_value):
+ self.disable_draw_callback()
+ bpy.context.area.tag_redraw()
+ return {return_value}
+
+ def create_scatter_object(self):
+ matrix_chunks = make_random_chunks(
+ self.get_all_matrices(), len(self.objects_to_scatter))
+
+ collection = bpy.data.collections.new("Scatter")
+ bpy.context.collection.children.link(collection)
+
+ for obj, matrices in zip(self.objects_to_scatter, matrix_chunks):
+ make_duplicator(collection, obj, matrices)
+
+ def get_all_matrices(self):
+ settings = self.get_current_settings()
+
+ matrices = []
+ for target in self.iter_targets():
+ self.ensure_target_is_in_cache(target)
+ matrices.extend(self.target_cache[target].get_matrices(settings))
+ return matrices
+
+ def iter_matrix_batches(self):
+ settings = self.get_current_settings()
+ for target in self.iter_targets():
+ self.ensure_target_is_in_cache(target)
+ yield self.target_cache[target].get_batch(settings)
+
+ def iter_targets(self):
+ yield from self.targets
+ if self.active_target is not None:
+ yield self.active_target
+
+ def ensure_target_is_in_cache(self, target):
+ if target not in self.target_cache:
+ entry = TargetCacheEntry(target, self.base_scale)
+ self.target_cache[target] = entry
+
+ def remove_target_from_cache(self, target):
+ self.target_cache.pop(self.active_target, None)
+
+ def get_current_settings(self):
+ return bpy.context.scene.scatter_properties.to_settings()
+
+class TargetCacheEntry:
+ def __init__(self, target, base_scale):
+ self.target = target
+ self.last_used_settings = None
+ self.base_scale = base_scale
+ self.settings_changed()
+
+ def get_matrices(self, settings):
+ self._handle_new_settings(settings)
+ if self.matrices is None:
+ self.matrices = self.target.get_matrices(settings)
+ return self.matrices
+
+ def get_batch(self, settings):
+ self._handle_new_settings(settings)
+ if self.gpu_batch is None:
+ self.gpu_batch = create_batch_for_matrices(self.get_matrices(settings), self.base_scale)
+ return self.gpu_batch
+
+ def _handle_new_settings(self, settings):
+ if settings != self.last_used_settings:
+ self.settings_changed()
+ self.last_used_settings = settings
+
+ def settings_changed(self):
+ self.matrices = None
+ self.gpu_batch = None
+
+
+# Duplicator Creation
+######################################################
+
+def make_duplicator(target_collection, source_object, matrices):
+ triangle_scale = 0.1
+
+ duplicator = triangle_object_from_matrices(source_object.name + " Duplicator", matrices, triangle_scale)
+ duplicator.instance_type = 'FACES'
+ duplicator.use_instance_faces_scale = True
+ duplicator.show_instancer_for_viewport = True
+ duplicator.show_instancer_for_render = False
+ duplicator.instance_faces_scale = 1 / triangle_scale
+
+ copy_obj = source_object.copy()
+ copy_obj.name = source_object.name + " - copy"
+ copy_obj.hide_viewport = True
+ copy_obj.hide_render = True
+ copy_obj.location = (0, 0, 0)
+ copy_obj.parent = duplicator
+
+ target_collection.objects.link(duplicator)
+ target_collection.objects.link(copy_obj)
+
+def triangle_object_from_matrices(name, matrices, triangle_scale):
+ mesh = triangle_mesh_from_matrices(name, matrices, triangle_scale)
+ return bpy.data.objects.new(name, mesh)
+
+def triangle_mesh_from_matrices(name, matrices, triangle_scale):
+ mesh = bpy.data.meshes.new(name)
+ vertices, polygons = mesh_data_from_matrices(matrices, triangle_scale)
+ mesh.from_pydata(vertices, [], polygons)
+ mesh.update()
+ mesh.validate()
+ return mesh
+
+unit_triangle_vertices = (
+ Vector((-3**-0.25, -3**-0.75, 0)),
+ Vector((3**-0.25, -3**-0.75, 0)),
+ Vector((0, 2/3**0.75, 0)))
+
+def mesh_data_from_matrices(matrices, triangle_scale):
+ vertices = []
+ polygons = []
+ triangle_vertices = [triangle_scale * v for v in unit_triangle_vertices]
+
+ for i, matrix in enumerate(matrices):
+ vertices.extend((matrix @ v for v in triangle_vertices))
+ polygons.append((i * 3 + 0, i * 3 + 1, i * 3 + 2))
+
+ return vertices, polygons
+
+
+# Target Provider
+#################################################
+
+class BuildState(enum.Enum):
+ FINISHED = enum.auto()
+ ONGOING = enum.auto()
+
+class TargetProvider:
+ def start_build(self, target_object):
+ pass
+
+ def continue_build(self, event):
+ return BuildState.FINISHED
+
+ def get_matrices(self, scatter_settings):
+ return []
+
+ def draw(self):
+ pass
+
+class StrokeTarget(TargetProvider):
+ def start_build(self, target_object):
+ self.points = []
+ self.bvhtree = bvhtree_from_object(target_object)
+ self.batch = None
+
+ def continue_build(self, event):
+ if event.type == 'LEFTMOUSE' and event.value == 'RELEASE':
+ return BuildState.FINISHED
+
+ mouse_pos = (event.mouse_region_x, event.mouse_region_y)
+ location, *_ = shoot_region_2d_ray(self.bvhtree, mouse_pos)
+ if location is not None:
+ self.points.append(location)
+ self.batch = None
+ return BuildState.ONGOING
+
+ def draw(self):
+ if self.batch is None:
+ self.batch = create_line_strip_batch(self.points)
+ draw_line_strip_batch(self.batch, color=(1.0, 0.4, 0.1, 1.0), thickness=5)
+
+ def get_matrices(self, scatter_settings):
+ return scatter_around_stroke(self.points, self.bvhtree, scatter_settings)
+
+def scatter_around_stroke(stroke_points, bvhtree, settings):
+ scattered_matrices = []
+ for point, local_seed in iter_points_on_stroke_with_seed(stroke_points, settings.density, settings.seed):
+ matrix = scatter_from_source_point(bvhtree, point, local_seed, settings)
+ scattered_matrices.append(matrix)
+ return scattered_matrices
+
+def iter_points_on_stroke_with_seed(stroke_points, density, seed):
+ for i, (start, end) in enumerate(iter_pairwise(stroke_points)):
+ segment_seed = sub_seed(seed, i)
+ segment_vector = end - start
+
+ segment_length = segment_vector.length
+ amount = round_random(segment_length * density, segment_seed)
+
+ for j in range(amount):
+ t = random_uniform(sub_seed(segment_seed, j, 0))
+ origin = start + t * segment_vector
+ yield origin, sub_seed(segment_seed, j, 1)
+
+def scatter_from_source_point(bvhtree, point, seed, settings):
+ # Project displaced point on surface
+ radius = random_uniform(sub_seed(seed, 0)) * settings.radius
+ offset = random_vector(sub_seed(seed, 2)) * radius
+ location, normal, *_ = bvhtree.find_nearest(point + offset)
+ assert location is not None
+ normal.normalize()
+
+ # Scale
+ min_scale = settings.scale * (1 - settings.random_scale)
+ max_scale = settings.scale
+ scale = random_uniform(sub_seed(seed, 1), min_scale, max_scale)
+
+ # Location
+ location += normal * settings.normal_offset * scale
+
+ # Rotation
+ z_rotation = Euler((0, 0, random_uniform(sub_seed(seed, 3), 0, 2 * math.pi))).to_matrix()
+ normal_rotation = normal.to_track_quat('Z', 'X').to_matrix()
+ local_rotation = random_euler(sub_seed(seed, 3), settings.rotation).to_matrix()
+ rotation = local_rotation @ normal_rotation @ z_rotation
+
+ return Matrix.Translation(location) @ rotation.to_4x4() @ scale_matrix(scale)
+
+
+# Drawing
+#################################################
+
+box_vertices = (
+ (-1, -1, 1), ( 1, -1, 1), ( 1, 1, 1), (-1, 1, 1),
+ (-1, -1, -1), ( 1, -1, -1), ( 1, 1, -1), (-1, 1, -1))
+
+box_indices = (
+ (0, 1, 2), (2, 3, 0), (1, 5, 6), (6, 2, 1),
+ (7, 6, 5), (5, 4, 7), (4, 0, 3), (3, 7, 4),
+ (4, 5, 1), (1, 0, 4), (3, 2, 6), (6, 7, 3))
+
+box_vertices = tuple(Vector(vertex) * 0.5 for vertex in box_vertices)
+
+def draw_matrices_batches(batches):
+ uniform_color_shader.bind()
+ uniform_color_shader.uniform_float("color", (0.4, 0.4, 1.0, 0.3))
+
+ bgl.glEnable(bgl.GL_BLEND)
+ bgl.glDepthMask(bgl.GL_FALSE)
+
+ for batch in batches:
+ batch.draw(uniform_color_shader)
+
+ bgl.glDisable(bgl.GL_BLEND)
+ bgl.glDepthMask(bgl.GL_TRUE)
+
+def create_batch_for_matrices(matrices, base_scale):
+ coords = []
+ indices = []
+
+ scaled_box_vertices = [base_scale * vertex for vertex in box_vertices]
+
+ for matrix in matrices:
+ offset = len(coords)
+ coords.extend((matrix @ vertex for vertex in scaled_box_vertices))
+ indices.extend(tuple(index + offset for index in element) for element in box_indices)
+
+ batch = batch_for_shader(uniform_color_shader, 'TRIS', {"pos" : coords}, indices = indices)
+ return batch
+
+
+def draw_line_strip_batch(batch, color, thickness=1):
+ bgl.glLineWidth(thickness)
+ uniform_color_shader.bind()
+ uniform_color_shader.uniform_float("color", color)
+ batch.draw(uniform_color_shader)
+
+def create_line_strip_batch(coords):
+ return batch_for_shader(uniform_color_shader, 'LINE_STRIP', {"pos" : coords})
+
+
+def draw_text(location, text, size=15, color=(1, 1, 1, 1)):
+ font_id = 0
+ blf.position(font_id, *location)
+ blf.size(font_id, size, 72)
+ blf.draw(font_id, text)
+
+
+# Utilities
+########################################################
+
+'''
+Pythons random functions are designed to be used in cases
+when a seed is set once and then many random numbers are
+generated. To improve the user experience I want to have
+full control over how random seeds propagate through the
+functions. This is why I use custom random functions.
+
+One benefit is that changing the object density does not
+generate new random positions for all objects.
+'''
+
+def round_random(value, seed):
+ probability = value % 1
+ if probability < random_uniform(seed):
+ return math.floor(value)
+ else:
+ return math.ceil(value)
+
+def random_vector(x, min=-1, max=1):
+ return Vector((
+ random_uniform(sub_seed(x, 0), min, max),
+ random_uniform(sub_seed(x, 1), min, max),
+ random_uniform(sub_seed(x, 2), min, max)))
+
+def random_euler(x, factor):
+ return Euler(tuple(random_vector(x) * factor))
+
+def random_uniform(x, min=0, max=1):
+ return random_int(x) / 2147483648 * (max - min) + min
+
+def random_int(x):
+ x = (x<<13) ^ x
+ return (x * (x * x * 15731 + 789221) + 1376312589) & 0x7fffffff
+
+def sub_seed(seed, index, index2=0):
+ return random_int(seed * 3243 + index * 5643 + index2 * 54243)
+
+
+def currently_in_3d_view(context):
+ return context.space_data.type == 'VIEW_3D'
+
+def get_selected_non_active_objects(context):
+ return set(context.selected_objects) - {context.active_object}
+
+def make_random_chunks(sequence, chunk_amount):
+ sequence = list(sequence)
+ random.shuffle(sequence)
+ return make_chunks(sequence, chunk_amount)
+
+def make_chunks(sequence, chunk_amount):
+ length = math.ceil(len(sequence) / chunk_amount)
+ return [sequence[i:i+length] for i in range(0, len(sequence), length)]
+
+def iter_pairwise(sequence):
+ return zip(sequence, islice(sequence, 1, None))
+
+def bvhtree_from_object(object):
+ import bmesh
+ bm = bmesh.new()
+
+ mesh = object.to_mesh(bpy.context.depsgraph, True)
+ bm.from_mesh(mesh)
+ bm.transform(object.matrix_world)
+
+ bvhtree = BVHTree.FromBMesh(bm)
+ bpy.data.meshes.remove(mesh)
+ return bvhtree
+
+def shoot_region_2d_ray(bvhtree, position_2d):
+ region = bpy.context.region
+ region_3d = bpy.context.space_data.region_3d
+
+ origin = region_2d_to_origin_3d(region, region_3d, position_2d)
+ direction = region_2d_to_vector_3d(region, region_3d, position_2d)
+
+ location, normal, index, distance = bvhtree.ray_cast(origin, direction)
+ return location, normal, index, distance
+
+def scale_matrix(factor):
+ m = Matrix.Identity(4)
+ m[0][0] = factor
+ m[1][1] = factor
+ m[2][2] = factor
+ return m
+
+def event_is_in_region(event, region):
+ return (region.x <= event.mouse_x <= region.x + region.width
+ and region.y <= event.mouse_y <= region.y + region.height)
+
+def get_max_object_side_length(objects):
+ return max(
+ max(obj.dimensions[0] for obj in objects),
+ max(obj.dimensions[1] for obj in objects),
+ max(obj.dimensions[2] for obj in objects)
+ )
+
+
+# Registration
+###############################################
+
+def register():
+ bpy.utils.register_class(ScatterObjects)
+
+def unregister():
+ bpy.utils.unregister_class(ScatterObjects)
diff --git a/object_scatter/ui.py b/object_scatter/ui.py
new file mode 100644
index 00000000..94d97a9c
--- /dev/null
+++ b/object_scatter/ui.py
@@ -0,0 +1,138 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+import bpy
+import math
+
+from collections import namedtuple
+
+from bpy.props import (
+ IntProperty,
+ FloatProperty,
+ PointerProperty
+)
+
+
+ScatterSettings = namedtuple("ScatterSettings",
+ ["seed", "density", "radius", "scale", "random_scale",
+ "rotation", "normal_offset"])
+
+class ObjectScatterProperties(bpy.types.PropertyGroup):
+ seed: IntProperty(
+ name="Seed",
+ default=0
+ )
+
+ density: FloatProperty(
+ name="Density",
+ default=10,
+ min=0,
+ soft_max=50
+ )
+
+ radius: FloatProperty(
+ name="Radius",
+ default=1,
+ min=0,
+ soft_max=5,
+ subtype='DISTANCE',
+ unit='LENGTH'
+ )
+
+ scale: FloatProperty(
+ name="Scale",
+ default=0.3,
+ min=0.00001,
+ soft_max=1
+ )
+
+ random_scale_percentage: FloatProperty(
+ name="Random Scale Percentage",
+ default=80,
+ min=0,
+ max=100,
+ subtype='PERCENTAGE',
+ precision=0
+ )
+
+ rotation: FloatProperty(
+ name="Rotation",
+ default=0.5,
+ min=0,
+ max=math.pi * 2,
+ soft_max=math.pi / 2,
+ subtype='ANGLE',
+ unit='ROTATION'
+ )
+
+ normal_offset: FloatProperty(
+ name="Normal Offset",
+ default=0,
+ soft_min=-1.0,
+ soft_max=1.0
+ )
+
+ def to_settings(self):
+ return ScatterSettings(
+ seed=self.seed,
+ density=self.density,
+ radius=self.radius,
+ scale=self.scale,
+ random_scale=self.random_scale_percentage / 100,
+ rotation=self.rotation,
+ normal_offset=self.normal_offset,
+ )
+
+
+class ObjectScatterPanel(bpy.types.Panel):
+ bl_label = "Object Scatter"
+ bl_space_type = 'PROPERTIES'
+ bl_region_type = 'WINDOW'
+ bl_context = '.objectmode'
+
+ def draw(self, context):
+ layout = self.layout
+ layout.use_property_split = True
+ scatter = context.scene.scatter_properties
+
+ layout.prop(scatter, "density", slider=True)
+ layout.prop(scatter, "radius", slider=True)
+
+ col = layout.column(align=True)
+ col.prop(scatter, "scale", slider=True)
+ col.prop(scatter, "random_scale_percentage", text="Randomness", slider=True)
+
+ layout.prop(scatter, "rotation", slider=True)
+ layout.prop(scatter, "normal_offset", text="Offset", slider=True)
+ layout.prop(scatter, "seed")
+
+
+classes = (
+ ObjectScatterProperties,
+ ObjectScatterPanel,
+)
+
+def register():
+ for cls in classes:
+ bpy.utils.register_class(cls)
+ bpy.types.Scene.scatter_properties = PointerProperty(type=ObjectScatterProperties)
+
+def unregister():
+ for cls in classes:
+ bpy.utils.unregister_class(cls)
+ del bpy.types.Scene.scatter_properties
diff --git a/oscurart_tools/__init__.py b/oscurart_tools/__init__.py
index 420a4990..4b7b5cf7 100644
--- a/oscurart_tools/__init__.py
+++ b/oscurart_tools/__init__.py
@@ -208,7 +208,7 @@ class OscPanelMesh(Panel):
colrow.operator("lattice.mirror_selected", icon="LATTICE_DATA")
colrow = col.row(align=1)
colrow.label(text="Edit Multimesh")
- colrow.prop_search(scene, "multimeshedit", bpy.data, "groups", text="")
+ colrow.prop_search(scene, "multimeshedit", bpy.data, "collections", text="")
colrow = col.row(align=1)
colrow.operator("mesh.create_edit_multimesh", icon="IMPORT", text= "StartEdit")
colrow.operator("mesh.apply_edit_multimesh", icon="EXPORT", text="FinishEdit")
diff --git a/oscurart_tools/oscurart_files.py b/oscurart_tools/oscurart_files.py
index 65c248eb..6c2f371d 100644
--- a/oscurart_tools/oscurart_files.py
+++ b/oscurart_tools/oscurart_files.py
@@ -97,10 +97,10 @@ class reFreshMissingGroups(Operator):
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
- for group in bpy.data.groups:
+ for group in bpy.data.collections:
if group.library is not None:
with bpy.data.libraries.load(group.library.filepath, link=True) as (linked, local):
- local.groups = linked.groups
+ local.collections = linked.collections
return {'FINISHED'}
diff --git a/oscurart_tools/oscurart_meshes.py b/oscurart_tools/oscurart_meshes.py
index 70f4df1c..39257542 100644
--- a/oscurart_tools/oscurart_meshes.py
+++ b/oscurart_tools/oscurart_meshes.py
@@ -321,8 +321,7 @@ def DefOscObjectToMesh():
MESH = ACTOBJ.to_mesh(
scene=bpy.context.scene,
apply_modifiers=True,
- settings="RENDER",
- calc_tessface=True)
+ settings="RENDER")
OBJECT = bpy.data.objects.new(("%s_Freeze") % (ACTOBJ.name), MESH)
bpy.context.scene.objects.link(OBJECT)
@@ -450,7 +449,7 @@ class ModalIndexOperator(Operator):
self.tsize -= 1
elif event.type in {'RIGHTMOUSE', 'ESC', 'TAB'}:
bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
- context.area.header_text_set("")
+ context.area.header_text_set(None)
return {'CANCELLED'}
return {'PASS_THROUGH'}
@@ -601,7 +600,7 @@ def defPasteUvsIsland(self, uvOffset, rotateUv,context):
bm = bmesh.from_edit_mesh(bpy.context.object.data)
bmesh.ops.reverse_uvs(bm, faces=[f for f in bm.faces if f.select])
bmesh.ops.rotate_uvs(bm, faces=[f for f in bm.faces if f.select])
- #bmesh.update_edit_mesh(bpy.context.object.data, tessface=False, destructive=False)
+ #bmesh.update_edit_mesh(bpy.context.object.data, loop_triangles=False, destructive=False)
@@ -660,7 +659,7 @@ class createEditMultimesh(Operator):
global relvert
global me
global ob
- temp = [[ob , [vert.co for vert in ob.data.vertices]]for ob in bpy.data.groups[bpy.context.scene.multimeshedit].objects]
+ temp = [[ob , [vert.co for vert in ob.data.vertices]]for ob in bpy.data.collections[bpy.context.scene.multimeshedit].objects]
vi = 0
pi = 0
relvert = {}
diff --git a/oscurart_tools/oscurart_objects.py b/oscurart_tools/oscurart_objects.py
index dd3d4624..b3773b7a 100644
--- a/oscurart_tools/oscurart_objects.py
+++ b/oscurart_tools/oscurart_objects.py
@@ -198,7 +198,7 @@ def CopyObjectGroupsAndLayers(self):
scene.object_bases[OBJECT.name].layers[:] = list(GLOBALLAYERS)
# REMUEVO DE TODO GRUPO
- for GROUP in bpy.data.groups[:]:
+ for GROUP in bpy.data.collections[:]:
if GROUP in OBJECT.users_group[:]:
GROUP.objects.unlink(OBJECT)
@@ -342,17 +342,17 @@ class SetLayersToOtherScenes (Operator):
def DefRenderOnlyInCamera():
# crea grupos
- if "INCAMERA" not in bpy.data.groups:
- bpy.data.groups.new("INCAMERA")
- if "NOTINCAMERA" not in bpy.data.groups:
- bpy.data.groups.new("NOTINCAMERA")
+ if "INCAMERA" not in bpy.data.collections:
+ bpy.data.collections.new("INCAMERA")
+ if "NOTINCAMERA" not in bpy.data.collections:
+ bpy.data.collections.new("NOTINCAMERA")
# limpio grupos
for ob in bpy.data.objects:
- if ob.name in bpy.data.groups["INCAMERA"].objects:
- bpy.data.groups["INCAMERA"].objects.unlink(ob)
- if ob.name in bpy.data.groups["NOTINCAMERA"].objects:
- bpy.data.groups["NOTINCAMERA"].objects.unlink(ob)
+ if ob.name in bpy.data.collections["INCAMERA"].objects:
+ bpy.data.collections["INCAMERA"].objects.unlink(ob)
+ if ob.name in bpy.data.collections["NOTINCAMERA"].objects:
+ bpy.data.collections["NOTINCAMERA"].objects.unlink(ob)
# ordeno grupos
for ob in bpy.data.objects:
@@ -370,9 +370,9 @@ def DefRenderOnlyInCamera():
else:
obs = True
if obs:
- bpy.data.groups["INCAMERA"].objects.link(ob)
+ bpy.data.collections["INCAMERA"].objects.link(ob)
else:
- bpy.data.groups["NOTINCAMERA"].objects.link(ob)
+ bpy.data.collections["NOTINCAMERA"].objects.link(ob)
class RenderOnlyInCamera (Operator):
@@ -528,15 +528,15 @@ class oscDuplicateSymmetricalOp (Operator):
def DefObjectToGroups():
try:
"%s_MSH" % (os.path.basename(bpy.data.filepath).replace(".blend", ""))
- scgr = bpy.data.groups["%s_MSH" % (os.path.basename(bpy.data.filepath).replace(".blend", ""))]
+ scgr = bpy.data.collections["%s_MSH" % (os.path.basename(bpy.data.filepath).replace(".blend", ""))]
except:
- scgr = bpy.data.groups.new(
+ scgr = bpy.data.collections.new(
"%s_MSH" %
(os.path.basename(bpy.data.filepath).replace(".blend", "")))
for ob in bpy.data.objects:
if ob.select:
if ob.type == "MESH":
- gr = bpy.data.groups.new(ob.name)
+ gr = bpy.data.collections.new(ob.name)
gr.objects.link(ob)
scgr.objects.link(ob)
diff --git a/oscurart_tools/oscurart_overrides.py b/oscurart_tools/oscurart_overrides.py
index 5709c5b5..9924fda5 100644
--- a/oscurart_tools/oscurart_overrides.py
+++ b/oscurart_tools/oscurart_overrides.py
@@ -39,7 +39,7 @@ def DefOscApplyOverrides(self):
scene = bpy.context.scene
proptolist = list(eval(scene.oscurart.overrides))
for group, material in proptolist:
- for object in bpy.data.groups[group].objects:
+ for object in bpy.data.collections[group].objects:
lenslots = len(object.material_slots)
if object.type in types:
if len(object.data.materials):
@@ -133,7 +133,7 @@ class OscCheckOverrides(Operator):
MATLIST.append(MATERIAL.name)
GROUPLIST = []
- for GROUP in bpy.data.groups[:]:
+ for GROUP in bpy.data.collections[:]:
if GROUP.users > 0:
GROUPLIST.append(GROUP.name)
@@ -194,7 +194,7 @@ class OscOverridesGUI(Panel):
for i, m in enumerate(bpy.context.scene.ovlist):
colrow = col.row(align=1)
- colrow.prop_search(m, "grooverride", bpy.data, "groups", text="")
+ colrow.prop_search(m, "grooverride", bpy.data, "collections", text="")
colrow.prop_search(
m,
"matoverride",
diff --git a/oscurart_tools/oscurart_render.py b/oscurart_tools/oscurart_render.py
index f6e083e9..67dacfb2 100644
--- a/oscurart_tools/oscurart_render.py
+++ b/oscurart_tools/oscurart_render.py
@@ -55,7 +55,7 @@ def defRenderAll(frametype, scenes):
scene.frame_start = FC
for group, material in proptolist:
- for object in bpy.data.groups[group].objects:
+ for object in bpy.data.collections[group].objects:
lenslots = len(object.material_slots)
if object.type in types:
if len(object.data.materials):
diff --git a/paint_palette.py b/paint_palette.py
index cf0e58e9..320163ad 100644
--- a/paint_palette.py
+++ b/paint_palette.py
@@ -462,12 +462,12 @@ def color_palette_draw(self, context):
row = layout.row(align=True)
row.menu("PALETTE_MT_menu", text=PALETTE_MT_menu.bl_label)
- row.operator("palette.preset_add", text="", icon="ZOOMIN").remove_active = False
- row.operator("palette.preset_add", text="", icon="ZOOMOUT").remove_active = True
+ row.operator("palette.preset_add", text="", icon='ADD').remove_active = False
+ row.operator("palette.preset_add", text="", icon='REMOVE').remove_active = True
col = layout.column(align=True)
row = col.row(align=True)
- row.operator("palette_props.add_color", icon="ZOOMIN")
+ row.operator("palette_props.add_color", icon='ADD')
row.prop(palette_props, "index")
row.operator("palette_props.remove_color", icon="PANEL_CLOSE")
diff --git a/pie_menus_official/__init__.py b/pie_menus_official/__init__.py
index 513ae2b3..52c8d346 100644
--- a/pie_menus_official/__init__.py
+++ b/pie_menus_official/__init__.py
@@ -31,7 +31,7 @@ bl_info = {
"name": "UI Pie Menu Official",
"author": "Antony Riakiotakis, Sebastian Koenig",
"version": (1, 1, 5),
- "blender": (2, 7, 7),
+ "blender": (2, 80, 0),
"description": "Individual Pie Menu Activation List",
"location": "Addons Preferences",
"warning": "",
@@ -129,42 +129,42 @@ class UIToolsPreferences(AddonPreferences):
op = sub.operator('wm.context_toggle', text='', icon=icon,
emboss=False)
op.data_path = 'addon_prefs.show_expanded_' + mod_name
- sub.label('{}: {}'.format(info['category'], info['name']))
+ sub.label(text='{}: {}'.format(info['category'], info['name']))
sub = row.row()
sub.alignment = 'RIGHT'
if info.get('warning'):
- sub.label('', icon='ERROR')
+ sub.label(text='', icon='ERROR')
sub.prop(self, 'use_' + mod_name, text='')
# The second stage
if expand:
if info.get('description'):
- split = col.row().split(percentage=0.15)
- split.label('Description:')
- split.label(info['description'])
+ split = col.row().split(factor=0.15)
+ split.label(text='Description:')
+ split.label(text=info['description'])
if info.get('location'):
- split = col.row().split(percentage=0.15)
- split.label('Location:')
- split.label(info['location'])
+ split = col.row().split(factor=0.15)
+ split.label(text='Location:')
+ split.label(text=info['location'])
"""
if info.get('author'):
- split = col.row().split(percentage=0.15)
- split.label('Author:')
- split.label(info['author'])
+ split = col.row().split(factor=0.15)
+ split.label(text='Author:')
+ split.label(text=info['author'])
"""
if info.get('version'):
- split = col.row().split(percentage=0.15)
- split.label('Version:')
- split.label('.'.join(str(x) for x in info['version']),
+ split = col.row().split(factor=0.15)
+ split.label(text='Version:')
+ split.label(text='.'.join(str(x) for x in info['version']),
translate=False)
if info.get('warning'):
- split = col.row().split(percentage=0.15)
- split.label('Warning:')
- split.label(' ' + info['warning'], icon='ERROR')
+ split = col.row().split(factor=0.15)
+ split.label(text='Warning:')
+ split.label(text=' ' + info['warning'], icon='ERROR')
tot_row = int(bool(info.get('wiki_url')))
if tot_row:
- split = col.row().split(percentage=0.15)
+ split = col.row().split(factor=0.15)
split.label(text='Internet:')
if info.get('wiki_url'):
op = split.operator('wm.url_open',
diff --git a/pie_menus_official/pie_manipulator_of.py b/pie_menus_official/pie_manipulator_of.py
index 04ced83a..eb475cf7 100644
--- a/pie_menus_official/pie_manipulator_of.py
+++ b/pie_menus_official/pie_manipulator_of.py
@@ -26,7 +26,7 @@ class VIEW3D_manipulator_set_of(Operator):
bl_label = "Set Manipulator"
bl_idname = "view3d.manipulator_set"
- type = EnumProperty(
+ type: EnumProperty(
name="Type",
items=(('TRANSLATE', "Translate", "Use the manipulator for movement transformations"),
('ROTATE', "Rotate", "Use the manipulator for rotation transformations"),
@@ -36,7 +36,7 @@ class VIEW3D_manipulator_set_of(Operator):
def execute(self, context):
# show manipulator if user selects an option
- context.space_data.show_manipulator = True
+ context.space_data.show_gizmo = True
context.space_data.transform_manipulators = {self.type}
return {'FINISHED'}
@@ -50,10 +50,10 @@ class VIEW3D_PIE_manipulator_of(Menu):
layout = self.layout
pie = layout.menu_pie()
- pie.operator("view3d.manipulator_set", icon='MAN_TRANS', text="Translate").type = 'TRANSLATE'
- pie.operator("view3d.manipulator_set", icon='MAN_ROT', text="Rotate").type = 'ROTATE'
- pie.operator("view3d.manipulator_set", icon='MAN_SCALE', text="Scale").type = 'SCALE'
- pie.prop(context.space_data, "show_manipulator")
+ pie.operator("wm.tool_set_by_name", icon='MAN_TRANS', text="Translate").name = "Move"
+ pie.operator("wm.tool_set_by_name", icon='MAN_ROT', text="Rotate").name = "Rotate"
+ pie.operator("wm.tool_set_by_name", icon='MAN_SCALE', text="Scale").name = "Scale"
+ pie.prop(context.space_data, "show_gizmo")
classes = (
diff --git a/pie_menus_official/pie_pivot_of.py b/pie_menus_official/pie_pivot_of.py
index 88677b69..ec48d359 100644
--- a/pie_menus_official/pie_pivot_of.py
+++ b/pie_menus_official/pie_pivot_of.py
@@ -24,9 +24,9 @@ class VIEW3D_PIE_pivot_of(Menu):
layout = self.layout
pie = layout.menu_pie()
- pie.prop(context.space_data, "pivot_point", expand=True)
+ pie.prop(context.scene.tool_settings, "transform_pivot_point", expand=True)
if context.active_object and context.active_object.mode == 'OBJECT':
- pie.prop(context.space_data, "use_pivot_point_align", text="Center Points")
+ pie.prop(context.scene.tool_settings, "use_transform_pivot_point_align", text="Center Points")
classes = (
diff --git a/pie_menus_official/pie_view_of.py b/pie_menus_official/pie_view_of.py
index 40b79217..1c1f3df2 100644
--- a/pie_menus_official/pie_view_of.py
+++ b/pie_menus_official/pie_view_of.py
@@ -38,7 +38,7 @@ class VIEW3D_PIE_view_of(Menu):
layout = self.layout
pie = layout.menu_pie()
- pie.operator_enum("VIEW3D_OT_viewnumpad", "type")
+ pie.operator_enum("VIEW3D_OT_view_axis", "type")
pie.operator("wm.call_menu_pie", text="More", icon='PLUS').name = "VIEW3D_PIE_view_more_of"
diff --git a/render_copy_settings/__init__.py b/render_copy_settings/__init__.py
index 5f7d8781..8c57f68f 100644
--- a/render_copy_settings/__init__.py
+++ b/render_copy_settings/__init__.py
@@ -19,10 +19,10 @@
# <pep8 compliant>
bl_info = {
- "name": "Copy Settings",
+ "name": "Copy Render Settings",
"author": "Bastien Montagne",
- "version": (0, 1, 7),
- "blender": (2, 79, 1),
+ "version": (1, 0, 0),
+ "blender": (2, 80, 0),
"location": "Render buttons (Properties window)",
"description": "Allows to copy a selection of render settings "
"from current scene to others.",
diff --git a/render_copy_settings/data.py b/render_copy_settings/data.py
index d370d7b7..59b91fcc 100644
--- a/render_copy_settings/data.py
+++ b/render_copy_settings/data.py
@@ -31,34 +31,34 @@ from bpy.props import (
########################################################################################################################
class RenderCopySettingsDataScene(bpy.types.PropertyGroup):
- allowed = BoolProperty(default=True)
+ allowed: BoolProperty(default=True)
class RenderCopySettingsDataSetting(bpy.types.PropertyGroup):
- strid = StringProperty(default="")
- copy = BoolProperty(default=False)
+ strid: StringProperty(default="")
+ copy: BoolProperty(default=False)
class RenderCopySettingsData(bpy.types.PropertyGroup):
# XXX: The consistency of this collection is delegated to the UI code.
# It should only contain one element for each render setting.
- affected_settings = CollectionProperty(type=RenderCopySettingsDataSetting,
- name="Affected Settings",
- description="The list of all available render settings")
+ affected_settings: CollectionProperty(type=RenderCopySettingsDataSetting,
+ name="Affected Settings",
+ description="The list of all available render settings")
# XXX Unused, but needed for template_list…
- affected_settings_idx = IntProperty()
+ affected_settings_idx: IntProperty()
# XXX: The consistency of this collection is delegated to the UI code.
# It should only contain one element for each scene.
- allowed_scenes = CollectionProperty(type=RenderCopySettingsDataScene,
- name="Allowed Scenes",
- description="The list all scenes in the file")
+ allowed_scenes: CollectionProperty(type=RenderCopySettingsDataScene,
+ name="Allowed Scenes",
+ description="The list all scenes in the file")
# XXX Unused, but needed for template_list…
- allowed_scenes_idx = IntProperty()
+ allowed_scenes_idx: IntProperty()
- filter_scene = StringProperty(name="Filter Scene",
- description="Regex to only affect scenes which name matches it",
- default="")
+ filter_scene: StringProperty(name="Filter Scene",
+ description="Regex to only affect scenes which name matches it",
+ default="")
classes = (
diff --git a/render_copy_settings/operator.py b/render_copy_settings/operator.py
index a88e7dc1..5c633f1f 100644
--- a/render_copy_settings/operator.py
+++ b/render_copy_settings/operator.py
@@ -47,7 +47,7 @@ def collection_property_sort(collection, sortkey, start_idx=0):
class RenderCopySettingsOPPrepare(bpy.types.Operator):
- """Prepare internal data for render_copy_settings (gathering all existingrender settings, and scenes)"""
+ """Prepare internal data for render_copy_settings (gathering all existing render settings, and scenes)"""
bl_idname = "scene.render_copy_settings_prepare"
bl_label = "Render: Copy Settings Prepare"
bl_option = {'REGISTER'}
@@ -127,9 +127,9 @@ class RenderCopySettingsOPPreset(bpy.types.Operator):
# Enable undo…
bl_option = {'REGISTER', 'UNDO'}
- presets = EnumProperty(items=(p.rna_enum for p in presets.presets),
- default=set(),
- options={'ENUM_FLAG'})
+ presets: EnumProperty(items=(p.rna_enum for p in presets.presets),
+ default=set(),
+ options={'ENUM_FLAG'})
@staticmethod
def process_elements(settings, elts):
diff --git a/render_copy_settings/panel.py b/render_copy_settings/panel.py
index 375e1bd3..88f4e940 100644
--- a/render_copy_settings/panel.py
+++ b/render_copy_settings/panel.py
@@ -28,14 +28,14 @@ class RENDER_UL_copy_settings(bpy.types.UIList):
#assert(isinstance(item, (data_types.RenderCopySettingsScene, data_types.RenderCopySettingsDataSetting)))
if self.layout_type in {'DEFAULT', 'COMPACT'}:
if isinstance(item, data_types.RenderCopySettingsDataSetting):
- layout.label(item.name, icon_value=icon)
+ layout.label(text=item.name, icon_value=icon)
layout.prop(item, "copy", text="")
else: #elif isinstance(item, data_types.RenderCopySettingsDataScene):
layout.prop(item, "allowed", text=item.name, toggle=True)
elif self.layout_type in {'GRID'}:
layout.alignment = 'CENTER'
if isinstance(item, data_types.RenderCopySettingsDataSetting):
- layout.label(item.name, icon_value=icon)
+ layout.label(text=item.name, icon_value=icon)
layout.prop(item, "copy", text="")
else: #elif isinstance(item, data_types.RenderCopySettingsDataScene):
layout.prop(item, "allowed", text=item.name, toggle=True)
@@ -60,9 +60,9 @@ class RENDER_PT_copy_settings(bpy.types.Panel):
if bpy.ops.scene.render_copy_settings_prepare.poll():
bpy.ops.scene.render_copy_settings_prepare()
- split = layout.split(0.75)
+ split = layout.split(factor=0.75)
split.template_list("RENDER_UL_copy_settings", "settings", cp_sett, "affected_settings",
- cp_sett, "affected_settings_idx", rows=6)
+ cp_sett, "affected_settings_idx", rows=5)
col = split.column()
all_set = {sett.strid for sett in cp_sett.affected_settings if sett.copy}
@@ -76,7 +76,7 @@ class RENDER_PT_copy_settings(bpy.types.Panel):
layout.prop(cp_sett, "filter_scene")
if len(cp_sett.allowed_scenes):
- layout.label("Affected Scenes:")
+ layout.label(text="Affected Scenes:")
layout.template_list("RENDER_UL_copy_settings", "scenes", cp_sett, "allowed_scenes",
# cp_sett, "allowed_scenes_idx", rows=6, type='GRID')
cp_sett, "allowed_scenes_idx", rows=6) # XXX Grid is not nice currently...
diff --git a/render_copy_settings/presets.py b/render_copy_settings/presets.py
index 8bc43afb..0fc53e39 100644
--- a/render_copy_settings/presets.py
+++ b/render_copy_settings/presets.py
@@ -38,9 +38,6 @@ presets = (CopyPreset("Resolution",
CopyPreset("Threads",
("threads", "Render Threads", "The thread mode and number settings"),
{"threads_mode", "threads"}),
- CopyPreset("Fields",
- ("fields", "Render Fields", "The Fields settings"),
- {"use_fields", "field_order", "use_fields_still"}),
CopyPreset("Stamp",
("stamp", "Render Stamp", "The Stamp toggle"),
{"use_stamp"})
diff --git a/render_povray/__init__.py b/render_povray/__init__.py
index a8b07887..9d7254ea 100644
--- a/render_povray/__init__.py
+++ b/render_povray/__init__.py
@@ -2274,11 +2274,11 @@ class PovrayPreferences(AddonPreferences):
def register():
bpy.utils.register_module(__name__)
- bpy.types.INFO_MT_add.prepend(ui.menu_func_add)
- bpy.types.INFO_MT_file_import.append(ui.menu_func_import)
+ bpy.types.VIEW3D_MT_add.prepend(ui.menu_func_add)
+ bpy.types.TOPBAR_MT_file_import.append(ui.menu_func_import)
bpy.types.TEXT_MT_templates.append(ui.menu_func_templates)
bpy.types.RENDER_PT_povray_radiosity.prepend(ui.rad_panel_func)
- bpy.types.LAMP_PT_POV_lamp.prepend(ui.lamp_panel_func)
+ bpy.types.LIGHT_PT_POV_light.prepend(ui.light_panel_func)
bpy.types.WORLD_PT_world.prepend(ui.world_panel_func)
# was used for parametric objects but made the other addon unreachable on
# unregister for other tools to use created a user action call instead
@@ -2312,11 +2312,11 @@ def unregister():
#bpy.types.TEXTURE_PT_context_texture.remove(TEXTURE_PT_povray_type)
#addon_utils.disable("add_mesh_extra_objects", default_set=False)
bpy.types.WORLD_PT_world.remove(ui.world_panel_func)
- bpy.types.LAMP_PT_POV_lamp.remove(ui.lamp_panel_func)
+ bpy.types.LIGHT_PT_POV_light.remove(ui.light_panel_func)
bpy.types.RENDER_PT_povray_radiosity.remove(ui.rad_panel_func)
bpy.types.TEXT_MT_templates.remove(ui.menu_func_templates)
- bpy.types.INFO_MT_file_import.remove(ui.menu_func_import)
- bpy.types.INFO_MT_add.remove(ui.menu_func_add)
+ bpy.types.TOPBAR_MT_file_import.remove(ui.menu_func_import)
+ bpy.types.VIEW3D_MT_add.remove(ui.menu_func_add)
bpy.utils.unregister_module(__name__)
diff --git a/render_povray/nodes.py b/render_povray/nodes.py
index a4acd138..a8281c8a 100644
--- a/render_povray/nodes.py
+++ b/render_povray/nodes.py
@@ -52,7 +52,7 @@ class ObjectNodeTree(bpy.types.NodeTree):
@classmethod
def get_from_context(cls, context):
ob = context.active_object
- if ob and ob.type not in {'LAMP'}:
+ if ob and ob.type not in {'LIGHT'}:
ma = ob.active_material
if ma is not None:
nt_name = ma.node_tree
@@ -517,7 +517,7 @@ class PovrayColorImageNode(Node, ObjectNodeTree):
im=image
split = column.split(percentage=0.8,align=True)
split.prop_search(self,"image",context.blend_data,"images",text="")
- split.operator("pov.imageopen",text="",icon="FILESEL")
+ split.operator("pov.imageopen",text="",icon="FILEBROWSER")
if im is not None:
column.prop(im,"source",text="")
column.prop(self,"map_type",text="")
@@ -535,7 +535,7 @@ class PovrayColorImageNode(Node, ObjectNodeTree):
im=image
split = column.split(percentage=0.8,align=True)
split.prop_search(self,"image",context.blend_data,"images",text="")
- split.operator("pov.imageopen",text="",icon="FILESEL")
+ split.operator("pov.imageopen",text="",icon="FILEBROWSER")
if im is not None:
column.prop(im,"source",text="")
column.prop(self,"map_type",text="")
@@ -597,7 +597,7 @@ class PovrayBumpMapNode(Node, ObjectNodeTree):
im=image
split = column.split(percentage=0.8,align=True)
split.prop_search(self,"image",context.blend_data,"images",text="")
- split.operator("pov.imageopen",text="",icon="FILESEL")
+ split.operator("pov.imageopen",text="",icon="FILEBROWSER")
if im is not None:
column.prop(im,"source",text="")
column.prop(self,"map_type",text="")
@@ -613,7 +613,7 @@ class PovrayBumpMapNode(Node, ObjectNodeTree):
im=image
split = column.split(percentage=0.8,align=True)
split.prop_search(self,"image",context.blend_data,"images",text="")
- split.operator("pov.imageopen",text="",icon="FILESEL")
+ split.operator("pov.imageopen",text="",icon="FILEBROWSER")
if im is not None:
column.prop(im,"source",text="")
column.prop(self,"map_type",text="")
@@ -668,7 +668,7 @@ class PovrayImagePatternNode(Node, ObjectNodeTree):
im=image
split = column.split(percentage=0.8,align=True)
split.prop_search(self,"image",context.blend_data,"images",text="")
- split.operator("pov.imageopen",text="",icon="FILESEL")
+ split.operator("pov.imageopen",text="",icon="FILEBROWSER")
if im is not None:
column.prop(im,"source",text="")
column.prop(self,"map_type",text="")
@@ -687,7 +687,7 @@ class PovrayImagePatternNode(Node, ObjectNodeTree):
im=image
split = column.split(percentage=0.8,align=True)
split.prop_search(self,"image",context.blend_data,"images",text="")
- split.operator("pov.imageopen",text="",icon="FILESEL")
+ split.operator("pov.imageopen",text="",icon="FILEBROWSER")
if im is not None:
column.prop(im,"source",text="")
column.prop(self,"map_type",text="")
diff --git a/render_povray/primitives.py b/render_povray/primitives.py
index d7dfe663..1ae43c1a 100644
--- a/render_povray/primitives.py
+++ b/render_povray/primitives.py
@@ -1052,7 +1052,7 @@ class POVRAY_OT_rainbow_add(bpy.types.Operator):
def execute(self,context):
cam = context.scene.camera
- bpy.ops.object.lamp_add(type='SPOT', radius=1)
+ bpy.ops.object.light_add(type='SPOT', radius=1)
ob = context.object
ob.data.show_cone = False
ob.data.spot_blend = 0.5
diff --git a/render_povray/render.py b/render_povray/render.py
index 26352b2d..1e27beab 100644
--- a/render_povray/render.py
+++ b/render_povray/render.py
@@ -2067,7 +2067,7 @@ def write_pov(filename, scene=None, info_callback=None):
# XXX I moved all those checks here, as there is no need to compute names
# for object we won't export here!
- if (ob.type in {'LAMP', 'CAMERA', #'EMPTY', #empties can bear dupligroups
+ if (ob.type in {'LIGHT', 'CAMERA', #'EMPTY', #empties can bear dupligroups
'META', 'ARMATURE', 'LATTICE'}):
continue
smokeFlag=False
@@ -2111,7 +2111,7 @@ def write_pov(filename, scene=None, info_callback=None):
strandShape = 0.0
# Set the number of particles to render count rather than 3d view display
pSys.set_resolution(scene, ob, 'RENDER')
- steps = pSys.settings.draw_step
+ steps = pSys.settings.display_step
steps = 3 ** steps # or (power of 2 rather than 3) + 1 # Formerly : len(particle.hair_keys)
totalNumberOfHairs = ( len(pSys.particles) + len(pSys.child_particles) )
@@ -2163,7 +2163,7 @@ def write_pov(filename, scene=None, info_callback=None):
elif step == 0:
hDiameter = strandStart
else:
- hDiameter += (strandEnd-strandStart)/(pSys.settings.draw_step+1) #XXX +1 or not?
+ hDiameter += (strandEnd-strandStart)/(pSys.settings.display_step+1) #XXX +1 or not?
if step == 0 and pSys.settings.use_hair_bspline:
# Write three times the first point to compensate pov Bezier handling
file.write('<%.6g,%.6g,%.6g>,%.7g,\n' % (co[0], co[1], co[2], abs(hDiameter)))
@@ -2266,10 +2266,10 @@ def write_pov(filename, scene=None, info_callback=None):
if ob.data:
name_orig = "OB" + ob.name
dataname_orig = "DATA" + ob.data.name
- elif ob.is_duplicator:
- if ob.dupli_type == 'GROUP':
+ elif ob.is_instancer:
+ if ob.instance_type == 'COLLECTION':
name_orig = "OB" + ob.name
- dataname_orig = "DATA" + ob.dupli_group.name
+ dataname_orig = "DATA" + ob.instance_collection.name
else:
#hoping only dupligroups have several source datablocks
ob.dupli_list_create(scene)
@@ -2671,7 +2671,7 @@ def write_pov(filename, scene=None, info_callback=None):
############################################else try to export mesh
- elif ob.is_duplicator == False: #except duplis which should be instances groups for now but all duplis later
+ elif ob.is_instancer == False: #except duplis which should be instances groups for now but all duplis later
if ob.type == 'EMPTY':
tabWrite("\n//dummy sphere to represent Empty location\n")
tabWrite("#declare %s =sphere {<0, 0, 0>,0 pigment{rgbt 1} no_image no_reflection no_radiosity photons{pass_through collect off} hollow}\n" % povdataname)
@@ -2689,7 +2689,7 @@ def write_pov(filename, scene=None, info_callback=None):
importance = ob.pov.importance_value
if me:
me_materials = me.materials
- me_faces = me.tessfaces[:]
+ me_faces = me.loop_triangles[:]
#if len(me_faces)==0:
#tabWrite("\n//dummy sphere to represent empty mesh location\n")
#tabWrite("#declare %s =sphere {<0, 0, 0>,0 pigment{rgbt 1} no_image no_reflection no_radiosity photons{pass_through collect off} hollow}\n" % povdataname)
@@ -2700,16 +2700,16 @@ def write_pov(filename, scene=None, info_callback=None):
tabWrite("#declare %s =sphere {<0, 0, 0>,0 pigment{rgbt 1} no_image no_reflection no_radiosity photons{pass_through collect off} hollow}\n" % povdataname)
continue
- uv_textures = me.tessface_uv_textures
- if len(uv_textures) > 0:
- if me.uv_textures.active and uv_textures.active.data:
- uv_layer = uv_textures.active.data
+ uv_layers = me.uv_layers
+ if len(uv_layers) > 0:
+ if me.uv_layers.active and uv_layers.active.data:
+ uv_layer = uv_layers.active.data
else:
uv_layer = None
try:
#vcol_layer = me.vertex_colors.active.data
- vcol_layer = me.tessface_vertex_colors.active.data
+ vcol_layer = me.vertex_colors.active.data
except AttributeError:
vcol_layer = None
@@ -2717,9 +2717,6 @@ def write_pov(filename, scene=None, info_callback=None):
faces_normals = [f.normal[:] for f in me_faces]
verts_normals = [v.normal[:] for v in me.vertices]
- # quads incur an extra face
- quadCount = sum(1 for f in faces_verts if len(f) == 4)
-
# Use named declaration to allow reference e.g. for baking. MR
file.write("\n")
tabWrite("#declare %s =\n" % povdataname)
@@ -2775,12 +2772,8 @@ def write_pov(filename, scene=None, info_callback=None):
# Generate unique UV's
uniqueUVs = {}
#n = 0
- for fi, uv in enumerate(uv_layer):
-
- if len(faces_verts[fi]) == 4:
- uvs = uv_layer[fi].uv[0], uv_layer[fi].uv[1], uv_layer[fi].uv[2], uv_layer[fi].uv[3]
- else:
- uvs = uv_layer[fi].uv[0], uv_layer[fi].uv[1], uv_layer[fi].uv[2]
+ for f in me.faces:
+ uvs = [uv_layer[l].uv[:] for l in f.loops]
for uv in uvs:
uniqueUVs[uv[:]] = [-1]
@@ -2811,7 +2804,7 @@ def write_pov(filename, scene=None, info_callback=None):
if me.vertex_colors:
#Write down vertex colors as a texture for each vertex
tabWrite("texture_list {\n")
- tabWrite("%d\n" % (((len(me_faces)-quadCount) * 3 )+ quadCount * 4)) # works only with tris and quad mesh for now
+ tabWrite("%d\n" % (len(me_faces) * 3)) # assumes we have only triangles
VcolIdx=0
if comments:
file.write("\n //Vertex colors: one simple pigment texture per vertex\n")
@@ -2824,12 +2817,7 @@ def write_pov(filename, scene=None, info_callback=None):
material = None
if material: #and material.use_vertex_color_paint: #Always use vertex color when there is some for now
- col = vcol_layer[fi]
-
- if len(faces_verts[fi]) == 4:
- cols = col.color1, col.color2, col.color3, col.color4
- else:
- cols = col.color1, col.color2, col.color3
+ cols = [vcol_layer[l].color[:] for l in f.loops]
for col in cols:
key = col[0], col[1], col[2], material_index # Material index!
@@ -2857,135 +2845,107 @@ def write_pov(filename, scene=None, info_callback=None):
tabWrite("\n}\n")
# Face indices
tabWrite("\nface_indices {\n")
- tabWrite("%d" % (len(me_faces) + quadCount)) # faces count
+ tabWrite("%d" % (len(me_faces))) # faces count
tabStr = tab * tabLevel
for fi, f in enumerate(me_faces):
fv = faces_verts[fi]
material_index = f.material_index
- if len(fv) == 4:
- indices = (0, 1, 2), (0, 2, 3)
- else:
- indices = ((0, 1, 2),)
if vcol_layer:
- col = vcol_layer[fi]
-
- if len(fv) == 4:
- cols = col.color1, col.color2, col.color3, col.color4
- else:
- cols = col.color1, col.color2, col.color3
+ cols = [vcol_layer[l].color[:] for l in f.loops]
if not me_materials or me_materials[material_index] is None: # No materials
- for i1, i2, i3 in indices:
- if linebreaksinlists:
- file.write(",\n")
- # vert count
- file.write(tabStr + "<%d,%d,%d>" % (fv[i1], fv[i2], fv[i3]))
- else:
- file.write(", ")
- file.write("<%d,%d,%d>" % (fv[i1], fv[i2], fv[i3])) # vert count
+ if linebreaksinlists:
+ file.write(",\n")
+ # vert count
+ file.write(tabStr + "<%d,%d,%d>" % (fv[0], fv[1], fv[2]))
+ else:
+ file.write(", ")
+ file.write("<%d,%d,%d>" % (fv[0], fv[1], fv[2])) # vert count
else:
material = me_materials[material_index]
- for i1, i2, i3 in indices:
- if me.vertex_colors: #and material.use_vertex_color_paint:
- # Color per vertex - vertex color
+ if me.vertex_colors: #and material.use_vertex_color_paint:
+ # Color per vertex - vertex color
- col1 = cols[i1]
- col2 = cols[i2]
- col3 = cols[i3]
+ col1 = cols[0]
+ col2 = cols[1]
+ col3 = cols[2]
- ci1 = vertCols[col1[0], col1[1], col1[2], material_index][0]
- ci2 = vertCols[col2[0], col2[1], col2[2], material_index][0]
- ci3 = vertCols[col3[0], col3[1], col3[2], material_index][0]
- else:
- # Color per material - flat material color
- if material.subsurface_scattering.use:
- diffuse_color = [i * j for i, j in zip(material.subsurface_scattering.color[:], material.diffuse_color[:])]
- else:
- diffuse_color = material.diffuse_color[:]
- ci1 = ci2 = ci3 = vertCols[diffuse_color[0], diffuse_color[1], \
- diffuse_color[2], f.material_index][0]
- # ci are zero based index so we'll subtract 1 from them
- if linebreaksinlists:
- file.write(",\n")
- file.write(tabStr + "<%d,%d,%d>, %d,%d,%d" % \
- (fv[i1], fv[i2], fv[i3], ci1-1, ci2-1, ci3-1)) # vert count
+ ci1 = vertCols[col1[0], col1[1], col1[2], material_index][0]
+ ci2 = vertCols[col2[0], col2[1], col2[2], material_index][0]
+ ci3 = vertCols[col3[0], col3[1], col3[2], material_index][0]
+ else:
+ # Color per material - flat material color
+ if material.subsurface_scattering.use:
+ diffuse_color = [i * j for i, j in zip(material.subsurface_scattering.color[:], material.diffuse_color[:])]
else:
- file.write(", ")
- file.write("<%d,%d,%d>, %d,%d,%d" % \
- (fv[i1], fv[i2], fv[i3], ci1-1, ci2-1, ci3-1)) # vert count
+ diffuse_color = material.diffuse_color[:]
+ ci1 = ci2 = ci3 = vertCols[diffuse_color[0], diffuse_color[1], \
+ diffuse_color[2], f.material_index][0]
+ # ci are zero based index so we'll subtract 1 from them
+ if linebreaksinlists:
+ file.write(",\n")
+ file.write(tabStr + "<%d,%d,%d>, %d,%d,%d" % \
+ (fv[0], fv[1], fv[2], ci1-1, ci2-1, ci3-1)) # vert count
+ else:
+ file.write(", ")
+ file.write("<%d,%d,%d>, %d,%d,%d" % \
+ (fv[0], fv[1], fv[2], ci1-1, ci2-1, ci3-1)) # vert count
file.write("\n")
tabWrite("}\n")
# normal_indices indices
tabWrite("normal_indices {\n")
- tabWrite("%d" % (len(me_faces) + quadCount)) # faces count
+ tabWrite("%d" % (len(me_faces))) # faces count
tabStr = tab * tabLevel
for fi, fv in enumerate(faces_verts):
- if len(fv) == 4:
- indices = (0, 1, 2), (0, 2, 3)
+ if me_faces[fi].use_smooth:
+ if linebreaksinlists:
+ file.write(",\n")
+ file.write(tabStr + "<%d,%d,%d>" %\
+ (uniqueNormals[verts_normals[fv[0]]][0],\
+ uniqueNormals[verts_normals[fv[1]]][0],\
+ uniqueNormals[verts_normals[fv[2]]][0])) # vert count
+ else:
+ file.write(", ")
+ file.write("<%d,%d,%d>" %\
+ (uniqueNormals[verts_normals[fv[0]]][0],\
+ uniqueNormals[verts_normals[fv[1]]][0],\
+ uniqueNormals[verts_normals[fv[2]]][0])) # vert count
else:
- indices = ((0, 1, 2),)
-
- for i1, i2, i3 in indices:
- if me_faces[fi].use_smooth:
- if linebreaksinlists:
- file.write(",\n")
- file.write(tabStr + "<%d,%d,%d>" %\
- (uniqueNormals[verts_normals[fv[i1]]][0],\
- uniqueNormals[verts_normals[fv[i2]]][0],\
- uniqueNormals[verts_normals[fv[i3]]][0])) # vert count
- else:
- file.write(", ")
- file.write("<%d,%d,%d>" %\
- (uniqueNormals[verts_normals[fv[i1]]][0],\
- uniqueNormals[verts_normals[fv[i2]]][0],\
- uniqueNormals[verts_normals[fv[i3]]][0])) # vert count
+ idx = uniqueNormals[faces_normals[fi]][0]
+ if linebreaksinlists:
+ file.write(",\n")
+ file.write(tabStr + "<%d,%d,%d>" % (idx, idx, idx)) # vert count
else:
- idx = uniqueNormals[faces_normals[fi]][0]
- if linebreaksinlists:
- file.write(",\n")
- file.write(tabStr + "<%d,%d,%d>" % (idx, idx, idx)) # vert count
- else:
- file.write(", ")
- file.write("<%d,%d,%d>" % (idx, idx, idx)) # vert count
+ file.write(", ")
+ file.write("<%d,%d,%d>" % (idx, idx, idx)) # vert count
file.write("\n")
tabWrite("}\n")
if uv_layer:
tabWrite("uv_indices {\n")
- tabWrite("%d" % (len(me_faces) + quadCount)) # faces count
+ tabWrite("%d" % (len(me_faces))) # faces count
tabStr = tab * tabLevel
- for fi, fv in enumerate(faces_verts):
-
- if len(fv) == 4:
- indices = (0, 1, 2), (0, 2, 3)
- else:
- indices = ((0, 1, 2),)
-
- uv = uv_layer[fi]
- if len(faces_verts[fi]) == 4:
- uvs = uv.uv[0][:], uv.uv[1][:], uv.uv[2][:], uv.uv[3][:]
+ for f in me_faces:
+ uvs = [uv_layer[l].uv[:] for l in f.loops]
+
+ if linebreaksinlists:
+ file.write(",\n")
+ file.write(tabStr + "<%d,%d,%d>" % (
+ uniqueUVs[uvs[0]][0],\
+ uniqueUVs[uvs[1]][0],\
+ uniqueUVs[uvs[2]][0]))
else:
- uvs = uv.uv[0][:], uv.uv[1][:], uv.uv[2][:]
-
- for i1, i2, i3 in indices:
- if linebreaksinlists:
- file.write(",\n")
- file.write(tabStr + "<%d,%d,%d>" % (
- uniqueUVs[uvs[i1]][0],\
- uniqueUVs[uvs[i2]][0],\
- uniqueUVs[uvs[i3]][0]))
- else:
- file.write(", ")
- file.write("<%d,%d,%d>" % (
- uniqueUVs[uvs[i1]][0],\
- uniqueUVs[uvs[i2]][0],\
- uniqueUVs[uvs[i3]][0]))
+ file.write(", ")
+ file.write("<%d,%d,%d>" % (
+ uniqueUVs[uvs[0]][0],\
+ uniqueUVs[uvs[1]][0],\
+ uniqueUVs[uvs[2]][0]))
file.write("\n")
tabWrite("}\n")
@@ -3108,140 +3068,111 @@ def write_pov(filename, scene=None, info_callback=None):
# Face indices
tabWrite("face_indices {\n")
- tabWrite("%d" % (len(me_faces) + quadCount)) # faces count
+ tabWrite("%d" % (len(me_faces))) # faces count
tabStr = tab * tabLevel
for fi, f in enumerate(me_faces):
fv = faces_verts[fi]
material_index = f.material_index
- if len(fv) == 4:
- indices = (0, 1, 2), (0, 2, 3)
- else:
- indices = ((0, 1, 2),)
if vcol_layer:
- col = vcol_layer[fi]
-
- if len(fv) == 4:
- cols = col.color1, col.color2, col.color3, col.color4
- else:
- cols = col.color1, col.color2, col.color3
+ cols = [vcol_layer[l].color[:] for l in f.loops]
if not me_materials or me_materials[material_index] is None: # No materials
- for i1, i2, i3 in indices:
- if linebreaksinlists:
- file.write(",\n")
- # vert count
- file.write(tabStr + "<%d,%d,%d>" % (fv[i1], fv[i2], fv[i3]))
- else:
- file.write(", ")
- file.write("<%d,%d,%d>" % (fv[i1], fv[i2], fv[i3])) # vert count
+ if linebreaksinlists:
+ file.write(",\n")
+ # vert count
+ file.write(tabStr + "<%d,%d,%d>" % (fv[0], fv[1], fv[2]))
+ else:
+ file.write(", ")
+ file.write("<%d,%d,%d>" % (fv[0], fv[1], fv[2])) # vert count
else:
material = me_materials[material_index]
- for i1, i2, i3 in indices:
- ci1 = ci2 = ci3 = f.material_index
- if me.vertex_colors: #and material.use_vertex_color_paint:
- # Color per vertex - vertex color
-
- col1 = cols[i1]
- col2 = cols[i2]
- col3 = cols[i3]
-
- ci1 = vertCols[col1[0], col1[1], col1[2], material_index][0]
- ci2 = vertCols[col2[0], col2[1], col2[2], material_index][0]
- ci3 = vertCols[col3[0], col3[1], col3[2], material_index][0]
- elif material.pov.material_use_nodes:
- ci1 = ci2 = ci3 = 0
+ ci1 = ci2 = ci3 = f.material_index
+ if me.vertex_colors: #and material.use_vertex_color_paint:
+ # Color per vertex - vertex color
+
+ col1 = cols[0]
+ col2 = cols[1]
+ col3 = cols[2]
+
+ ci1 = vertCols[col1[0], col1[1], col1[2], material_index][0]
+ ci2 = vertCols[col2[0], col2[1], col2[2], material_index][0]
+ ci3 = vertCols[col3[0], col3[1], col3[2], material_index][0]
+ elif material.pov.material_use_nodes:
+ ci1 = ci2 = ci3 = 0
+ else:
+ # Color per material - flat material color
+ if material.subsurface_scattering.use:
+ diffuse_color = [i * j for i, j in
+ zip(material.subsurface_scattering.color[:],
+ material.diffuse_color[:])]
else:
- # Color per material - flat material color
- if material.subsurface_scattering.use:
- diffuse_color = [i * j for i, j in
- zip(material.subsurface_scattering.color[:],
- material.diffuse_color[:])]
- else:
- diffuse_color = material.diffuse_color[:]
- ci1 = ci2 = ci3 = vertCols[diffuse_color[0], diffuse_color[1], \
- diffuse_color[2], f.material_index][0]
+ diffuse_color = material.diffuse_color[:]
+ ci1 = ci2 = ci3 = vertCols[diffuse_color[0], diffuse_color[1], \
+ diffuse_color[2], f.material_index][0]
- if linebreaksinlists:
- file.write(",\n")
- file.write(tabStr + "<%d,%d,%d>, %d,%d,%d" % \
- (fv[i1], fv[i2], fv[i3], ci1, ci2, ci3)) # vert count
- else:
- file.write(", ")
- file.write("<%d,%d,%d>, %d,%d,%d" % \
- (fv[i1], fv[i2], fv[i3], ci1, ci2, ci3)) # vert count
+ if linebreaksinlists:
+ file.write(",\n")
+ file.write(tabStr + "<%d,%d,%d>, %d,%d,%d" % \
+ (fv[0], fv[1], fv[2], ci1, ci2, ci3)) # vert count
+ else:
+ file.write(", ")
+ file.write("<%d,%d,%d>, %d,%d,%d" % \
+ (fv[0], fv[1], fv[2], ci1, ci2, ci3)) # vert count
file.write("\n")
tabWrite("}\n")
# normal_indices indices
tabWrite("normal_indices {\n")
- tabWrite("%d" % (len(me_faces) + quadCount)) # faces count
+ tabWrite("%d" % (len(me_faces))) # faces count
tabStr = tab * tabLevel
for fi, fv in enumerate(faces_verts):
-
- if len(fv) == 4:
- indices = (0, 1, 2), (0, 2, 3)
+ if me_faces[fi].use_smooth:
+ if linebreaksinlists:
+ file.write(",\n")
+ file.write(tabStr + "<%d,%d,%d>" %\
+ (uniqueNormals[verts_normals[fv[0]]][0],\
+ uniqueNormals[verts_normals[fv[1]]][0],\
+ uniqueNormals[verts_normals[fv[2]]][0])) # vert count
+ else:
+ file.write(", ")
+ file.write("<%d,%d,%d>" %\
+ (uniqueNormals[verts_normals[fv[0]]][0],\
+ uniqueNormals[verts_normals[fv[1]]][0],\
+ uniqueNormals[verts_normals[fv[2]]][0])) # vert count
else:
- indices = ((0, 1, 2),)
-
- for i1, i2, i3 in indices:
- if me_faces[fi].use_smooth:
- if linebreaksinlists:
- file.write(",\n")
- file.write(tabStr + "<%d,%d,%d>" %\
- (uniqueNormals[verts_normals[fv[i1]]][0],\
- uniqueNormals[verts_normals[fv[i2]]][0],\
- uniqueNormals[verts_normals[fv[i3]]][0])) # vert count
- else:
- file.write(", ")
- file.write("<%d,%d,%d>" %\
- (uniqueNormals[verts_normals[fv[i1]]][0],\
- uniqueNormals[verts_normals[fv[i2]]][0],\
- uniqueNormals[verts_normals[fv[i3]]][0])) # vert count
+ idx = uniqueNormals[faces_normals[fi]][0]
+ if linebreaksinlists:
+ file.write(",\n")
+ file.write(tabStr + "<%d,%d,%d>" % (idx, idx, idx)) # vertcount
else:
- idx = uniqueNormals[faces_normals[fi]][0]
- if linebreaksinlists:
- file.write(",\n")
- file.write(tabStr + "<%d,%d,%d>" % (idx, idx, idx)) # vertcount
- else:
- file.write(", ")
- file.write("<%d,%d,%d>" % (idx, idx, idx)) # vert count
+ file.write(", ")
+ file.write("<%d,%d,%d>" % (idx, idx, idx)) # vert count
file.write("\n")
tabWrite("}\n")
if uv_layer:
tabWrite("uv_indices {\n")
- tabWrite("%d" % (len(me_faces) + quadCount)) # faces count
+ tabWrite("%d" % (len(me_faces))) # faces count
tabStr = tab * tabLevel
- for fi, fv in enumerate(faces_verts):
-
- if len(fv) == 4:
- indices = (0, 1, 2), (0, 2, 3)
- else:
- indices = ((0, 1, 2),)
-
- uv = uv_layer[fi]
- if len(faces_verts[fi]) == 4:
- uvs = uv.uv[0][:], uv.uv[1][:], uv.uv[2][:], uv.uv[3][:]
+ for f in me_faces:
+ uvs = [uv_layer[l].uv[:] for l in f.loops]
+
+ if linebreaksinlists:
+ file.write(",\n")
+ file.write(tabStr + "<%d,%d,%d>" % (
+ uniqueUVs[uvs[0]][0],\
+ uniqueUVs[uvs[1]][0],\
+ uniqueUVs[uvs[2]][0]))
else:
- uvs = uv.uv[0][:], uv.uv[1][:], uv.uv[2][:]
-
- for i1, i2, i3 in indices:
- if linebreaksinlists:
- file.write(",\n")
- file.write(tabStr + "<%d,%d,%d>" % (
- uniqueUVs[uvs[i1]][0],\
- uniqueUVs[uvs[i2]][0],\
- uniqueUVs[uvs[i3]][0]))
- else:
- file.write(", ")
- file.write("<%d,%d,%d>" % (
- uniqueUVs[uvs[i1]][0],\
- uniqueUVs[uvs[i2]][0],\
- uniqueUVs[uvs[i3]][0]))
+ file.write(", ")
+ file.write("<%d,%d,%d>" % (
+ uniqueUVs[uvs[0]][0],\
+ uniqueUVs[uvs[1]][0],\
+ uniqueUVs[uvs[2]][0]))
file.write("\n")
tabWrite("}\n")
@@ -3285,7 +3216,7 @@ def write_pov(filename, scene=None, info_callback=None):
duplidata_ref = []
for ob in sel:
#matrix = global_matrix * ob.matrix_world
- if ob.is_duplicator:
+ if ob.is_instancer:
tabWrite("\n//--DupliObjects in %s--\n\n"% ob.name)
ob.dupli_list_create(scene)
dup = ""
@@ -3297,8 +3228,8 @@ def write_pov(filename, scene=None, info_callback=None):
for eachduplicate in ob.dupli_list:
duplidataname = "OB"+string_strip_hyphen(bpy.path.clean_name(bpy.data.objects[eachduplicate.object.name].data.name))
dup += ("\tobject {\n\t\tDATA%s\n\t\t%s\t}\n" %(string_strip_hyphen(bpy.path.clean_name(bpy.data.objects[eachduplicate.object.name].data.name)), MatrixAsPovString(ob.matrix_world.inverted() * eachduplicate.matrix)))
- #add object to a list so that it is not rendered for some dupli_types
- if ob.dupli_type not in {'GROUP'} and duplidataname not in duplidata_ref:
+ #add object to a list so that it is not rendered for some instance_types
+ if ob.instance_type not in {'COLLECTION'} and duplidataname not in duplidata_ref:
duplidata_ref.append(duplidataname) #older key [string_strip_hyphen(bpy.path.clean_name("OB"+ob.name))]
dup += "}\n"
ob.dupli_list_clear()
@@ -3666,11 +3597,11 @@ def write_pov(filename, scene=None, info_callback=None):
csg = True
sel = renderable_objects(scene)
- exportLamps([L for L in sel if (L.type == 'LAMP' and L.pov.object_as != 'RAINBOW')])
+ exportLamps([L for L in sel if (L.type == 'LIGHT' and L.pov.object_as != 'RAINBOW')])
if comments:
file.write("\n//--Rainbows--\n\n")
- exportRainbows([L for L in sel if (L.type == 'LAMP' and L.pov.object_as == 'RAINBOW')])
+ exportRainbows([L for L in sel if (L.type == 'LIGHT' and L.pov.object_as == 'RAINBOW')])
if comments:
diff --git a/render_povray/ui.py b/render_povray/ui.py
index 34123794..1d5a621f 100644
--- a/render_povray/ui.py
+++ b/render_povray/ui.py
@@ -214,15 +214,12 @@ del properties_data_modifier
from bl_ui import properties_material
for member in dir(properties_material):
subclass = getattr(properties_material, member)
- if subclass not in (properties_material.MATERIAL_PT_transp_game,
- properties_material.MATERIAL_PT_game_settings,
- properties_material.MATERIAL_PT_physics):
- try:
- #mat=context.material
- #if mat and mat.type == "SURFACE" and (engine in cls.COMPAT_ENGINES) and not (mat.pov.material_use_nodes or mat.use_nodes):
- subclass.COMPAT_ENGINES.add('POVRAY_RENDER')
- except:
- pass
+ try:
+ #mat=context.material
+ #if mat and mat.type == "SURFACE" and (engine in cls.COMPAT_ENGINES) and not (mat.pov.material_use_nodes or mat.use_nodes):
+ subclass.COMPAT_ENGINES.add('POVRAY_RENDER')
+ except:
+ pass
del properties_material
@@ -294,7 +291,7 @@ class RenderButtonsPanel():
@classmethod
def poll(cls, context):
rd = context.scene.render
- return (rd.use_game_engine is False) and (rd.engine in cls.COMPAT_ENGINES)
+ return (rd.engine in cls.COMPAT_ENGINES)
class ModifierButtonsPanel():
bl_space_type = 'PROPERTIES'
@@ -306,7 +303,7 @@ class ModifierButtonsPanel():
def poll(cls, context):
mods = context.object.modifiers
rd = context.scene.render
- return mods and (rd.use_game_engine is False) and (rd.engine in cls.COMPAT_ENGINES)
+ return mods and (rd.engine in cls.COMPAT_ENGINES)
class MaterialButtonsPanel():
bl_space_type = 'PROPERTIES'
@@ -318,7 +315,7 @@ class MaterialButtonsPanel():
def poll(cls, context):
mat = context.material
rd = context.scene.render
- return mat and (rd.use_game_engine is False) and (rd.engine in cls.COMPAT_ENGINES)
+ return mat (rd.engine in cls.COMPAT_ENGINES)
class TextureButtonsPanel():
@@ -331,7 +328,7 @@ class TextureButtonsPanel():
def poll(cls, context):
tex = context.texture
rd = context.scene.render
- return tex and (rd.use_game_engine is False) and (rd.engine in cls.COMPAT_ENGINES)
+ return tex and (rd.engine in cls.COMPAT_ENGINES)
# class TextureTypePanel(TextureButtonsPanel):
@@ -352,7 +349,7 @@ class ObjectButtonsPanel():
def poll(cls, context):
obj = context.object
rd = context.scene.render
- return obj and (rd.use_game_engine is False) and (rd.engine in cls.COMPAT_ENGINES)
+ return obj and (rd.engine in cls.COMPAT_ENGINES)
class CameraDataButtonsPanel():
bl_space_type = 'PROPERTIES'
@@ -364,7 +361,7 @@ class CameraDataButtonsPanel():
def poll(cls, context):
cam = context.camera
rd = context.scene.render
- return cam and (rd.use_game_engine is False) and (rd.engine in cls.COMPAT_ENGINES)
+ return cam and (rd.engine in cls.COMPAT_ENGINES)
class WorldButtonsPanel():
bl_space_type = 'PROPERTIES'
@@ -376,7 +373,7 @@ class WorldButtonsPanel():
def poll(cls, context):
wld = context.world
rd = context.scene.render
- return wld and (rd.use_game_engine is False) and (rd.engine in cls.COMPAT_ENGINES)
+ return wld and (rd.engine in cls.COMPAT_ENGINES)
class TextButtonsPanel():
bl_space_type = 'TEXT_EDITOR'
@@ -388,7 +385,7 @@ class TextButtonsPanel():
def poll(cls, context):
text = context.space_data
rd = context.scene.render
- return text and (rd.use_game_engine is False) and (rd.engine in cls.COMPAT_ENGINES)
+ return text and (rd.engine in cls.COMPAT_ENGINES)
from bl_ui import properties_data_mesh
# These panels are kept
@@ -464,25 +461,25 @@ del properties_data_mesh
################################################################################
-# from bl_ui import properties_data_lamp
-# for member in dir(properties_data_lamp):
- # subclass = getattr(properties_data_lamp, member)
+# from bl_ui import properties_data_light
+# for member in dir(properties_data_light):
+ # subclass = getattr(properties_data_light, member)
# try:
# subclass.COMPAT_ENGINES.add('POVRAY_RENDER')
# except:
# pass
-# del properties_data_lamp
-#########################LAMPS################################
+# del properties_data_light
+#########################LIGHTS################################
-from bl_ui import properties_data_lamp
+from bl_ui import properties_data_light
# These panels are kept
-properties_data_lamp.DATA_PT_custom_props_lamp.COMPAT_ENGINES.add('POVRAY_RENDER')
-properties_data_lamp.DATA_PT_context_lamp.COMPAT_ENGINES.add('POVRAY_RENDER')
+properties_data_light.DATA_PT_custom_props_light.COMPAT_ENGINES.add('POVRAY_RENDER')
+properties_data_light.DATA_PT_context_light.COMPAT_ENGINES.add('POVRAY_RENDER')
## make some native panels contextual to some object variable
## by recreating custom panels inheriting their properties
-class PovLampButtonsPanel(properties_data_lamp.DataButtonsPanel):
+class PovLampButtonsPanel(properties_data_light.DataButtonsPanel):
COMPAT_ENGINES = {'POVRAY_RENDER'}
POV_OBJECT_TYPES = {'RAINBOW'}
@@ -499,17 +496,17 @@ class PovLampButtonsPanel(properties_data_lamp.DataButtonsPanel):
# Complex py/bpy/rna interactions (with metaclass and all) simply do not allow it to work.
# So we simply have to explicitly copy here the interesting bits. ;)
-class LAMP_PT_POV_preview(PovLampButtonsPanel, bpy.types.Panel):
- bl_label = properties_data_lamp.DATA_PT_preview.bl_label
+class LIGHT_PT_POV_preview(PovLampButtonsPanel, bpy.types.Panel):
+ bl_label = properties_data_light.DATA_PT_preview.bl_label
- draw = properties_data_lamp.DATA_PT_preview.draw
+ draw = properties_data_light.DATA_PT_preview.draw
-class LAMP_PT_POV_lamp(PovLampButtonsPanel, bpy.types.Panel):
- bl_label = properties_data_lamp.DATA_PT_lamp.bl_label
+class LIGHT_PT_POV_light(PovLampButtonsPanel, bpy.types.Panel):
+ bl_label = properties_data_light.DATA_PT_light.bl_label
- draw = properties_data_lamp.DATA_PT_lamp.draw
+ draw = properties_data_light.DATA_PT_light.draw
-class POV_LAMP_MT_presets(bpy.types.Menu):
+class POV_LIGHT_MT_presets(bpy.types.Menu):
bl_label = "Lamp Presets"
preset_subdir = "pov/lamp"
preset_operator = "script.execute_preset"
@@ -518,19 +515,19 @@ class POV_LAMP_MT_presets(bpy.types.Menu):
class AddPresetLamp(AddPresetBase, bpy.types.Operator):
'''Add a Lamp Preset'''
- bl_idname = "object.lamp_preset_add"
+ bl_idname = "object.light_preset_add"
bl_label = "Add Lamp Preset"
- preset_menu = "POV_LAMP_MT_presets"
+ preset_menu = "POV_LIGHT_MT_presets"
# variable used for all preset values
preset_defines = [
- "lampdata = bpy.context.object.data"
+ "lightdata = bpy.context.object.data"
]
# properties to store in the preset
preset_values = [
- "lampdata.type",
- "lampdata.color",
+ "lightdata.type",
+ "lightdata.color",
]
# where to store the preset
@@ -541,68 +538,68 @@ class AddPresetLamp(AddPresetBase, bpy.types.Operator):
# Draw into an existing panel
-def lamp_panel_func(self, context):
+def light_panel_func(self, context):
layout = self.layout
row = layout.row(align=True)
- row.menu(POV_LAMP_MT_presets.__name__, text=POV_LAMP_MT_presets.bl_label)
+ row.menu(POV_LIGHT_MT_presets.__name__, text=POV_LIGHT_MT_presets.bl_label)
row.operator(AddPresetLamp.bl_idname, text="", icon='ZOOMIN')
row.operator(AddPresetLamp.bl_idname, text="", icon='ZOOMOUT').remove_active = True
classes = (
- POV_LAMP_MT_presets,
+ POV_LIGHT_MT_presets,
AddPresetLamp,
)
-class LAMP_PT_POV_sunsky(PovLampButtonsPanel, bpy.types.Panel):
- bl_label = properties_data_lamp.DATA_PT_sunsky.bl_label
+class LIGHT_PT_POV_sunsky(PovLampButtonsPanel, bpy.types.Panel):
+ bl_label = properties_data_light.DATA_PT_sunsky.bl_label
@classmethod
def poll(cls, context):
- lamp = context.lamp
+ lamp = context.light
engine = context.scene.render.engine
return (lamp and lamp.type == 'SUN') and (engine in cls.COMPAT_ENGINES)
- draw = properties_data_lamp.DATA_PT_sunsky.draw
+ draw = properties_data_light.DATA_PT_sunsky.draw
-class LAMP_PT_POV_shadow(PovLampButtonsPanel, bpy.types.Panel):
- bl_label = properties_data_lamp.DATA_PT_shadow.bl_label
+class LIGHT_PT_POV_shadow(PovLampButtonsPanel, bpy.types.Panel):
+ bl_label = properties_data_light.DATA_PT_shadow.bl_label
- draw = properties_data_lamp.DATA_PT_shadow.draw
+ draw = properties_data_light.DATA_PT_shadow.draw
-class LAMP_PT_POV_area(PovLampButtonsPanel, bpy.types.Panel):
- bl_label = properties_data_lamp.DATA_PT_area.bl_label
+class LIGHT_PT_POV_area(PovLampButtonsPanel, bpy.types.Panel):
+ bl_label = properties_data_light.DATA_PT_area.bl_label
@classmethod
def poll(cls, context):
- lamp = context.lamp
+ lamp = context.light
engine = context.scene.render.engine
return (lamp and lamp.type == 'AREA') and (engine in cls.COMPAT_ENGINES)
- draw = properties_data_lamp.DATA_PT_area.draw
+ draw = properties_data_light.DATA_PT_area.draw
-class LAMP_PT_POV_spot(PovLampButtonsPanel, bpy.types.Panel):
- bl_label = properties_data_lamp.DATA_PT_spot.bl_label
+class LIGHT_PT_POV_spot(PovLampButtonsPanel, bpy.types.Panel):
+ bl_label = properties_data_light.DATA_PT_spot.bl_label
@classmethod
def poll(cls, context):
- lamp = context.lamp
+ lamp = context.light
engine = context.scene.render.engine
return (lamp and lamp.type == 'SPOT') and (engine in cls.COMPAT_ENGINES)
- draw = properties_data_lamp.DATA_PT_spot.draw
+ draw = properties_data_light.DATA_PT_spot.draw
-class LAMP_PT_POV_falloff_curve(PovLampButtonsPanel, bpy.types.Panel):
- bl_label = properties_data_lamp.DATA_PT_falloff_curve.bl_label
- bl_options = properties_data_lamp.DATA_PT_falloff_curve.bl_options
+class LIGHT_PT_POV_falloff_curve(PovLampButtonsPanel, bpy.types.Panel):
+ bl_label = properties_data_light.DATA_PT_falloff_curve.bl_label
+ bl_options = properties_data_light.DATA_PT_falloff_curve.bl_options
@classmethod
def poll(cls, context):
- lamp = context.lamp
+ lamp = context.light
engine = context.scene.render.engine
return (lamp and lamp.type in {'POINT', 'SPOT'} and lamp.falloff_type == 'CUSTOM_CURVE') and (engine in cls.COMPAT_ENGINES)
- draw = properties_data_lamp.DATA_PT_falloff_curve.draw
+ draw = properties_data_light.DATA_PT_falloff_curve.draw
class OBJECT_PT_povray_obj_rainbow(PovLampButtonsPanel, bpy.types.Panel):
bl_label = "POV-Ray Rainbow"
@@ -644,7 +641,7 @@ class OBJECT_PT_povray_obj_rainbow(PovLampButtonsPanel, bpy.types.Panel):
col.prop(obj.pov, "arc_angle")
col.prop(obj.pov, "falloff_angle")
-del properties_data_lamp
+del properties_data_light
###############################################################################
class RENDER_PT_povray_export_settings(RenderButtonsPanel, bpy.types.Panel):
diff --git a/rigify/__init__.py b/rigify/__init__.py
index fa93533b..302fc453 100644
--- a/rigify/__init__.py
+++ b/rigify/__init__.py
@@ -22,7 +22,7 @@ bl_info = {
"name": "Rigify",
"version": (0, 5),
"author": "Nathan Vegdahl, Lucio Rossi, Ivan Cappiello",
- "blender": (2, 78, 0),
+ "blender": (2, 80, 0),
"description": "Automatic rigging from building-block components",
"location": "Armature properties, Bone properties, View3d tools panel, Armature Add menu",
"wiki_url": "http://wiki.blender.org/index.php/Extensions:2.5/Py/"
@@ -44,7 +44,15 @@ import bpy
import sys
import os
from bpy.types import AddonPreferences
-from bpy.props import BoolProperty
+from bpy.props import (
+ BoolProperty,
+ IntProperty,
+ EnumProperty,
+ StringProperty,
+ FloatVectorProperty,
+ PointerProperty,
+ CollectionProperty,
+)
class RigifyPreferences(AddonPreferences):
@@ -118,14 +126,13 @@ class RigifyPreferences(AddonPreferences):
register()
- legacy_mode = BoolProperty(
+ legacy_mode: BoolProperty(
name='Rigify Legacy Mode',
description='Select if you want to use Rigify in legacy mode',
default=False,
update=update_legacy
)
-
- show_expanded = BoolProperty()
+ show_expanded: BoolProperty()
def draw(self, context):
layout = self.layout
@@ -143,71 +150,80 @@ class RigifyPreferences(AddonPreferences):
op = sub.operator('wm.context_toggle', text='', icon=icon,
emboss=False)
op.data_path = 'addon_prefs.show_expanded'
- sub.label('{}: {}'.format('Rigify', 'Enable Legacy Mode'))
+ sub.label(text='{}: {}'.format('Rigify', 'Enable Legacy Mode'))
sub = row.row()
sub.alignment = 'RIGHT'
sub.prop(self, 'legacy_mode')
if expand:
- split = col.row().split(percentage=0.15)
- split.label('Description:')
+ split = col.row().split(factor=0.15)
+ split.label(text='Description:')
split.label(text='When enabled the add-on will run in legacy mode using the old 2.76b feature set.')
row = layout.row()
- row.label("End of Rigify Preferences")
+ row.label(text="End of Rigify Preferences")
class RigifyName(bpy.types.PropertyGroup):
- name = bpy.props.StringProperty()
+ name: StringProperty()
class RigifyColorSet(bpy.types.PropertyGroup):
- name = bpy.props.StringProperty(name="Color Set", default=" ")
- active = bpy.props.FloatVectorProperty(
- name="object_color",
- subtype='COLOR',
- default=(1.0, 1.0, 1.0),
- min=0.0, max=1.0,
- description="color picker"
- )
- normal = bpy.props.FloatVectorProperty(
- name="object_color",
- subtype='COLOR',
- default=(1.0, 1.0, 1.0),
- min=0.0, max=1.0,
- description="color picker"
- )
- select = bpy.props.FloatVectorProperty(
- name="object_color",
- subtype='COLOR',
- default=(1.0, 1.0, 1.0),
- min=0.0, max=1.0,
- description="color picker"
- )
- standard_colors_lock = bpy.props.BoolProperty(default=True)
+ name: StringProperty(name="Color Set", default=" ")
+ active: FloatVectorProperty(
+ name="object_color",
+ subtype='COLOR',
+ default=(1.0, 1.0, 1.0),
+ min=0.0, max=1.0,
+ description="color picker"
+ )
+ normal: FloatVectorProperty(
+ name="object_color",
+ subtype='COLOR',
+ default=(1.0, 1.0, 1.0),
+ min=0.0, max=1.0,
+ description="color picker"
+ )
+ select: FloatVectorProperty(
+ name="object_color",
+ subtype='COLOR',
+ default=(1.0, 1.0, 1.0),
+ min=0.0, max=1.0,
+ description="color picker"
+ )
+ standard_colors_lock: BoolProperty(default=True)
class RigifySelectionColors(bpy.types.PropertyGroup):
- select = bpy.props.FloatVectorProperty(
- name="object_color",
- subtype='COLOR',
- default=(0.314, 0.784, 1.0),
- min=0.0, max=1.0,
- description="color picker"
- )
+ select: FloatVectorProperty(
+ name="object_color",
+ subtype='COLOR',
+ default=(0.314, 0.784, 1.0),
+ min=0.0, max=1.0,
+ description="color picker"
+ )
- active = bpy.props.FloatVectorProperty(
- name="object_color",
- subtype='COLOR',
- default=(0.549, 1.0, 1.0),
- min=0.0, max=1.0,
- description="color picker"
- )
+ active: FloatVectorProperty(
+ name="object_color",
+ subtype='COLOR',
+ default=(0.549, 1.0, 1.0),
+ min=0.0, max=1.0,
+ description="color picker"
+ )
class RigifyParameters(bpy.types.PropertyGroup):
- name = bpy.props.StringProperty()
+ name: StringProperty()
+
+
+# Remember the initial property set
+RIGIFY_PARAMETERS_BASE_DIR = set(dir(RigifyParameters))
+
+def clear_rigify_parameters():
+ for name in list(dir(RigifyParameters)):
+ if name not in RIGIFY_PARAMETERS_BASE_DIR:
+ delattr(RigifyParameters, name)
class RigifyArmatureLayer(bpy.types.PropertyGroup):
@@ -225,104 +241,123 @@ class RigifyArmatureLayer(bpy.types.PropertyGroup):
else:
self['group_prop'] = value
- name = bpy.props.StringProperty(name="Layer Name", default=" ")
- row = bpy.props.IntProperty(name="Layer Row", default=1, min=1, max=32, description='UI row for this layer')
- set = bpy.props.BoolProperty(name="Selection Set", default=False, description='Add Selection Set for this layer')
- group = bpy.props.IntProperty(name="Bone Group", default=0, min=0, max=32,
- get=get_group, set=set_group, description='Assign Bone Group to this layer')
+ name: StringProperty(name="Layer Name", default=" ")
+ row: IntProperty(name="Layer Row", default=1, min=1, max=32, description='UI row for this layer')
+ selset: BoolProperty(name="Selection Set", default=False, description='Add Selection Set for this layer')
+ group: IntProperty(name="Bone Group", default=0, min=0, max=32,
+ get=get_group, set=set_group, description='Assign Bone Group to this layer')
+
##### REGISTER #####
+classes = (
+ RigifyName,
+ RigifyParameters,
+ RigifyColorSet,
+ RigifySelectionColors,
+ RigifyArmatureLayer,
+ RigifyPreferences,
+)
+
+
def register():
+ from bpy.utils import register_class
+
+ # Sub-modules.
ui.register()
metarig_menu.register()
- bpy.utils.register_class(RigifyName)
- bpy.utils.register_class(RigifyParameters)
-
- bpy.utils.register_class(RigifyColorSet)
- bpy.utils.register_class(RigifySelectionColors)
- bpy.utils.register_class(RigifyArmatureLayer)
- bpy.utils.register_class(RigifyPreferences)
- bpy.types.Armature.rigify_layers = bpy.props.CollectionProperty(type=RigifyArmatureLayer)
-
- bpy.types.PoseBone.rigify_type = bpy.props.StringProperty(name="Rigify Type", description="Rig type for this bone")
- bpy.types.PoseBone.rigify_parameters = bpy.props.PointerProperty(type=RigifyParameters)
-
- bpy.types.Armature.rigify_colors = bpy.props.CollectionProperty(type=RigifyColorSet)
-
- bpy.types.Armature.rigify_selection_colors = bpy.props.PointerProperty(type=RigifySelectionColors)
-
- bpy.types.Armature.rigify_colors_index = bpy.props.IntProperty(default=-1)
- bpy.types.Armature.rigify_colors_lock = bpy.props.BoolProperty(default=True)
- bpy.types.Armature.rigify_theme_to_add = bpy.props.EnumProperty(items=(('THEME01', 'THEME01', ''),
- ('THEME02', 'THEME02', ''),
- ('THEME03', 'THEME03', ''),
- ('THEME04', 'THEME04', ''),
- ('THEME05', 'THEME05', ''),
- ('THEME06', 'THEME06', ''),
- ('THEME07', 'THEME07', ''),
- ('THEME08', 'THEME08', ''),
- ('THEME09', 'THEME09', ''),
- ('THEME10', 'THEME10', ''),
- ('THEME11', 'THEME11', ''),
- ('THEME12', 'THEME12', ''),
- ('THEME13', 'THEME13', ''),
- ('THEME14', 'THEME14', ''),
- ('THEME15', 'THEME15', ''),
- ('THEME16', 'THEME16', ''),
- ('THEME17', 'THEME17', ''),
- ('THEME18', 'THEME18', ''),
- ('THEME19', 'THEME19', ''),
- ('THEME20', 'THEME20', '')
- ), name='Theme')
+ # Classes.
+ for cls in classes:
+ register_class(cls)
+
+ # Properties.
+ bpy.types.Armature.rigify_layers = CollectionProperty(type=RigifyArmatureLayer)
+
+ bpy.types.PoseBone.rigify_type = StringProperty(name="Rigify Type", description="Rig type for this bone")
+ bpy.types.PoseBone.rigify_parameters = PointerProperty(type=RigifyParameters)
+
+ bpy.types.Armature.rigify_colors = CollectionProperty(type=RigifyColorSet)
+
+ bpy.types.Armature.rigify_selection_colors = PointerProperty(type=RigifySelectionColors)
+
+ bpy.types.Armature.rigify_colors_index = IntProperty(default=-1)
+ bpy.types.Armature.rigify_colors_lock = BoolProperty(default=True)
+ bpy.types.Armature.rigify_theme_to_add = EnumProperty(items=(
+ ('THEME01', 'THEME01', ''),
+ ('THEME02', 'THEME02', ''),
+ ('THEME03', 'THEME03', ''),
+ ('THEME04', 'THEME04', ''),
+ ('THEME05', 'THEME05', ''),
+ ('THEME06', 'THEME06', ''),
+ ('THEME07', 'THEME07', ''),
+ ('THEME08', 'THEME08', ''),
+ ('THEME09', 'THEME09', ''),
+ ('THEME10', 'THEME10', ''),
+ ('THEME11', 'THEME11', ''),
+ ('THEME12', 'THEME12', ''),
+ ('THEME13', 'THEME13', ''),
+ ('THEME14', 'THEME14', ''),
+ ('THEME15', 'THEME15', ''),
+ ('THEME16', 'THEME16', ''),
+ ('THEME17', 'THEME17', ''),
+ ('THEME18', 'THEME18', ''),
+ ('THEME19', 'THEME19', ''),
+ ('THEME20', 'THEME20', '')
+ ), name='Theme')
IDStore = bpy.types.WindowManager
- IDStore.rigify_collection = bpy.props.EnumProperty(items=rig_lists.col_enum_list, default="All",
- name="Rigify Active Collection",
- description="The selected rig collection")
+ IDStore.rigify_collection = EnumProperty(items=rig_lists.col_enum_list, default="All",
+ name="Rigify Active Collection",
+ description="The selected rig collection")
- IDStore.rigify_types = bpy.props.CollectionProperty(type=RigifyName)
- IDStore.rigify_active_type = bpy.props.IntProperty(name="Rigify Active Type", description="The selected rig type")
+ IDStore.rigify_types = CollectionProperty(type=RigifyName)
+ IDStore.rigify_active_type = IntProperty(name="Rigify Active Type", description="The selected rig type")
- IDStore.rigify_advanced_generation = bpy.props.BoolProperty(name="Advanced Options",
- description="Enables/disables advanced options for Rigify rig generation",
- default=False)
+ IDStore.rigify_advanced_generation = BoolProperty(name="Advanced Options",
+ description="Enables/disables advanced options for Rigify rig generation",
+ default=False)
def update_mode(self, context):
if self.rigify_generate_mode == 'new':
self.rigify_force_widget_update = False
- IDStore.rigify_generate_mode = bpy.props.EnumProperty(name="Rigify Generate Rig Mode",
- description="'Generate Rig' mode. In 'overwrite' mode the features of the target rig will be updated as defined by the metarig. In 'new' mode a new rig will be created as defined by the metarig. Current mode",
- update=update_mode,
- items=(('overwrite', 'overwrite', ''),
- ('new', 'new', '')))
-
- IDStore.rigify_force_widget_update = bpy.props.BoolProperty(name="Force Widget Update",
- description="Forces Rigify to delete and rebuild all the rig widgets. if unset, only missing widgets will be created",
- default=False)
-
- IDStore.rigify_target_rigs = bpy.props.CollectionProperty(type=RigifyName)
- IDStore.rigify_target_rig = bpy.props.StringProperty(name="Rigify Target Rig",
- description="Defines which rig to overwrite. If unset, a new one called 'rig' will be created",
- default="")
-
- IDStore.rigify_rig_uis = bpy.props.CollectionProperty(type=RigifyName)
- IDStore.rigify_rig_ui = bpy.props.StringProperty(name="Rigify Target Rig UI",
- description="Defines the UI to overwrite. It should always be the same as the target rig. If unset, 'rig_ui.py' will be used",
- default="")
-
- IDStore.rigify_rig_basename = bpy.props.StringProperty(name="Rigify Rig Name",
- description="Defines the name of the Rig. If unset, in 'new' mode 'rig' will be used, in 'overwrite' mode the target rig name will be used",
- default="")
-
- IDStore.rigify_transfer_only_selected = bpy.props.BoolProperty(name="Transfer Only Selected", description="Transfer selected bones only", default=True)
- IDStore.rigify_transfer_start_frame = bpy.props.IntProperty(name="Start Frame", description="First Frame to Transfer", default=0, min= 0)
- IDStore.rigify_transfer_end_frame = bpy.props.IntProperty(name="End Frame", description="Last Frame to Transfer", default=0, min= 0)
-
+ IDStore.rigify_generate_mode = EnumProperty(name="Rigify Generate Rig Mode",
+ description="'Generate Rig' mode. In 'overwrite' mode the features of the target rig will be updated as defined by the metarig. In 'new' mode a new rig will be created as defined by the metarig. Current mode",
+ update=update_mode,
+ items=( ('overwrite', 'overwrite', ''),
+ ('new', 'new', '')))
+
+ IDStore.rigify_force_widget_update = BoolProperty(name="Force Widget Update",
+ description="Forces Rigify to delete and rebuild all the rig widgets. if unset, only missing widgets will be created",
+ default=False)
+
+ IDStore.rigify_target_rigs = CollectionProperty(type=RigifyName)
+ IDStore.rigify_target_rig = StringProperty(name="Rigify Target Rig",
+ description="Defines which rig to overwrite. If unset, a new one called 'rig' will be created",
+ default="")
+
+ IDStore.rigify_rig_uis = CollectionProperty(type=RigifyName)
+ IDStore.rigify_rig_ui = StringProperty(name="Rigify Target Rig UI",
+ description="Defines the UI to overwrite. It should always be the same as the target rig. If unset, 'rig_ui.py' will be used",
+ default="")
+
+ IDStore.rigify_rig_basename = StringProperty(name="Rigify Rig Name",
+ description="Defines the name of the Rig. If unset, in 'new' mode 'rig' will be used, in 'overwrite' mode the target rig name will be used",
+ default="")
+
+ IDStore.rigify_transfer_only_selected = BoolProperty(
+ name="Transfer Only Selected",
+ description="Transfer selected bones only", default=True)
+ IDStore.rigify_transfer_start_frame = IntProperty(
+ name="Start Frame",
+ description="First Frame to Transfer", default=0, min= 0)
+ IDStore.rigify_transfer_end_frame = IntProperty(
+ name="End Frame",
+ description="Last Frame to Transfer", default=0, min= 0)
+
+ # Update legacy on restart or reload.
if (ui and 'legacy' in str(ui)) or bpy.context.user_preferences.addons['rigify'].preferences.legacy_mode:
- # update legacy on restart or reload
bpy.context.user_preferences.addons['rigify'].preferences.legacy_mode = True
# Add rig parameters
@@ -335,6 +370,9 @@ def register():
def unregister():
+ from bpy.utils import unregister_class
+
+ # Properties.
del bpy.types.PoseBone.rigify_type
del bpy.types.PoseBone.rigify_parameters
@@ -354,14 +392,12 @@ def unregister():
del IDStore.rigify_transfer_start_frame
del IDStore.rigify_transfer_end_frame
- bpy.utils.unregister_class(RigifyName)
- bpy.utils.unregister_class(RigifyParameters)
-
- bpy.utils.unregister_class(RigifyColorSet)
- bpy.utils.unregister_class(RigifySelectionColors)
+ # Classes.
+ for cls in classes:
+ unregister_class(cls)
- bpy.utils.unregister_class(RigifyArmatureLayer)
- bpy.utils.unregister_class(RigifyPreferences)
+ clear_rigify_parameters()
+ # Sub-modules.
metarig_menu.unregister()
ui.unregister()
diff --git a/rigify/generate.py b/rigify/generate.py
index e2492708..c804cb69 100644
--- a/rigify/generate.py
+++ b/rigify/generate.py
@@ -29,6 +29,7 @@ from .utils import MetarigError, new_bone, get_rig_type
from .utils import ORG_PREFIX, MCH_PREFIX, DEF_PREFIX, WGT_PREFIX, ROOT_NAME, make_original_name
from .utils import RIG_DIR
from .utils import create_root_widget
+from .utils import ensure_widget_collection
from .utils import random_id
from .utils import copy_attributes
from .utils import gamma_correct
@@ -40,7 +41,6 @@ ORG_LAYER = [n == 31 for n in range(0, 32)] # Armature layer that original bone
MCH_LAYER = [n == 30 for n in range(0, 32)] # Armature layer that mechanism bones should be moved to.
DEF_LAYER = [n == 29 for n in range(0, 32)] # Armature layer that deformation bones should be moved to.
ROOT_LAYER = [n == 28 for n in range(0, 32)] # Armature layer that root bone should be moved to.
-WGT_LAYERS = [x == 19 for x in range(0, 20)] # Widgets go on the last scene layer.
class Timer:
@@ -72,7 +72,11 @@ def generate_rig(context, metarig):
bpy.ops.object.mode_set(mode='OBJECT')
scene = context.scene
+ view_layer = context.view_layer
+ collection = context.collection
+ layer_collection = context.layer_collection
id_store = context.window_manager
+
#------------------------------------------
# Create/find the rig object and set it up
@@ -96,13 +100,13 @@ def generate_rig(context, metarig):
rig_old_name = name
name = rig_new_name or name
obj = bpy.data.objects.new(name, bpy.data.armatures.new(name))
- obj.draw_type = 'WIRE'
- scene.objects.link(obj)
+ obj.display_type = 'WIRE'
+ collection.objects.link(obj)
else:
name = rig_new_name or "rig"
obj = bpy.data.objects.new(name, bpy.data.armatures.new(name)) # in case name 'rig' exists it will be rig.001
- obj.draw_type = 'WIRE'
- scene.objects.link(obj)
+ obj.display_type = 'WIRE'
+ collection.objects.link(obj)
id_store.rigify_target_rig = obj.name
obj.data.pose_position = 'POSE'
@@ -112,23 +116,17 @@ def generate_rig(context, metarig):
obj.animation_data_clear()
# Select generated rig object
- metarig.select = False
- obj.select = True
- scene.objects.active = obj
+ metarig.select_set(False)
+ obj.select_set(True)
+ view_layer.objects.active = obj
# Remove wgts if force update is set
wgts_group_name = "WGTS_" + (rig_old_name or obj.name)
if wgts_group_name in scene.objects and id_store.rigify_force_widget_update:
bpy.ops.object.select_all(action='DESELECT')
- for i, lyr in enumerate(WGT_LAYERS):
- if lyr:
- context.scene.layers[i] = True
for wgt in bpy.data.objects[wgts_group_name].children:
- wgt.select = True
+ wgt.select_set(True)
bpy.ops.object.delete(use_global=False)
- for i, lyr in enumerate(WGT_LAYERS):
- if lyr:
- context.scene.layers[i] = False
if rig_old_name:
bpy.data.objects[wgts_group_name].name = "WGTS_" + obj.name
@@ -148,18 +146,18 @@ def generate_rig(context, metarig):
# Create temporary duplicates for merging
temp_rig_1 = metarig.copy()
temp_rig_1.data = metarig.data.copy()
- scene.objects.link(temp_rig_1)
+ collection.objects.link(temp_rig_1)
temp_rig_2 = metarig.copy()
temp_rig_2.data = obj.data
- scene.objects.link(temp_rig_2)
+ collection.objects.link(temp_rig_2)
# Select the temp rigs for merging
for objt in scene.objects:
- objt.select = False # deselect all objects
- temp_rig_1.select = True
- temp_rig_2.select = True
- scene.objects.active = temp_rig_2
+ objt.select_set(False) # deselect all objects
+ temp_rig_1.select_set(True)
+ temp_rig_2.select_set(True)
+ view_layer.objects.active = temp_rig_2
# Merge the temporary rigs
bpy.ops.object.join()
@@ -169,9 +167,9 @@ def generate_rig(context, metarig):
# Select the generated rig
for objt in scene.objects:
- objt.select = False # deselect all objects
- obj.select = True
- scene.objects.active = obj
+ objt.select_set(False) # deselect all objects
+ obj.select_set(True)
+ view_layer.objects.active = obj
# Copy over bone properties
for bone in metarig.data.bones:
@@ -303,7 +301,8 @@ def generate_rig(context, metarig):
rna_idprop_ui_prop_get(obj.data, "rig_id", create=True)
obj.data["rig_id"] = rig_id
- t.tick("Create root bone: ")
+ # Create/find widge collection
+ widget_collection = ensure_widget_collection(context)
# Create Group widget
# wgts_group_name = "WGTS"
@@ -313,21 +312,14 @@ def generate_rig(context, metarig):
bpy.data.objects.remove(bpy.data.objects[wgts_group_name])
mesh = bpy.data.meshes.new(wgts_group_name)
wgts_obj = bpy.data.objects.new(wgts_group_name, mesh)
- scene.objects.link(wgts_obj)
- wgts_obj.layers = WGT_LAYERS
+ widget_collection.objects.link(wgts_obj)
t.tick("Create main WGTS: ")
#
# if id_store.rigify_generate_mode == 'new':
# bpy.ops.object.select_all(action='DESELECT')
# for wgt in bpy.data.objects[wgts_group_name].children:
- # wgt.select = True
- # for i, lyr in enumerate(WGT_LAYERS):
- # if lyr:
- # context.scene.layers[i] = True
+ # wgt.select_set(True)
# bpy.ops.object.make_single_user(obdata=True)
- # for i, lyr in enumerate(WGT_LAYERS):
- # if lyr:
- # context.scene.layers[i] = False
#----------------------------------
try:
@@ -343,8 +335,8 @@ def generate_rig(context, metarig):
for rig in rigs:
# Go into editmode in the rig armature
bpy.ops.object.mode_set(mode='OBJECT')
- context.scene.objects.active = obj
- obj.select = True
+ context.view_layer.objects.active = obj
+ obj.select_set(True)
bpy.ops.object.mode_set(mode='EDIT')
scripts = rig.generate()
if scripts is not None:
@@ -511,18 +503,7 @@ def generate_rig(context, metarig):
create_bone_groups(obj, metarig)
# Add rig_ui to logic
- skip = False
- ctrls = obj.game.controllers
-
- for c in ctrls:
- if 'Python' in c.name and c.text.name == script.name:
- skip = True
- break
- if not skip:
- bpy.ops.logic.controller_add(type='PYTHON', object=obj.name)
- ctrl = obj.game.controllers[-1]
- ctrl.text = bpy.data.texts[script.name]
-
+ create_persistent_rig_ui(obj, script)
t.tick("The rest: ")
#----------------------------------
@@ -538,6 +519,11 @@ def generate_rig(context, metarig):
child.parent_bone = sub_parent
child.matrix_world = mat
+ #----------------------------------
+ # Restore active collection
+ view_layer.active_layer_collection = layer_collection
+
+
def create_selection_sets(obj, metarig):
# Check if selection sets addon is installed
@@ -547,13 +533,13 @@ def create_selection_sets(obj, metarig):
bpy.ops.object.mode_set(mode='POSE')
- bpy.context.scene.objects.active = obj
- obj.select = True
- metarig.select = False
+ bpy.context.view_layer.objects.active = obj
+ obj.select_set(True)
+ metarig.select_set(False)
pbones = obj.pose.bones
for i, name in enumerate(metarig.data.rigify_layers.keys()):
- if name == '' or not metarig.data.rigify_layers[i].set:
+ if name == '' or not metarig.data.rigify_layers[i].selset:
continue
bpy.ops.pose.select_all(action='DESELECT')
@@ -588,7 +574,7 @@ def create_bone_groups(obj, metarig):
g_id = l.group - 1
name = groups[g_id].name
if name not in obj.pose.bone_groups.keys():
- bg = obj.pose.bone_groups.new(name)
+ bg = obj.pose.bone_groups.new(name=name)
bg.color_set = 'CUSTOM'
bg.colors.normal = gamma_correct(groups[g_id].normal)
bg.colors.select = gamma_correct(groups[g_id].select)
@@ -607,6 +593,31 @@ def create_bone_groups(obj, metarig):
b.bone_group = obj.pose.bone_groups[name]
+def create_persistent_rig_ui(obj, script):
+ """Make sure the ui script always follows the rig around"""
+ skip = False
+ driver = None
+
+ for fcurve in obj.animation_data.drivers:
+ if fcurve.data_path == 'pass_index':
+ driver = fcurve.driver
+ for variable in driver.variables:
+ if variable.name == script.name:
+ skip = True
+ break
+ break
+
+ if not skip:
+ if not driver:
+ fcurve = obj.driver_add("pass_index")
+ driver = fcurve.driver
+
+ variable = driver.variables.new()
+ variable.name = script.name
+ variable.targets[0].id_type = 'TEXT'
+ variable.targets[0].id = script
+
+
def get_bone_rigs(obj, bone_name, halt_on_missing=False):
""" Fetch all the rigs specified on a bone.
"""
diff --git a/rigify/legacy/__init__.py b/rigify/legacy/__init__.py
index 6e6f751c..029d877e 100644
--- a/rigify/legacy/__init__.py
+++ b/rigify/legacy/__init__.py
@@ -45,16 +45,16 @@ import bpy
class RigifyName(bpy.types.PropertyGroup):
- name = bpy.props.StringProperty()
+ name: bpy.props.StringProperty()
class RigifyParameters(bpy.types.PropertyGroup):
- name = bpy.props.StringProperty()
+ name: bpy.props.StringProperty()
class RigifyArmatureLayer(bpy.types.PropertyGroup):
- name = bpy.props.StringProperty(name="Layer Name", default=" ")
- row = bpy.props.IntProperty(name="Layer Row", default=1, min=1, max=32)
+ name: bpy.props.StringProperty(name="Layer Name", default=" ")
+ row: bpy.props.IntProperty(name="Layer Row", default=1, min=1, max=32)
##### REGISTER #####
diff --git a/rigify/legacy/generate.py b/rigify/legacy/generate.py
index ec822b9e..476cdbe8 100644
--- a/rigify/legacy/generate.py
+++ b/rigify/legacy/generate.py
@@ -28,7 +28,7 @@ from rna_prop_ui import rna_idprop_ui_prop_get
from .utils import MetarigError, new_bone, get_rig_type
from .utils import ORG_PREFIX, MCH_PREFIX, DEF_PREFIX, WGT_PREFIX, ROOT_NAME, make_original_name
from .utils import RIG_DIR
-from .utils import create_root_widget
+from .utils import create_root_widget, ensure_widget_collection
from .utils import random_id
from .utils import copy_attributes
from .rig_ui_template import UI_SLIDERS, layers_ui, UI_REGISTER
@@ -71,6 +71,9 @@ def generate_rig(context, metarig):
bpy.ops.object.mode_set(mode='OBJECT')
scene = context.scene
+ view_layer = context.view_layer
+ collection = context.collection
+ layer_collection = context.layer_collection
#------------------------------------------
# Create/find the rig object and set it up
@@ -88,8 +91,8 @@ def generate_rig(context, metarig):
obj = scene.objects[name]
except KeyError:
obj = bpy.data.objects.new(name, bpy.data.armatures.new(name))
- obj.draw_type = 'WIRE'
- scene.objects.link(obj)
+ obj.display_type = 'WIRE'
+ collection.objects.link(obj)
obj.data.pose_position = 'POSE'
@@ -98,9 +101,9 @@ def generate_rig(context, metarig):
obj.animation_data_clear()
# Select generated rig object
- metarig.select = False
- obj.select = True
- scene.objects.active = obj
+ metarig.select_set(False)
+ obj.select_set(True)
+ view_layer.objects.active = obj
# Remove all bones from the generated rig armature.
bpy.ops.object.mode_set(mode='EDIT')
@@ -111,18 +114,18 @@ def generate_rig(context, metarig):
# Create temporary duplicates for merging
temp_rig_1 = metarig.copy()
temp_rig_1.data = metarig.data.copy()
- scene.objects.link(temp_rig_1)
+ collection.objects.link(temp_rig_1)
temp_rig_2 = metarig.copy()
temp_rig_2.data = obj.data
- scene.objects.link(temp_rig_2)
+ collection.objects.link(temp_rig_2)
# Select the temp rigs for merging
for objt in scene.objects:
- objt.select = False # deselect all objects
- temp_rig_1.select = True
- temp_rig_2.select = True
- scene.objects.active = temp_rig_2
+ objt.select_set(False) # deselect all objects
+ temp_rig_1.select_set(True)
+ temp_rig_2.select_set(True)
+ view_layer.objects.active = temp_rig_2
# Merge the temporary rigs
bpy.ops.object.join()
@@ -132,9 +135,9 @@ def generate_rig(context, metarig):
# Select the generated rig
for objt in scene.objects:
- objt.select = False # deselect all objects
- obj.select = True
- scene.objects.active = obj
+ objt.select_set(False) # deselect all objects
+ obj.select_set(True)
+ view_layer.objects.active = obj
# Copy over bone properties
for bone in metarig.data.bones:
@@ -262,6 +265,9 @@ def generate_rig(context, metarig):
rna_idprop_ui_prop_get(obj.data, "rig_id", create=True)
obj.data["rig_id"] = rig_id
+ # Create/find widget collection
+ ensure_widget_collection(context)
+
t.tick("Create root bone: ")
#----------------------------------
try:
@@ -277,8 +283,8 @@ def generate_rig(context, metarig):
for rig in rigs:
# Go into editmode in the rig armature
bpy.ops.object.mode_set(mode='OBJECT')
- context.scene.objects.active = obj
- obj.select = True
+ context.view_layer.objects.active = obj
+ obj.select_set(True)
bpy.ops.object.mode_set(mode='EDIT')
scripts = rig.generate()
if scripts is not None:
@@ -353,7 +359,7 @@ def generate_rig(context, metarig):
if obj.data.bones[bone].name.startswith(DEF_PREFIX):
obj.data.bones[bone].layers = DEF_LAYER
- # Create root bone widget
+ # Create root bone widget
create_root_widget(obj, "root")
# Assign shapes to bones
@@ -427,6 +433,10 @@ def generate_rig(context, metarig):
metarig.data.pose_position = rest_backup
obj.data.pose_position = 'POSE'
+ #----------------------------------
+ # Restore active collection
+ view_layer.active_layer_collection = layer_collection
+
def get_bone_rigs(obj, bone_name, halt_on_missing=False):
""" Fetch all the rigs specified on a bone.
diff --git a/rigify/legacy/metarig_menu.py b/rigify/legacy/metarig_menu.py
index 4bdf2701..c0c20955 100644
--- a/rigify/legacy/metarig_menu.py
+++ b/rigify/legacy/metarig_menu.py
@@ -114,7 +114,7 @@ def register():
bpy.utils.register_class(mop)
for mf in menu_funcs:
- bpy.types.INFO_MT_armature_add.append(mf)
+ bpy.types.VIEW3D_MT_armature_add.append(mf)
def unregister():
@@ -122,4 +122,4 @@ def unregister():
bpy.utils.unregister_class(mop)
for mf in menu_funcs:
- bpy.types.INFO_MT_armature_add.remove(mf)
+ bpy.types.VIEW3D_MT_armature_add.remove(mf)
diff --git a/rigify/legacy/rig_ui_pitchipoy_template.py b/rigify/legacy/rig_ui_pitchipoy_template.py
index 5817f9c5..45e4f83a 100644
--- a/rigify/legacy/rig_ui_pitchipoy_template.py
+++ b/rigify/legacy/rig_ui_pitchipoy_template.py
@@ -135,11 +135,11 @@ def get_pose_matrix_in_other_space(mat, pose_bone):
par_rest = Matrix()
# Get matrix in bone's current transform space
- smat = rest_inv * (par_rest * (par_inv * mat))
+ smat = rest_inv @ (par_rest @ (par_inv @ mat))
# Compensate for non-local location
#if not pose_bone.bone.use_local_location:
- # loc = smat.to_translation() * (par_rest.inverted() * rest).to_quaternion()
+ # loc = smat.to_translation() @ (par_rest.inverted() @ rest).to_quaternion()
# smat.translation = loc
return smat
@@ -166,8 +166,8 @@ def set_pose_translation(pose_bone, mat):
else:
par_rest = Matrix()
- q = (par_rest.inverted() * rest).to_quaternion()
- pose_bone.location = q * loc
+ q = (par_rest.inverted() @ rest).to_quaternion()
+ pose_bone.location = q @ loc
def set_pose_rotation(pose_bone, mat):
@@ -283,11 +283,11 @@ def match_pole_target(ik_first, ik_last, pole, match_bone, length):
angle = rotation_difference(ik_first.matrix, match_bone.matrix)
# Try compensating for the rotation difference in both directions
- pv1 = Matrix.Rotation(angle, 4, ikv) * pv
+ pv1 = Matrix.Rotation(angle, 4, ikv) @ pv
set_pole(pv1)
ang1 = rotation_difference(ik_first.matrix, match_bone.matrix)
- pv2 = Matrix.Rotation(-angle, 4, ikv) * pv
+ pv2 = Matrix.Rotation(-angle, 4, ikv) @ pv
set_pole(pv2)
ang2 = rotation_difference(ik_first.matrix, match_bone.matrix)
@@ -421,8 +421,8 @@ def fk2ik_leg(obj, fk, ik):
match_pose_scale(shin, shini)
# Foot position
- mat = mfoot.bone.matrix_local.inverted() * foot.bone.matrix_local
- footmat = get_pose_matrix_in_other_space(mfooti.matrix, foot) * mat
+ mat = mfoot.bone.matrix_local.inverted() @ foot.bone.matrix_local
+ footmat = get_pose_matrix_in_other_space(mfooti.matrix, foot) @ mat
set_pose_rotation(foot, footmat)
set_pose_scale(foot, footmat)
bpy.ops.object.mode_set(mode='OBJECT')
@@ -439,8 +439,8 @@ def fk2ik_leg(obj, fk, ik):
match_pose_scale(shin, shini)
# Foot position
- mat = mfoot.bone.matrix_local.inverted() * foot.bone.matrix_local
- footmat = get_pose_matrix_in_other_space(mfooti.matrix, foot) * mat
+ mat = mfoot.bone.matrix_local.inverted() @ foot.bone.matrix_local
+ footmat = get_pose_matrix_in_other_space(mfooti.matrix, foot) @ mat
set_pose_rotation(foot, footmat)
set_pose_scale(foot, footmat)
bpy.ops.object.mode_set(mode='OBJECT')
@@ -478,8 +478,8 @@ def ik2fk_leg(obj, fk, ik):
set_pose_rotation(footroll, Matrix())
# Foot position
- mat = mfooti.bone.matrix_local.inverted() * footi.bone.matrix_local
- footmat = get_pose_matrix_in_other_space(foot.matrix, footi) * mat
+ mat = mfooti.bone.matrix_local.inverted() @ footi.bone.matrix_local
+ footmat = get_pose_matrix_in_other_space(foot.matrix, footi) @ mat
set_pose_translation(footi, footmat)
set_pose_rotation(footi, footmat)
set_pose_scale(footi, footmat)
@@ -505,8 +505,8 @@ def ik2fk_leg(obj, fk, ik):
set_pose_rotation(footroll, Matrix())
# Foot position
- mat = mfooti.bone.matrix_local.inverted() * footi.bone.matrix_local
- footmat = get_pose_matrix_in_other_space(mfoot.matrix, footi) * mat
+ mat = mfooti.bone.matrix_local.inverted() @ footi.bone.matrix_local
+ footmat = get_pose_matrix_in_other_space(mfoot.matrix, footi) @ mat
set_pose_translation(footi, footmat)
set_pose_rotation(footi, footmat)
set_pose_scale(footi, footmat)
@@ -528,13 +528,13 @@ class Rigify_Arm_FK2IK(bpy.types.Operator):
bl_label = "Rigify Snap FK arm to IK"
bl_options = {'UNDO'}
- uarm_fk = bpy.props.StringProperty(name="Upper Arm FK Name")
- farm_fk = bpy.props.StringProperty(name="Forerm FK Name")
- hand_fk = bpy.props.StringProperty(name="Hand FK Name")
+ uarm_fk: bpy.props.StringProperty(name="Upper Arm FK Name")
+ farm_fk: bpy.props.StringProperty(name="Forerm FK Name")
+ hand_fk: bpy.props.StringProperty(name="Hand FK Name")
- uarm_ik = bpy.props.StringProperty(name="Upper Arm IK Name")
- farm_ik = bpy.props.StringProperty(name="Forearm IK Name")
- hand_ik = bpy.props.StringProperty(name="Hand IK Name")
+ uarm_ik: bpy.props.StringProperty(name="Upper Arm IK Name")
+ farm_ik: bpy.props.StringProperty(name="Forearm IK Name")
+ hand_ik: bpy.props.StringProperty(name="Hand IK Name")
@classmethod
def poll(cls, context):
@@ -557,14 +557,14 @@ class Rigify_Arm_IK2FK(bpy.types.Operator):
bl_label = "Rigify Snap IK arm to FK"
bl_options = {'UNDO'}
- uarm_fk = bpy.props.StringProperty(name="Upper Arm FK Name")
- farm_fk = bpy.props.StringProperty(name="Forerm FK Name")
- hand_fk = bpy.props.StringProperty(name="Hand FK Name")
+ uarm_fk: bpy.props.StringProperty(name="Upper Arm FK Name")
+ farm_fk: bpy.props.StringProperty(name="Forerm FK Name")
+ hand_fk: bpy.props.StringProperty(name="Hand FK Name")
- uarm_ik = bpy.props.StringProperty(name="Upper Arm IK Name")
- farm_ik = bpy.props.StringProperty(name="Forearm IK Name")
- hand_ik = bpy.props.StringProperty(name="Hand IK Name")
- pole = bpy.props.StringProperty(name="Pole IK Name")
+ uarm_ik: bpy.props.StringProperty(name="Upper Arm IK Name")
+ farm_ik: bpy.props.StringProperty(name="Forearm IK Name")
+ hand_ik: bpy.props.StringProperty(name="Hand IK Name")
+ pole: bpy.props.StringProperty(name="Pole IK Name")
@classmethod
def poll(cls, context):
@@ -587,15 +587,15 @@ class Rigify_Leg_FK2IK(bpy.types.Operator):
bl_label = "Rigify Snap FK leg to IK"
bl_options = {'UNDO'}
- thigh_fk = bpy.props.StringProperty(name="Thigh FK Name")
- shin_fk = bpy.props.StringProperty(name="Shin FK Name")
- foot_fk = bpy.props.StringProperty(name="Foot FK Name")
- mfoot_fk = bpy.props.StringProperty(name="MFoot FK Name")
+ thigh_fk: bpy.props.StringProperty(name="Thigh FK Name")
+ shin_fk: bpy.props.StringProperty(name="Shin FK Name")
+ foot_fk: bpy.props.StringProperty(name="Foot FK Name")
+ mfoot_fk: bpy.props.StringProperty(name="MFoot FK Name")
- thigh_ik = bpy.props.StringProperty(name="Thigh IK Name")
- shin_ik = bpy.props.StringProperty(name="Shin IK Name")
- foot_ik = bpy.props.StringProperty(name="Foot IK Name")
- mfoot_ik = bpy.props.StringProperty(name="MFoot IK Name")
+ thigh_ik: bpy.props.StringProperty(name="Thigh IK Name")
+ shin_ik: bpy.props.StringProperty(name="Shin IK Name")
+ foot_ik: bpy.props.StringProperty(name="Foot IK Name")
+ mfoot_ik: bpy.props.StringProperty(name="MFoot IK Name")
@classmethod
def poll(cls, context):
@@ -618,16 +618,16 @@ class Rigify_Leg_IK2FK(bpy.types.Operator):
bl_label = "Rigify Snap IK leg to FK"
bl_options = {'UNDO'}
- thigh_fk = bpy.props.StringProperty(name="Thigh FK Name")
- shin_fk = bpy.props.StringProperty(name="Shin FK Name")
- mfoot_fk = bpy.props.StringProperty(name="MFoot FK Name")
- foot_fk = bpy.props.StringProperty(name="Foot FK Name")
- thigh_ik = bpy.props.StringProperty(name="Thigh IK Name")
- shin_ik = bpy.props.StringProperty(name="Shin IK Name")
- foot_ik = bpy.props.StringProperty(name="Foot IK Name")
- footroll = bpy.props.StringProperty(name="Foot Roll Name")
- pole = bpy.props.StringProperty(name="Pole IK Name")
- mfoot_ik = bpy.props.StringProperty(name="MFoot IK Name")
+ thigh_fk: bpy.props.StringProperty(name="Thigh FK Name")
+ shin_fk: bpy.props.StringProperty(name="Shin FK Name")
+ mfoot_fk: bpy.props.StringProperty(name="MFoot FK Name")
+ foot_fk: bpy.props.StringProperty(name="Foot FK Name")
+ thigh_ik: bpy.props.StringProperty(name="Thigh IK Name")
+ shin_ik: bpy.props.StringProperty(name="Shin IK Name")
+ foot_ik: bpy.props.StringProperty(name="Foot IK Name")
+ footroll: bpy.props.StringProperty(name="Foot Roll Name")
+ pole: bpy.props.StringProperty(name="Pole IK Name")
+ mfoot_ik: bpy.props.StringProperty(name="MFoot IK Name")
@classmethod
@@ -653,6 +653,7 @@ class RigUI(bpy.types.Panel):
bl_region_type = 'UI'
bl_label = "Rig Main Properties"
bl_idname = rig_id + "_PT_rig_ui"
+ bl_category = 'View'
@classmethod
def poll(self, context):
@@ -696,6 +697,7 @@ class RigLayers(bpy.types.Panel):
bl_region_type = 'UI'
bl_label = "Rig Layers"
bl_idname = rig_id + "_PT_rig_layers"
+ bl_category = 'View'
@classmethod
def poll(self, context):
diff --git a/rigify/legacy/rig_ui_template.py b/rigify/legacy/rig_ui_template.py
index 717410da..cab5674f 100644
--- a/rigify/legacy/rig_ui_template.py
+++ b/rigify/legacy/rig_ui_template.py
@@ -83,11 +83,11 @@ def get_pose_matrix_in_other_space(mat, pose_bone):
par_rest = Matrix()
# Get matrix in bone's current transform space
- smat = rest_inv * (par_rest * (par_inv * mat))
+ smat = rest_inv @ (par_rest @ (par_inv @ mat))
# Compensate for non-local location
#if not pose_bone.bone.use_local_location:
- # loc = smat.to_translation() * (par_rest.inverted() * rest).to_quaternion()
+ # loc = smat.to_translation() @ (par_rest.inverted() @ rest).to_quaternion()
# smat.translation = loc
return smat
@@ -114,8 +114,8 @@ def set_pose_translation(pose_bone, mat):
else:
par_rest = Matrix()
- q = (par_rest.inverted() * rest).to_quaternion()
- pose_bone.location = q * loc
+ q = (par_rest.inverted() @ rest).to_quaternion()
+ pose_bone.location = q @ loc
def set_pose_rotation(pose_bone, mat):
@@ -219,11 +219,11 @@ def match_pole_target(ik_first, ik_last, pole, match_bone, length):
angle = rotation_difference(ik_first.matrix, match_bone.matrix)
# Try compensating for the rotation difference in both directions
- pv1 = Matrix.Rotation(angle, 4, ikv) * pv
+ pv1 = Matrix.Rotation(angle, 4, ikv) @ pv
set_pole(pv1)
ang1 = rotation_difference(ik_first.matrix, match_bone.matrix)
- pv2 = Matrix.Rotation(-angle, 4, ikv) * pv
+ pv2 = Matrix.Rotation(-angle, 4, ikv) @ pv
set_pole(pv2)
ang2 = rotation_difference(ik_first.matrix, match_bone.matrix)
@@ -322,8 +322,8 @@ def fk2ik_leg(obj, fk, ik):
match_pose_scale(shin, shini)
# Foot position
- mat = mfoot.bone.matrix_local.inverted() * foot.bone.matrix_local
- footmat = get_pose_matrix_in_other_space(mfooti.matrix, foot) * mat
+ mat = mfoot.bone.matrix_local.inverted() @ foot.bone.matrix_local
+ footmat = get_pose_matrix_in_other_space(mfooti.matrix, foot) @ mat
set_pose_rotation(foot, footmat)
set_pose_scale(foot, footmat)
bpy.ops.object.mode_set(mode='OBJECT')
@@ -353,8 +353,8 @@ def ik2fk_leg(obj, fk, ik):
set_pose_rotation(footroll, Matrix())
# Foot position
- mat = mfooti.bone.matrix_local.inverted() * footi.bone.matrix_local
- footmat = get_pose_matrix_in_other_space(mfoot.matrix, footi) * mat
+ mat = mfooti.bone.matrix_local.inverted() @ footi.bone.matrix_local
+ footmat = get_pose_matrix_in_other_space(mfoot.matrix, footi) @ mat
set_pose_translation(footi, footmat)
set_pose_rotation(footi, footmat)
set_pose_scale(footi, footmat)
@@ -376,13 +376,13 @@ class Rigify_Arm_FK2IK(bpy.types.Operator):
bl_label = "Rigify Snap FK arm to IK"
bl_options = {'UNDO'}
- uarm_fk = bpy.props.StringProperty(name="Upper Arm FK Name")
- farm_fk = bpy.props.StringProperty(name="Forerm FK Name")
- hand_fk = bpy.props.StringProperty(name="Hand FK Name")
+ uarm_fk: bpy.props.StringProperty(name="Upper Arm FK Name")
+ farm_fk: bpy.props.StringProperty(name="Forerm FK Name")
+ hand_fk: bpy.props.StringProperty(name="Hand FK Name")
- uarm_ik = bpy.props.StringProperty(name="Upper Arm IK Name")
- farm_ik = bpy.props.StringProperty(name="Forearm IK Name")
- hand_ik = bpy.props.StringProperty(name="Hand IK Name")
+ uarm_ik: bpy.props.StringProperty(name="Upper Arm IK Name")
+ farm_ik: bpy.props.StringProperty(name="Forearm IK Name")
+ hand_ik: bpy.props.StringProperty(name="Hand IK Name")
@classmethod
def poll(cls, context):
@@ -405,14 +405,14 @@ class Rigify_Arm_IK2FK(bpy.types.Operator):
bl_label = "Rigify Snap IK arm to FK"
bl_options = {'UNDO'}
- uarm_fk = bpy.props.StringProperty(name="Upper Arm FK Name")
- farm_fk = bpy.props.StringProperty(name="Forerm FK Name")
- hand_fk = bpy.props.StringProperty(name="Hand FK Name")
+ uarm_fk: bpy.props.StringProperty(name="Upper Arm FK Name")
+ farm_fk: bpy.props.StringProperty(name="Forerm FK Name")
+ hand_fk: bpy.props.StringProperty(name="Hand FK Name")
- uarm_ik = bpy.props.StringProperty(name="Upper Arm IK Name")
- farm_ik = bpy.props.StringProperty(name="Forearm IK Name")
- hand_ik = bpy.props.StringProperty(name="Hand IK Name")
- pole = bpy.props.StringProperty(name="Pole IK Name")
+ uarm_ik: bpy.props.StringProperty(name="Upper Arm IK Name")
+ farm_ik: bpy.props.StringProperty(name="Forearm IK Name")
+ hand_ik: bpy.props.StringProperty(name="Hand IK Name")
+ pole: bpy.props.StringProperty(name="Pole IK Name")
@classmethod
def poll(cls, context):
@@ -435,15 +435,15 @@ class Rigify_Leg_FK2IK(bpy.types.Operator):
bl_label = "Rigify Snap FK leg to IK"
bl_options = {'UNDO'}
- thigh_fk = bpy.props.StringProperty(name="Thigh FK Name")
- shin_fk = bpy.props.StringProperty(name="Shin FK Name")
- foot_fk = bpy.props.StringProperty(name="Foot FK Name")
- mfoot_fk = bpy.props.StringProperty(name="MFoot FK Name")
+ thigh_fk: bpy.props.StringProperty(name="Thigh FK Name")
+ shin_fk: bpy.props.StringProperty(name="Shin FK Name")
+ foot_fk: bpy.props.StringProperty(name="Foot FK Name")
+ mfoot_fk: bpy.props.StringProperty(name="MFoot FK Name")
- thigh_ik = bpy.props.StringProperty(name="Thigh IK Name")
- shin_ik = bpy.props.StringProperty(name="Shin IK Name")
- foot_ik = bpy.props.StringProperty(name="Foot IK Name")
- mfoot_ik = bpy.props.StringProperty(name="MFoot IK Name")
+ thigh_ik: bpy.props.StringProperty(name="Thigh IK Name")
+ shin_ik: bpy.props.StringProperty(name="Shin IK Name")
+ foot_ik: bpy.props.StringProperty(name="Foot IK Name")
+ mfoot_ik: bpy.props.StringProperty(name="MFoot IK Name")
@classmethod
def poll(cls, context):
@@ -466,16 +466,16 @@ class Rigify_Leg_IK2FK(bpy.types.Operator):
bl_label = "Rigify Snap IK leg to FK"
bl_options = {'UNDO'}
- thigh_fk = bpy.props.StringProperty(name="Thigh FK Name")
- shin_fk = bpy.props.StringProperty(name="Shin FK Name")
- mfoot_fk = bpy.props.StringProperty(name="MFoot FK Name")
+ thigh_fk: bpy.props.StringProperty(name="Thigh FK Name")
+ shin_fk: bpy.props.StringProperty(name="Shin FK Name")
+ mfoot_fk: bpy.props.StringProperty(name="MFoot FK Name")
- thigh_ik = bpy.props.StringProperty(name="Thigh IK Name")
- shin_ik = bpy.props.StringProperty(name="Shin IK Name")
- foot_ik = bpy.props.StringProperty(name="Foot IK Name")
- footroll = bpy.props.StringProperty(name="Foot Roll Name")
- pole = bpy.props.StringProperty(name="Pole IK Name")
- mfoot_ik = bpy.props.StringProperty(name="MFoot IK Name")
+ thigh_ik: bpy.props.StringProperty(name="Thigh IK Name")
+ shin_ik: bpy.props.StringProperty(name="Shin IK Name")
+ foot_ik: bpy.props.StringProperty(name="Foot IK Name")
+ footroll: bpy.props.StringProperty(name="Foot Roll Name")
+ pole: bpy.props.StringProperty(name="Pole IK Name")
+ mfoot_ik: bpy.props.StringProperty(name="MFoot IK Name")
@classmethod
def poll(cls, context):
@@ -500,6 +500,7 @@ class RigUI(bpy.types.Panel):
bl_region_type = 'UI'
bl_label = "Rig Main Properties"
bl_idname = rig_id + "_PT_rig_ui"
+ bl_category = 'View'
@classmethod
def poll(self, context):
@@ -543,6 +544,7 @@ class RigLayers(bpy.types.Panel):
bl_region_type = 'UI'
bl_label = "Rig Layers"
bl_idname = rig_id + "_PT_rig_layers"
+ bl_category = 'View'
@classmethod
def poll(self, context):
diff --git a/rigify/legacy/rigs/biped/limb_common.py b/rigify/legacy/rigs/biped/limb_common.py
index 73e9ad34..05e3e59c 100644
--- a/rigify/legacy/rigs/biped/limb_common.py
+++ b/rigify/legacy/rigs/biped/limb_common.py
@@ -416,11 +416,11 @@ class IKLimb:
v1 = flimb_e.tail - ulimb_e.head
if 'X' in self.primary_rotation_axis or 'Y' in self.primary_rotation_axis:
v2 = v1.cross(flimb_e.x_axis)
- if (v2 * flimb_e.z_axis) > 0.0:
+ if (v2 @ flimb_e.z_axis) > 0.0:
v2 *= -1.0
else:
v2 = v1.cross(flimb_e.z_axis)
- if (v2 * flimb_e.x_axis) < 0.0:
+ if (v2 @ flimb_e.x_axis) < 0.0:
v2 *= -1.0
v2.normalize()
v2 *= v1.length
diff --git a/rigify/legacy/rigs/pitchipoy/limbs/limb_utils.py b/rigify/legacy/rigs/pitchipoy/limbs/limb_utils.py
index b0b62d79..69449bb7 100644
--- a/rigify/legacy/rigs/pitchipoy/limbs/limb_utils.py
+++ b/rigify/legacy/rigs/pitchipoy/limbs/limb_utils.py
@@ -11,11 +11,11 @@ def orient_bone( cls, eb, axis, scale = 1.0, reverse = False ):
setattr(v,axis,scale)
if reverse:
- tail_vec = v * cls.obj.matrix_world
+ tail_vec = v @ cls.obj.matrix_world
eb.head[:] = eb.tail
eb.tail[:] = eb.head + tail_vec
else:
- tail_vec = v * cls.obj.matrix_world
+ tail_vec = v @ cls.obj.matrix_world
eb.tail[:] = eb.head + tail_vec
eb.roll = 0.0
diff --git a/rigify/legacy/rigs/pitchipoy/super_face.py b/rigify/legacy/rigs/pitchipoy/super_face.py
index c999ae9a..32097d43 100644
--- a/rigify/legacy/rigs/pitchipoy/super_face.py
+++ b/rigify/legacy/rigs/pitchipoy/super_face.py
@@ -741,8 +741,10 @@ class Rig:
'DEF-chin.R' : 'lips.R',
'DEF-jaw.R.001' : 'chin.R',
'DEF-brow.T.L.003' : 'nose',
+ 'DEF-ear.L' : None,
'DEF-ear.L.003' : 'ear.L.004',
'DEF-ear.L.004' : 'ear.L',
+ 'DEF-ear.R' : None,
'DEF-ear.R.003' : 'ear.R.004',
'DEF-ear.R.004' : 'ear.R',
'DEF-lip.B.L.001' : 'lips.L',
@@ -769,8 +771,9 @@ class Rig:
pattern = r'^DEF-(\w+\.?\w?\.?\w?)(\.?)(\d*?)(\d?)$'
for bone in [ bone for bone in all_bones['deform']['all'] if 'lid' not in bone ]:
- if bone in list( def_specials.keys() ):
- self.make_constraits('def_tweak', bone, def_specials[bone] )
+ if bone in def_specials:
+ if def_specials[bone] is not None:
+ self.make_constraits('def_tweak', bone, def_specials[bone] )
else:
matches = re.match( pattern, bone ).groups()
if len( matches ) > 1 and matches[-1]:
diff --git a/rigify/legacy/rigs/pitchipoy/super_torso_turbo.py b/rigify/legacy/rigs/pitchipoy/super_torso_turbo.py
index 38d5887b..d9645adb 100644
--- a/rigify/legacy/rigs/pitchipoy/super_torso_turbo.py
+++ b/rigify/legacy/rigs/pitchipoy/super_torso_turbo.py
@@ -99,11 +99,11 @@ class Rig:
setattr(v,axis,scale)
if reverse:
- tail_vec = v * self.obj.matrix_world
+ tail_vec = v @ self.obj.matrix_world
eb.head[:] = eb.tail
eb.tail[:] = eb.head + tail_vec
else:
- tail_vec = v * self.obj.matrix_world
+ tail_vec = v @ self.obj.matrix_world
eb.tail[:] = eb.head + tail_vec
diff --git a/rigify/legacy/rigs/pitchipoy/super_widgets.py b/rigify/legacy/rigs/pitchipoy/super_widgets.py
index 72384a7c..f442c590 100644
--- a/rigify/legacy/rigs/pitchipoy/super_widgets.py
+++ b/rigify/legacy/rigs/pitchipoy/super_widgets.py
@@ -3,7 +3,6 @@ import importlib
import importlib
from ...utils import create_widget
-WGT_LAYERS = [x == 19 for x in range(0, 20)] # Widgets go on the last scene layer.
MODULE_NAME = "super_widgets" # Windows/Mac blender is weird, so __package__ doesn't work
diff --git a/rigify/legacy/ui.py b/rigify/legacy/ui.py
index dec5b73c..f04ec335 100644
--- a/rigify/legacy/ui.py
+++ b/rigify/legacy/ui.py
@@ -124,7 +124,7 @@ class DATA_PT_rigify_layer_names(bpy.types.Panel):
col = layout.column(align=True)
row = col.row()
row.prop(arm, "layers", index=i, text="", toggle=True)
- split = row.split(percentage=0.8)
+ split = row.split(factor=0.8)
split.prop(rigify_layer, "name", text="Layer %d" % (i + 1))
split.prop(rigify_layer, "row", text="")
@@ -200,9 +200,9 @@ class BONE_PT_rigify_buttons(bpy.types.Panel):
class VIEW3D_PT_tools_rigify_dev(bpy.types.Panel):
bl_label = "Rigify Dev Tools"
- bl_category = 'Tools'
bl_space_type = 'VIEW_3D'
- bl_region_type = 'TOOLS'
+ bl_region_type = 'UI'
+ bl_category = 'View'
@classmethod
def poll(cls, context):
@@ -221,8 +221,8 @@ class VIEW3D_PT_tools_rigify_dev(bpy.types.Panel):
r = self.layout.row()
r.operator("mesh.rigify_encode_mesh_widget", text="Encode Mesh Widget to Python")
-#~ class INFO_MT_armature_metarig_add(bpy.types.Menu):
- #~ bl_idname = "INFO_MT_armature_metarig_add"
+#~ class VIEW3D_MT_armature_metarig_add(bpy.types.Menu):
+ #~ bl_idname = "VIEW3D_MT_armature_metarig_add"
#~ bl_label = "Meta-Rig"
#~ def draw(self, context):
@@ -304,11 +304,11 @@ class Sample(bpy.types.Operator):
bl_label = "Add a sample metarig for a rig type"
bl_options = {'UNDO'}
- metarig_type = StringProperty(
- name="Type",
- description="Name of the rig type to generate a sample of",
- maxlen=128,
- )
+ metarig_type: StringProperty(
+ name="Type",
+ description="Name of the rig type to generate a sample of",
+ maxlen=128,
+ )
def execute(self, context):
if context.mode == 'EDIT_ARMATURE' and self.metarig_type != "":
@@ -410,7 +410,7 @@ class EncodeWidget(bpy.types.Operator):
return {'FINISHED'}
-#menu_func = (lambda self, context: self.layout.menu("INFO_MT_armature_metarig_add", icon='OUTLINER_OB_ARMATURE'))
+#menu_func = (lambda self, context: self.layout.menu("VIEW3D_MT_armature_metarig_add", icon='OUTLINER_OB_ARMATURE'))
#from bl_ui import space_info # ensure the menu is loaded first
@@ -425,7 +425,7 @@ def register():
bpy.utils.register_class(EncodeMetarig)
bpy.utils.register_class(EncodeMetarigSample)
bpy.utils.register_class(EncodeWidget)
- #space_info.INFO_MT_armature_add.append(ui.menu_func)
+ #space_info.VIEW3D_MT_armature_add.append(ui.menu_func)
def unregister():
diff --git a/rigify/legacy/utils.py b/rigify/legacy/utils.py
index e3927ac3..f74a7abf 100644
--- a/rigify/legacy/utils.py
+++ b/rigify/legacy/utils.py
@@ -36,8 +36,6 @@ DEF_PREFIX = "DEF-" # Prefix of deformation bones.
WGT_PREFIX = "WGT-" # Prefix for widget objects
ROOT_NAME = "root" # Name of the root bone.
-WGT_LAYERS = [x == 19 for x in range(0, 20)] # Widgets go on the last scene layer.
-
MODULE_NAME = "rigify" # Windows/Mac blender is weird, so __package__ doesn't work
@@ -342,7 +340,7 @@ def obj_to_bone(obj, rig, bone_name):
bone = rig.data.bones[bone_name]
- mat = rig.matrix_world * bone.matrix_local
+ mat = rig.matrix_world @ bone.matrix_local
obj.location = mat.to_translation()
@@ -397,6 +395,7 @@ def create_widget(rig, bone_name, bone_transform_name=None):
obj_name = WGT_PREFIX + bone_name
scene = bpy.context.scene
+ collection = bpy.context.collection
# Check if it already exists in the scene
if obj_name in scene.objects:
@@ -416,11 +415,10 @@ def create_widget(rig, bone_name, bone_transform_name=None):
# Create mesh object
mesh = bpy.data.meshes.new(obj_name)
obj = bpy.data.objects.new(obj_name, mesh)
- scene.objects.link(obj)
+ collection.objects.link(obj)
# Move object to bone position and set layers
obj_to_bone(obj, rig, bone_transform_name)
- obj.layers = WGT_LAYERS
return obj
@@ -606,8 +604,8 @@ def align_bone_roll(obj, bone1, bone2):
rot_mat = Matrix.Rotation(angle, 3, axis)
# Roll factor
- x3 = rot_mat * x1
- dot = x2 * x3
+ x3 = rot_mat @ x1
+ dot = x2 @ x3
if dot > 1.0:
dot = 1.0
elif dot < -1.0:
@@ -618,8 +616,8 @@ def align_bone_roll(obj, bone1, bone2):
bone1_e.roll = roll
# Check if we rolled in the right direction
- x3 = rot_mat * bone1_e.x_axis
- check = x2 * x3
+ x3 = rot_mat @ bone1_e.x_axis
+ check = x2 @ x3
# If not, reverse
if check < 0.9999:
@@ -937,3 +935,43 @@ def random_id(length=8):
text += random.choice(chars)
text += str(hex(int(time.time())))[2:][-tlength:].rjust(tlength, '0')[::-1]
return text
+
+
+def find_layer_collection_by_collection(layer_collection, collection):
+ if collection == layer_collection.collection:
+ return layer_collection
+
+ # go recursive
+ for child in layer_collection.children:
+ layer_collection = find_layer_collection_by_collection(child, collection)
+ if layer_collection:
+ return layer_collection
+
+
+def ensure_widget_collection(context):
+ wgts_collection_name = "Widgets"
+
+ view_layer = context.view_layer
+ layer_collection = bpy.context.layer_collection
+ collection = layer_collection.collection
+
+ widget_collection = bpy.data.collections.get(wgts_collection_name)
+ if not widget_collection:
+ # ------------------------------------------
+ # Create the widget collection
+ widget_collection = bpy.data.collections.new(wgts_collection_name)
+ widget_collection.hide_viewport = True
+ widget_collection.hide_render = True
+
+ widget_layer_collection = None
+ else:
+ widget_layer_collection = find_layer_collection_by_collection(view_layer.layer_collection, widget_collection)
+
+ if not widget_layer_collection:
+ # Add the widget collection to the tree
+ collection.children.link(widget_collection)
+ widget_layer_collection = [c for c in layer_collection.children if c.collection == widget_collection][0]
+
+ # Make the widget the active collection for the upcoming added (widget) objects
+ view_layer.active_layer_collection = widget_layer_collection
+ return widget_collection
diff --git a/rigify/metarig_menu.py b/rigify/metarig_menu.py
index 6b12abad..102d366d 100644
--- a/rigify/metarig_menu.py
+++ b/rigify/metarig_menu.py
@@ -31,7 +31,7 @@ class ArmatureSubMenu(bpy.types.Menu):
def draw(self, context):
layout = self.layout
- layout.label(self.bl_label)
+ layout.label(text=self.bl_label)
for op, name in self.operators:
text = capwords(name.replace("_", " ")) + " (Meta-Rig)"
layout.operator(op, icon='OUTLINER_OB_ARMATURE', text=text)
@@ -159,25 +159,33 @@ for metarig_class in metarig_classes:
arm_sub = next((e for e in armature_submenus if e.bl_label == metarig_class + ' (submenu)'), '')
arm_sub.operators.append((mop.bl_idname, name,))
+
+### Registering ###
+
+
def register():
+ from bpy.utils import register_class
+
for cl in metarig_ops:
for mop, name in metarig_ops[cl]:
- bpy.utils.register_class(mop)
+ register_class(mop)
for arm_sub in armature_submenus:
- bpy.utils.register_class(arm_sub)
+ register_class(arm_sub)
for mf in menu_funcs:
- bpy.types.INFO_MT_armature_add.append(mf)
+ bpy.types.VIEW3D_MT_armature_add.append(mf)
def unregister():
+ from bpy.utils import unregister_class
+
for cl in metarig_ops:
for mop, name in metarig_ops[cl]:
- bpy.utils.unregister_class(mop)
+ unregister_class(mop)
for arm_sub in armature_submenus:
- bpy.utils.unregister_class(arm_sub)
+ unregister_class(arm_sub)
for mf in menu_funcs:
- bpy.types.INFO_MT_armature_add.remove(mf)
+ bpy.types.VIEW3D_MT_armature_add.remove(mf)
diff --git a/rigify/metarigs/Animals/bird.py b/rigify/metarigs/Animals/bird.py
index e31ce7fe..a3331c29 100644
--- a/rigify/metarigs/Animals/bird.py
+++ b/rigify/metarigs/Animals/bird.py
@@ -48,119 +48,119 @@ def create(obj):
arm.rigify_layers[0].name = "Face"
arm.rigify_layers[0].row = 1
- arm.rigify_layers[0].set = False
+ arm.rigify_layers[0].selset = False
arm.rigify_layers[0].group = 6
arm.rigify_layers[1].name = "Face (Tweak)"
arm.rigify_layers[1].row = 2
- arm.rigify_layers[1].set = False
+ arm.rigify_layers[1].selset = False
arm.rigify_layers[1].group = 4
arm.rigify_layers[2].name = " "
arm.rigify_layers[2].row = 1
- arm.rigify_layers[2].set = False
+ arm.rigify_layers[2].selset = False
arm.rigify_layers[2].group = 0
arm.rigify_layers[3].name = "Spine"
arm.rigify_layers[3].row = 3
- arm.rigify_layers[3].set = False
+ arm.rigify_layers[3].selset = False
arm.rigify_layers[3].group = 3
arm.rigify_layers[4].name = "Spine (Tweak)"
arm.rigify_layers[4].row = 4
- arm.rigify_layers[4].set = False
+ arm.rigify_layers[4].selset = False
arm.rigify_layers[4].group = 4
arm.rigify_layers[5].name = " "
arm.rigify_layers[5].row = 1
- arm.rigify_layers[5].set = False
+ arm.rigify_layers[5].selset = False
arm.rigify_layers[5].group = 0
arm.rigify_layers[6].name = " "
arm.rigify_layers[6].row = 1
- arm.rigify_layers[6].set = False
+ arm.rigify_layers[6].selset = False
arm.rigify_layers[6].group = 0
arm.rigify_layers[7].name = "Wing.L"
arm.rigify_layers[7].row = 6
- arm.rigify_layers[7].set = False
+ arm.rigify_layers[7].selset = False
arm.rigify_layers[7].group = 5
arm.rigify_layers[8].name = ""
arm.rigify_layers[8].row = 8
- arm.rigify_layers[8].set = False
+ arm.rigify_layers[8].selset = False
arm.rigify_layers[8].group = 0
arm.rigify_layers[9].name = "Wing.L (Tweak)"
arm.rigify_layers[9].row = 7
- arm.rigify_layers[9].set = False
+ arm.rigify_layers[9].selset = False
arm.rigify_layers[9].group = 4
arm.rigify_layers[10].name = "Wing.R"
arm.rigify_layers[10].row = 6
- arm.rigify_layers[10].set = False
+ arm.rigify_layers[10].selset = False
arm.rigify_layers[10].group = 5
arm.rigify_layers[11].name = ""
arm.rigify_layers[11].row = 8
- arm.rigify_layers[11].set = False
+ arm.rigify_layers[11].selset = False
arm.rigify_layers[11].group = 0
arm.rigify_layers[12].name = "Wing.R (Tweak)"
arm.rigify_layers[12].row = 7
- arm.rigify_layers[12].set = False
+ arm.rigify_layers[12].selset = False
arm.rigify_layers[12].group = 4
arm.rigify_layers[13].name = "Leg.L (IK)"
arm.rigify_layers[13].row = 10
- arm.rigify_layers[13].set = False
+ arm.rigify_layers[13].selset = False
arm.rigify_layers[13].group = 2
arm.rigify_layers[14].name = "Leg.L (FK)"
arm.rigify_layers[14].row = 11
- arm.rigify_layers[14].set = False
+ arm.rigify_layers[14].selset = False
arm.rigify_layers[14].group = 5
arm.rigify_layers[15].name = "Leg.L (Tweak)"
arm.rigify_layers[15].row = 12
- arm.rigify_layers[15].set = False
+ arm.rigify_layers[15].selset = False
arm.rigify_layers[15].group = 4
arm.rigify_layers[16].name = " Leg.R (IK)"
arm.rigify_layers[16].row = 10
- arm.rigify_layers[16].set = False
+ arm.rigify_layers[16].selset = False
arm.rigify_layers[16].group = 2
arm.rigify_layers[17].name = " Leg.R (FK)"
arm.rigify_layers[17].row = 11
- arm.rigify_layers[17].set = False
+ arm.rigify_layers[17].selset = False
arm.rigify_layers[17].group = 5
arm.rigify_layers[18].name = " Leg.R (Tweak)"
arm.rigify_layers[18].row = 12
- arm.rigify_layers[18].set = False
+ arm.rigify_layers[18].selset = False
arm.rigify_layers[18].group = 4
arm.rigify_layers[19].name = " "
arm.rigify_layers[19].row = 1
- arm.rigify_layers[19].set = False
+ arm.rigify_layers[19].selset = False
arm.rigify_layers[19].group = 0
arm.rigify_layers[20].name = " "
arm.rigify_layers[20].row = 1
- arm.rigify_layers[20].set = False
+ arm.rigify_layers[20].selset = False
arm.rigify_layers[20].group = 0
arm.rigify_layers[21].name = "Claws"
arm.rigify_layers[21].row = 13
- arm.rigify_layers[21].set = False
+ arm.rigify_layers[21].selset = False
arm.rigify_layers[21].group = 6
arm.rigify_layers[22].name = "Claws (Tweak)"
arm.rigify_layers[22].row = 14
- arm.rigify_layers[22].set = False
+ arm.rigify_layers[22].selset = False
arm.rigify_layers[22].group = 4
arm.rigify_layers[23].name = " "
arm.rigify_layers[23].row = 1
- arm.rigify_layers[23].set = False
+ arm.rigify_layers[23].selset = False
arm.rigify_layers[23].group = 0
arm.rigify_layers[24].name = "Feathers"
arm.rigify_layers[24].row = 8
- arm.rigify_layers[24].set = False
+ arm.rigify_layers[24].selset = False
arm.rigify_layers[24].group = 6
arm.rigify_layers[25].name = " "
arm.rigify_layers[25].row = 1
- arm.rigify_layers[25].set = False
+ arm.rigify_layers[25].selset = False
arm.rigify_layers[25].group = 0
arm.rigify_layers[26].name = " "
arm.rigify_layers[26].row = 1
- arm.rigify_layers[26].set = False
+ arm.rigify_layers[26].selset = False
arm.rigify_layers[26].group = 0
arm.rigify_layers[27].name = " "
arm.rigify_layers[27].row = 1
- arm.rigify_layers[27].set = False
+ arm.rigify_layers[27].selset = False
arm.rigify_layers[27].group = 0
arm.rigify_layers[28].name = "Root"
arm.rigify_layers[28].row = 14
- arm.rigify_layers[28].set = False
+ arm.rigify_layers[28].selset = False
arm.rigify_layers[28].group = 1
bones = {}
diff --git a/rigify/metarigs/Animals/cat.py b/rigify/metarigs/Animals/cat.py
index 836c9794..d3de1192 100644
--- a/rigify/metarigs/Animals/cat.py
+++ b/rigify/metarigs/Animals/cat.py
@@ -48,119 +48,119 @@ def create(obj):
arm.rigify_layers[0].name = "Face"
arm.rigify_layers[0].row = 1
- arm.rigify_layers[0].set = False
+ arm.rigify_layers[0].selset = False
arm.rigify_layers[0].group = 5
arm.rigify_layers[1].name = "Face (Primary)"
arm.rigify_layers[1].row = 2
- arm.rigify_layers[1].set = False
+ arm.rigify_layers[1].selset = False
arm.rigify_layers[1].group = 2
arm.rigify_layers[2].name = "Face (Secondary)"
arm.rigify_layers[2].row = 2
- arm.rigify_layers[2].set = False
+ arm.rigify_layers[2].selset = False
arm.rigify_layers[2].group = 3
arm.rigify_layers[3].name = "Spine"
arm.rigify_layers[3].row = 3
- arm.rigify_layers[3].set = False
+ arm.rigify_layers[3].selset = False
arm.rigify_layers[3].group = 3
arm.rigify_layers[4].name = "Spine (Tweak)"
arm.rigify_layers[4].row = 4
- arm.rigify_layers[4].set = False
+ arm.rigify_layers[4].selset = False
arm.rigify_layers[4].group = 4
arm.rigify_layers[5].name = "Paws"
arm.rigify_layers[5].row = 5
- arm.rigify_layers[5].set = False
+ arm.rigify_layers[5].selset = False
arm.rigify_layers[5].group = 6
arm.rigify_layers[6].name = "Paws (Tweak)"
arm.rigify_layers[6].row = 6
- arm.rigify_layers[6].set = False
+ arm.rigify_layers[6].selset = False
arm.rigify_layers[6].group = 4
arm.rigify_layers[7].name = "Arm.L (IK)"
arm.rigify_layers[7].row = 7
- arm.rigify_layers[7].set = False
+ arm.rigify_layers[7].selset = False
arm.rigify_layers[7].group = 2
arm.rigify_layers[8].name = "Arm.L (FK)"
arm.rigify_layers[8].row = 8
- arm.rigify_layers[8].set = False
+ arm.rigify_layers[8].selset = False
arm.rigify_layers[8].group = 5
arm.rigify_layers[9].name = "Arm,L (Tweak)"
arm.rigify_layers[9].row = 9
- arm.rigify_layers[9].set = False
+ arm.rigify_layers[9].selset = False
arm.rigify_layers[9].group = 4
arm.rigify_layers[10].name = "Arm.R (IK)"
arm.rigify_layers[10].row = 7
- arm.rigify_layers[10].set = False
+ arm.rigify_layers[10].selset = False
arm.rigify_layers[10].group = 2
arm.rigify_layers[11].name = "Arm.R (FK)"
arm.rigify_layers[11].row = 8
- arm.rigify_layers[11].set = False
+ arm.rigify_layers[11].selset = False
arm.rigify_layers[11].group = 5
arm.rigify_layers[12].name = "Arm.R (Tweak)"
arm.rigify_layers[12].row = 9
- arm.rigify_layers[12].set = False
+ arm.rigify_layers[12].selset = False
arm.rigify_layers[12].group = 4
arm.rigify_layers[13].name = "Leg.L (IK)"
arm.rigify_layers[13].row = 10
- arm.rigify_layers[13].set = False
+ arm.rigify_layers[13].selset = False
arm.rigify_layers[13].group = 2
arm.rigify_layers[14].name = "Leg.L (FK)"
arm.rigify_layers[14].row = 11
- arm.rigify_layers[14].set = False
+ arm.rigify_layers[14].selset = False
arm.rigify_layers[14].group = 5
arm.rigify_layers[15].name = "Leg.L (Tweak)"
arm.rigify_layers[15].row = 12
- arm.rigify_layers[15].set = False
+ arm.rigify_layers[15].selset = False
arm.rigify_layers[15].group = 4
arm.rigify_layers[16].name = "Leg.R (IK)"
arm.rigify_layers[16].row = 10
- arm.rigify_layers[16].set = False
+ arm.rigify_layers[16].selset = False
arm.rigify_layers[16].group = 2
arm.rigify_layers[17].name = "Leg.R (FK)"
arm.rigify_layers[17].row = 11
- arm.rigify_layers[17].set = False
+ arm.rigify_layers[17].selset = False
arm.rigify_layers[17].group = 5
arm.rigify_layers[18].name = "Leg.R (Tweak)"
arm.rigify_layers[18].row = 12
- arm.rigify_layers[18].set = False
+ arm.rigify_layers[18].selset = False
arm.rigify_layers[18].group = 4
arm.rigify_layers[19].name = "Tail"
arm.rigify_layers[19].row = 13
- arm.rigify_layers[19].set = False
+ arm.rigify_layers[19].selset = False
arm.rigify_layers[19].group = 3
arm.rigify_layers[20].name = "Tail (Tweaks)"
arm.rigify_layers[20].row = 14
- arm.rigify_layers[20].set = False
+ arm.rigify_layers[20].selset = False
arm.rigify_layers[20].group = 4
arm.rigify_layers[21].name = " "
arm.rigify_layers[21].row = 1
- arm.rigify_layers[21].set = False
+ arm.rigify_layers[21].selset = False
arm.rigify_layers[21].group = 0
arm.rigify_layers[22].name = " "
arm.rigify_layers[22].row = 1
- arm.rigify_layers[22].set = False
+ arm.rigify_layers[22].selset = False
arm.rigify_layers[22].group = 0
arm.rigify_layers[23].name = " "
arm.rigify_layers[23].row = 1
- arm.rigify_layers[23].set = False
+ arm.rigify_layers[23].selset = False
arm.rigify_layers[23].group = 0
arm.rigify_layers[24].name = " "
arm.rigify_layers[24].row = 1
- arm.rigify_layers[24].set = False
+ arm.rigify_layers[24].selset = False
arm.rigify_layers[24].group = 0
arm.rigify_layers[25].name = " "
arm.rigify_layers[25].row = 1
- arm.rigify_layers[25].set = False
+ arm.rigify_layers[25].selset = False
arm.rigify_layers[25].group = 0
arm.rigify_layers[26].name = " "
arm.rigify_layers[26].row = 1
- arm.rigify_layers[26].set = False
+ arm.rigify_layers[26].selset = False
arm.rigify_layers[26].group = 0
arm.rigify_layers[27].name = " "
arm.rigify_layers[27].row = 1
- arm.rigify_layers[27].set = False
+ arm.rigify_layers[27].selset = False
arm.rigify_layers[27].group = 0
arm.rigify_layers[28].name = "Root"
arm.rigify_layers[28].row = 16
- arm.rigify_layers[28].set = False
+ arm.rigify_layers[28].selset = False
arm.rigify_layers[28].group = 1
bones = {}
diff --git a/rigify/metarigs/Animals/horse.py b/rigify/metarigs/Animals/horse.py
index 72ed1de2..358a9ceb 100644
--- a/rigify/metarigs/Animals/horse.py
+++ b/rigify/metarigs/Animals/horse.py
@@ -48,119 +48,119 @@ def create(obj):
arm.rigify_layers[0].name = "Face"
arm.rigify_layers[0].row = 1
- arm.rigify_layers[0].set = False
+ arm.rigify_layers[0].selset = False
arm.rigify_layers[0].group = 5
arm.rigify_layers[1].name = "Face (Primary)"
arm.rigify_layers[1].row = 2
- arm.rigify_layers[1].set = False
+ arm.rigify_layers[1].selset = False
arm.rigify_layers[1].group = 4
arm.rigify_layers[2].name = " "
arm.rigify_layers[2].row = 3
- arm.rigify_layers[2].set = False
+ arm.rigify_layers[2].selset = False
arm.rigify_layers[2].group = 0
arm.rigify_layers[3].name = "Spine"
arm.rigify_layers[3].row = 4
- arm.rigify_layers[3].set = False
+ arm.rigify_layers[3].selset = False
arm.rigify_layers[3].group = 3
arm.rigify_layers[4].name = "Spine (Tweak)"
arm.rigify_layers[4].row = 5
- arm.rigify_layers[4].set = False
+ arm.rigify_layers[4].selset = False
arm.rigify_layers[4].group = 4
arm.rigify_layers[5].name = " "
arm.rigify_layers[5].row = 1
- arm.rigify_layers[5].set = False
+ arm.rigify_layers[5].selset = False
arm.rigify_layers[5].group = 0
arm.rigify_layers[6].name = " "
arm.rigify_layers[6].row = 1
- arm.rigify_layers[6].set = False
+ arm.rigify_layers[6].selset = False
arm.rigify_layers[6].group = 0
arm.rigify_layers[7].name = "Arm.L (IK)"
arm.rigify_layers[7].row = 7
- arm.rigify_layers[7].set = False
+ arm.rigify_layers[7].selset = False
arm.rigify_layers[7].group = 2
arm.rigify_layers[8].name = "Arm.L (FK)"
arm.rigify_layers[8].row = 8
- arm.rigify_layers[8].set = False
+ arm.rigify_layers[8].selset = False
arm.rigify_layers[8].group = 5
arm.rigify_layers[9].name = "Arm.L (Tweak)"
arm.rigify_layers[9].row = 9
- arm.rigify_layers[9].set = False
+ arm.rigify_layers[9].selset = False
arm.rigify_layers[9].group = 4
arm.rigify_layers[10].name = "Arm.R (IK)"
arm.rigify_layers[10].row = 7
- arm.rigify_layers[10].set = False
+ arm.rigify_layers[10].selset = False
arm.rigify_layers[10].group = 2
arm.rigify_layers[11].name = "Arm.R (FK)"
arm.rigify_layers[11].row = 8
- arm.rigify_layers[11].set = False
+ arm.rigify_layers[11].selset = False
arm.rigify_layers[11].group = 5
arm.rigify_layers[12].name = "Arm.R (Tweak)"
arm.rigify_layers[12].row = 9
- arm.rigify_layers[12].set = False
+ arm.rigify_layers[12].selset = False
arm.rigify_layers[12].group = 4
arm.rigify_layers[13].name = "Leg.L (IK)"
arm.rigify_layers[13].row = 10
- arm.rigify_layers[13].set = False
+ arm.rigify_layers[13].selset = False
arm.rigify_layers[13].group = 2
arm.rigify_layers[14].name = "Leg.L (FK)"
arm.rigify_layers[14].row = 11
- arm.rigify_layers[14].set = False
+ arm.rigify_layers[14].selset = False
arm.rigify_layers[14].group = 5
arm.rigify_layers[15].name = "Leg.L (Tweak)"
arm.rigify_layers[15].row = 12
- arm.rigify_layers[15].set = False
+ arm.rigify_layers[15].selset = False
arm.rigify_layers[15].group = 4
arm.rigify_layers[16].name = "Leg.R (IK)"
arm.rigify_layers[16].row = 10
- arm.rigify_layers[16].set = False
+ arm.rigify_layers[16].selset = False
arm.rigify_layers[16].group = 2
arm.rigify_layers[17].name = "Leg.R (FK)"
arm.rigify_layers[17].row = 11
- arm.rigify_layers[17].set = False
+ arm.rigify_layers[17].selset = False
arm.rigify_layers[17].group = 5
arm.rigify_layers[18].name = "Leg.R (Tweak)"
arm.rigify_layers[18].row = 12
- arm.rigify_layers[18].set = False
+ arm.rigify_layers[18].selset = False
arm.rigify_layers[18].group = 4
arm.rigify_layers[19].name = "Tail"
arm.rigify_layers[19].row = 13
- arm.rigify_layers[19].set = False
+ arm.rigify_layers[19].selset = False
arm.rigify_layers[19].group = 6
arm.rigify_layers[20].name = " "
arm.rigify_layers[20].row = 1
- arm.rigify_layers[20].set = False
+ arm.rigify_layers[20].selset = False
arm.rigify_layers[20].group = 4
arm.rigify_layers[21].name = "Hair"
arm.rigify_layers[21].row = 14
- arm.rigify_layers[21].set = False
+ arm.rigify_layers[21].selset = False
arm.rigify_layers[21].group = 6
arm.rigify_layers[22].name = " "
arm.rigify_layers[22].row = 1
- arm.rigify_layers[22].set = False
+ arm.rigify_layers[22].selset = False
arm.rigify_layers[22].group = 0
arm.rigify_layers[23].name = " "
arm.rigify_layers[23].row = 1
- arm.rigify_layers[23].set = False
+ arm.rigify_layers[23].selset = False
arm.rigify_layers[23].group = 0
arm.rigify_layers[24].name = " "
arm.rigify_layers[24].row = 1
- arm.rigify_layers[24].set = False
+ arm.rigify_layers[24].selset = False
arm.rigify_layers[24].group = 0
arm.rigify_layers[25].name = " "
arm.rigify_layers[25].row = 1
- arm.rigify_layers[25].set = False
+ arm.rigify_layers[25].selset = False
arm.rigify_layers[25].group = 0
arm.rigify_layers[26].name = " "
arm.rigify_layers[26].row = 1
- arm.rigify_layers[26].set = False
+ arm.rigify_layers[26].selset = False
arm.rigify_layers[26].group = 0
arm.rigify_layers[27].name = " "
arm.rigify_layers[27].row = 1
- arm.rigify_layers[27].set = False
+ arm.rigify_layers[27].selset = False
arm.rigify_layers[27].group = 0
arm.rigify_layers[28].name = "Root"
arm.rigify_layers[28].row = 14
- arm.rigify_layers[28].set = False
+ arm.rigify_layers[28].selset = False
arm.rigify_layers[28].group = 1
diff --git a/rigify/metarigs/Animals/shark.py b/rigify/metarigs/Animals/shark.py
index bcc53df3..1ee1d0d9 100644
--- a/rigify/metarigs/Animals/shark.py
+++ b/rigify/metarigs/Animals/shark.py
@@ -48,119 +48,119 @@ def create(obj):
arm.rigify_layers[0].name = "Face"
arm.rigify_layers[0].row = 1
- arm.rigify_layers[0].set = False
+ arm.rigify_layers[0].selset = False
arm.rigify_layers[0].group = 5
arm.rigify_layers[1].name = "Face (Tweak)"
arm.rigify_layers[1].row = 2
- arm.rigify_layers[1].set = False
+ arm.rigify_layers[1].selset = False
arm.rigify_layers[1].group = 4
arm.rigify_layers[2].name = " "
arm.rigify_layers[2].row = 1
- arm.rigify_layers[2].set = False
+ arm.rigify_layers[2].selset = False
arm.rigify_layers[2].group = 0
arm.rigify_layers[3].name = "Spine"
arm.rigify_layers[3].row = 3
- arm.rigify_layers[3].set = False
+ arm.rigify_layers[3].selset = False
arm.rigify_layers[3].group = 3
arm.rigify_layers[4].name = "Spine (Tweak)"
arm.rigify_layers[4].row = 4
- arm.rigify_layers[4].set = False
+ arm.rigify_layers[4].selset = False
arm.rigify_layers[4].group = 4
arm.rigify_layers[5].name = "Tail"
arm.rigify_layers[5].row = 5
- arm.rigify_layers[5].set = False
+ arm.rigify_layers[5].selset = False
arm.rigify_layers[5].group = 6
arm.rigify_layers[6].name = "Fins.L"
arm.rigify_layers[6].row = 6
- arm.rigify_layers[6].set = False
+ arm.rigify_layers[6].selset = False
arm.rigify_layers[6].group = 5
arm.rigify_layers[7].name = "Fins.L (Tweak)"
arm.rigify_layers[7].row = 7
- arm.rigify_layers[7].set = False
+ arm.rigify_layers[7].selset = False
arm.rigify_layers[7].group = 4
arm.rigify_layers[8].name = "Fins.R"
arm.rigify_layers[8].row = 6
- arm.rigify_layers[8].set = False
+ arm.rigify_layers[8].selset = False
arm.rigify_layers[8].group = 5
arm.rigify_layers[9].name = "Fins.R (Tweak)"
arm.rigify_layers[9].row = 7
- arm.rigify_layers[9].set = False
+ arm.rigify_layers[9].selset = False
arm.rigify_layers[9].group = 4
arm.rigify_layers[10].name = "Fins"
arm.rigify_layers[10].row = 8
- arm.rigify_layers[10].set = False
+ arm.rigify_layers[10].selset = False
arm.rigify_layers[10].group = 3
arm.rigify_layers[11].name = "Fins (Tweak)"
arm.rigify_layers[11].row = 9
- arm.rigify_layers[11].set = False
+ arm.rigify_layers[11].selset = False
arm.rigify_layers[11].group = 4
arm.rigify_layers[12].name = " "
arm.rigify_layers[12].row = 1
- arm.rigify_layers[12].set = False
+ arm.rigify_layers[12].selset = False
arm.rigify_layers[12].group = 0
arm.rigify_layers[13].name = " "
arm.rigify_layers[13].row = 1
- arm.rigify_layers[13].set = False
+ arm.rigify_layers[13].selset = False
arm.rigify_layers[13].group = 6
arm.rigify_layers[14].name = " "
arm.rigify_layers[14].row = 1
- arm.rigify_layers[14].set = False
+ arm.rigify_layers[14].selset = False
arm.rigify_layers[14].group = 0
arm.rigify_layers[15].name = " "
arm.rigify_layers[15].row = 1
- arm.rigify_layers[15].set = False
+ arm.rigify_layers[15].selset = False
arm.rigify_layers[15].group = 0
arm.rigify_layers[16].name = " "
arm.rigify_layers[16].row = 1
- arm.rigify_layers[16].set = False
+ arm.rigify_layers[16].selset = False
arm.rigify_layers[16].group = 0
arm.rigify_layers[17].name = " "
arm.rigify_layers[17].row = 1
- arm.rigify_layers[17].set = False
+ arm.rigify_layers[17].selset = False
arm.rigify_layers[17].group = 0
arm.rigify_layers[18].name = " "
arm.rigify_layers[18].row = 1
- arm.rigify_layers[18].set = False
+ arm.rigify_layers[18].selset = False
arm.rigify_layers[18].group = 0
arm.rigify_layers[19].name = " "
arm.rigify_layers[19].row = 1
- arm.rigify_layers[19].set = False
+ arm.rigify_layers[19].selset = False
arm.rigify_layers[19].group = 0
arm.rigify_layers[20].name = " "
arm.rigify_layers[20].row = 1
- arm.rigify_layers[20].set = False
+ arm.rigify_layers[20].selset = False
arm.rigify_layers[20].group = 0
arm.rigify_layers[21].name = " "
arm.rigify_layers[21].row = 1
- arm.rigify_layers[21].set = False
+ arm.rigify_layers[21].selset = False
arm.rigify_layers[21].group = 0
arm.rigify_layers[22].name = " "
arm.rigify_layers[22].row = 1
- arm.rigify_layers[22].set = False
+ arm.rigify_layers[22].selset = False
arm.rigify_layers[22].group = 0
arm.rigify_layers[23].name = " "
arm.rigify_layers[23].row = 1
- arm.rigify_layers[23].set = False
+ arm.rigify_layers[23].selset = False
arm.rigify_layers[23].group = 0
arm.rigify_layers[24].name = " "
arm.rigify_layers[24].row = 1
- arm.rigify_layers[24].set = False
+ arm.rigify_layers[24].selset = False
arm.rigify_layers[24].group = 0
arm.rigify_layers[25].name = " "
arm.rigify_layers[25].row = 1
- arm.rigify_layers[25].set = False
+ arm.rigify_layers[25].selset = False
arm.rigify_layers[25].group = 0
arm.rigify_layers[26].name = " "
arm.rigify_layers[26].row = 1
- arm.rigify_layers[26].set = False
+ arm.rigify_layers[26].selset = False
arm.rigify_layers[26].group = 0
arm.rigify_layers[27].name = " "
arm.rigify_layers[27].row = 1
- arm.rigify_layers[27].set = False
+ arm.rigify_layers[27].selset = False
arm.rigify_layers[27].group = 0
arm.rigify_layers[28].name = "Root"
arm.rigify_layers[28].row = 14
- arm.rigify_layers[28].set = False
+ arm.rigify_layers[28].selset = False
arm.rigify_layers[28].group = 1
bones = {}
diff --git a/rigify/metarigs/Animals/wolf.py b/rigify/metarigs/Animals/wolf.py
index 20aa121c..4cccf085 100644
--- a/rigify/metarigs/Animals/wolf.py
+++ b/rigify/metarigs/Animals/wolf.py
@@ -48,119 +48,119 @@ def create(obj):
arm.rigify_layers[0].name = "Face"
arm.rigify_layers[0].row = 1
- arm.rigify_layers[0].set = False
+ arm.rigify_layers[0].selset = False
arm.rigify_layers[0].group = 5
arm.rigify_layers[1].name = "Face (Primary)"
arm.rigify_layers[1].row = 2
- arm.rigify_layers[1].set = False
+ arm.rigify_layers[1].selset = False
arm.rigify_layers[1].group = 2
arm.rigify_layers[2].name = "Face (Secondary)"
arm.rigify_layers[2].row = 2
- arm.rigify_layers[2].set = False
+ arm.rigify_layers[2].selset = False
arm.rigify_layers[2].group = 3
arm.rigify_layers[3].name = "Spine"
arm.rigify_layers[3].row = 3
- arm.rigify_layers[3].set = False
+ arm.rigify_layers[3].selset = False
arm.rigify_layers[3].group = 3
arm.rigify_layers[4].name = "Spine (Tweak)"
arm.rigify_layers[4].row = 4
- arm.rigify_layers[4].set = False
+ arm.rigify_layers[4].selset = False
arm.rigify_layers[4].group = 4
arm.rigify_layers[5].name = "Paws"
arm.rigify_layers[5].row = 5
- arm.rigify_layers[5].set = False
+ arm.rigify_layers[5].selset = False
arm.rigify_layers[5].group = 6
arm.rigify_layers[6].name = "Paws (Tweak)"
arm.rigify_layers[6].row = 6
- arm.rigify_layers[6].set = False
+ arm.rigify_layers[6].selset = False
arm.rigify_layers[6].group = 4
arm.rigify_layers[7].name = "Arm.L (IK)"
arm.rigify_layers[7].row = 7
- arm.rigify_layers[7].set = False
+ arm.rigify_layers[7].selset = False
arm.rigify_layers[7].group = 2
arm.rigify_layers[8].name = "Arm.L (FK)"
arm.rigify_layers[8].row = 8
- arm.rigify_layers[8].set = False
+ arm.rigify_layers[8].selset = False
arm.rigify_layers[8].group = 5
arm.rigify_layers[9].name = "Arm.L (Tweak)"
arm.rigify_layers[9].row = 9
- arm.rigify_layers[9].set = False
+ arm.rigify_layers[9].selset = False
arm.rigify_layers[9].group = 4
arm.rigify_layers[10].name = "Arm.R (IK)"
arm.rigify_layers[10].row = 7
- arm.rigify_layers[10].set = False
+ arm.rigify_layers[10].selset = False
arm.rigify_layers[10].group = 2
arm.rigify_layers[11].name = "Arm.R (FK)"
arm.rigify_layers[11].row = 8
- arm.rigify_layers[11].set = False
+ arm.rigify_layers[11].selset = False
arm.rigify_layers[11].group = 5
arm.rigify_layers[12].name = "Arm.R (Tweak)"
arm.rigify_layers[12].row = 9
- arm.rigify_layers[12].set = False
+ arm.rigify_layers[12].selset = False
arm.rigify_layers[12].group = 4
arm.rigify_layers[13].name = "Leg.L (IK)"
arm.rigify_layers[13].row = 10
- arm.rigify_layers[13].set = False
+ arm.rigify_layers[13].selset = False
arm.rigify_layers[13].group = 2
arm.rigify_layers[14].name = "Leg.L (FK)"
arm.rigify_layers[14].row = 11
- arm.rigify_layers[14].set = False
+ arm.rigify_layers[14].selset = False
arm.rigify_layers[14].group = 5
arm.rigify_layers[15].name = "Leg.L (Tweak)"
arm.rigify_layers[15].row = 12
- arm.rigify_layers[15].set = False
+ arm.rigify_layers[15].selset = False
arm.rigify_layers[15].group = 4
arm.rigify_layers[16].name = "Leg.R (IK)"
arm.rigify_layers[16].row = 10
- arm.rigify_layers[16].set = False
+ arm.rigify_layers[16].selset = False
arm.rigify_layers[16].group = 2
arm.rigify_layers[17].name = "Leg.R (FK)"
arm.rigify_layers[17].row = 11
- arm.rigify_layers[17].set = False
+ arm.rigify_layers[17].selset = False
arm.rigify_layers[17].group = 5
arm.rigify_layers[18].name = "Leg.R (Tweak)"
arm.rigify_layers[18].row = 12
- arm.rigify_layers[18].set = False
+ arm.rigify_layers[18].selset = False
arm.rigify_layers[18].group = 4
arm.rigify_layers[19].name = "Tail"
arm.rigify_layers[19].row = 13
- arm.rigify_layers[19].set = False
+ arm.rigify_layers[19].selset = False
arm.rigify_layers[19].group = 6
arm.rigify_layers[20].name = ""
arm.rigify_layers[20].row = 1
- arm.rigify_layers[20].set = False
+ arm.rigify_layers[20].selset = False
arm.rigify_layers[20].group = 0
arm.rigify_layers[21].name = ""
arm.rigify_layers[21].row = 13
- arm.rigify_layers[21].set = False
+ arm.rigify_layers[21].selset = False
arm.rigify_layers[21].group = 0
arm.rigify_layers[22].name = ""
arm.rigify_layers[22].row = 13
- arm.rigify_layers[22].set = False
+ arm.rigify_layers[22].selset = False
arm.rigify_layers[22].group = 0
arm.rigify_layers[23].name = ""
arm.rigify_layers[23].row = 1
- arm.rigify_layers[23].set = False
+ arm.rigify_layers[23].selset = False
arm.rigify_layers[23].group = 0
arm.rigify_layers[24].name = ""
arm.rigify_layers[24].row = 1
- arm.rigify_layers[24].set = False
+ arm.rigify_layers[24].selset = False
arm.rigify_layers[24].group = 0
arm.rigify_layers[25].name = ""
arm.rigify_layers[25].row = 1
- arm.rigify_layers[25].set = False
+ arm.rigify_layers[25].selset = False
arm.rigify_layers[25].group = 0
arm.rigify_layers[26].name = ""
arm.rigify_layers[26].row = 1
- arm.rigify_layers[26].set = False
+ arm.rigify_layers[26].selset = False
arm.rigify_layers[26].group = 0
arm.rigify_layers[27].name = ""
arm.rigify_layers[27].row = 1
- arm.rigify_layers[27].set = False
+ arm.rigify_layers[27].selset = False
arm.rigify_layers[27].group = 0
arm.rigify_layers[28].name = "Root"
arm.rigify_layers[28].row = 14
- arm.rigify_layers[28].set = False
+ arm.rigify_layers[28].selset = False
arm.rigify_layers[28].group = 1
bones = {}
diff --git a/rigify/metarigs/Basic/basic_human.py b/rigify/metarigs/Basic/basic_human.py
index 5b1a401d..01367a7b 100644
--- a/rigify/metarigs/Basic/basic_human.py
+++ b/rigify/metarigs/Basic/basic_human.py
@@ -48,119 +48,119 @@ def create(obj):
arm.rigify_layers[0].name = " "
arm.rigify_layers[0].row = 1
- arm.rigify_layers[0].set = False
+ arm.rigify_layers[0].selset = False
arm.rigify_layers[0].group = 0
arm.rigify_layers[1].name = " "
arm.rigify_layers[1].row = 1
- arm.rigify_layers[1].set = False
+ arm.rigify_layers[1].selset = False
arm.rigify_layers[1].group = 0
arm.rigify_layers[2].name = " "
arm.rigify_layers[2].row = 1
- arm.rigify_layers[2].set = False
+ arm.rigify_layers[2].selset = False
arm.rigify_layers[2].group = 0
arm.rigify_layers[3].name = "Torso"
arm.rigify_layers[3].row = 3
- arm.rigify_layers[3].set = False
+ arm.rigify_layers[3].selset = False
arm.rigify_layers[3].group = 3
arm.rigify_layers[4].name = "Torso (Tweak)"
arm.rigify_layers[4].row = 4
- arm.rigify_layers[4].set = False
+ arm.rigify_layers[4].selset = False
arm.rigify_layers[4].group = 4
arm.rigify_layers[5].name = " "
arm.rigify_layers[5].row = 1
- arm.rigify_layers[5].set = False
+ arm.rigify_layers[5].selset = False
arm.rigify_layers[5].group = 0
arm.rigify_layers[6].name = " "
arm.rigify_layers[6].row = 1
- arm.rigify_layers[6].set = False
+ arm.rigify_layers[6].selset = False
arm.rigify_layers[6].group = 0
arm.rigify_layers[7].name = "Arm.L (IK)"
arm.rigify_layers[7].row = 7
- arm.rigify_layers[7].set = False
+ arm.rigify_layers[7].selset = False
arm.rigify_layers[7].group = 2
arm.rigify_layers[8].name = "Arm.L (FK)"
arm.rigify_layers[8].row = 8
- arm.rigify_layers[8].set = False
+ arm.rigify_layers[8].selset = False
arm.rigify_layers[8].group = 5
arm.rigify_layers[9].name = "Arm.L (Tweak)"
arm.rigify_layers[9].row = 9
- arm.rigify_layers[9].set = False
+ arm.rigify_layers[9].selset = False
arm.rigify_layers[9].group = 4
arm.rigify_layers[10].name = "Arm.R (IK)"
arm.rigify_layers[10].row = 7
- arm.rigify_layers[10].set = False
+ arm.rigify_layers[10].selset = False
arm.rigify_layers[10].group = 2
arm.rigify_layers[11].name = "Arm.R (FK)"
arm.rigify_layers[11].row = 8
- arm.rigify_layers[11].set = False
+ arm.rigify_layers[11].selset = False
arm.rigify_layers[11].group = 5
arm.rigify_layers[12].name = "Arm.R (Tweak)"
arm.rigify_layers[12].row = 9
- arm.rigify_layers[12].set = False
+ arm.rigify_layers[12].selset = False
arm.rigify_layers[12].group = 4
arm.rigify_layers[13].name = "Leg.L (IK)"
arm.rigify_layers[13].row = 10
- arm.rigify_layers[13].set = False
+ arm.rigify_layers[13].selset = False
arm.rigify_layers[13].group = 2
arm.rigify_layers[14].name = "Leg.L (FK)"
arm.rigify_layers[14].row = 11
- arm.rigify_layers[14].set = False
+ arm.rigify_layers[14].selset = False
arm.rigify_layers[14].group = 5
arm.rigify_layers[15].name = "Leg.L (Tweak)"
arm.rigify_layers[15].row = 12
- arm.rigify_layers[15].set = False
+ arm.rigify_layers[15].selset = False
arm.rigify_layers[15].group = 4
arm.rigify_layers[16].name = "Leg.R (IK)"
arm.rigify_layers[16].row = 10
- arm.rigify_layers[16].set = False
+ arm.rigify_layers[16].selset = False
arm.rigify_layers[16].group = 2
arm.rigify_layers[17].name = "Leg.R (FK)"
arm.rigify_layers[17].row = 11
- arm.rigify_layers[17].set = False
+ arm.rigify_layers[17].selset = False
arm.rigify_layers[17].group = 5
arm.rigify_layers[18].name = "Leg.R (Tweak)"
arm.rigify_layers[18].row = 12
- arm.rigify_layers[18].set = False
+ arm.rigify_layers[18].selset = False
arm.rigify_layers[18].group = 4
arm.rigify_layers[19].name = ""
arm.rigify_layers[19].row = 1
- arm.rigify_layers[19].set = False
+ arm.rigify_layers[19].selset = False
arm.rigify_layers[19].group = 0
arm.rigify_layers[20].name = ""
arm.rigify_layers[20].row = 1
- arm.rigify_layers[20].set = False
+ arm.rigify_layers[20].selset = False
arm.rigify_layers[20].group = 0
arm.rigify_layers[21].name = ""
arm.rigify_layers[21].row = 1
- arm.rigify_layers[21].set = False
+ arm.rigify_layers[21].selset = False
arm.rigify_layers[21].group = 0
arm.rigify_layers[22].name = ""
arm.rigify_layers[22].row = 1
- arm.rigify_layers[22].set = False
+ arm.rigify_layers[22].selset = False
arm.rigify_layers[22].group = 0
arm.rigify_layers[23].name = ""
arm.rigify_layers[23].row = 1
- arm.rigify_layers[23].set = False
+ arm.rigify_layers[23].selset = False
arm.rigify_layers[23].group = 0
arm.rigify_layers[24].name = ""
arm.rigify_layers[24].row = 1
- arm.rigify_layers[24].set = False
+ arm.rigify_layers[24].selset = False
arm.rigify_layers[24].group = 0
arm.rigify_layers[25].name = ""
arm.rigify_layers[25].row = 1
- arm.rigify_layers[25].set = False
+ arm.rigify_layers[25].selset = False
arm.rigify_layers[25].group = 0
arm.rigify_layers[26].name = ""
arm.rigify_layers[26].row = 1
- arm.rigify_layers[26].set = False
+ arm.rigify_layers[26].selset = False
arm.rigify_layers[26].group = 0
arm.rigify_layers[27].name = ""
arm.rigify_layers[27].row = 1
- arm.rigify_layers[27].set = False
+ arm.rigify_layers[27].selset = False
arm.rigify_layers[27].group = 0
arm.rigify_layers[28].name = "Root"
arm.rigify_layers[28].row = 14
- arm.rigify_layers[28].set = False
+ arm.rigify_layers[28].selset = False
arm.rigify_layers[28].group = 1
bones = {}
diff --git a/rigify/metarigs/Basic/basic_quadruped.py b/rigify/metarigs/Basic/basic_quadruped.py
index c46743c7..5aa9f657 100644
--- a/rigify/metarigs/Basic/basic_quadruped.py
+++ b/rigify/metarigs/Basic/basic_quadruped.py
@@ -48,119 +48,119 @@ def create(obj):
arm.rigify_layers[0].name = " "
arm.rigify_layers[0].row = 1
- arm.rigify_layers[0].set = False
+ arm.rigify_layers[0].selset = False
arm.rigify_layers[0].group = 0
arm.rigify_layers[1].name = " "
arm.rigify_layers[1].row = 2
- arm.rigify_layers[1].set = False
+ arm.rigify_layers[1].selset = False
arm.rigify_layers[1].group = 0
arm.rigify_layers[2].name = " "
arm.rigify_layers[2].row = 2
- arm.rigify_layers[2].set = False
+ arm.rigify_layers[2].selset = False
arm.rigify_layers[2].group = 0
arm.rigify_layers[3].name = "Spine"
arm.rigify_layers[3].row = 3
- arm.rigify_layers[3].set = False
+ arm.rigify_layers[3].selset = False
arm.rigify_layers[3].group = 3
arm.rigify_layers[4].name = "Spine (Tweak)"
arm.rigify_layers[4].row = 4
- arm.rigify_layers[4].set = False
+ arm.rigify_layers[4].selset = False
arm.rigify_layers[4].group = 4
arm.rigify_layers[5].name = " "
arm.rigify_layers[5].row = 5
- arm.rigify_layers[5].set = False
+ arm.rigify_layers[5].selset = False
arm.rigify_layers[5].group = 0
arm.rigify_layers[6].name = " "
arm.rigify_layers[6].row = 6
- arm.rigify_layers[6].set = False
+ arm.rigify_layers[6].selset = False
arm.rigify_layers[6].group = 0
arm.rigify_layers[7].name = "Arm.L (IK)"
arm.rigify_layers[7].row = 7
- arm.rigify_layers[7].set = False
+ arm.rigify_layers[7].selset = False
arm.rigify_layers[7].group = 2
arm.rigify_layers[8].name = "Arm.L (FK)"
arm.rigify_layers[8].row = 8
- arm.rigify_layers[8].set = False
+ arm.rigify_layers[8].selset = False
arm.rigify_layers[8].group = 5
arm.rigify_layers[9].name = "Arm.L (Tweak)"
arm.rigify_layers[9].row = 9
- arm.rigify_layers[9].set = False
+ arm.rigify_layers[9].selset = False
arm.rigify_layers[9].group = 4
arm.rigify_layers[10].name = "Arm.R (IK)"
arm.rigify_layers[10].row = 7
- arm.rigify_layers[10].set = False
+ arm.rigify_layers[10].selset = False
arm.rigify_layers[10].group = 2
arm.rigify_layers[11].name = "Arm.R (FK)"
arm.rigify_layers[11].row = 8
- arm.rigify_layers[11].set = False
+ arm.rigify_layers[11].selset = False
arm.rigify_layers[11].group = 5
arm.rigify_layers[12].name = "Arm.R (Tweak)"
arm.rigify_layers[12].row = 9
- arm.rigify_layers[12].set = False
+ arm.rigify_layers[12].selset = False
arm.rigify_layers[12].group = 4
arm.rigify_layers[13].name = "Leg.L (IK)"
arm.rigify_layers[13].row = 10
- arm.rigify_layers[13].set = False
+ arm.rigify_layers[13].selset = False
arm.rigify_layers[13].group = 2
arm.rigify_layers[14].name = "Leg.L (FK)"
arm.rigify_layers[14].row = 11
- arm.rigify_layers[14].set = False
+ arm.rigify_layers[14].selset = False
arm.rigify_layers[14].group = 5
arm.rigify_layers[15].name = "Leg.L (Tweak)"
arm.rigify_layers[15].row = 12
- arm.rigify_layers[15].set = False
+ arm.rigify_layers[15].selset = False
arm.rigify_layers[15].group = 4
arm.rigify_layers[16].name = "Leg.R (IK)"
arm.rigify_layers[16].row = 10
- arm.rigify_layers[16].set = False
+ arm.rigify_layers[16].selset = False
arm.rigify_layers[16].group = 2
arm.rigify_layers[17].name = "Leg.R (FK)"
arm.rigify_layers[17].row = 11
- arm.rigify_layers[17].set = False
+ arm.rigify_layers[17].selset = False
arm.rigify_layers[17].group = 5
arm.rigify_layers[18].name = "Leg.R (Tweak)"
arm.rigify_layers[18].row = 12
- arm.rigify_layers[18].set = False
+ arm.rigify_layers[18].selset = False
arm.rigify_layers[18].group = 4
arm.rigify_layers[19].name = "Tail"
arm.rigify_layers[19].row = 13
- arm.rigify_layers[19].set = False
+ arm.rigify_layers[19].selset = False
arm.rigify_layers[19].group = 6
arm.rigify_layers[20].name = ""
arm.rigify_layers[20].row = 1
- arm.rigify_layers[20].set = False
+ arm.rigify_layers[20].selset = False
arm.rigify_layers[20].group = 0
arm.rigify_layers[21].name = ""
arm.rigify_layers[21].row = 13
- arm.rigify_layers[21].set = False
+ arm.rigify_layers[21].selset = False
arm.rigify_layers[21].group = 0
arm.rigify_layers[22].name = ""
arm.rigify_layers[22].row = 13
- arm.rigify_layers[22].set = False
+ arm.rigify_layers[22].selset = False
arm.rigify_layers[22].group = 0
arm.rigify_layers[23].name = ""
arm.rigify_layers[23].row = 1
- arm.rigify_layers[23].set = False
+ arm.rigify_layers[23].selset = False
arm.rigify_layers[23].group = 0
arm.rigify_layers[24].name = ""
arm.rigify_layers[24].row = 1
- arm.rigify_layers[24].set = False
+ arm.rigify_layers[24].selset = False
arm.rigify_layers[24].group = 0
arm.rigify_layers[25].name = ""
arm.rigify_layers[25].row = 1
- arm.rigify_layers[25].set = False
+ arm.rigify_layers[25].selset = False
arm.rigify_layers[25].group = 0
arm.rigify_layers[26].name = ""
arm.rigify_layers[26].row = 1
- arm.rigify_layers[26].set = False
+ arm.rigify_layers[26].selset = False
arm.rigify_layers[26].group = 0
arm.rigify_layers[27].name = ""
arm.rigify_layers[27].row = 1
- arm.rigify_layers[27].set = False
+ arm.rigify_layers[27].selset = False
arm.rigify_layers[27].group = 0
arm.rigify_layers[28].name = "Root"
arm.rigify_layers[28].row = 14
- arm.rigify_layers[28].set = False
+ arm.rigify_layers[28].selset = False
arm.rigify_layers[28].group = 1
bones = {}
diff --git a/rigify/metarigs/human.py b/rigify/metarigs/human.py
index a31a107c..bbba8eed 100644
--- a/rigify/metarigs/human.py
+++ b/rigify/metarigs/human.py
@@ -48,119 +48,119 @@ def create(obj):
arm.rigify_layers[0].name = "Face"
arm.rigify_layers[0].row = 1
- arm.rigify_layers[0].set = False
+ arm.rigify_layers[0].selset = False
arm.rigify_layers[0].group = 5
arm.rigify_layers[1].name = "Face (Primary)"
arm.rigify_layers[1].row = 2
- arm.rigify_layers[1].set = False
+ arm.rigify_layers[1].selset = False
arm.rigify_layers[1].group = 2
arm.rigify_layers[2].name = "Face (Secondary)"
arm.rigify_layers[2].row = 2
- arm.rigify_layers[2].set = False
+ arm.rigify_layers[2].selset = False
arm.rigify_layers[2].group = 3
arm.rigify_layers[3].name = "Torso"
arm.rigify_layers[3].row = 3
- arm.rigify_layers[3].set = False
+ arm.rigify_layers[3].selset = False
arm.rigify_layers[3].group = 3
arm.rigify_layers[4].name = "Torso (Tweak)"
arm.rigify_layers[4].row = 4
- arm.rigify_layers[4].set = False
+ arm.rigify_layers[4].selset = False
arm.rigify_layers[4].group = 4
arm.rigify_layers[5].name = "Fingers"
arm.rigify_layers[5].row = 5
- arm.rigify_layers[5].set = False
+ arm.rigify_layers[5].selset = False
arm.rigify_layers[5].group = 6
arm.rigify_layers[6].name = "Fingers (Tweak)"
arm.rigify_layers[6].row = 6
- arm.rigify_layers[6].set = False
+ arm.rigify_layers[6].selset = False
arm.rigify_layers[6].group = 4
arm.rigify_layers[7].name = "Arm.L (IK)"
arm.rigify_layers[7].row = 7
- arm.rigify_layers[7].set = False
+ arm.rigify_layers[7].selset = False
arm.rigify_layers[7].group = 2
arm.rigify_layers[8].name = "Arm.L (FK)"
arm.rigify_layers[8].row = 8
- arm.rigify_layers[8].set = False
+ arm.rigify_layers[8].selset = False
arm.rigify_layers[8].group = 5
arm.rigify_layers[9].name = "Arm.L (Tweak)"
arm.rigify_layers[9].row = 9
- arm.rigify_layers[9].set = False
+ arm.rigify_layers[9].selset = False
arm.rigify_layers[9].group = 4
arm.rigify_layers[10].name = "Arm.R (IK)"
arm.rigify_layers[10].row = 7
- arm.rigify_layers[10].set = False
+ arm.rigify_layers[10].selset = False
arm.rigify_layers[10].group = 2
arm.rigify_layers[11].name = "Arm.R (FK)"
arm.rigify_layers[11].row = 8
- arm.rigify_layers[11].set = False
+ arm.rigify_layers[11].selset = False
arm.rigify_layers[11].group = 5
arm.rigify_layers[12].name = "Arm.R (Tweak)"
arm.rigify_layers[12].row = 9
- arm.rigify_layers[12].set = False
+ arm.rigify_layers[12].selset = False
arm.rigify_layers[12].group = 4
arm.rigify_layers[13].name = "Leg.L (IK)"
arm.rigify_layers[13].row = 10
- arm.rigify_layers[13].set = False
+ arm.rigify_layers[13].selset = False
arm.rigify_layers[13].group = 2
arm.rigify_layers[14].name = "Leg.L (FK)"
arm.rigify_layers[14].row = 11
- arm.rigify_layers[14].set = False
+ arm.rigify_layers[14].selset = False
arm.rigify_layers[14].group = 5
arm.rigify_layers[15].name = "Leg.L (Tweak)"
arm.rigify_layers[15].row = 12
- arm.rigify_layers[15].set = False
+ arm.rigify_layers[15].selset = False
arm.rigify_layers[15].group = 4
arm.rigify_layers[16].name = "Leg.R (IK)"
arm.rigify_layers[16].row = 10
- arm.rigify_layers[16].set = False
+ arm.rigify_layers[16].selset = False
arm.rigify_layers[16].group = 2
arm.rigify_layers[17].name = "Leg.R (FK)"
arm.rigify_layers[17].row = 11
- arm.rigify_layers[17].set = False
+ arm.rigify_layers[17].selset = False
arm.rigify_layers[17].group = 5
arm.rigify_layers[18].name = "Leg.R (Tweak)"
arm.rigify_layers[18].row = 12
- arm.rigify_layers[18].set = False
+ arm.rigify_layers[18].selset = False
arm.rigify_layers[18].group = 4
arm.rigify_layers[19].name = ""
arm.rigify_layers[19].row = 1
- arm.rigify_layers[19].set = False
+ arm.rigify_layers[19].selset = False
arm.rigify_layers[19].group = 0
arm.rigify_layers[20].name = ""
arm.rigify_layers[20].row = 1
- arm.rigify_layers[20].set = False
+ arm.rigify_layers[20].selset = False
arm.rigify_layers[20].group = 0
arm.rigify_layers[21].name = ""
arm.rigify_layers[21].row = 1
- arm.rigify_layers[21].set = False
+ arm.rigify_layers[21].selset = False
arm.rigify_layers[21].group = 0
arm.rigify_layers[22].name = ""
arm.rigify_layers[22].row = 1
- arm.rigify_layers[22].set = False
+ arm.rigify_layers[22].selset = False
arm.rigify_layers[22].group = 0
arm.rigify_layers[23].name = ""
arm.rigify_layers[23].row = 1
- arm.rigify_layers[23].set = False
+ arm.rigify_layers[23].selset = False
arm.rigify_layers[23].group = 0
arm.rigify_layers[24].name = ""
arm.rigify_layers[24].row = 1
- arm.rigify_layers[24].set = False
+ arm.rigify_layers[24].selset = False
arm.rigify_layers[24].group = 0
arm.rigify_layers[25].name = ""
arm.rigify_layers[25].row = 1
- arm.rigify_layers[25].set = False
+ arm.rigify_layers[25].selset = False
arm.rigify_layers[25].group = 0
arm.rigify_layers[26].name = ""
arm.rigify_layers[26].row = 1
- arm.rigify_layers[26].set = False
+ arm.rigify_layers[26].selset = False
arm.rigify_layers[26].group = 0
arm.rigify_layers[27].name = ""
arm.rigify_layers[27].row = 1
- arm.rigify_layers[27].set = False
+ arm.rigify_layers[27].selset = False
arm.rigify_layers[27].group = 0
arm.rigify_layers[28].name = "Root"
arm.rigify_layers[28].row = 14
- arm.rigify_layers[28].set = False
+ arm.rigify_layers[28].selset = False
arm.rigify_layers[28].group = 1
bones = {}
diff --git a/rigify/rig_ui_template.py b/rigify/rig_ui_template.py
index 3cdda311..aec675e3 100644
--- a/rigify/rig_ui_template.py
+++ b/rigify/rig_ui_template.py
@@ -20,6 +20,7 @@
UI_SLIDERS = '''
import bpy
+from bpy.props import StringProperty
from mathutils import Matrix, Vector
from math import acos, pi, radians
@@ -135,11 +136,11 @@ def get_pose_matrix_in_other_space(mat, pose_bone):
par_rest = Matrix()
# Get matrix in bone's current transform space
- smat = rest_inv * (par_rest * (par_inv * mat))
+ smat = rest_inv @ (par_rest @ (par_inv @ mat))
# Compensate for non-local location
#if not pose_bone.bone.use_local_location:
- # loc = smat.to_translation() * (par_rest.inverted() * rest).to_quaternion()
+ # loc = smat.to_translation() @ (par_rest.inverted() @ rest).to_quaternion()
# smat.translation = loc
return smat
@@ -166,8 +167,8 @@ def set_pose_translation(pose_bone, mat):
else:
par_rest = Matrix()
- q = (par_rest.inverted() * rest).to_quaternion()
- pose_bone.location = q * loc
+ q = (par_rest.inverted() @ rest).to_quaternion()
+ pose_bone.location = q @ loc
def set_pose_rotation(pose_bone, mat):
@@ -283,11 +284,11 @@ def match_pole_target(ik_first, ik_last, pole, match_bone, length):
angle = rotation_difference(ik_first.matrix, match_bone.matrix)
# Try compensating for the rotation difference in both directions
- pv1 = Matrix.Rotation(angle, 4, ikv) * pv
+ pv1 = Matrix.Rotation(angle, 4, ikv) @ pv
set_pole(pv1)
ang1 = rotation_difference(ik_first.matrix, match_bone.matrix)
- pv2 = Matrix.Rotation(-angle, 4, ikv) * pv
+ pv2 = Matrix.Rotation(-angle, 4, ikv) @ pv
set_pole(pv2)
ang2 = rotation_difference(ik_first.matrix, match_bone.matrix)
@@ -424,8 +425,8 @@ def fk2ik_leg(obj, fk, ik):
match_pose_scale(shin, shini)
# Foot position
- mat = mfoot.bone.matrix_local.inverted() * foot.bone.matrix_local
- footmat = get_pose_matrix_in_other_space(mfooti.matrix, foot) * mat
+ mat = mfoot.bone.matrix_local.inverted() @ foot.bone.matrix_local
+ footmat = get_pose_matrix_in_other_space(mfooti.matrix, foot) @ mat
set_pose_rotation(foot, footmat)
set_pose_scale(foot, footmat)
bpy.ops.object.mode_set(mode='OBJECT')
@@ -442,8 +443,8 @@ def fk2ik_leg(obj, fk, ik):
match_pose_scale(shin, shini)
# Foot position
- mat = mfoot.bone.matrix_local.inverted() * foot.bone.matrix_local
- footmat = get_pose_matrix_in_other_space(mfooti.matrix, foot) * mat
+ mat = mfoot.bone.matrix_local.inverted() @ foot.bone.matrix_local
+ footmat = get_pose_matrix_in_other_space(mfooti.matrix, foot) @ mat
set_pose_rotation(foot, footmat)
set_pose_scale(foot, footmat)
bpy.ops.object.mode_set(mode='OBJECT')
@@ -482,8 +483,8 @@ def ik2fk_leg(obj, fk, ik):
set_pose_rotation(footroll, Matrix())
# Foot position
- mat = mfooti.bone.matrix_local.inverted() * footi.bone.matrix_local
- footmat = get_pose_matrix_in_other_space(foot.matrix, footi) * mat
+ mat = mfooti.bone.matrix_local.inverted() @ footi.bone.matrix_local
+ footmat = get_pose_matrix_in_other_space(foot.matrix, footi) @ mat
set_pose_translation(footi, footmat)
set_pose_rotation(footi, footmat)
set_pose_scale(footi, footmat)
@@ -508,8 +509,8 @@ def ik2fk_leg(obj, fk, ik):
set_pose_rotation(footroll, Matrix())
# Foot position
- mat = mfooti.bone.matrix_local.inverted() * footi.bone.matrix_local
- footmat = get_pose_matrix_in_other_space(mfoot.matrix, footi) * mat
+ mat = mfooti.bone.matrix_local.inverted() @ footi.bone.matrix_local
+ footmat = get_pose_matrix_in_other_space(mfoot.matrix, footi) @ mat
set_pose_translation(footi, footmat)
set_pose_rotation(footi, footmat)
set_pose_scale(footi, footmat)
@@ -600,13 +601,13 @@ class Rigify_Arm_FK2IK(bpy.types.Operator):
bl_label = "Rigify Snap FK arm to IK"
bl_options = {'UNDO'}
- uarm_fk = bpy.props.StringProperty(name="Upper Arm FK Name")
- farm_fk = bpy.props.StringProperty(name="Forerm FK Name")
- hand_fk = bpy.props.StringProperty(name="Hand FK Name")
+ uarm_fk: StringProperty(name="Upper Arm FK Name")
+ farm_fk: StringProperty(name="Forerm FK Name")
+ hand_fk: StringProperty(name="Hand FK Name")
- uarm_ik = bpy.props.StringProperty(name="Upper Arm IK Name")
- farm_ik = bpy.props.StringProperty(name="Forearm IK Name")
- hand_ik = bpy.props.StringProperty(name="Hand IK Name")
+ uarm_ik: StringProperty(name="Upper Arm IK Name")
+ farm_ik: StringProperty(name="Forearm IK Name")
+ hand_ik: StringProperty(name="Hand IK Name")
@classmethod
def poll(cls, context):
@@ -629,16 +630,16 @@ class Rigify_Arm_IK2FK(bpy.types.Operator):
bl_label = "Rigify Snap IK arm to FK"
bl_options = {'UNDO'}
- uarm_fk = bpy.props.StringProperty(name="Upper Arm FK Name")
- farm_fk = bpy.props.StringProperty(name="Forerm FK Name")
- hand_fk = bpy.props.StringProperty(name="Hand FK Name")
+ uarm_fk: StringProperty(name="Upper Arm FK Name")
+ farm_fk: StringProperty(name="Forerm FK Name")
+ hand_fk: StringProperty(name="Hand FK Name")
- uarm_ik = bpy.props.StringProperty(name="Upper Arm IK Name")
- farm_ik = bpy.props.StringProperty(name="Forearm IK Name")
- hand_ik = bpy.props.StringProperty(name="Hand IK Name")
- pole = bpy.props.StringProperty(name="Pole IK Name")
+ uarm_ik: StringProperty(name="Upper Arm IK Name")
+ farm_ik: StringProperty(name="Forearm IK Name")
+ hand_ik: StringProperty(name="Hand IK Name")
+ pole : StringProperty(name="Pole IK Name")
- main_parent = bpy.props.StringProperty(name="Main Parent", default="")
+ main_parent: StringProperty(name="Main Parent", default="")
@classmethod
def poll(cls, context):
@@ -661,15 +662,15 @@ class Rigify_Leg_FK2IK(bpy.types.Operator):
bl_label = "Rigify Snap FK leg to IK"
bl_options = {'UNDO'}
- thigh_fk = bpy.props.StringProperty(name="Thigh FK Name")
- shin_fk = bpy.props.StringProperty(name="Shin FK Name")
- foot_fk = bpy.props.StringProperty(name="Foot FK Name")
- mfoot_fk = bpy.props.StringProperty(name="MFoot FK Name")
+ thigh_fk: StringProperty(name="Thigh FK Name")
+ shin_fk: StringProperty(name="Shin FK Name")
+ foot_fk: StringProperty(name="Foot FK Name")
+ mfoot_fk: StringProperty(name="MFoot FK Name")
- thigh_ik = bpy.props.StringProperty(name="Thigh IK Name")
- shin_ik = bpy.props.StringProperty(name="Shin IK Name")
- foot_ik = bpy.props.StringProperty(name="Foot IK Name")
- mfoot_ik = bpy.props.StringProperty(name="MFoot IK Name")
+ thigh_ik: StringProperty(name="Thigh IK Name")
+ shin_ik: StringProperty(name="Shin IK Name")
+ foot_ik: StringProperty(name="Foot IK Name")
+ mfoot_ik: StringProperty(name="MFoot IK Name")
@classmethod
def poll(cls, context):
@@ -692,18 +693,18 @@ class Rigify_Leg_IK2FK(bpy.types.Operator):
bl_label = "Rigify Snap IK leg to FK"
bl_options = {'UNDO'}
- thigh_fk = bpy.props.StringProperty(name="Thigh FK Name")
- shin_fk = bpy.props.StringProperty(name="Shin FK Name")
- mfoot_fk = bpy.props.StringProperty(name="MFoot FK Name")
- foot_fk = bpy.props.StringProperty(name="Foot FK Name", default="")
- thigh_ik = bpy.props.StringProperty(name="Thigh IK Name")
- shin_ik = bpy.props.StringProperty(name="Shin IK Name")
- foot_ik = bpy.props.StringProperty(name="Foot IK Name")
- footroll = bpy.props.StringProperty(name="Foot Roll Name")
- pole = bpy.props.StringProperty(name="Pole IK Name")
- mfoot_ik = bpy.props.StringProperty(name="MFoot IK Name")
+ thigh_fk: StringProperty(name="Thigh FK Name")
+ shin_fk: StringProperty(name="Shin FK Name")
+ mfoot_fk: StringProperty(name="MFoot FK Name")
+ foot_fk: StringProperty(name="Foot FK Name", default="")
+ thigh_ik: StringProperty(name="Thigh IK Name")
+ shin_ik: StringProperty(name="Shin IK Name")
+ foot_ik: StringProperty(name="Foot IK Name")
+ footroll: StringProperty(name="Foot Roll Name")
+ pole: StringProperty(name="Pole IK Name")
+ mfoot_ik: StringProperty(name="MFoot IK Name")
- main_parent = bpy.props.StringProperty(name="Main Parent", default="")
+ main_parent: StringProperty(name="Main Parent", default="")
@classmethod
def poll(cls, context):
@@ -726,13 +727,14 @@ class Rigify_Rot2PoleSwitch(bpy.types.Operator):
bl_idname = "pose.rigify_rot2pole_" + rig_id
bl_label = "Rotation - Pole toggle"
bl_description = "Toggles IK chain between rotation and pole target"
- bone_name = bpy.props.StringProperty(default='')
- limb_type = bpy.props.StringProperty(name="Limb Type")
- controls = bpy.props.StringProperty(name="Controls string")
- ik_ctrl = bpy.props.StringProperty(name="IK Controls string")
- fk_ctrl = bpy.props.StringProperty(name="FK Controls string")
- parent = bpy.props.StringProperty(name="Parent name")
- pole = bpy.props.StringProperty(name="Pole name")
+
+ bone_name: StringProperty(default='')
+ limb_type: StringProperty(name="Limb Type")
+ controls: StringProperty(name="Controls string")
+ ik_ctrl: StringProperty(name="IK Controls string")
+ fk_ctrl: StringProperty(name="FK Controls string")
+ parent: StringProperty(name="Parent name")
+ pole: StringProperty(name="Pole name")
def execute(self, context):
rig = context.object
@@ -753,6 +755,7 @@ class RigUI(bpy.types.Panel):
bl_region_type = 'UI'
bl_label = "Rig Main Properties"
bl_idname = rig_id + "_PT_rig_ui"
+ bl_category = 'View'
@classmethod
def poll(self, context):
@@ -796,6 +799,7 @@ class RigLayers(bpy.types.Panel):
bl_region_type = 'UI'
bl_label = "Rig Layers"
bl_idname = rig_id + "_PT_rig_layers"
+ bl_category = 'View'
@classmethod
def poll(self, context):
@@ -841,23 +845,26 @@ class RigLayers(bpy.types.Panel):
UI_REGISTER = '''
+classes = (
+ Rigify_Arm_FK2IK,
+ Rigify_Arm_IK2FK,
+ Rigify_Leg_FK2IK,
+ Rigify_Leg_IK2FK,
+ Rigify_Rot2PoleSwitch,
+ RigUI,
+ RigLayers,
+)
+
def register():
- bpy.utils.register_class(Rigify_Arm_FK2IK)
- bpy.utils.register_class(Rigify_Arm_IK2FK)
- bpy.utils.register_class(Rigify_Leg_FK2IK)
- bpy.utils.register_class(Rigify_Leg_IK2FK)
- bpy.utils.register_class(Rigify_Rot2PoleSwitch)
- bpy.utils.register_class(RigUI)
- bpy.utils.register_class(RigLayers)
+ from bpy.utils import register_class
+ for cls in classes:
+ register_class(cls)
+
def unregister():
- bpy.utils.unregister_class(Rigify_Arm_FK2IK)
- bpy.utils.unregister_class(Rigify_Arm_IK2FK)
- bpy.utils.unregister_class(Rigify_Leg_FK2IK)
- bpy.utils.unregister_class(Rigify_Leg_IK2FK)
- bpy.utils.register_class(Rigify_Rot2PoleSwitch)
- bpy.utils.unregister_class(RigUI)
- bpy.utils.unregister_class(RigLayers)
+ from bpy.utils import unregister_class
+ for cls in classes:
+ unregister_class(cls)
register()
'''
diff --git a/rigify/rigs/experimental/super_chain.py b/rigify/rigs/experimental/super_chain.py
index 408bce3f..2b2c472b 100644
--- a/rigify/rigs/experimental/super_chain.py
+++ b/rigify/rigs/experimental/super_chain.py
@@ -115,11 +115,11 @@ class Rig:
setattr(v,axis,scale)
if reverse:
- tail_vec = v * self.obj.matrix_world
+ tail_vec = v @ self.obj.matrix_world
eb.head[:] = eb.tail
eb.tail[:] = eb.head + tail_vec
else:
- tail_vec = v * self.obj.matrix_world
+ tail_vec = v @ self.obj.matrix_world
eb.tail[:] = eb.head + tail_vec
def create_pivot(self, bones=None, pivot=None):
diff --git a/rigify/rigs/faces/super_face.py b/rigify/rigs/faces/super_face.py
index 2c4999d5..b899c0b2 100644
--- a/rigify/rigs/faces/super_face.py
+++ b/rigify/rigs/faces/super_face.py
@@ -758,8 +758,10 @@ class Rig:
'DEF-chin.R' : 'lips.R',
'DEF-jaw.R.001' : 'chin.R',
'DEF-brow.T.L.003' : 'nose',
+ 'DEF-ear.L' : None,
'DEF-ear.L.003' : 'ear.L.004',
'DEF-ear.L.004' : 'ear.L',
+ 'DEF-ear.R' : None,
'DEF-ear.R.003' : 'ear.R.004',
'DEF-ear.R.004' : 'ear.R',
'DEF-lip.B.L.001' : 'lips.L',
@@ -786,8 +788,9 @@ class Rig:
pattern = r'^DEF-(\w+\.?\w?\.?\w?)(\.?)(\d*?)(\d?)$'
for bone in [ bone for bone in all_bones['deform']['all'] if 'lid' not in bone ]:
- if bone in list( def_specials.keys() ):
- self.make_constraits('def_tweak', bone, def_specials[bone] )
+ if bone in def_specials:
+ if def_specials[bone] is not None:
+ self.make_constraits('def_tweak', bone, def_specials[bone] )
else:
matches = re.match( pattern, bone ).groups()
if len( matches ) > 1 and matches[-1]:
diff --git a/rigify/rigs/limbs/limb_utils.py b/rigify/rigs/limbs/limb_utils.py
index 80588516..609da256 100644
--- a/rigify/rigs/limbs/limb_utils.py
+++ b/rigify/rigs/limbs/limb_utils.py
@@ -11,11 +11,11 @@ def orient_bone( cls, eb, axis, scale = 1.0, reverse = False ):
setattr(v,axis,scale)
if reverse:
- tail_vec = v * cls.obj.matrix_world
+ tail_vec = v @ cls.obj.matrix_world
eb.head[:] = eb.tail
eb.tail[:] = eb.head + tail_vec
else:
- tail_vec = v * cls.obj.matrix_world
+ tail_vec = v @ cls.obj.matrix_world
eb.tail[:] = eb.head + tail_vec
eb.roll = 0.0
diff --git a/rigify/rigs/spines/super_spine.py b/rigify/rigs/spines/super_spine.py
index 115d0450..6d28de69 100644
--- a/rigify/rigs/spines/super_spine.py
+++ b/rigify/rigs/spines/super_spine.py
@@ -123,11 +123,11 @@ class Rig:
setattr(v, axis, scale)
if reverse:
- tail_vec = v * self.obj.matrix_world
+ tail_vec = v @ self.obj.matrix_world
eb.head[:] = eb.tail
eb.tail[:] = eb.head + tail_vec
else:
- tail_vec = v * self.obj.matrix_world
+ tail_vec = v @ self.obj.matrix_world
eb.tail[:] = eb.head + tail_vec
def create_pivot(self, pivot):
diff --git a/rigify/rigs/widgets.py b/rigify/rigs/widgets.py
index 8461d82a..aebe7139 100644
--- a/rigify/rigs/widgets.py
+++ b/rigify/rigs/widgets.py
@@ -4,7 +4,6 @@ import importlib
from mathutils import Matrix
from ..utils import create_widget
-WGT_LAYERS = [x == 19 for x in range(0, 20)] # Widgets go on the last scene layer.
MODULE_NAME = "super_widgets" # Windows/Mac blender is weird, so __package__ doesn't work
diff --git a/rigify/rot_mode.py b/rigify/rot_mode.py
index e30e28d1..9abfecea 100644
--- a/rigify/rot_mode.py
+++ b/rigify/rot_mode.py
@@ -38,8 +38,10 @@ blender.stackexchange.com/questions/40711/how-to-convert-quaternions-keyframes-t
# "category": "Animation"}
import bpy
-
-order_list = ['QUATERNION', 'XYZ', 'XZY', 'YXZ', 'YZX', 'ZXY', 'ZYX']
+from bpy.props import (
+ BoolProperty,
+ EnumProperty,
+)
class convert():
@@ -219,32 +221,10 @@ class convert():
convert = convert()
-# def initSceneProperties(scn):
-#
-# bpy.types.Scene.order_list = bpy.props.EnumProperty(
-# items = [('QUATERNION', 'QUATERNION', 'QUATERNION' ),
-# ('XYZ', 'XYZ', 'XYZ' ),
-# ('XZY', 'XZY', 'XZY' ),
-# ('YXZ', 'YXZ', 'YXZ' ),
-# ('YZX', 'YZX', 'YZX' ),
-# ('ZXY', 'ZXY', 'ZXY' ),
-# ('ZYX', 'ZYX', 'ZYX' ) ],
-# name = "Order",
-# description = "The target rotation mode")
-#
-# scn['order_list'] = 0
-#
-# return
-#
-# initSceneProperties(bpy.context.scene)
-
-
-# GUI (Panel)
-#
class ToolsPanel(bpy.types.Panel):
bl_space_type = 'VIEW_3D'
- bl_region_type = 'TOOLS'
- bl_category = "Tools"
+ bl_region_type = 'UI'
+ bl_category = 'View'
bl_context = "posemode"
bl_label = 'Rigify Quat/Euler Converter'
@@ -290,7 +270,7 @@ class CONVERT_OT_quat2eu_current_action(bpy.types.Operator):
obj = bpy.context.active_object
pose_bones = bpy.context.selected_pose_bones
action = obj.animation_data.action
- order = order_list[bpy.context.scene['order_list']]
+ order = bpy.context.scene.order_list
id_store = context.window_manager
if id_store.rigify_convert_only_selected:
@@ -315,7 +295,7 @@ class CONVERT_OT_quat2eu_all_actions(bpy.types.Operator):
def execute(op, context):
obj = bpy.context.active_object
pose_bones = bpy.context.selected_pose_bones
- order = order_list[bpy.context.scene['order_list']]
+ order = bpy.context.scene.order_list
id_store = context.window_manager
if id_store.rigify_convert_only_selected:
@@ -326,9 +306,19 @@ class CONVERT_OT_quat2eu_all_actions(bpy.types.Operator):
return {'FINISHED'}
+### Registering ###
+
+classes = (
+ ToolsPanel,
+ CONVERT_OT_quat2eu_current_action,
+ CONVERT_OT_quat2eu_all_actions,
+)
+
+
def register():
- IDStore = bpy.types.WindowManager
+ from bpy.utils import register_class
+ # Properties.
items = [('QUATERNION', 'QUATERNION', 'QUATERNION'),
('XYZ', 'XYZ', 'XYZ'),
('XZY', 'XZY', 'XZY'),
@@ -336,24 +326,27 @@ def register():
('YZX', 'YZX', 'YZX'),
('ZXY', 'ZXY', 'ZXY'),
('ZYX', 'ZYX', 'ZYX')]
+ bpy.types.Scene.order_list = EnumProperty(
+ items=items, name='Convert to',
+ description="The target rotation mode", default='QUATERNION')
- bpy.types.Scene.order_list = bpy.props.EnumProperty(items=items, name='Convert to',
- description="The target rotation mode", default='QUATERNION')
+ IDStore = bpy.types.WindowManager
+ IDStore.rigify_convert_only_selected = BoolProperty(
+ name="Convert Only Selected",
+ description="Convert selected bones only", default=True)
- IDStore.rigify_convert_only_selected = bpy.props.BoolProperty(
- name="Convert Only Selected", description="Convert selected bones only", default=True)
+ # Classes.
+ for cls in classes:
+ register_class(cls)
- bpy.utils.register_class(ToolsPanel)
- bpy.utils.register_class(CONVERT_OT_quat2eu_current_action)
- bpy.utils.register_class(CONVERT_OT_quat2eu_all_actions)
def unregister():
- IDStore = bpy.types.WindowManager
+ from bpy.utils import unregister_class
- bpy.utils.unregister_class(ToolsPanel)
- bpy.utils.unregister_class(CONVERT_OT_quat2eu_current_action)
- bpy.utils.unregister_class(CONVERT_OT_quat2eu_all_actions)
+ # Classes.
+ for cls in classes:
+ unregister_class(cls)
+ # Properties.
+ IDStore = bpy.types.WindowManager
del IDStore.rigify_convert_only_selected
-
-# bpy.utils.register_module(__name__)
diff --git a/rigify/ui.py b/rigify/ui.py
index 76b1fd23..77398ff0 100644
--- a/rigify/ui.py
+++ b/rigify/ui.py
@@ -19,7 +19,13 @@
# <pep8 compliant>
import bpy
-from bpy.props import StringProperty
+from bpy.props import (
+ BoolProperty,
+ IntProperty,
+ EnumProperty,
+ StringProperty
+)
+
from mathutils import Color
from .utils import get_rig_type, MetarigError
@@ -89,7 +95,7 @@ class DATA_PT_rigify_buttons(bpy.types.Panel):
if show_update_metarig:
layout.label(text="This metarig contains old rig-types that can be automatically upgraded to benefit of rigify's new features.", icon='ERROR')
- layout.label(text= "To use it as-is you need to enable legacy mode.",)
+ layout.label(text="To use it as-is you need to enable legacy mode.",)
layout.operator("pose.rigify_upgrade_types", text="Upgrade Metarig")
row = layout.row()
@@ -111,7 +117,7 @@ class DATA_PT_rigify_buttons(bpy.types.Panel):
row = col.row(align=True)
row.prop(id_store, "rigify_generate_mode", expand=True)
- main_row = col.row(align=True).split(percentage=0.3)
+ main_row = col.row(align=True).split(factor=0.3)
col1 = main_row.column()
col2 = main_row.column()
col1.label(text="Rig Name")
@@ -208,14 +214,14 @@ class DATA_PT_rigify_layer_names(bpy.types.Panel):
return
# UI
- main_row = layout.row(align=True).split(0.05)
+ main_row = layout.row(align=True).split(factor=0.05)
col1 = main_row.column()
col2 = main_row.column()
col1.label()
for i in range(32):
if i == 16 or i == 29:
col1.label()
- col1.label(str(i+1) + '.')
+ col1.label(text=str(i+1) + '.')
for i, rigify_layer in enumerate(arm.rigify_layers):
# note: rigify_layer == arm.rigify_layers[i]
@@ -234,8 +240,8 @@ class DATA_PT_rigify_layer_names(bpy.types.Panel):
#row.prop(arm, "layers", index=i, text="Layer %d" % (i + 1), toggle=True, icon=icon)
row.prop(rigify_layer, "name", text="")
row.prop(rigify_layer, "row", text="UI Row")
- icon = 'RADIOBUT_ON' if rigify_layer.set else 'RADIOBUT_OFF'
- row.prop(rigify_layer, "set", text="", toggle=True, icon=icon)
+ icon = 'RADIOBUT_ON' if rigify_layer.selset else 'RADIOBUT_OFF'
+ row.prop(rigify_layer, "selset", text="", toggle=True, icon=icon)
row.prop(rigify_layer, "group", text="Bone Group")
else:
row = col.row(align=True)
@@ -247,8 +253,8 @@ class DATA_PT_rigify_layer_names(bpy.types.Panel):
row1.prop(rigify_layer, "name", text="")
row1.prop(rigify_layer, "row", text="UI Row")
row1.enabled = False
- icon = 'RADIOBUT_ON' if rigify_layer.set else 'RADIOBUT_OFF'
- row.prop(rigify_layer, "set", text="", toggle=True, icon=icon)
+ icon = 'RADIOBUT_ON' if rigify_layer.selset else 'RADIOBUT_OFF'
+ row.prop(rigify_layer, "selset", text="", toggle=True, icon=icon)
row.prop(rigify_layer, "group", text="Bone Group")
if rigify_layer.group == 0:
row.label(text='None')
@@ -395,28 +401,29 @@ class DATA_OT_rigify_bone_group_add_theme(bpy.types.Operator):
bl_label = "Rigify Add Bone Group color set from Theme"
bl_options = {"REGISTER", "UNDO"}
- theme = bpy.props.EnumProperty(items=(('THEME01', 'THEME01', ''),
- ('THEME02', 'THEME02', ''),
- ('THEME03', 'THEME03', ''),
- ('THEME04', 'THEME04', ''),
- ('THEME05', 'THEME05', ''),
- ('THEME06', 'THEME06', ''),
- ('THEME07', 'THEME07', ''),
- ('THEME08', 'THEME08', ''),
- ('THEME09', 'THEME09', ''),
- ('THEME10', 'THEME10', ''),
- ('THEME11', 'THEME11', ''),
- ('THEME12', 'THEME12', ''),
- ('THEME13', 'THEME13', ''),
- ('THEME14', 'THEME14', ''),
- ('THEME15', 'THEME15', ''),
- ('THEME16', 'THEME16', ''),
- ('THEME17', 'THEME17', ''),
- ('THEME18', 'THEME18', ''),
- ('THEME19', 'THEME19', ''),
- ('THEME20', 'THEME20', '')
- ),
- name='Theme')
+ theme: EnumProperty(items=(
+ ('THEME01', 'THEME01', ''),
+ ('THEME02', 'THEME02', ''),
+ ('THEME03', 'THEME03', ''),
+ ('THEME04', 'THEME04', ''),
+ ('THEME05', 'THEME05', ''),
+ ('THEME06', 'THEME06', ''),
+ ('THEME07', 'THEME07', ''),
+ ('THEME08', 'THEME08', ''),
+ ('THEME09', 'THEME09', ''),
+ ('THEME10', 'THEME10', ''),
+ ('THEME11', 'THEME11', ''),
+ ('THEME12', 'THEME12', ''),
+ ('THEME13', 'THEME13', ''),
+ ('THEME14', 'THEME14', ''),
+ ('THEME15', 'THEME15', ''),
+ ('THEME16', 'THEME16', ''),
+ ('THEME17', 'THEME17', ''),
+ ('THEME18', 'THEME18', ''),
+ ('THEME19', 'THEME19', ''),
+ ('THEME20', 'THEME20', '')
+ ),
+ name='Theme')
@classmethod
def poll(cls, context):
@@ -448,7 +455,7 @@ class DATA_OT_rigify_bone_group_remove(bpy.types.Operator):
bl_idname = "armature.rigify_bone_group_remove"
bl_label = "Rigify Remove Bone Group color set"
- idx = bpy.props.IntProperty()
+ idx: IntProperty()
@classmethod
def poll(cls, context):
@@ -492,9 +499,9 @@ class DATA_OT_rigify_bone_group_remove_all(bpy.types.Operator):
class DATA_UL_rigify_bone_groups(bpy.types.UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
row = layout.row(align=True)
- row = row.split(percentage=0.1)
+ row = row.split(factor=0.1)
row.label(text=str(index+1))
- row = row.split(percentage=0.7)
+ row = row.split(factor=0.7)
row.prop(item, "name", text='', emboss=False)
row = row.row(align=True)
icon = 'LOCKED' if item.standard_colors_lock else 'UNLOCKED'
@@ -547,8 +554,8 @@ class DATA_PT_rigify_bone_groups(bpy.types.Panel):
row.template_list("DATA_UL_rigify_bone_groups", "", obj.data, "rigify_colors", obj.data, "rigify_colors_index")
col = row.column(align=True)
- col.operator("armature.rigify_bone_group_add", icon='ZOOMIN', text="")
- col.operator("armature.rigify_bone_group_remove", icon='ZOOMOUT', text="").idx = obj.data.rigify_colors_index
+ col.operator("armature.rigify_bone_group_add", icon='ZOOM_IN', text="")
+ col.operator("armature.rigify_bone_group_remove", icon='ZOOM_OUT', text="").idx = obj.data.rigify_colors_index
col.menu("DATA_MT_rigify_bone_groups_specials", icon='DOWNARROW_HLT', text="")
row = layout.row()
row.prop(armature, 'rigify_theme_to_add', text = 'Theme')
@@ -626,9 +633,9 @@ class BONE_PT_rigify_buttons(bpy.types.Panel):
class VIEW3D_PT_tools_rigify_dev(bpy.types.Panel):
bl_label = "Rigify Dev Tools"
- bl_category = 'Tools'
bl_space_type = 'VIEW_3D'
- bl_region_type = 'TOOLS'
+ bl_region_type = 'UI'
+ bl_category = 'View'
@classmethod
def poll(cls, context):
@@ -650,10 +657,10 @@ class VIEW3D_PT_tools_rigify_dev(bpy.types.Panel):
class VIEW3D_PT_rigify_animation_tools(bpy.types.Panel):
bl_label = "Rigify Animation Tools"
- bl_category = 'Tools'
bl_context = "posemode"
bl_space_type = 'VIEW_3D'
- bl_region_type = 'TOOLS'
+ bl_region_type = 'UI'
+ bl_category = 'View'
@classmethod
def poll(cls, context):
@@ -681,8 +688,8 @@ class VIEW3D_PT_rigify_animation_tools(bpy.types.Panel):
row.operator("rigify.transfer_ik_to_fk", text='FK2IK Action', icon='ACTION_TWEAK')
row = self.layout.row(align=True)
- row.operator("rigify.clear_animation", text="Clear IK Action", icon='CANCEL').type = "IK"
- row.operator("rigify.clear_animation", text="Clear FK Action", icon='CANCEL').type = "FK"
+ row.operator("rigify.clear_animation", text="Clear IK Action", icon='CANCEL').anim_type = "IK"
+ row.operator("rigify.clear_animation", text="Clear FK Action", icon='CANCEL').anim_type = "FK"
row = self.layout.row(align=True)
op = row.operator("rigify.rotation_pole", icon='FORCE_HARMONIC', text='Switch to pole')
@@ -794,18 +801,17 @@ class SwitchToLegacy(bpy.types.Operator):
class Sample(bpy.types.Operator):
- """Create a sample metarig to be modified before generating """ \
- """the final rig"""
+ """Create a sample metarig to be modified before generating the final rig"""
bl_idname = "armature.metarig_sample_add"
bl_label = "Add a sample metarig for a rig type"
bl_options = {'UNDO'}
- metarig_type = StringProperty(
- name="Type",
- description="Name of the rig type to generate a sample of",
- maxlen=128,
- )
+ metarig_type: StringProperty(
+ name="Type",
+ description="Name of the rig type to generate a sample of",
+ maxlen=128,
+ )
def execute(self, context):
if context.mode == 'EDIT_ARMATURE' and self.metarig_type != "":
@@ -1074,20 +1080,20 @@ def IktoFk(rig, window='ALL'):
break
-def clearAnimation(act, type, names):
+def clearAnimation(act, anim_type, names):
bones = []
for group in names:
if names[group]['limb_type'] == 'arm':
- if type == 'IK':
+ if anim_type == 'IK':
bones.extend([names[group]['controls'][0], names[group]['controls'][4]])
- elif type == 'FK':
+ elif anim_type == 'FK':
bones.extend([names[group]['controls'][1], names[group]['controls'][2], names[group]['controls'][3]])
else:
- if type == 'IK':
+ if anim_type == 'IK':
bones.extend([names[group]['controls'][0], names[group]['controls'][6], names[group]['controls'][5],
names[group]['controls'][4]])
- elif type == 'FK':
+ elif anim_type == 'FK':
bones.extend([names[group]['controls'][1], names[group]['controls'][2], names[group]['controls'][3],
names[group]['controls'][4]])
FCurves = []
@@ -1271,7 +1277,8 @@ class OBJECT_OT_ClearAnimation(bpy.types.Operator):
bl_idname = "rigify.clear_animation"
bl_label = "Clear Animation"
bl_description = "Clear Animation For FK or IK Bones"
- type = StringProperty()
+
+ anim_type: StringProperty()
def execute(self, context):
@@ -1286,7 +1293,7 @@ class OBJECT_OT_ClearAnimation(bpy.types.Operator):
if not act:
return {'FINISHED'}
- clearAnimation(act, self.type, names=get_limb_generated_names(rig))
+ clearAnimation(act, self.anim_type, names=get_limb_generated_names(rig))
finally:
context.user_preferences.edit.use_global_undo = use_global_undo
return {'FINISHED'}
@@ -1296,11 +1303,12 @@ class OBJECT_OT_Rot2Pole(bpy.types.Operator):
bl_idname = "rigify.rotation_pole"
bl_label = "Rotation - Pole toggle"
bl_description = "Toggles IK chain between rotation and pole target"
- bone_name = bpy.props.StringProperty(default='')
- window = bpy.props.StringProperty(default='ALL')
- toggle = bpy.props.BoolProperty(default=True)
- value = bpy.props.BoolProperty(default=True)
- bake = bpy.props.BoolProperty(default=True)
+
+ bone_name: StringProperty(default='')
+ window: StringProperty(default='ALL')
+ toggle: BoolProperty(default=True)
+ value: BoolProperty(default=True)
+ bake: BoolProperty(default=True)
def execute(self, context):
rig = context.object
@@ -1313,73 +1321,60 @@ class OBJECT_OT_Rot2Pole(bpy.types.Operator):
return {'FINISHED'}
+### Registering ###
+
+
+classes = (
+ DATA_OT_rigify_add_bone_groups,
+ DATA_OT_rigify_use_standard_colors,
+ DATA_OT_rigify_apply_selection_colors,
+ DATA_OT_rigify_bone_group_add,
+ DATA_OT_rigify_bone_group_add_theme,
+ DATA_OT_rigify_bone_group_remove,
+ DATA_OT_rigify_bone_group_remove_all,
+ DATA_UL_rigify_bone_groups,
+ DATA_MT_rigify_bone_groups_specials,
+ DATA_PT_rigify_bone_groups,
+ DATA_PT_rigify_layer_names,
+ DATA_PT_rigify_buttons,
+ BONE_PT_rigify_buttons,
+ VIEW3D_PT_rigify_animation_tools,
+ VIEW3D_PT_tools_rigify_dev,
+ LayerInit,
+ Generate,
+ UpgradeMetarigTypes,
+ SwitchToLegacy,
+ Sample,
+ EncodeMetarig,
+ EncodeMetarigSample,
+ EncodeWidget,
+ OBJECT_OT_GetFrameRange,
+ OBJECT_OT_FK2IK,
+ OBJECT_OT_IK2FK,
+ OBJECT_OT_TransferFKtoIK,
+ OBJECT_OT_TransferIKtoFK,
+ OBJECT_OT_ClearAnimation,
+ OBJECT_OT_Rot2Pole,
+)
+
+
def register():
+ from bpy.utils import register_class
- bpy.utils.register_class(DATA_OT_rigify_add_bone_groups)
- bpy.utils.register_class(DATA_OT_rigify_use_standard_colors)
- bpy.utils.register_class(DATA_OT_rigify_apply_selection_colors)
- bpy.utils.register_class(DATA_OT_rigify_bone_group_add)
- bpy.utils.register_class(DATA_OT_rigify_bone_group_add_theme)
- bpy.utils.register_class(DATA_OT_rigify_bone_group_remove)
- bpy.utils.register_class(DATA_OT_rigify_bone_group_remove_all)
- bpy.utils.register_class(DATA_UL_rigify_bone_groups)
- bpy.utils.register_class(DATA_MT_rigify_bone_groups_specials)
- bpy.utils.register_class(DATA_PT_rigify_bone_groups)
- bpy.utils.register_class(DATA_PT_rigify_layer_names)
- bpy.utils.register_class(DATA_PT_rigify_buttons)
- bpy.utils.register_class(BONE_PT_rigify_buttons)
- bpy.utils.register_class(VIEW3D_PT_rigify_animation_tools)
- bpy.utils.register_class(VIEW3D_PT_tools_rigify_dev)
- bpy.utils.register_class(LayerInit)
- bpy.utils.register_class(Generate)
- bpy.utils.register_class(UpgradeMetarigTypes)
- bpy.utils.register_class(SwitchToLegacy)
- bpy.utils.register_class(Sample)
- bpy.utils.register_class(EncodeMetarig)
- bpy.utils.register_class(EncodeMetarigSample)
- bpy.utils.register_class(EncodeWidget)
- bpy.utils.register_class(OBJECT_OT_GetFrameRange)
- bpy.utils.register_class(OBJECT_OT_FK2IK)
- bpy.utils.register_class(OBJECT_OT_IK2FK)
- bpy.utils.register_class(OBJECT_OT_TransferFKtoIK)
- bpy.utils.register_class(OBJECT_OT_TransferIKtoFK)
- bpy.utils.register_class(OBJECT_OT_ClearAnimation)
- bpy.utils.register_class(OBJECT_OT_Rot2Pole)
+ # Classes.
+ for cls in classes:
+ register_class(cls)
+ # Sub-modules.
rot_mode.register()
def unregister():
+ from bpy.utils import unregister_class
- bpy.utils.unregister_class(DATA_OT_rigify_add_bone_groups)
- bpy.utils.unregister_class(DATA_OT_rigify_use_standard_colors)
- bpy.utils.unregister_class(DATA_OT_rigify_apply_selection_colors)
- bpy.utils.unregister_class(DATA_OT_rigify_bone_group_add)
- bpy.utils.unregister_class(DATA_OT_rigify_bone_group_add_theme)
- bpy.utils.unregister_class(DATA_OT_rigify_bone_group_remove)
- bpy.utils.unregister_class(DATA_OT_rigify_bone_group_remove_all)
- bpy.utils.unregister_class(DATA_UL_rigify_bone_groups)
- bpy.utils.unregister_class(DATA_MT_rigify_bone_groups_specials)
- bpy.utils.unregister_class(DATA_PT_rigify_bone_groups)
- bpy.utils.unregister_class(DATA_PT_rigify_layer_names)
- bpy.utils.unregister_class(DATA_PT_rigify_buttons)
- bpy.utils.unregister_class(BONE_PT_rigify_buttons)
- bpy.utils.unregister_class(VIEW3D_PT_rigify_animation_tools)
- bpy.utils.unregister_class(VIEW3D_PT_tools_rigify_dev)
- bpy.utils.unregister_class(LayerInit)
- bpy.utils.unregister_class(Generate)
- bpy.utils.unregister_class(UpgradeMetarigTypes)
- bpy.utils.unregister_class(SwitchToLegacy)
- bpy.utils.unregister_class(Sample)
- bpy.utils.unregister_class(EncodeMetarig)
- bpy.utils.unregister_class(EncodeMetarigSample)
- bpy.utils.unregister_class(EncodeWidget)
- bpy.utils.unregister_class(OBJECT_OT_GetFrameRange)
- bpy.utils.unregister_class(OBJECT_OT_FK2IK)
- bpy.utils.unregister_class(OBJECT_OT_IK2FK)
- bpy.utils.unregister_class(OBJECT_OT_TransferFKtoIK)
- bpy.utils.unregister_class(OBJECT_OT_TransferIKtoFK)
- bpy.utils.unregister_class(OBJECT_OT_ClearAnimation)
- bpy.utils.unregister_class(OBJECT_OT_Rot2Pole)
-
+ # Sub-modules.
rot_mode.unregister()
+
+ # Classes.
+ for cls in classes:
+ unregister_class(cls)
diff --git a/rigify/utils.py b/rigify/utils.py
index 1a711a3d..73b64112 100644
--- a/rigify/utils.py
+++ b/rigify/utils.py
@@ -38,8 +38,6 @@ DEF_PREFIX = "DEF-" # Prefix of deformation bones.
WGT_PREFIX = "WGT-" # Prefix for widget objects
ROOT_NAME = "root" # Name of the root bone.
-WGT_LAYERS = [x == 19 for x in range(0, 20)] # Widgets go on the last scene layer.
-
MODULE_NAME = "rigify" # Windows/Mac blender is weird, so __package__ doesn't work
outdated_types = {"pitchipoy.limbs.super_limb": "limbs.super_limb",
@@ -420,7 +418,7 @@ def obj_to_bone(obj, rig, bone_name):
bone = rig.data.bones[bone_name]
- mat = rig.matrix_world * bone.matrix_local
+ mat = rig.matrix_world @ bone.matrix_local
obj.location = mat.to_translation()
@@ -440,6 +438,7 @@ def create_widget(rig, bone_name, bone_transform_name=None):
obj_name = WGT_PREFIX + rig.name + '_' + bone_name
scene = bpy.context.scene
+ collection = bpy.context.collection
id_store = bpy.context.window_manager
# Check if it already exists in the scene
@@ -460,14 +459,13 @@ def create_widget(rig, bone_name, bone_transform_name=None):
# Create mesh object
mesh = bpy.data.meshes.new(obj_name)
obj = bpy.data.objects.new(obj_name, mesh)
- scene.objects.link(obj)
+ collection.objects.link(obj)
# Move object to bone position and set layers
obj_to_bone(obj, rig, bone_transform_name)
wgts_group_name = 'WGTS_' + rig.name
if wgts_group_name in bpy.data.objects.keys():
obj.parent = bpy.data.objects[wgts_group_name]
- obj.layers = WGT_LAYERS
return obj
@@ -802,8 +800,8 @@ def align_bone_roll(obj, bone1, bone2):
rot_mat = Matrix.Rotation(angle, 3, axis)
# Roll factor
- x3 = rot_mat * x1
- dot = x2 * x3
+ x3 = rot_mat @ x1
+ dot = x2 @ x3
if dot > 1.0:
dot = 1.0
elif dot < -1.0:
@@ -814,8 +812,8 @@ def align_bone_roll(obj, bone1, bone2):
bone1_e.roll = roll
# Check if we rolled in the right direction
- x3 = rot_mat * bone1_e.x_axis
- check = x2 * x3
+ x3 = rot_mat @ bone1_e.x_axis
+ check = x2 @ x3
# If not, reverse
if check < 0.9999:
@@ -1026,11 +1024,11 @@ def write_metarig(obj, layers=False, func_name="create", groups=False):
for i in range(len(arm.rigify_layers)):
name = arm.rigify_layers[i].name
row = arm.rigify_layers[i].row
- set = arm.rigify_layers[i].set
+ selset = arm.rigify_layers[i].selset
group = arm.rigify_layers[i].group
code.append(' arm.rigify_layers[' + str(i) + '].name = "' + name + '"')
code.append(' arm.rigify_layers[' + str(i) + '].row = ' + str(row))
- code.append(' arm.rigify_layers[' + str(i) + '].set = ' + str(set))
+ code.append(' arm.rigify_layers[' + str(i) + '].selset = ' + str(selset))
code.append(' arm.rigify_layers[' + str(i) + '].group = ' + str(group))
# write parents first
@@ -1262,3 +1260,43 @@ def overwrite_prop_animation(rig, bone, prop_name, value, frames):
for kp in curve.keyframe_points:
if kp.co[0] in frames:
kp.co[1] = value
+
+
+def find_layer_collection_by_collection(layer_collection, collection):
+ if collection == layer_collection.collection:
+ return layer_collection
+
+ # go recursive
+ for child in layer_collection.children:
+ layer_collection = find_layer_collection_by_collection(child, collection)
+ if layer_collection:
+ return layer_collection
+
+
+def ensure_widget_collection(context):
+ wgts_collection_name = "Widgets"
+
+ view_layer = context.view_layer
+ layer_collection = bpy.context.layer_collection
+ collection = layer_collection.collection
+
+ widget_collection = bpy.data.collections.get(wgts_collection_name)
+ if not widget_collection:
+ # ------------------------------------------
+ # Create the widget collection
+ widget_collection = bpy.data.collections.new(wgts_collection_name)
+ widget_collection.hide_viewport = True
+ widget_collection.hide_render = True
+
+ widget_layer_collection = None
+ else:
+ widget_layer_collection = find_layer_collection_by_collection(view_layer.layer_collection, widget_collection)
+
+ if not widget_layer_collection:
+ # Add the widget collection to the tree
+ collection.children.link(widget_collection)
+ widget_layer_collection = [c for c in layer_collection.children if c.collection == widget_collection][0]
+
+ # Make the widget the active collection for the upcoming added (widget) objects
+ view_layer.active_layer_collection = widget_layer_collection
+ return widget_collection
diff --git a/sequencer_kinoraw_tools/ui.py b/sequencer_kinoraw_tools/ui.py
index 9e7f2762..221f4f47 100644
--- a/sequencer_kinoraw_tools/ui.py
+++ b/sequencer_kinoraw_tools/ui.py
@@ -321,7 +321,7 @@ class JumptoCut(Panel):
if prefs.kr_mini_ui:
row = layout.row(align=True)
row.operator("sequencerextra.extrasnap", text="", icon="SNAP_ON").align = 0
- row.operator("sequencerextra.extrasnap", text="", icon="SNAP_SURFACE").align = 1
+ row.operator("sequencerextra.extrasnap", text="", icon="NONE").align = 1
row.operator("sequencerextra.extrasnap", text="", icon="SNAP_ON").align = 2
row.separator()
diff --git a/space_clip_editor_refine_solution.py b/space_clip_editor_refine_solution.py
index a3287b78..2287714b 100644
--- a/space_clip_editor_refine_solution.py
+++ b/space_clip_editor_refine_solution.py
@@ -18,12 +18,14 @@
#
# ##### END GPL LICENSE BLOCK #####
+# <pep8 compliant>
+
bl_info = {
"name": "Refine tracking solution",
"author": "Stephen Leger",
"license": "GPL",
- "version": (1, 1, 3),
- "blender": (2, 7, 8),
+ "version": (1, 1, 4),
+ "blender": (2, 80, 0),
"location": "Clip Editor > Tools > Solve > Refine Solution",
"description": "Refine motion solution by setting track weight according"
" to reprojection error",
@@ -70,10 +72,9 @@ class OP_Tracking_refine_solution(Operator):
marker_position = Vector()
for frame in range(start, end):
- camera = tracking.reconstruction.cameras.find_frame(frame)
+ camera = tracking.reconstruction.cameras.find_frame(frame=frame)
if camera is not None:
- imat = camera.matrix.inverted()
- projection_matrix = imat.transposed()
+ camera_invert = camera.matrix.inverted()
else:
continue
@@ -102,7 +103,7 @@ class OP_Tracking_refine_solution(Operator):
else:
tw = 1.0
- reprojected_position = track.bundle * projection_matrix
+ reprojected_position = camera_invert @ track.bundle
if reprojected_position.z == 0:
track.weight = 0
track.keyframe_insert("weight", frame=frame)
@@ -152,11 +153,11 @@ class OP_Tracking_reset_solution(Operator):
start = tracking.reconstruction.cameras[0].frame
end = tracking.reconstruction.cameras[-1].frame
for frame in range(start, end):
- camera = tracking.reconstruction.cameras.find_frame(frame)
+ camera = tracking.reconstruction.cameras.find_frame(frame=frame)
if camera is None:
continue
for track in tracking.tracks:
- marker = track.markers.find_frame(frame)
+ marker = track.markers.find_frame(frame=frame)
if marker is None:
continue
track.weight = 1.0
@@ -191,6 +192,13 @@ class RefineMotionTrackingPanel(Panel):
row.operator("tracking.reset_solution")
+classes =(
+ OP_Tracking_refine_solution,
+ OP_Tracking_reset_solution,
+ RefineMotionTrackingPanel
+ )
+
+
def register():
bpy.types.WindowManager.TrackingTargetError = FloatProperty(
name="Target Error",
@@ -204,11 +212,13 @@ def register():
default=25,
min=1
)
- bpy.utils.register_module(__name__)
+ for cls in classes:
+ bpy.utils.register_class(cls)
def unregister():
- bpy.utils.unregister_module(__name__)
+ for cls in reversed(classes):
+ bpy.utils.unregister_class(cls)
del bpy.types.WindowManager.TrackingTargetError
del bpy.types.WindowManager.TrackingSmooth
diff --git a/space_view3d_3d_navigation.py b/space_view3d_3d_navigation.py
index a4c359cc..8ed4bf00 100644
--- a/space_view3d_3d_navigation.py
+++ b/space_view3d_3d_navigation.py
@@ -252,9 +252,9 @@ class VIEW3D_PT_3dnavigationPanel(Panel):
col.operator("view3d.view_selected", text="View to Selected")
col = layout.column(align=True)
- col.label(text="Cursor:", icon="CURSOR")
+ col.label(text="Cursor:", icon='PIVOT_CURSOR')
row = col.row(align=True)
- row.operator("view3d.snap_cursor_to_center", text="Center")
+ row.operator("view3d.snap_cursor_to_center", text="World Origin")
row.operator("view3d.view_center_cursor", text="View")
col.operator("view3d.snap_cursor_to_selected", text="Cursor to Selected")
@@ -299,8 +299,8 @@ class VIEW3D_PT_pan_navigation1(Panel):
row = layout.row()
row.label(text="Zoom:")
row = layout.row()
- row.operator("opr.zoom_in_view1", text="In", icon="ZOOMIN")
- row.operator("opr.zoom_out_view1", text="Out", icon="ZOOMOUT")
+ row.operator("opr.zoom_in_view1", text="In", icon='ADD')
+ row.operator("opr.zoom_out_view1", text="Out", icon='REMOVE')
row = layout.row()
row.label(text="Roll:")
diff --git a/space_view3d_copy_attributes.py b/space_view3d_copy_attributes.py
index 762eb953..7037354c 100644
--- a/space_view3d_copy_attributes.py
+++ b/space_view3d_copy_attributes.py
@@ -22,7 +22,7 @@ bl_info = {
"name": "Copy Attributes Menu",
"author": "Bassam Kurdali, Fabian Fricke, Adam Wiseman",
"version": (0, 4, 8),
- "blender": (2, 63, 0),
+ "blender": (2, 80, 0),
"location": "View3D > Ctrl-C",
"description": "Copy Attributes Menu from Blender 2.4",
"wiki_url": "https://wiki.blender.org/index.php/Extensions:2.6/Py/"
@@ -33,13 +33,13 @@ bl_info = {
import bpy
from mathutils import Matrix
from bpy.types import (
- Operator,
- Menu,
- )
+ Operator,
+ Menu,
+)
from bpy.props import (
- BoolVectorProperty,
- StringProperty,
- )
+ BoolVectorProperty,
+ StringProperty,
+)
# First part of the operator Info message
INFO_MESSAGE = "Copy Attributes: "
@@ -82,7 +82,7 @@ def genops(copylist, oplist, prefix, poll_func, loopfunc):
exec_func = build_exec(loopfunc, op[3])
invoke_func = build_invoke(loopfunc, op[3])
opclass = build_op(prefix + op[0], "Copy " + op[1], op[2],
- poll_func, exec_func, invoke_func)
+ poll_func, exec_func, invoke_func)
oplist.append(opclass)
@@ -101,22 +101,25 @@ def getmat(bone, active, context, ignoreparent):
"""Helper function for visual transform copy,
gets the active transform in bone space
"""
- obj_act = context.active_object
- data_bone = obj_act.data.bones[bone.name]
+ obj_bone = bone.id_data
+ obj_active = active.id_data
+ data_bone = obj_bone.data.bones[bone.name]
# all matrices are in armature space unless commented otherwise
- otherloc = active.matrix # final 4x4 mat of target, location.
+ active_to_selected = obj_bone.matrix_world.inverted() @ obj_active.matrix_world
+ active_matrix = active_to_selected @ active.matrix
+ otherloc = active_matrix # final 4x4 mat of target, location.
bonemat_local = data_bone.matrix_local.copy() # self rest matrix
if data_bone.parent:
- parentposemat = obj_act.pose.bones[data_bone.parent.name].matrix.copy()
+ parentposemat = obj_bone.pose.bones[data_bone.parent.name].matrix.copy()
parentbonemat = data_bone.parent.matrix_local.copy()
else:
parentposemat = parentbonemat = Matrix()
if parentbonemat == parentposemat or ignoreparent:
- newmat = bonemat_local.inverted() * otherloc
+ newmat = bonemat_local.inverted() @ otherloc
else:
- bonemat = parentbonemat.inverted() * bonemat_local
+ bonemat = parentbonemat.inverted() @ bonemat_local
- newmat = bonemat.inverted() * parentposemat.inverted() * otherloc
+ newmat = bonemat.inverted() @ parentposemat.inverted() @ otherloc
return newmat
@@ -160,14 +163,16 @@ def pVisLocExec(bone, active, context):
def pVisRotExec(bone, active, context):
+ obj_bone = bone.id_data
rotcopy(bone, getmat(bone, active,
- context, not context.active_object.data.bones[bone.name].use_inherit_rotation))
+ context, not obj_bone.data.bones[bone.name].use_inherit_rotation))
def pVisScaExec(bone, active, context):
+ obj_bone = bone.id_data
bone.scale = getmat(bone, active, context,
- not context.active_object.data.bones[bone.name].use_inherit_scale)\
- .to_scale()
+ not obj_bone.data.bones[bone.name].use_inherit_scale)\
+ .to_scale()
def pDrwExec(bone, active, context):
@@ -207,30 +212,29 @@ def pBBonesExec(bone, active, context):
pose_copies = (
- ('pose_loc_loc', "Local Location",
- "Copy Location from Active to Selected", pLocLocExec),
- ('pose_loc_rot', "Local Rotation",
- "Copy Rotation from Active to Selected", pLocRotExec),
- ('pose_loc_sca', "Local Scale",
- "Copy Scale from Active to Selected", pLocScaExec),
- ('pose_vis_loc', "Visual Location",
- "Copy Location from Active to Selected", pVisLocExec),
- ('pose_vis_rot', "Visual Rotation",
- "Copy Rotation from Active to Selected", pVisRotExec),
- ('pose_vis_sca', "Visual Scale",
- "Copy Scale from Active to Selected", pVisScaExec),
- ('pose_drw', "Bone Shape",
- "Copy Bone Shape from Active to Selected", pDrwExec),
- ('pose_lok', "Protected Transform",
- "Copy Protected Transforms from Active to Selected", pLokExec),
- ('pose_con', "Bone Constraints",
- "Copy Object Constraints from Active to Selected", pConExec),
- ('pose_iks', "IK Limits",
- "Copy IK Limits from Active to Selected", pIKsExec),
- ('bbone_settings', "BBone Settings",
- "Copy BBone Settings from Active to Selected", pBBonesExec),
- )
-
+ ('pose_loc_loc', "Local Location",
+ "Copy Location from Active to Selected", pLocLocExec),
+ ('pose_loc_rot', "Local Rotation",
+ "Copy Rotation from Active to Selected", pLocRotExec),
+ ('pose_loc_sca', "Local Scale",
+ "Copy Scale from Active to Selected", pLocScaExec),
+ ('pose_vis_loc', "Visual Location",
+ "Copy Location from Active to Selected", pVisLocExec),
+ ('pose_vis_rot', "Visual Rotation",
+ "Copy Rotation from Active to Selected", pVisRotExec),
+ ('pose_vis_sca', "Visual Scale",
+ "Copy Scale from Active to Selected", pVisScaExec),
+ ('pose_drw', "Bone Shape",
+ "Copy Bone Shape from Active to Selected", pDrwExec),
+ ('pose_lok', "Protected Transform",
+ "Copy Protected Transforms from Active to Selected", pLokExec),
+ ('pose_con', "Bone Constraints",
+ "Copy Object Constraints from Active to Selected", pConExec),
+ ('pose_iks', "IK Limits",
+ "Copy IK Limits from Active to Selected", pIKsExec),
+ ('bbone_settings', "BBone Settings",
+ "Copy BBone Settings from Active to Selected", pBBonesExec),
+)
@classmethod
def pose_poll_func(cls, context):
@@ -248,10 +252,10 @@ class CopySelectedPoseConstraints(Operator):
bl_idname = "pose.copy_selected_constraints"
bl_label = "Copy Selected Constraints"
- selection = BoolVectorProperty(
- size=32,
- options={'SKIP_SAVE'}
- )
+ selection: BoolVectorProperty(
+ size=32,
+ options={'SKIP_SAVE'}
+ )
poll = pose_poll_func
invoke = pose_invoke_func
@@ -260,7 +264,7 @@ class CopySelectedPoseConstraints(Operator):
layout = self.layout
for idx, const in enumerate(context.active_pose_bone.constraints):
layout.prop(self, "selection", index=idx, text=const.name,
- toggle=True)
+ toggle=True)
def execute(self, context):
active = context.active_pose_bone
@@ -271,8 +275,8 @@ class CopySelectedPoseConstraints(Operator):
if flag:
old_constraint = active.constraints[index]
new_constraint = bone.constraints.new(
- active.constraints[index].type
- )
+ active.constraints[index].type
+ )
generic_copy(old_constraint, new_constraint)
return {'FINISHED'}
@@ -306,9 +310,9 @@ def obLoopExec(self, context, funk):
def world_to_basis(active, ob, context):
"""put world coords of active as basis coords of ob"""
- local = ob.parent.matrix_world.inverted() * active.matrix_world
- P = ob.matrix_basis * ob.matrix_local.inverted()
- mat = P * local
+ local = ob.parent.matrix_world.inverted() @ active.matrix_world
+ P = ob.matrix_basis @ ob.matrix_local.inverted()
+ mat = P @ local
return(mat)
@@ -355,17 +359,17 @@ def obVisSca(ob, active, context):
def obDrw(ob, active, context):
- ob.draw_type = active.draw_type
+ ob.display_type = active.display_type
ob.show_axis = active.show_axis
ob.show_bounds = active.show_bounds
- ob.draw_bounds_type = active.draw_bounds_type
+ ob.display_bounds_type = active.display_bounds_type
ob.show_name = active.show_name
ob.show_texture_space = active.show_texture_space
ob.show_transparent = active.show_transparent
ob.show_wire = active.show_wire
- ob.show_x_ray = active.show_x_ray
- ob.empty_draw_type = active.empty_draw_type
- ob.empty_draw_size = active.empty_draw_size
+ ob.show_in_front = active.show_in_front
+ ob.empty_display_type = active.empty_display_type
+ ob.empty_display_size = active.empty_display_size
def obOfs(ob, active, context):
@@ -382,11 +386,6 @@ def obCol(ob, active, context):
ob.color = active.color
-def obMas(ob, active, context):
- ob.game.mass = active.game.mass
- return('INFO', "Mass copied")
-
-
def obLok(ob, active, context):
for index, state in enumerate(active.lock_location):
ob.lock_location[index] = state
@@ -429,13 +428,13 @@ def obMod(ob, active, context):
ob.modifiers.remove(modifier)
for old_modifier in active.modifiers.values():
new_modifier = ob.modifiers.new(name=old_modifier.name,
- type=old_modifier.type)
+ type=old_modifier.type)
generic_copy(old_modifier, new_modifier)
return('INFO', "Modifiers copied")
def obGrp(ob, active, context):
- for grp in bpy.data.groups:
+ for grp in bpy.data.collections:
if active.name in grp.objects and ob.name not in grp.objects:
grp.objects.link(ob)
return('INFO', "Groups copied")
@@ -483,62 +482,60 @@ def obWei(ob, active, context):
for vgs in range(0, len(groups)):
if groups[vgs].name == groupName:
groups[vgs].add((v.index,),
- vgroupIndex_weight[i][1], "REPLACE")
+ vgroupIndex_weight[i][1], "REPLACE")
return('INFO', "Weights copied")
object_copies = (
- # ('obj_loc', "Location",
- # "Copy Location from Active to Selected", obLoc),
- # ('obj_rot', "Rotation",
- # "Copy Rotation from Active to Selected", obRot),
- # ('obj_sca', "Scale",
- # "Copy Scale from Active to Selected", obSca),
- ('obj_vis_loc', "Location",
- "Copy Location from Active to Selected", obVisLoc),
- ('obj_vis_rot', "Rotation",
- "Copy Rotation from Active to Selected", obVisRot),
- ('obj_vis_sca', "Scale",
- "Copy Scale from Active to Selected", obVisSca),
- ('obj_drw', "Draw Options",
- "Copy Draw Options from Active to Selected", obDrw),
- ('obj_ofs', "Time Offset",
- "Copy Time Offset from Active to Selected", obOfs),
- ('obj_dup', "Dupli",
- "Copy Dupli from Active to Selected", obDup),
- ('obj_col', "Object Color",
- "Copy Object Color from Active to Selected", obCol),
- ('obj_mas', "Mass",
- "Copy Mass from Active to Selected", obMas),
- # ('obj_dmp', "Damping",
- # "Copy Damping from Active to Selected"),
- # ('obj_all', "All Physical Attributes",
- # "Copy Physical Attributes from Active to Selected"),
- # ('obj_prp', "Properties",
- # "Copy Properties from Active to Selected"),
- # ('obj_log', "Logic Bricks",
- # "Copy Logic Bricks from Active to Selected"),
- ('obj_lok', "Protected Transform",
- "Copy Protected Transforms from Active to Selected", obLok),
- ('obj_con', "Object Constraints",
- "Copy Object Constraints from Active to Selected", obCon),
- # ('obj_nla', "NLA Strips",
- # "Copy NLA Strips from Active to Selected"),
- # ('obj_tex', "Texture Space",
- # "Copy Texture Space from Active to Selected", obTex),
- # ('obj_sub', "Subsurf Settings",
- # "Copy Subsurf Settings from Active to Selected"),
- # ('obj_smo', "AutoSmooth",
- # "Copy AutoSmooth from Active to Selected"),
- ('obj_idx', "Pass Index",
- "Copy Pass Index from Active to Selected", obIdx),
- ('obj_mod', "Modifiers",
- "Copy Modifiers from Active to Selected", obMod),
- ('obj_wei', "Vertex Weights",
- "Copy vertex weights based on indices", obWei),
- ('obj_grp', "Group Links",
- "Copy selected into active object's groups", obGrp)
- )
+ # ('obj_loc', "Location",
+ # "Copy Location from Active to Selected", obLoc),
+ # ('obj_rot', "Rotation",
+ # "Copy Rotation from Active to Selected", obRot),
+ # ('obj_sca', "Scale",
+ # "Copy Scale from Active to Selected", obSca),
+ ('obj_vis_loc', "Location",
+ "Copy Location from Active to Selected", obVisLoc),
+ ('obj_vis_rot', "Rotation",
+ "Copy Rotation from Active to Selected", obVisRot),
+ ('obj_vis_sca', "Scale",
+ "Copy Scale from Active to Selected", obVisSca),
+ ('obj_drw', "Draw Options",
+ "Copy Draw Options from Active to Selected", obDrw),
+ ('obj_ofs', "Time Offset",
+ "Copy Time Offset from Active to Selected", obOfs),
+ ('obj_dup', "Dupli",
+ "Copy Dupli from Active to Selected", obDup),
+ ('obj_col', "Object Color",
+ "Copy Object Color from Active to Selected", obCol),
+ # ('obj_dmp', "Damping",
+ # "Copy Damping from Active to Selected"),
+ # ('obj_all', "All Physical Attributes",
+ # "Copy Physical Attributes from Active to Selected"),
+ # ('obj_prp', "Properties",
+ # "Copy Properties from Active to Selected"),
+ # ('obj_log', "Logic Bricks",
+ # "Copy Logic Bricks from Active to Selected"),
+ ('obj_lok', "Protected Transform",
+ "Copy Protected Transforms from Active to Selected", obLok),
+ ('obj_con', "Object Constraints",
+ "Copy Object Constraints from Active to Selected", obCon),
+ # ('obj_nla', "NLA Strips",
+ # "Copy NLA Strips from Active to Selected"),
+ # ('obj_tex', "Texture Space",
+ # "Copy Texture Space from Active to Selected", obTex),
+ # ('obj_sub', "Subsurf Settings",
+ # "Copy Subsurf Settings from Active to Selected"),
+ # ('obj_smo', "AutoSmooth",
+ # "Copy AutoSmooth from Active to Selected"),
+ ('obj_idx', "Pass Index",
+ "Copy Pass Index from Active to Selected", obIdx),
+ ('obj_mod', "Modifiers",
+ "Copy Modifiers from Active to Selected", obMod),
+ ('obj_wei', "Vertex Weights",
+ "Copy vertex weights based on indices", obWei),
+ ('obj_grp', "Group Links",
+ "Copy selected into active object's groups", obGrp)
+)
@classmethod
@@ -557,10 +554,10 @@ class CopySelectedObjectConstraints(Operator):
bl_idname = "object.copy_selected_constraints"
bl_label = "Copy Selected Constraints"
- selection = BoolVectorProperty(
- size=32,
- options={'SKIP_SAVE'}
- )
+ selection: BoolVectorProperty(
+ size=32,
+ options={'SKIP_SAVE'}
+ )
poll = object_poll_func
invoke = object_invoke_func
@@ -569,7 +566,7 @@ class CopySelectedObjectConstraints(Operator):
layout = self.layout
for idx, const in enumerate(context.active_object.constraints):
layout.prop(self, "selection", index=idx, text=const.name,
- toggle=True)
+ toggle=True)
def execute(self, context):
active = context.active_object
@@ -580,8 +577,8 @@ class CopySelectedObjectConstraints(Operator):
if flag:
old_constraint = active.constraints[index]
new_constraint = obj.constraints.new(
- active.constraints[index].type
- )
+ active.constraints[index].type
+ )
generic_copy(old_constraint, new_constraint)
return{'FINISHED'}
@@ -591,10 +588,10 @@ class CopySelectedObjectModifiers(Operator):
bl_idname = "object.copy_selected_modifiers"
bl_label = "Copy Selected Modifiers"
- selection = BoolVectorProperty(
- size=32,
- options={'SKIP_SAVE'}
- )
+ selection: BoolVectorProperty(
+ size=32,
+ options={'SKIP_SAVE'}
+ )
poll = object_poll_func
invoke = object_invoke_func
@@ -603,7 +600,7 @@ class CopySelectedObjectModifiers(Operator):
layout = self.layout
for idx, const in enumerate(context.active_object.modifiers):
layout.prop(self, 'selection', index=idx, text=const.name,
- toggle=True)
+ toggle=True)
def execute(self, context):
active = context.active_object
@@ -614,9 +611,9 @@ class CopySelectedObjectModifiers(Operator):
if flag:
old_modifier = active.modifiers[index]
new_modifier = obj.modifiers.new(
- type=active.modifiers[index].type,
- name=active.modifiers[index].name
- )
+ type=active.modifiers[index].type,
+ name=active.modifiers[index].name
+ )
generic_copy(old_modifier, new_modifier)
return{'FINISHED'}
@@ -664,23 +661,23 @@ class MESH_MT_CopyFaceSettings(Menu):
layout = self.layout
op = layout.operator(MESH_OT_CopyFaceSettings.bl_idname,
- text="Copy Material")
+ text="Copy Material")
op['layer'] = ''
op['mode'] = 'MAT'
if mesh.uv_textures.active:
op = layout.operator(MESH_OT_CopyFaceSettings.bl_idname,
- text="Copy Active UV Image")
+ text="Copy Active UV Image")
op['layer'] = ''
op['mode'] = 'IMAGE'
op = layout.operator(MESH_OT_CopyFaceSettings.bl_idname,
- text="Copy Active UV Coords")
+ text="Copy Active UV Coords")
op['layer'] = ''
op['mode'] = 'UV'
if mesh.vertex_colors.active:
op = layout.operator(MESH_OT_CopyFaceSettings.bl_idname,
- text="Copy Active Vertex Colors")
+ text="Copy Active Vertex Colors")
op['layer'] = ''
op['mode'] = 'VCOL'
@@ -704,7 +701,7 @@ class MESH_MT_CopyImagesFromLayer(Menu):
def poll(cls, context):
obj = context.active_object
return obj and obj.mode == "EDIT_MESH" and len(
- obj.data.uv_layers) > 1
+ obj.data.uv_layers) > 1
def draw(self, context):
mesh = context.active_object.data
@@ -718,7 +715,7 @@ class MESH_MT_CopyUVCoordsFromLayer(Menu):
def poll(cls, context):
obj = context.active_object
return obj and obj.mode == "EDIT_MESH" and len(
- obj.data.uv_layers) > 1
+ obj.data.uv_layers) > 1
def draw(self, context):
mesh = context.active_object.data
@@ -732,7 +729,7 @@ class MESH_MT_CopyVertexColorsFromLayer(Menu):
def poll(cls, context):
obj = context.active_object
return obj and obj.mode == "EDIT_MESH" and len(
- obj.data.vertex_colors) > 1
+ obj.data.vertex_colors) > 1
def draw(self, context):
mesh = context.active_object.data
@@ -759,14 +756,14 @@ class MESH_OT_CopyFaceSettings(Operator):
bl_label = "Copy Face Settings"
bl_options = {'REGISTER', 'UNDO'}
- mode = StringProperty(
- name="Mode",
- options={"HIDDEN"},
- )
- layer = StringProperty(
- name="Layer",
- options={"HIDDEN"},
- )
+ mode: StringProperty(
+ name="Mode",
+ options={"HIDDEN"},
+ )
+ layer: StringProperty(
+ name="Layer",
+ options={"HIDDEN"},
+ )
@classmethod
def poll(cls, context):
@@ -838,8 +835,25 @@ class MESH_OT_CopyFaceSettings(Operator):
return(retval)
+classes = (
+ CopySelectedPoseConstraints,
+ VIEW3D_MT_posecopypopup,
+ CopySelectedObjectConstraints,
+ CopySelectedObjectModifiers,
+ VIEW3D_MT_copypopup,
+ MESH_MT_CopyFaceSettings,
+ MESH_MT_CopyImagesFromLayer,
+ MESH_MT_CopyUVCoordsFromLayer,
+ MESH_MT_CopyVertexColorsFromLayer,
+ MESH_OT_CopyFaceSettings,
+ *pose_ops,
+ *object_ops,
+)
+
def register():
- bpy.utils.register_module(__name__)
+ from bpy.utils import register_class
+ for cls in classes:
+ register_class(cls)
# mostly to get the keymap working
kc = bpy.context.window_manager.keyconfigs.addon
@@ -888,7 +902,9 @@ def unregister():
if kmi.properties.name == 'VIEW3D_MT_copypopup':
km.keymap_items.remove(kmi)
- bpy.utils.unregister_module(__name__)
+ from bpy.utils import unregister_class
+ for cls in classes:
+ unregister_class(cls)
if __name__ == "__main__":
diff --git a/space_view3d_display_tools/__init__.py b/space_view3d_display_tools/__init__.py
index 58699f98..462f417e 100644
--- a/space_view3d_display_tools/__init__.py
+++ b/space_view3d_display_tools/__init__.py
@@ -80,7 +80,7 @@ class DisplayToolsPanel(Panel):
bl_category = "Display"
bl_options = {'DEFAULT_CLOSED'}
- draw_type_icons = {
+ display_type_icons = {
'BOUNDS': 'BBOX',
'WIRE': 'WIRE',
'SOLID': 'SOLID',
@@ -142,7 +142,7 @@ class DisplayToolsPanel(Panel):
col.prop(view, "show_all_objects_origin", toggle=True)
col.prop(view, "show_backface_culling", toggle=True)
if obj:
- col.prop(obj, "show_x_ray", text="X-Ray", toggle=True)
+ col.prop(obj, "show_in_front", text="X-Ray", toggle=True)
if obj and obj_type == 'MESH':
col.prop(obj, "show_transparent", text="Transparency", toggle=True)
@@ -176,8 +176,8 @@ class DisplayToolsPanel(Panel):
if obj:
col = layout.column(align=True)
col.alignment = 'EXPAND'
- col.label(text="Maximum:")
- col.prop(obj, "draw_type", text="", icon=self.draw_type_icons[obj.draw_type])
+ col.label(text="Display As:")
+ col.prop(obj, "display_type", text="", icon=self.display_type_icons[obj.display_type])
col = layout.column(align=True)
col.alignment = 'CENTER'
@@ -394,7 +394,7 @@ class DisplayToolsPanel(Panel):
row.prop(display_tools, "UiTabDrop", index=4, text="Selection", icon=icon_active_4)
if not SELECT2DROP:
- row.operator("view3d.select_border", text="", icon="MESH_PLANE")
+ row.operator("view3d.select_box", text="", icon="MESH_PLANE")
row.operator("view3d.select_circle", text="", icon="MESH_CIRCLE")
row.label(text="", icon="BLANK1")
else:
diff --git a/space_view3d_display_tools/display.py b/space_view3d_display_tools/display.py
index d0310ee8..6c1c6796 100644
--- a/space_view3d_display_tools/display.py
+++ b/space_view3d_display_tools/display.py
@@ -82,10 +82,10 @@ class DisplayDrawChange(Operator, BasePollCheck):
if not selection:
for obj in bpy.data.objects:
- obj.draw_type = self.drawing
+ obj.display_type = self.drawing
else:
for obj in selection:
- obj.draw_type = self.drawing
+ obj.display_type = self.drawing
except:
self.report({'ERROR'}, "Setting Draw Type could not be applied")
return {'CANCELLED'}
@@ -110,12 +110,12 @@ class DisplayBoundsSwitch(Operator, BasePollCheck):
for obj in bpy.data.objects:
obj.show_bounds = self.bounds
if self.bounds:
- obj.draw_bounds_type = scene.BoundingMode
+ obj.display_bounds_type = scene.BoundingMode
else:
for obj in selection:
obj.show_bounds = self.bounds
if self.bounds:
- obj.draw_bounds_type = scene.BoundingMode
+ obj.display_bounds_type = scene.BoundingMode
except:
self.report({'ERROR'}, "Display/Hide Bounding box overlay failed")
return {'CANCELLED'}
@@ -164,10 +164,10 @@ class DisplayXRayOn(Operator, BasePollCheck):
if not selection:
for obj in bpy.data.objects:
- obj.show_x_ray = self.xrays
+ obj.show_in_front = self.xrays
else:
for obj in selection:
- obj.show_x_ray = self.xrays
+ obj.show_in_front = self.xrays
except:
self.report({'ERROR'}, "Turn on/off X-ray mode failed")
return {'CANCELLED'}
diff --git a/space_view3d_display_tools/fast_navigate.py b/space_view3d_display_tools/fast_navigate.py
index 269aec2c..5d61138a 100644
--- a/space_view3d_display_tools/fast_navigate.py
+++ b/space_view3d_display_tools/fast_navigate.py
@@ -43,14 +43,14 @@ def display_particles(mode, dis_particles):
for particles in bpy.data.particles:
if scene.ShowParticles is False:
- particles.draw_method = 'NONE'
+ particles.display_method = 'NONE'
else:
if particles.type == 'EMITTER':
- particles.draw_method = 'DOT'
- particles.draw_percentage = 100
+ particles.display_method = 'DOT'
+ particles.display_percentage = 100
else:
- particles.draw_method = 'RENDER'
- particles.draw_percentage = dis_particles
+ particles.display_method = 'RENDER'
+ particles.display_percentage = dis_particles
return dis_particles
@@ -149,15 +149,15 @@ class FastNavigate(Operator):
self.store_viewport_shade = shade
for particle in bpy.data.particles:
self.store_init_particles[particle.name] = \
- [particle.draw_method, particle.draw_percentage]
+ [particle.display_method, particle.display_percentage]
else:
if not shade:
self.store_fail = True
else:
shade = self.store_viewport_shade or 'SOLID'
for particle in bpy.data.particles:
- particle.draw_method = self.store_init_particles[particle.name][0]
- particle.draw_percentage = self.store_init_particles[particle.name][1]
+ particle.display_method = self.store_init_particles[particle.name][0]
+ particle.display_percentage = self.store_init_particles[particle.name][1]
except:
self.store_fail = True
diff --git a/space_view3d_display_tools/select_tools.py b/space_view3d_display_tools/select_tools.py
index 95dc4ff2..2eb79dfc 100644
--- a/space_view3d_display_tools/select_tools.py
+++ b/space_view3d_display_tools/select_tools.py
@@ -65,7 +65,7 @@ class ShowHideObject(Operator):
i.hide = True
i.select = False
- if i.type not in ['CAMERA', 'LAMP']:
+ if i.type not in ['CAMERA', 'LIGHT']:
i.hide_render = True
except:
continue
@@ -103,7 +103,7 @@ class HideAllObjects(Operator):
i.hide = True
i.select = False
- if i.type not in ['CAMERA', 'LAMP']:
+ if i.type not in ['CAMERA', 'LIGHT']:
i.hide_render = True
else:
obj_name = context.object.name
@@ -113,7 +113,7 @@ class HideAllObjects(Operator):
i.hide = True
i.select = False
- if i.type not in ['CAMERA', 'LAMP']:
+ if i.type not in ['CAMERA', 'LIGHT']:
i.hide_render = True
return {'FINISHED'}
@@ -232,11 +232,11 @@ class OBJECT_OT_HideShowByTypeTemplate():
('LATTICE', 'Lattice', ''),
('EMPTY', 'Empty', ''),
('CAMERA', 'Camera', ''),
- ('LAMP', 'Lamp', ''),
+ ('LIGHT', 'Lamp', ''),
('ALL', 'All', '')),
name="Type",
description="Type",
- default='LAMP',
+ default='LIGHT',
options={'ANIMATABLE'}
)
diff --git a/space_view3d_display_tools/selection_restrictor.py b/space_view3d_display_tools/selection_restrictor.py
index feb3b8b8..71561bfe 100644
--- a/space_view3d_display_tools/selection_restrictor.py
+++ b/space_view3d_display_tools/selection_restrictor.py
@@ -45,7 +45,7 @@ curve = 'OUTLINER_OB_CURVE'
arm = 'OUTLINER_OB_ARMATURE'
empty = 'OUTLINER_OB_EMPTY'
cam = 'OUTLINER_OB_CAMERA'
-lamp = 'OUTLINER_OB_LAMP'
+lamp = 'OUTLINER_OB_LIGHT'
lat = 'OUTLINER_OB_LATTICE'
font = 'OUTLINER_OB_FONT'
meta = 'OUTLINER_OB_META'
@@ -135,10 +135,10 @@ def check_restrictors(dummy):
# lamp
if bpy.context.scene.get('lamprestrictor') is None:
lamprestrictorenabled = True
- lamp = 'OUTLINER_OB_LAMP'
+ lamp = 'OUTLINER_OB_LIGHT'
else:
lamprestrictorenabled = False
- lamp = 'LAMP_DATA'
+ lamp = 'LIGHT_DATA'
# lattice
if bpy.context.scene.get('latrestrictor') is None:
@@ -408,7 +408,7 @@ class RestrictorCam(Operator):
# Restrictor for Lamps
class RestrictorLamp(Operator):
- bl_idname = "restrictor.lamp"
+ bl_idname = "restrictor.light"
bl_label = "Restrictor Lamps"
bl_option = {'REGISTER', 'UNDO'}
bl_description = "Lamps selection restrictor"
@@ -421,18 +421,18 @@ class RestrictorLamp(Operator):
lamprestrictorenabled = True
if bpy.context.scene.get('lamprestrictor') is not None:
del bpy.context.scene['lamprestrictor']
- lamp = 'OUTLINER_OB_LAMP'
+ lamp = 'OUTLINER_OB_LIGHT'
for ob in bpy.context.scene.objects:
- if ob.type == 'LAMP':
+ if ob.type == 'LIGHT':
if ob.get('ignore_restrictors') is None:
ob.hide_select = False
else:
lamprestrictorenabled = False
bpy.context.scene['lamprestrictor'] = 1
- lamp = 'LAMP_DATA'
+ lamp = 'LIGHT_DATA'
for ob in bpy.context.scene.objects:
- if ob.type == 'LAMP':
+ if ob.type == 'LIGHT':
if ob.get('ignore_restrictors') is None:
ob.hide_select = True
ob.select = False
@@ -638,7 +638,7 @@ class RefreshRestrictors(Operator):
'armrestrictor': ("OUTLINER_OB_ARMATURE", "ARMATURE_DATA", "ARMATURE"),
'emptyrestrictor': ("OUTLINER_OB_EMPTY", "EMPTY_DATA", "EMPTY"),
'camrestrictor': ("OUTLINER_OB_CAMERA", "CAMERA_DATA", "CAMERA"),
- 'lamprestrictor': ("OUTLINER_OB_LAMP", "LAMP_DATA", "LAMP"),
+ 'lamprestrictor': ("OUTLINER_OB_LIGHT", "LIGHT_DATA", "LIGHT"),
'latrestrictor': ("OUTLINER_OB_LATTICE", "LATTICE", "LATTICE"),
'fontrestrictor': ("OUTLINER_OB_FONT", "FONT", "FONT"),
'metarestrictor': ("OUTLINER_OB_META", "META_DATA", "META"),
@@ -664,7 +664,7 @@ class RefreshRestrictors(Operator):
arm = gl_icon if types == "ARMATURE" else arm
empty = gl_icon if types == "EMPTY" else empty
cam = gl_icon if types == "CAMERA" else cam
- lamp = gl_icon if types == "LAMP" else lamp
+ lamp = gl_icon if types == "LIGHT" else lamp
lat = gl_icon if types == "LATTICE" else lat
font = gl_icon if types == "FONT" else font
meta = gl_icon if types == "META" else meta
@@ -701,7 +701,7 @@ class RestrictorSelection(Menu):
layout.operator("restrictor.arm", icon=arm, text="Armature")
layout.operator("restrictor.empty", icon=empty, text="Empty")
layout.operator("restrictor.cam", icon=cam, text="Camera")
- layout.operator("restrictor.lamp", icon=lamp, text="Lamp")
+ layout.operator("restrictor.light", icon=lamp, text="Lamp")
layout.operator("restrictor.lat", icon=lat, text="Lattice")
layout.operator("restrictor.font", icon=font, text="Font")
layout.operator("restrictor.meta", icon=meta, text="MetaBall")
diff --git a/space_view3d_display_tools/useless_tools.py b/space_view3d_display_tools/useless_tools.py
index 512fcf2d..22931423 100644
--- a/space_view3d_display_tools/useless_tools.py
+++ b/space_view3d_display_tools/useless_tools.py
@@ -204,7 +204,7 @@ class UTSubsurfHideSelAll(Operator):
objects = bpy.context.selected_objects if self.selected else bpy.data.objects
for e in objects:
try:
- if e.type not in {"LAMP", "CAMERA", "EMPTY"}:
+ if e.type not in {"LIGHT", "CAMERA", "EMPTY"}:
e.modifiers['Subsurf'].show_viewport = self.show
except Exception as k:
name = getattr(e, "name", "Nameless")
@@ -233,7 +233,7 @@ class UTOptimalDisplaySelAll(Operator):
objects = bpy.context.selected_objects if self.selected else bpy.data.objects
for e in objects:
try:
- if e.type not in {"LAMP", "CAMERA", "EMPTY"}:
+ if e.type not in {"LIGHT", "CAMERA", "EMPTY"}:
e.modifiers['Subsurf'].show_only_control_edges = self.on
except Exception as k:
name = getattr(e, "name", "Nameless")
diff --git a/space_view3d_math_vis/__init__.py b/space_view3d_math_vis/__init__.py
index 50d399f2..0dcb7b7a 100644
--- a/space_view3d_math_vis/__init__.py
+++ b/space_view3d_math_vis/__init__.py
@@ -22,7 +22,7 @@ bl_info = {
"name": "Math Vis (Console)",
"author": "Campbell Barton",
"version": (0, 2, 1),
- "blender": (2, 57, 0),
+ "blender": (2, 80, 0),
"location": "Properties: Scene > Math Vis Console and Python Console: Menu",
"description": "Display console defined mathutils variables in the 3D view",
"wiki_url": "https://wiki.blender.org/index.php/Extensions:2.6/Py/"
@@ -74,7 +74,7 @@ class PanelConsoleVars(Panel):
if len(state_props) == 0:
box = layout.box()
col = box.column(align=True)
- col.label("No vars to display")
+ col.label(text="No vars to display")
else:
layout.template_list(
'MathVisVarList',
@@ -98,7 +98,7 @@ class DeleteVar(Operator):
bl_description = "Remove the variable from the Console"
bl_options = {'REGISTER'}
- key = StringProperty(name="Key")
+ key: StringProperty(name="Key")
def execute(self, context):
locals = utils.console_namespace()
@@ -114,7 +114,7 @@ class ToggleDisplay(Operator):
bl_description = "Change the display state of the var"
bl_options = {'REGISTER'}
- key = StringProperty(name="Key")
+ key: StringProperty(name="Key")
def execute(self, context):
utils.VarStates.toggle_display_state(self.key)
@@ -128,7 +128,7 @@ class ToggleLock(Operator):
bl_description = "Lock the var from being deleted"
bl_options = {'REGISTER'}
- key = StringProperty(name="Key")
+ key: StringProperty(name="Key")
def execute(self, context):
utils.VarStates.toggle_lock_state(self.key)
@@ -177,8 +177,8 @@ def call_console_hook(self, context):
class MathVisStateProp(PropertyGroup):
- ktype = StringProperty()
- state = BoolVectorProperty(default=(False, False), size=2)
+ ktype: StringProperty()
+ state: BoolVectorProperty(default=(False, False), size=2)
class MathVisVarList(UIList):
@@ -219,22 +219,22 @@ class MathVisVarList(UIList):
class MathVis(PropertyGroup):
- index = IntProperty(
+ index: IntProperty(
name="index"
)
- bbox_hide = BoolProperty(
+ bbox_hide: BoolProperty(
name="Hide BBoxes",
default=False,
description="Hide the bounding boxes rendered for Matrix like items",
update=call_console_hook
)
- name_hide = BoolProperty(
+ name_hide: BoolProperty(
name="Hide Names",
default=False,
description="Hide the names of the rendered items",
update=call_console_hook
)
- bbox_scale = FloatProperty(
+ bbox_scale: FloatProperty(
name="Scale factor",
min=0, default=1,
description="Resize the Bounding Box and the coordinate "
diff --git a/space_view3d_math_vis/draw.py b/space_view3d_math_vis/draw.py
index 8a8cfa7a..d4acda2f 100644
--- a/space_view3d_math_vis/draw.py
+++ b/space_view3d_math_vis/draw.py
@@ -20,6 +20,8 @@
import bpy
import blf
+import gpu
+from gpu_extras.batch import batch_for_shader
from . import utils
from mathutils import Vector
@@ -27,6 +29,8 @@ from mathutils import Vector
SpaceView3D = bpy.types.SpaceView3D
callback_handle = []
+single_color_shader = gpu.shader.from_builtin('3D_UNIFORM_COLOR')
+smooth_color_shader = gpu.shader.from_builtin('3D_SMOOTH_COLOR')
def tag_redraw_areas():
context = bpy.context
@@ -64,28 +68,16 @@ def callback_disable():
def draw_callback_px():
context = bpy.context
- from bgl import glColor3f
- font_id = 0 # XXX, need to find out how best to get this.
+ if context.window_manager.MathVisProp.name_hide:
+ return
+
+ font_id = 0
blf.size(font_id, 12, 72)
data_matrix, data_quat, data_euler, data_vector, data_vector_array = utils.console_math_data()
-
- name_hide = context.window_manager.MathVisProp.name_hide
-
- if name_hide:
- return
-
if not data_matrix and not data_quat and not data_euler and not data_vector and not data_vector_array:
-
- '''
- # draw some text
- glColor3f(1.0, 0.0, 0.0)
- blf.position(font_id, 180, 10, 0)
- blf.draw(font_id, "Python Console has no mathutils definitions")
- '''
return
- glColor3f(1.0, 1.0, 1.0)
region = context.region
region3d = context.space_data.region_3d
@@ -93,11 +85,10 @@ def draw_callback_px():
region_mid_width = region.width / 2.0
region_mid_height = region.height / 2.0
- # vars for projection
perspective_matrix = region3d.perspective_matrix.copy()
def draw_text(text, vec, dx=3.0, dy=-4.0):
- vec_4d = perspective_matrix * vec.to_4d()
+ vec_4d = perspective_matrix @ vec.to_4d()
if vec_4d.w > 0.0:
x = region_mid_width + region_mid_width * (vec_4d.x / vec_4d.w)
y = region_mid_height + region_mid_height * (vec_4d.y / vec_4d.w)
@@ -105,191 +96,138 @@ def draw_callback_px():
blf.position(font_id, x + dx, y + dy, 0.0)
blf.draw(font_id, text)
- # points
if data_vector:
for key, vec in data_vector.items():
draw_text(key, vec)
- # lines
if data_vector_array:
for key, vec in data_vector_array.items():
if vec:
draw_text(key, vec[0])
- # matrix
if data_matrix:
for key, mat in data_matrix.items():
loc = Vector((mat[0][3], mat[1][3], mat[2][3]))
draw_text(key, loc, dx=10, dy=-20)
- line = 20
+ offset_y = 20
if data_quat:
loc = context.scene.cursor_location.copy()
for key, mat in data_quat.items():
- draw_text(key, loc, dy=-line)
- line += 20
+ draw_text(key, loc, dy=-offset_y)
+ offset_y += 20
if data_euler:
loc = context.scene.cursor_location.copy()
for key, mat in data_euler.items():
- draw_text(key, loc, dy=-line)
- line += 20
-
+ draw_text(key, loc, dy=-offset_y)
+ offset_y += 20
def draw_callback_view():
- context = bpy.context
-
- from bgl import (
- glEnable,
- glDisable,
- glColor3f,
- glVertex3f,
- glPointSize,
- glLineWidth,
- glBegin,
- glEnd,
- glLineStipple,
- GL_POINTS,
- GL_LINE_STRIP,
- GL_LINES,
- GL_LINE_STIPPLE
- )
+ settings = bpy.context.window_manager.MathVisProp
+ scale = settings.bbox_scale
+ with_bounding_box = not settings.bbox_hide
data_matrix, data_quat, data_euler, data_vector, data_vector_array = utils.console_math_data()
- # draw_matrix modifiers
- bbox_hide = context.window_manager.MathVisProp.bbox_hide
- bbox_scale = context.window_manager.MathVisProp.bbox_scale
-
- # draw_matrix vars
- zero = Vector((0.0, 0.0, 0.0))
- x_p = Vector((bbox_scale, 0.0, 0.0))
- x_n = Vector((-bbox_scale, 0.0, 0.0))
- y_p = Vector((0.0, bbox_scale, 0.0))
- y_n = Vector((0.0, -bbox_scale, 0.0))
- z_p = Vector((0.0, 0.0, bbox_scale))
- z_n = Vector((0.0, 0.0, -bbox_scale))
- bb = [Vector() for i in range(8)]
-
- def draw_matrix(mat):
- zero_tx = mat * zero
-
- glLineWidth(2.0)
-
- # x
- glColor3f(1.0, 0.2, 0.2)
- glBegin(GL_LINES)
- glVertex3f(*(zero_tx))
- glVertex3f(*(mat * x_p))
- glEnd()
-
- glColor3f(0.6, 0.0, 0.0)
- glBegin(GL_LINES)
- glVertex3f(*(zero_tx))
- glVertex3f(*(mat * x_n))
- glEnd()
-
- # y
- glColor3f(0.2, 1.0, 0.2)
- glBegin(GL_LINES)
- glVertex3f(*(zero_tx))
- glVertex3f(*(mat * y_p))
- glEnd()
-
- glColor3f(0.0, 0.6, 0.0)
- glBegin(GL_LINES)
- glVertex3f(*(zero_tx))
- glVertex3f(*(mat * y_n))
- glEnd()
-
- # z
- glColor3f(0.4, 0.4, 1.0)
- glBegin(GL_LINES)
- glVertex3f(*(zero_tx))
- glVertex3f(*(mat * z_p))
- glEnd()
-
- glColor3f(0.0, 0.0, 0.6)
- glBegin(GL_LINES)
- glVertex3f(*(zero_tx))
- glVertex3f(*(mat * z_n))
- glEnd()
-
- # bounding box
- if bbox_hide:
- return
-
- i = 0
- glColor3f(1.0, 1.0, 1.0)
- for x in (-bbox_scale, bbox_scale):
- for y in (-bbox_scale, bbox_scale):
- for z in (-bbox_scale, bbox_scale):
- bb[i][:] = x, y, z
- bb[i] = mat * bb[i]
- i += 1
-
- # strip
- glLineWidth(1.0)
- glLineStipple(1, 0xAAAA)
- glEnable(GL_LINE_STIPPLE)
-
- glBegin(GL_LINE_STRIP)
- for i in 0, 1, 3, 2, 0, 4, 5, 7, 6, 4:
- glVertex3f(*bb[i])
- glEnd()
-
- # not done by the strip
- glBegin(GL_LINES)
- glVertex3f(*bb[1])
- glVertex3f(*bb[5])
-
- glVertex3f(*bb[2])
- glVertex3f(*bb[6])
-
- glVertex3f(*bb[3])
- glVertex3f(*bb[7])
- glEnd()
- glDisable(GL_LINE_STIPPLE)
-
- # points
if data_vector:
- glPointSize(3.0)
- glBegin(GL_POINTS)
- glColor3f(0.5, 0.5, 1)
- for key, vec in data_vector.items():
- glVertex3f(*vec.to_3d())
- glEnd()
- glPointSize(1.0)
+ coords = [tuple(vec.to_3d()) for vec in data_vector.values()]
+ draw_points(coords)
- # lines
if data_vector_array:
- glColor3f(0.5, 0.5, 1)
- glLineWidth(2.0)
-
for line in data_vector_array.values():
- glBegin(GL_LINE_STRIP)
- for vec in line:
- glVertex3f(*vec)
- glEnd()
- glPointSize(1.0)
+ coords = [tuple(vec.to_3d()) for vec in line]
+ draw_line(coords)
- glLineWidth(1.0)
-
- # matrix
if data_matrix:
- for mat in data_matrix.values():
- draw_matrix(mat)
+ draw_matrices(list(data_matrix.values()), scale, with_bounding_box)
- if data_quat:
- loc = context.scene.cursor_location.copy()
+ if data_euler or data_quat:
+ cursor = bpy.context.scene.cursor_location.copy()
+ derived_matrices = []
for quat in data_quat.values():
- mat = quat.to_matrix().to_4x4()
- mat.translation = loc
- draw_matrix(mat)
-
- if data_euler:
- loc = context.scene.cursor_location.copy()
+ matrix = quat.to_matrix().to_4x4()
+ matrix.translation = cursor
+ derived_matrices.append(matrix)
for eul in data_euler.values():
- mat = eul.to_matrix().to_4x4()
- mat.translation = loc
- draw_matrix(mat)
+ matrix = eul.to_matrix().to_4x4()
+ matrix.translation = cursor
+ derived_matrices.append(matrix)
+ draw_matrices(derived_matrices, scale, with_bounding_box)
+
+
+def draw_points(points):
+ batch = batch_from_points(points, "POINTS")
+ single_color_shader.bind()
+ single_color_shader.uniform_float("color", (0.5, 0.5, 1, 1))
+ batch.draw(single_color_shader)
+
+def draw_line(points):
+ batch = batch_from_points(points, "LINE_STRIP")
+ single_color_shader.bind()
+ single_color_shader.uniform_float("color", (0.5, 0.5, 1, 1))
+ batch.draw(single_color_shader)
+
+def batch_from_points(points, type):
+ return batch_for_shader(single_color_shader, type, {"pos" : points})
+
+def draw_matrices(matrices, scale, with_bounding_box):
+ x_p = Vector(( scale, 0.0, 0.0))
+ x_n = Vector((-scale, 0.0, 0.0))
+ y_p = Vector((0.0, scale, 0.0))
+ y_n = Vector((0.0, -scale, 0.0))
+ z_p = Vector((0.0, 0.0, scale))
+ z_n = Vector((0.0, 0.0, -scale))
+
+ red_dark = (0.2, 0.0, 0.0, 1.0)
+ red_light = (1.0, 0.2, 0.2, 1.0)
+ green_dark = (0.0, 0.2, 0.0, 1.0)
+ green_light = (0.2, 1.0, 0.2, 1.0)
+ blue_dark = (0.0, 0.0, 0.2, 1.0)
+ blue_light = (0.4, 0.4, 1.0, 1.0)
+
+ coords = []
+ colors = []
+ for matrix in matrices:
+ coords.append(matrix @ x_n)
+ coords.append(matrix @ x_p)
+ colors.extend((red_dark, red_light))
+ coords.append(matrix @ y_n)
+ coords.append(matrix @ y_p)
+ colors.extend((green_dark, green_light))
+ coords.append(matrix @ z_n)
+ coords.append(matrix @ z_p)
+ colors.extend((blue_dark, blue_light))
+
+ batch = batch_for_shader(smooth_color_shader, "LINES", {
+ "pos" : coords,
+ "color" : colors
+ })
+ batch.draw(smooth_color_shader)
+
+ if with_bounding_box:
+ draw_bounding_boxes(matrices, scale, (1.0, 1.0, 1.0, 1.0))
+
+def draw_bounding_boxes(matrices, scale, color):
+ boundbox_points = []
+ for x in (-scale, scale):
+ for y in (-scale, scale):
+ for z in (-scale, scale):
+ boundbox_points.append(Vector((x, y, z)))
+
+ boundbox_lines = [
+ (0, 1), (1, 3), (3, 2), (2, 0), (0, 4), (4, 5),
+ (5, 7), (7, 6), (6, 4), (1, 5), (2, 6), (3, 7)
+ ]
+
+ points = []
+ for matrix in matrices:
+ for v1, v2 in boundbox_lines:
+ points.append(matrix @ boundbox_points[v1])
+ points.append(matrix @ boundbox_points[v2])
+
+ batch = batch_from_points(points, "LINES")
+
+ single_color_shader.bind()
+ single_color_shader.uniform_float("color", color)
+ batch.draw(single_color_shader) \ No newline at end of file
diff --git a/space_view3d_pie_menus/__init__.py b/space_view3d_pie_menus/__init__.py
index 486d538f..26ac9958 100644
--- a/space_view3d_pie_menus/__init__.py
+++ b/space_view3d_pie_menus/__init__.py
@@ -21,20 +21,20 @@
import bpy
from bpy.props import (
- BoolProperty,
- PointerProperty,
- )
+ BoolProperty,
+ PointerProperty,
+)
from bpy.types import (
- PropertyGroup,
- AddonPreferences,
- )
+ PropertyGroup,
+ AddonPreferences,
+)
bl_info = {
"name": "3D Viewport Pie Menus",
"author": "meta-androcto, pitiwazou, chromoly, italic",
"version": (1, 1, 8),
- "blender": (2, 7, 7),
+ "blender": (2, 80, 0),
"description": "Individual Pie Menu Activation List",
"location": "Addons Preferences",
"warning": "",
@@ -149,32 +149,54 @@ def disable_all_modules(self, context):
class PieToolsPreferences(AddonPreferences):
bl_idname = __name__
- enable_all = BoolProperty(
- name="Enable all",
- description="Enable all Pie Modules",
- default=False,
- update=enable_all_modules
- )
- disable_all = BoolProperty(
- name="Disable all",
- description="Disable all Pie Modules",
- default=False,
- update=disable_all_modules
- )
+ enable_all: BoolProperty(
+ name="Enable all",
+ description="Enable all Pie Modules",
+ default=False,
+ update=enable_all_modules
+ )
+ disable_all: BoolProperty(
+ name="Disable all",
+ description="Disable all Pie Modules",
+ default=False,
+ update=disable_all_modules
+ )
+
+ for mod in sub_modules:
+ mod_name = mod.__name__.split('.')[-1]
+
+ def gen_update(mod, use_prop_name):
+ def update(self, context):
+ if getattr(self, use_prop_name):
+ if not mod.__addon_enabled__:
+ register_submodule(mod)
+ else:
+ if mod.__addon_enabled__:
+ unregister_submodule(mod)
+ return update
+
+ use_prop_name = 'use_' + mod_name
+ __annotations__[use_prop_name] = BoolProperty(
+ name=mod.bl_info['name'],
+ description=mod.bl_info.get('description', ''),
+ update=gen_update(mod, use_prop_name),
+ )
+
+ __annotations__['show_expanded_' + mod_name] = BoolProperty()
def draw(self, context):
layout = self.layout
- split = layout.split(percentage=0.5, align=True)
+ split = layout.split(factor=0.5, align=True)
row = split.row()
row.alignment = "LEFT"
sub_box = row.box()
sub_box.prop(self, "enable_all", emboss=False,
icon="VISIBLE_IPO_ON", icon_only=True)
- row.label("Enable All")
+ row.label(text="Enable All")
row = split.row()
row.alignment = "RIGHT"
- row.label("Disable All")
+ row.label(text="Disable All")
sub_box = row.box()
sub_box.prop(self, "disable_all", emboss=False,
icon="VISIBLE_IPO_OFF", icon_only=True)
@@ -195,42 +217,42 @@ class PieToolsPreferences(AddonPreferences):
op = sub.operator('wm.context_toggle', text='', icon=icon,
emboss=False)
op.data_path = 'addon_prefs.show_expanded_' + mod_name
- sub.label('{}: {}'.format(info['category'], info['name']))
+ sub.label(text='{}: {}'.format(info['category'], info['name']))
sub = row.row()
sub.alignment = 'RIGHT'
if info.get('warning'):
- sub.label('', icon='ERROR')
+ sub.label(text='', icon='ERROR')
sub.prop(self, 'use_' + mod_name, text='')
# The second stage
if expand:
if info.get('description'):
- split = col.row().split(percentage=0.15)
- split.label('Description:')
- split.label(info['description'])
+ split = col.row().split(factor=0.15)
+ split.label(text='Description:')
+ split.label(text=info['description'])
if info.get('location'):
- split = col.row().split(percentage=0.15)
- split.label('Location:')
- split.label(info['location'])
+ split = col.row().split(factor=0.15)
+ split.label(text='Location:')
+ split.label(text=info['location'])
"""
if info.get('author'):
- split = col.row().split(percentage=0.15)
- split.label('Author:')
+ split = col.row().split(factor=0.15)
+ split.label(text='Author:')
split.label(info['author'])
"""
if info.get('version'):
- split = col.row().split(percentage=0.15)
- split.label('Version:')
- split.label('.'.join(str(x) for x in info['version']),
+ split = col.row().split(factor=0.15)
+ split.label(text='Version:')
+ split.label(text='.'.join(str(x) for x in info['version']),
translate=False)
if info.get('warning'):
- split = col.row().split(percentage=0.15)
- split.label('Warning:')
- split.label(' ' + info['warning'], icon='ERROR')
+ split = col.row().split(factor=0.15)
+ split.label(text='Warning:')
+ split.label(text=' ' + info['warning'], icon='ERROR')
tot_row = int(bool(info.get('wiki_url')))
if tot_row:
- split = col.row().split(percentage=0.15)
+ split = col.row().split(factor=0.15)
split.label(text='Internet:')
if info.get('wiki_url'):
op = split.operator('wm.url_open',
@@ -259,32 +281,9 @@ class PieToolsPreferences(AddonPreferences):
icon="FILE_PARENT")
-for mod in sub_modules:
- info = mod.bl_info
- mod_name = mod.__name__.split('.')[-1]
-
- def gen_update(mod):
- def update(self, context):
- if getattr(self, 'use_' + mod.__name__.split('.')[-1]):
- if not mod.__addon_enabled__:
- register_submodule(mod)
- else:
- if mod.__addon_enabled__:
- unregister_submodule(mod)
- return update
-
- prop = BoolProperty(
- name=info['name'],
- description=info.get('description', ''),
- update=gen_update(mod),
- )
- setattr(PieToolsPreferences, 'use_' + mod_name, prop)
- prop = BoolProperty()
- setattr(PieToolsPreferences, 'show_expanded_' + mod_name, prop)
-
classes = (
PieToolsPreferences,
- )
+)
def register():
diff --git a/space_view3d_pie_menus/pie_align_menu.py b/space_view3d_pie_menus/pie_align_menu.py
index c8ce2e15..c59a7c97 100644
--- a/space_view3d_pie_menus/pie_align_menu.py
+++ b/space_view3d_pie_menus/pie_align_menu.py
@@ -23,7 +23,7 @@ bl_info = {
"description": "V/E/F Align tools",
"author": "pitiwazou, meta-androcto",
"version": (0, 1, 2),
- "blender": (2, 77, 0),
+ "blender": (2, 80, 0),
"location": "Mesh Edit Mode",
"warning": "",
"wiki_url": "",
@@ -68,7 +68,7 @@ class PieAlign(Menu):
box = pie.split().box().column()
row = box.row(align=True)
- row.label("X")
+ row.label(text="X")
align_1 = row.operator("alignxyz.all", text="Neg")
align_1.axis = '0'
align_1.side = 'NEGATIVE'
@@ -77,7 +77,7 @@ class PieAlign(Menu):
align_2.side = 'POSITIVE'
row = box.row(align=True)
- row.label("Y")
+ row.label(text="Y")
align_3 = row.operator("alignxyz.all", text="Neg")
align_3.axis = '1'
align_3.side = 'NEGATIVE'
@@ -86,7 +86,7 @@ class PieAlign(Menu):
align_4.side = 'POSITIVE'
row = box.row(align=True)
- row.label("Z")
+ row.label(text="Z")
align_5 = row.operator("alignxyz.all", text="Neg")
align_5.axis = '2'
align_5.side = 'NEGATIVE'
@@ -102,7 +102,7 @@ class AlignSelectedXYZ(Operator):
bl_description = "Align Selected Along the chosen axis"
bl_options = {'REGISTER', 'UNDO'}
- axis = EnumProperty(
+ axis: EnumProperty(
name="Axis",
items=[
('X', "X", "X Axis"),
@@ -147,7 +147,7 @@ class AlignToXYZ0(Operator):
bl_description = "Align Active Object To a chosen X, Y or Z equals 0 Location"
bl_options = {'REGISTER', 'UNDO'}
- axis = EnumProperty(
+ axis: EnumProperty(
name="Axis",
items=[
('0', "X", "X Axis"),
@@ -181,7 +181,7 @@ class AlignXYZAll(Operator):
bl_description = "Align to a Front or Back along the chosen Axis"
bl_options = {'REGISTER', 'UNDO'}
- axis = EnumProperty(
+ axis: EnumProperty(
name="Axis",
items=[
('0', "X", "X Axis"),
@@ -191,7 +191,7 @@ class AlignXYZAll(Operator):
description="Choose an axis for alignment",
default='0'
)
- side = EnumProperty(
+ side: EnumProperty(
name="Side",
items=[
('POSITIVE', "Front", "Align on the positive chosen axis"),
diff --git a/space_view3d_pie_menus/pie_animation_menu.py b/space_view3d_pie_menus/pie_animation_menu.py
index d65891f9..774aa8e0 100644
--- a/space_view3d_pie_menus/pie_animation_menu.py
+++ b/space_view3d_pie_menus/pie_animation_menu.py
@@ -23,7 +23,7 @@ bl_info = {
"description": "Pie menu for Timeline controls",
"author": "pitiwazou, meta-androcto",
"version": (0, 1, 1),
- "blender": (2, 77, 0),
+ "blender": (2, 80, 0),
"location": "3D View",
"warning": "",
"wiki_url": "",
diff --git a/space_view3d_pie_menus/pie_apply_transform_menu.py b/space_view3d_pie_menus/pie_apply_transform_menu.py
index e22f23b4..a92920db 100644
--- a/space_view3d_pie_menus/pie_apply_transform_menu.py
+++ b/space_view3d_pie_menus/pie_apply_transform_menu.py
@@ -23,7 +23,7 @@ bl_info = {
"description": "Apply Transform Menu",
"author": "pitiwazou, meta-androcto",
"version": (0, 1, 1),
- "blender": (2, 77, 0),
+ "blender": (2, 80, 0),
"location": "3D View",
"warning": "",
"wiki_url": "",
@@ -49,15 +49,15 @@ class PieApplyTransforms(Menu):
# 4 - LEFT
pie.operator("apply.transformall", text="Apply All", icon='FREEZE')
# 6 - RIGHT
- pie.operator("clear.all", text="Clear All", icon='MANIPUL')
+ pie.operator("clear.all", text="Clear All", icon='NONE')
# 2 - BOTTOM
pie.operator("object.duplicates_make_real", text="Make Duplicates Real")
# 8 - TOP
- pie.operator("apply.transformlocrotscale", text="Rotation", icon='MAN_ROT').option = 'ROT'
+ pie.operator("apply.transformlocrotscale", text="Rotation", icon='NONE').option = 'ROT'
# 7 - TOP - LEFT
- pie.operator("apply.transformlocrotscale", text="Location", icon='MAN_ROT').option = 'LOC'
+ pie.operator("apply.transformlocrotscale", text="Location", icon='NONE').option = 'LOC'
# 9 - TOP - RIGHT
- pie.operator("apply.transformlocrotscale", text="Scale", icon='MAN_ROT').option = 'SCALE'
+ pie.operator("apply.transformlocrotscale", text="Scale", icon='NONE').option = 'SCALE'
# 1 - BOTTOM - LEFT
pie.operator("object.visual_transform_apply", text="Visual Transforms")
# 3 - BOTTOM - RIGHT
@@ -71,7 +71,7 @@ class ApplyTransLocRotPie(Operator):
bl_description = "Apply Transform: Location, Rotation or Scale"
bl_options = {'REGISTER', 'UNDO'}
- option = EnumProperty(
+ option: EnumProperty(
name="Type",
items=[
("LOC", "Location", "Apply Location"),
@@ -109,10 +109,10 @@ class ClearMenu(Menu):
def draw(self, context):
layout = self.layout
- layout.operator("object.location_clear", text="Clear Location", icon='MAN_TRANS')
- layout.operator("object.rotation_clear", text="Clear Rotation", icon='MAN_ROT')
- layout.operator("object.scale_clear", text="Clear Scale", icon='MAN_SCALE')
- layout.operator("object.origin_clear", text="Clear Origin", icon='MANIPUL')
+ layout.operator("object.location_clear", text="Clear Location", icon='NONE')
+ layout.operator("object.rotation_clear", text="Clear Rotation", icon='NONE')
+ layout.operator("object.scale_clear", text="Clear Scale", icon='NONE')
+ layout.operator("object.origin_clear", text="Clear Origin", icon='NONE')
# Clear all
diff --git a/space_view3d_pie_menus/pie_cursor.py b/space_view3d_pie_menus/pie_cursor.py
index 568cdd6d..337c0c12 100644
--- a/space_view3d_pie_menus/pie_cursor.py
+++ b/space_view3d_pie_menus/pie_cursor.py
@@ -23,7 +23,7 @@ bl_info = {
"description": "Cursor Menu",
"author": "pitiwazou, meta-androcto",
"version": (0, 1, 0),
- "blender": (2, 77, 0),
+ "blender": (2, 80, 0),
"location": "3D View",
"warning": "",
"wiki_url": "",
@@ -69,16 +69,16 @@ class Snap_CursorMenu(Menu):
icon='CLIPUV_HLT').use_offset = False
# 6 - RIGHT
pie.operator("view3d.snap_selected_to_cursor",
- text="Selection to Cursor (Offset)", icon='CURSOR').use_offset = True
+ text="Selection to Cursor (Keep Offset)", icon='PIVOT_CURSOR').use_offset = True
# 2 - BOTTOM
pie.operator("view3d.snap_cursor_selected_to_center1",
- text="Selected & Cursor to Center", icon='ALIGN')
+ text="Selected & Cursor to Center", icon='NONE')
# 8 - TOP
- pie.operator("view3d.snap_cursor_to_center", text="Cursor to Center", icon='CLIPUV_DEHLT')
+ pie.operator("view3d.snap_cursor_to_center", text="Cursor to World Origin", icon='CLIPUV_DEHLT')
# 7 - TOP - LEFT
- pie.operator("view3d.snap_cursor_to_selected", text="Cursor to Selected", icon='ROTACTIVE')
+ pie.operator("view3d.snap_cursor_to_selected", text="Cursor to Selected", icon='NONE')
# 9 - TOP - RIGHT
- pie.operator("view3d.snap_cursor_to_active", text="Cursor to Active", icon='BBOX')
+ pie.operator("view3d.snap_cursor_to_active", text="Cursor to Active", icon='NONE')
# 1 - BOTTOM - LEFT
pie.operator("view3d.snap_selected_to_grid", text="Selection to Grid", icon='GRID')
# 3 - BOTTOM - RIGHT
diff --git a/space_view3d_pie_menus/pie_delete_menu.py b/space_view3d_pie_menus/pie_delete_menu.py
index 4a8e002c..0632877e 100644
--- a/space_view3d_pie_menus/pie_delete_menu.py
+++ b/space_view3d_pie_menus/pie_delete_menu.py
@@ -23,7 +23,7 @@ bl_info = {
"description": "Edit mode V/E/F Delete Modes",
"author": "pitiwazou, meta-androcto",
"version": (0, 1, 0),
- "blender": (2, 77, 0),
+ "blender": (2, 80, 0),
"location": "Mesh Edit Mode",
"warning": "",
"wiki_url": "",
@@ -57,13 +57,13 @@ class PieDelete(Menu):
# 1 - BOTTOM - LEFT
box = pie.split().column()
box.operator("mesh.dissolve_limited", text="Limited Dissolve", icon='STICKY_UVS_LOC')
- box.operator("mesh.delete_edgeloop", text="Delete Edge Loops", icon='BORDER_LASSO')
+ box.operator("mesh.delete_edgeloop", text="Delete Edge Loops", icon='NONE')
box.operator("mesh.edge_collapse", text="Edge Collapse", icon='UV_EDGESEL')
# 3 - BOTTOM - RIGHT
box = pie.split().column()
- box.operator("mesh.delete", text="Only Edge & Faces", icon='SPACE2').type = 'EDGE_FACE'
+ box.operator("mesh.delete", text="Only Edge & Faces", icon='NONE').type = 'EDGE_FACE'
box.operator("mesh.delete", text="Only Faces", icon='UV_FACESEL').type = 'ONLY_FACE'
- box.operator("mesh.remove_doubles", text="Remove Doubles", icon='ORTHO')
+ box.operator("mesh.remove_doubles", text="Remove Doubles", icon='NONE')
classes = (
diff --git a/space_view3d_pie_menus/pie_editor_switch_menu.py b/space_view3d_pie_menus/pie_editor_switch_menu.py
index 67f40ff8..093f46b6 100644
--- a/space_view3d_pie_menus/pie_editor_switch_menu.py
+++ b/space_view3d_pie_menus/pie_editor_switch_menu.py
@@ -23,7 +23,7 @@ bl_info = {
"description": "Switch Editor Type Menu",
"author": "saidenka",
"version": (0, 1, 0),
- "blender": (2, 77, 0),
+ "blender": (2, 80, 0),
"location": "All Editors",
"warning": "",
"wiki_url": "",
@@ -41,7 +41,7 @@ from bpy.props import (
class AreaPieMenu(Menu):
- bl_idname = "INFO_MT_window_pie"
+ bl_idname = "TOPBAR_MT_window_pie"
bl_label = "Pie Menu"
bl_description = "Window Pie Menus"
@@ -73,21 +73,21 @@ class AreaPieEditor(Menu):
# 6 - RIGHT
pie.menu(AreaTypePieAnim.bl_idname, text="Animation Editors", icon="ACTION")
# 2 - BOTTOM
- pie.operator(SetAreaType.bl_idname, text="Property", icon="BUTS").types = "PROPERTIES"
+ pie.operator(SetAreaType.bl_idname, text="Property", icon="PROPERTIES").types = "PROPERTIES"
# 8 - TOP
pie.operator(SetAreaType.bl_idname, text="3D View", icon="MESH_CUBE").types = "VIEW_3D"
# 7 - TOP - LEFT
- pie.operator(SetAreaType.bl_idname, text="UV/Image Editor", icon="IMAGE_COL").types = "IMAGE_EDITOR"
+ pie.operator(SetAreaType.bl_idname, text="UV/Image Editor", icon="NONE").types = "IMAGE_EDITOR"
# 9 - TOP - RIGHT
pie.operator(SetAreaType.bl_idname, text="Node Editor", icon="NODETREE").types = "NODE_EDITOR"
# 1 - BOTTOM - LEFT
- pie.operator(SetAreaType.bl_idname, text="Outliner", icon="OOPS").types = "OUTLINER"
+ pie.operator(SetAreaType.bl_idname, text="Outliner", icon="NONE").types = "OUTLINER"
# 3 - BOTTOM - RIGHT
pie.menu(AreaTypePieOther.bl_idname, text="More Editors", icon="QUESTION")
class AreaTypePieOther(Menu):
- bl_idname = "INFO_MT_window_pie_area_type_other"
+ bl_idname = "TOPBAR_MT_window_pie_area_type_other"
bl_label = "Editor Type (other)"
bl_description = "Is pie menu change editor type (other)"
@@ -95,7 +95,7 @@ class AreaTypePieOther(Menu):
# 4 - LEFT
self.layout.operator(SetAreaType.bl_idname, text="Logic Editor", icon="LOGIC").types = "LOGIC_EDITOR"
# 6 - RIGHT
- self.layout.operator(SetAreaType.bl_idname, text="File Browser", icon="FILESEL").types = "FILE_BROWSER"
+ self.layout.operator(SetAreaType.bl_idname, text="File Browser", icon="FILEBROWSER").types = "FILE_BROWSER"
# 2 - BOTTOM
self.layout.operator(SetAreaType.bl_idname, text="Python Console", icon="CONSOLE").types = "CONSOLE"
# 8 - TOP
@@ -114,7 +114,7 @@ class SetAreaType(Operator):
bl_description = "Change Editor Type"
bl_options = {'REGISTER'}
- types = StringProperty(name="Area Type")
+ types: StringProperty(name="Area Type")
def execute(self, context):
context.area.type = self.types
@@ -122,7 +122,7 @@ class SetAreaType(Operator):
class AreaTypePieAnim(Menu):
- bl_idname = "INFO_MT_window_pie_area_type_anim"
+ bl_idname = "TOPBAR_MT_window_pie_area_type_anim"
bl_label = "Editor Type (Animation)"
bl_description = "Menu for changing editor type (animation related)"
diff --git a/space_view3d_pie_menus/pie_manipulator_menu.py b/space_view3d_pie_menus/pie_manipulator_menu.py
index 2690c094..68895901 100644
--- a/space_view3d_pie_menus/pie_manipulator_menu.py
+++ b/space_view3d_pie_menus/pie_manipulator_menu.py
@@ -23,7 +23,7 @@ bl_info = {
"description": "Extended Manipulator Menu",
"author": "pitiwazou, meta-androcto",
"version": (0, 1, 1),
- "blender": (2, 77, 0),
+ "blender": (2, 80, 0),
"location": "3D View",
"warning": "",
"wiki_url": "",
@@ -37,111 +37,6 @@ from bpy.types import (
)
-class ManipTranslate(Operator):
- bl_idname = "manip.translate"
- bl_label = "Manip Translate"
- bl_options = {'REGISTER', 'UNDO'}
- bl_description = " Show Translate"
-
- def execute(self, context):
- if context.space_data.show_manipulator is False:
- context.space_data.show_manipulator = True
- context.space_data.transform_manipulators = {'TRANSLATE'}
- if context.space_data.transform_manipulators != {'TRANSLATE'}:
- context.space_data.transform_manipulators = {'TRANSLATE'}
- return {'FINISHED'}
-
-
-class ManipRotate(Operator):
- bl_idname = "manip.rotate"
- bl_label = "Manip Rotate"
- bl_options = {'REGISTER', 'UNDO'}
- bl_description = " Show Rotate"
-
- def execute(self, context):
- if context.space_data.show_manipulator is False:
- context.space_data.show_manipulator = True
- context.space_data.transform_manipulators = {'ROTATE'}
- if context.space_data.transform_manipulators != {'ROTATE'}:
- context.space_data.transform_manipulators = {'ROTATE'}
- return {'FINISHED'}
-
-
-class ManipScale(Operator):
- bl_idname = "manip.scale"
- bl_label = "Manip Scale"
- bl_options = {'REGISTER', 'UNDO'}
- bl_description = " Show Scale"
-
- def execute(self, context):
- if context.space_data.show_manipulator is False:
- context.space_data.show_manipulator = True
- context.space_data.transform_manipulators = {'SCALE'}
- if context.space_data.transform_manipulators != {'SCALE'}:
- context.space_data.transform_manipulators = {'SCALE'}
- return {'FINISHED'}
-
-
-class TranslateRotate(Operator):
- bl_idname = "translate.rotate"
- bl_label = "Translate Rotate"
- bl_options = {'REGISTER', 'UNDO'}
- bl_description = " Show Translate/Rotate"
-
- def execute(self, context):
- if context.space_data.show_manipulator is False:
- context.space_data.show_manipulator = True
- context.space_data.transform_manipulators = {'TRANSLATE', 'ROTATE'}
- if context.space_data.transform_manipulators != {'TRANSLATE', 'ROTATE'}:
- context.space_data.transform_manipulators = {'TRANSLATE', 'ROTATE'}
- return {'FINISHED'}
-
-
-class TranslateScale(Operator):
- bl_idname = "translate.scale"
- bl_label = "Translate Scale"
- bl_options = {'REGISTER', 'UNDO'}
- bl_description = " Show Translate/Scale"
-
- def execute(self, context):
- if context.space_data.show_manipulator is False:
- context.space_data.show_manipulator = True
- context.space_data.transform_manipulators = {'TRANSLATE', 'SCALE'}
- if context.space_data.transform_manipulators != {'TRANSLATE', 'SCALE'}:
- context.space_data.transform_manipulators = {'TRANSLATE', 'SCALE'}
- return {'FINISHED'}
-
-
-class RotateScale(Operator):
- bl_idname = "rotate.scale"
- bl_label = "Rotate Scale"
- bl_options = {'REGISTER', 'UNDO'}
- bl_description = " Show Rotate/Scale"
-
- def execute(self, context):
- if context.space_data.show_manipulator is False:
- context.space_data.show_manipulator = True
- context.space_data.transform_manipulators = {'ROTATE', 'SCALE'}
- if context.space_data.transform_manipulators != {'ROTATE', 'SCALE'}:
- context.space_data.transform_manipulators = {'ROTATE', 'SCALE'}
- return {'FINISHED'}
-
-
-class TranslateRotateScale(Operator):
- bl_idname = "translate.rotatescale"
- bl_label = "Translate Rotate Scale"
- bl_options = {'REGISTER', 'UNDO'}
- bl_description = "Show All"
-
- def execute(self, context):
- if context.space_data.show_manipulator is False:
- context.space_data.show_manipulator = True
- context.space_data.transform_manipulators = {'TRANSLATE', 'ROTATE', 'SCALE'}
- if context.space_data.transform_manipulators != {'TRANSLATE', 'ROTATE', 'SCALE'}:
- context.space_data.transform_manipulators = {'TRANSLATE', 'ROTATE', 'SCALE'}
- return {'FINISHED'}
-
-
class WManupulators(Operator):
bl_idname = "w.manupulators"
bl_label = "W Manupulators"
@@ -149,13 +44,7 @@ class WManupulators(Operator):
bl_description = " Show/Hide Manipulator"
def execute(self, context):
-
- if context.space_data.show_manipulator is True:
- context.space_data.show_manipulator = False
-
- elif context.space_data.show_manipulator is False:
- context.space_data.show_manipulator = True
-
+ context.space_data.show_gizmo_tool = not context.space_data.show_gizmo_tool
return {'FINISHED'}
@@ -168,32 +57,17 @@ class PieManipulator(Menu):
layout = self.layout
pie = layout.menu_pie()
# 4 - LEFT
- pie.operator("rotate.scale", text="Rotate/Scale")
+ pie.operator("wm.tool_set_by_name", text="Translate", icon='NONE').name = "Move"
# 6 - RIGHT
- pie.operator("manip.rotate", text="Rotate", icon='MAN_ROT')
+ pie.operator("wm.tool_set_by_name", text="Rotate", icon='NONE').name = "Rotate"
# 2 - BOTTOM
- pie.operator("translate.rotatescale", text="Translate/Rotate/Scale")
+ pie.operator("wm.tool_set_by_name", text="Scale", icon='NONE').name = "Scale"
# 8 - TOP
- pie.operator("w.manupulators", text="Show/Hide Toggle", icon='MANIPUL')
- # 7 - TOP - LEFT
- pie.operator("translate.rotate", text="Translate/Rotate")
- # 9 - TOP - RIGHT
- pie.operator("manip.translate", text="Translate", icon='MAN_TRANS')
- # 1 - BOTTOM - LEFT
- pie.operator("translate.scale", text="Translate/Scale")
- # 3 - BOTTOM - RIGHT
- pie.operator("manip.scale", text="Scale", icon='MAN_SCALE')
+ pie.operator("w.manupulators", text="Show/Hide Toggle", icon='NONE')
classes = (
PieManipulator,
- ManipTranslate,
- ManipRotate,
- ManipScale,
- TranslateRotate,
- TranslateScale,
- RotateScale,
- TranslateRotateScale,
WManupulators,
)
diff --git a/space_view3d_pie_menus/pie_modes_menu.py b/space_view3d_pie_menus/pie_modes_menu.py
index 40f6ba4a..943e85ce 100644
--- a/space_view3d_pie_menus/pie_modes_menu.py
+++ b/space_view3d_pie_menus/pie_modes_menu.py
@@ -23,7 +23,7 @@ bl_info = {
"description": "Switch between 3d view object/edit modes",
"author": "pitiwazou, meta-androcto, italic",
"version": (0, 1, 2),
- "blender": (2, 77, 0),
+ "blender": (2, 80, 0),
"location": "3D View",
"warning": "",
"wiki_url": "",
@@ -163,7 +163,7 @@ class SetObjectModePie(Operator):
bl_description = "I set the interactive mode of object"
bl_options = {'REGISTER'}
- mode = bpy.props.StringProperty(name="Interactive mode", default="OBJECT")
+ mode: bpy.props.StringProperty(name="Interactive mode", default="OBJECT")
def execute(self, context):
if (context.active_object):
@@ -276,7 +276,7 @@ class PieObjectEditotherModes(Menu):
box.operator("verts.edges", text="Vertex/Edges", icon='VERTEXSEL')
box.operator("verts.edgesfaces", text="Vertex/Edges/Faces", icon='OBJECT_DATAMODE')
box.operator("wm.context_toggle", text="Limit to Visible",
- icon="ORTHO").data_path = "space_data.use_occlude_geometry"
+ icon="NONE").data_path = "space_data.use_occlude_geometry"
class PieObjectEditMode(Menu):
@@ -429,7 +429,7 @@ class PieObjectEditMode(Menu):
pie.operator("view3d.pie_interactive_mode_grease_pencil", icon="GREASEPENCIL")
else:
message = "Active Object has only Object Mode available" if ob \
- and ob.type in {"LAMP", "CAMERA", "EMPTY", "SPEAKER"} else \
+ and ob.type in {"LIGHT", "CAMERA", "EMPTY", "SPEAKER"} else \
"No active object found. Please select one first"
pie = layout.menu_pie()
pie.separator()
diff --git a/space_view3d_pie_menus/pie_orientation_menu.py b/space_view3d_pie_menus/pie_orientation_menu.py
index a9de9fa6..04e77c95 100644
--- a/space_view3d_pie_menus/pie_orientation_menu.py
+++ b/space_view3d_pie_menus/pie_orientation_menu.py
@@ -24,7 +24,7 @@ bl_info = {
"name": "Hotkey: 'Alt + Spacebar'",
"author": "Italic_",
"version": (1, 1, 0),
- "blender": (2, 77, 0),
+ "blender": (2, 80, 0),
"description": "Set Transform Orientations",
"location": "3D View",
"category": "Orientation Pie"}
@@ -44,14 +44,14 @@ class OrientPoll(Operator):
bl_label = "Orientation Poll"
bl_options = {'INTERNAL'}
- space = StringProperty()
+ space: StringProperty()
@classmethod
def poll(cls, context):
return context.space_data.type == "VIEW_3D"
def execute(self, context):
- context.space_data.transform_orientation = self.space
+ context.scene.transform_orientation = self.space
return {'FINISHED'}
diff --git a/space_view3d_pie_menus/pie_origin.py b/space_view3d_pie_menus/pie_origin.py
index 84344d7e..58f22d41 100644
--- a/space_view3d_pie_menus/pie_origin.py
+++ b/space_view3d_pie_menus/pie_origin.py
@@ -23,7 +23,7 @@ bl_info = {
"description": "Origin Snap/Place Menu",
"author": "pitiwazou, meta-androcto",
"version": (0, 1, 1),
- "blender": (2, 77, 0),
+ "blender": (2, 80, 0),
"location": "3D View",
"warning": "",
"wiki_url": "",
@@ -105,10 +105,10 @@ class PieOriginPivot(Menu):
if obj and obj.type == 'MESH':
# 4 - LEFT
pie.operator("object.origin_set", text="Origin to Center of Mass",
- icon='BBOX').type = 'ORIGIN_CENTER_OF_MASS'
+ icon='NONE').type = 'ORIGIN_CENTER_OF_MASS'
# 6 - RIGHT
pie.operator("object.origin_set", text="Origin To 3D Cursor",
- icon='CURSOR').type = 'ORIGIN_CURSOR'
+ icon='PIVOT_CURSOR').type = 'ORIGIN_CURSOR'
# 2 - BOTTOM
pie.operator("object.pivotobottom", text="Origin to Bottom",
icon='TRIA_DOWN')
@@ -117,27 +117,27 @@ class PieOriginPivot(Menu):
icon='SNAP_INCREMENT')
# 7 - TOP - LEFT
pie.operator("object.origin_set", text="Geometry To Origin",
- icon='BBOX').type = 'GEOMETRY_ORIGIN'
+ icon='NONE').type = 'GEOMETRY_ORIGIN'
# 9 - TOP - RIGHT
pie.operator("object.origin_set", text="Origin To Geometry",
- icon='ROTATE').type = 'ORIGIN_GEOMETRY'
+ icon='NONE').type = 'ORIGIN_GEOMETRY'
else:
# 4 - LEFT
pie.operator("object.origin_set", text="Origin to Center of Mass",
- icon='BBOX').type = 'ORIGIN_CENTER_OF_MASS'
+ icon='NONE').type = 'ORIGIN_CENTER_OF_MASS'
# 6 - RIGHT
pie.operator("object.origin_set", text="Origin To 3D Cursor",
- icon='CURSOR').type = 'ORIGIN_CURSOR'
+ icon='PIVOT_CURSOR').type = 'ORIGIN_CURSOR'
# 2 - BOTTOM
pie.operator("object.pivot2selection", text="Origin To Selection",
icon='SNAP_INCREMENT')
# 8 - TOP
pie.operator("object.origin_set", text="Origin To Geometry",
- icon='ROTATE').type = 'ORIGIN_GEOMETRY'
+ icon='NONE').type = 'ORIGIN_GEOMETRY'
# 7 - TOP - LEFT
pie.operator("object.origin_set", text="Geometry To Origin",
- icon='BBOX').type = 'GEOMETRY_ORIGIN'
+ icon='NONE').type = 'GEOMETRY_ORIGIN'
classes = (
diff --git a/space_view3d_pie_menus/pie_pivot_point_menu.py b/space_view3d_pie_menus/pie_pivot_point_menu.py
index df6600a3..157364ff 100644
--- a/space_view3d_pie_menus/pie_pivot_point_menu.py
+++ b/space_view3d_pie_menus/pie_pivot_point_menu.py
@@ -23,7 +23,7 @@ bl_info = {
"description": "Set Pivot Point Menu",
"author": "seb_k, meta-androcto",
"version": (0, 1, 1),
- "blender": (2, 77, 0),
+ "blender": (2, 80, 0),
"location": "3D View",
"warning": "",
"wiki_url": "",
diff --git a/space_view3d_pie_menus/pie_proportional_menu.py b/space_view3d_pie_menus/pie_proportional_menu.py
index 479b57d4..f1722ce1 100644
--- a/space_view3d_pie_menus/pie_proportional_menu.py
+++ b/space_view3d_pie_menus/pie_proportional_menu.py
@@ -23,7 +23,7 @@ bl_info = {
"description": "Proportional Object/Edit Tools",
"author": "pitiwazou, meta-androcto",
"version": (0, 1, 1),
- "blender": (2, 77, 0),
+ "blender": (2, 80, 0),
"location": "3D View Object & Edit modes",
"warning": "",
"wiki_url": "",
diff --git a/space_view3d_pie_menus/pie_save_open_menu.py b/space_view3d_pie_menus/pie_save_open_menu.py
index 3d48e591..e71f7924 100644
--- a/space_view3d_pie_menus/pie_save_open_menu.py
+++ b/space_view3d_pie_menus/pie_save_open_menu.py
@@ -21,7 +21,7 @@
bl_info = {
"name": "Hotkey: 'Ctrl S'",
"description": "Save/Open & File Menus",
- "blender": (2, 77, 0),
+ "blender": (2, 80, 0),
"location": "All Editors",
"warning": "",
"wiki_url": "",
@@ -46,17 +46,17 @@ class PieSaveOpen(Menu):
layout = self.layout
pie = layout.menu_pie()
# 4 - LEFT
- pie.operator("wm.read_homefile", text="New", icon='NEW')
+ pie.operator("wm.read_homefile", text="New", icon='FILE_NEW')
# 6 - RIGHT
pie.menu("pie.link", text="Link", icon='LINK_BLEND')
# 2 - BOTTOM
pie.menu("pie.fileio", text="Import/Export Menu", icon='IMPORT')
# 8 - TOP
- pie.operator("file.save_incremental", text="Incremental Save", icon='SAVE_COPY')
+ pie.operator("file.save_incremental", text="Incremental Save", icon='NONE')
# 7 - TOP - LEFT
pie.operator("wm.save_mainfile", text="Save", icon='FILE_TICK')
# 9 - TOP - RIGHT
- pie.operator("wm.save_as_mainfile", text="Save As...", icon='SAVE_AS')
+ pie.operator("wm.save_as_mainfile", text="Save As...", icon='NONE')
# 1 - BOTTOM - LEFT
pie.operator("wm.open_mainfile", text="Open file", icon='FILE_FOLDER')
# 3 - BOTTOM - RIGHT
@@ -84,7 +84,7 @@ class pie_recover(Menu):
layout = self.layout
pie = layout.menu_pie()
box = pie.split().column()
- box.operator("wm.recover_auto_save", text="Recover Auto Save...", icon='RECOVER_AUTO')
+ box.operator("wm.recover_auto_save", text="Recover Auto Save...", icon='NONE')
box.operator("wm.recover_last_session", text="Recover Last Session", icon='RECOVER_LAST')
box.operator("wm.revert_mainfile", text="Revert", icon='FILE_REFRESH')
@@ -97,9 +97,9 @@ class pie_fileio(Menu):
layout = self.layout
pie = layout.menu_pie()
box = pie.split().column()
- box.menu("INFO_MT_file_import", icon='IMPORT')
+ box.menu("TOPBAR_MT_file_import", icon='IMPORT')
box.separator()
- box.menu("INFO_MT_file_export", icon='EXPORT')
+ box.menu("TOPBAR_MT_file_export", icon='EXPORT')
class ExternalData(Menu):
diff --git a/space_view3d_pie_menus/pie_sculpt_menu.py b/space_view3d_pie_menus/pie_sculpt_menu.py
index 91f3147a..5c70718e 100644
--- a/space_view3d_pie_menus/pie_sculpt_menu.py
+++ b/space_view3d_pie_menus/pie_sculpt_menu.py
@@ -23,7 +23,7 @@ bl_info = {
"description": "Sculpt Brush Menu",
"author": "pitiwazou, meta-androcto",
"version": (0, 1, 0),
- "blender": (2, 77, 0),
+ "blender": (2, 80, 0),
"location": "W key",
"warning": "",
"wiki_url": "",
@@ -123,7 +123,7 @@ class PieSculptthree(Menu):
layout.operator("paint.brush_select",
text='Snakehook', icon='BRUSH_SNAKE_HOOK').sculpt_tool = 'SNAKE_HOOK'
layout.operator("paint.brush_select",
- text='Twist', icon='BRUSH_ROTATE').sculpt_tool = 'ROTATE'
+ text='Twist', icon='BRUSH_ROTATE').sculpt_tool = 'NONE'
classes = (
diff --git a/space_view3d_pie_menus/pie_select_menu.py b/space_view3d_pie_menus/pie_select_menu.py
index 091e18ed..e47fb5e6 100644
--- a/space_view3d_pie_menus/pie_select_menu.py
+++ b/space_view3d_pie_menus/pie_select_menu.py
@@ -23,7 +23,7 @@ bl_info = {
"description": "Object/Edit mode Selection Menu",
"author": "pitiwazou, meta-androcto",
"version": (0, 1, 1),
- "blender": (2, 77, 0),
+ "blender": (2, 80, 0),
"location": "3D View",
"warning": "",
"wiki_url": "",
@@ -44,7 +44,7 @@ class PieSelectionsMore(Menu):
pie = layout.menu_pie()
box = pie.split().column()
box.operator("object.select_by_type", text="Select By Type", icon='SNAP_VOLUME')
- box.operator("object.select_grouped", text="Select Grouped", icon='ROTATE')
+ box.operator("object.select_grouped", text="Select Grouped", icon='NONE')
box.operator("object.select_linked", text="Select Linked", icon='CONSTRAINT_BONE')
box.menu("VIEW3D_MT_select_object_more_less", text="More/Less")
@@ -58,7 +58,7 @@ class PieSelectionsOM(Menu):
layout = self.layout
pie = layout.menu_pie()
# 4 - LEFT
- pie.operator("object.select_by_layer", text="Select By Layer", icon='LAYER_ACTIVE')
+ pie.row().label(text="")
# 6 - RIGHT
pie.operator("object.select_random", text="Select Random", icon='GROUP_VERTEX')
# 2 - BOTTOM
@@ -66,11 +66,11 @@ class PieSelectionsOM(Menu):
icon='ZOOM_PREVIOUS').action = 'INVERT'
# 8 - TOP
pie.operator("object.select_all", text="Select All Toggle",
- icon='RENDER_REGION').action = 'TOGGLE'
+ icon='NONE').action = 'TOGGLE'
# 7 - TOP - LEFT
- pie.operator("view3d.select_circle", text="Circle Select", icon='BORDER_LASSO')
+ pie.operator("view3d.select_circle", text="Circle Select", icon='NONE')
# 9 - TOP - RIGHT
- pie.operator("view3d.select_border", text="Border Select", icon='BORDER_RECT')
+ pie.operator("view3d.select_box", text="Box Select", icon='NONE')
# 1 - BOTTOM - LEFT
pie.operator("object.select_camera", text="Select Camera", icon='CAMERA_DATA')
# 3 - BOTTOM - RIGHT
@@ -86,8 +86,8 @@ class PieSelectionsEM(Menu):
layout = self.layout
pie = layout.menu_pie()
# 4 - LEFT
- pie.operator("view3d.select_border", text="Border Select",
- icon='BORDER_RECT')
+ pie.operator("view3d.select_box", text="Box Select",
+ icon='NONE')
# 6 - RIGHT
pie.menu("object.selectloopselection", text="Select Loop Menu", icon='LOOPSEL')
# 2 - BOTTOM
@@ -104,7 +104,7 @@ class PieSelectionsEM(Menu):
icon='FULLSCREEN_EXIT').action = 'INVERT'
# 1 - BOTTOM - LEFT
pie.operator("view3d.select_circle", text="Circle Select",
- icon='BORDER_LASSO')
+ icon='NONE')
# 3 - BOTTOM - RIGHT
pie.menu("object.selectallbyselection", text="Multi Select Menu", icon='SNAP_EDGE')
diff --git a/space_view3d_pie_menus/pie_shading_menu.py b/space_view3d_pie_menus/pie_shading_menu.py
index e7eb03b9..26da5d45 100644
--- a/space_view3d_pie_menus/pie_shading_menu.py
+++ b/space_view3d_pie_menus/pie_shading_menu.py
@@ -23,7 +23,7 @@ bl_info = {
"description": "Viewport Shading Menus",
"author": "pitiwazou, meta-androcto",
"version": (0, 1, 1),
- "blender": (2, 77, 0),
+ "blender": (2, 80, 0),
"location": "3D View",
"warning": "",
"wiki_url": "",
@@ -43,7 +43,7 @@ class PieShadingView(Menu):
layout = self.layout
pie = layout.menu_pie()
- pie.prop(context.space_data, "viewport_shade", expand=True)
+ pie.prop(context.space_data.shading, "type", expand=True)
if context.active_object:
if context.mode == 'EDIT_MESH':
diff --git a/space_view3d_pie_menus/pie_snap_menu.py b/space_view3d_pie_menus/pie_snap_menu.py
index b5ca502c..c1ee22e7 100644
--- a/space_view3d_pie_menus/pie_snap_menu.py
+++ b/space_view3d_pie_menus/pie_snap_menu.py
@@ -23,7 +23,7 @@ bl_info = {
"description": "Snap Element Menu",
"author": "pitiwazou, meta-androcto",
"version": (0, 1, 1),
- "blender": (2, 77, 0),
+ "blender": (2, 80, 0),
"location": "3d View",
"warning": "",
"wiki_url": "",
@@ -60,7 +60,7 @@ class PieSnaping(Menu):
# 1 - BOTTOM - LEFT
pie.operator("snap.alignrotation", text="Align rotation", icon='SNAP_NORMAL')
# 3 - BOTTOM - RIGHT
- pie.operator("wm.call_menu_pie", text="Snap Target", icon='SNAP_SURFACE').name = "snap.targetmenu"
+ pie.operator("wm.call_menu_pie", text="Snap Target", icon='NONE').name = "snap.targetmenu"
class SnapActive(Operator):
@@ -89,10 +89,9 @@ class SnapVolume(Operator):
ts = context.tool_settings
if ts.use_snap is False:
ts.use_snap = True
- ts.snap_element = 'VOLUME'
-
- if ts.snap_element != 'VOLUME':
- ts.snap_element = 'VOLUME'
+ ts.snap_elements = {'VOLUME'}
+ if ts.snap_elements != {'VOLUME'}:
+ ts.snap_elements = {'VOLUME'}
return {'FINISHED'}
@@ -106,10 +105,10 @@ class SnapFace(Operator):
if ts.use_snap is False:
ts.use_snap = True
- ts.snap_element = 'FACE'
+ ts.snap_elements = {'FACE'}
- if ts.snap_element != 'FACE':
- ts.snap_element = 'FACE'
+ if ts.snap_elements != {'FACE'}:
+ ts.snap_elements = {'FACE'}
return {'FINISHED'}
@@ -123,10 +122,10 @@ class SnapEdge(Operator):
if ts.use_snap is False:
ts.use_snap = True
- ts.snap_element = 'EDGE'
+ ts.snap_elements = {'EDGE'}
- if ts.snap_element != 'EDGE':
- ts.snap_element = 'EDGE'
+ if ts.snap_elements != {'EDGE'}:
+ ts.snap_elements = {'EDGE'}
return {'FINISHED'}
@@ -140,10 +139,10 @@ class SnapVertex(Operator):
if ts.use_snap is False:
ts.use_snap = True
- ts.snap_element = 'VERTEX'
+ ts.snap_elements = {'VERTEX'}
- if ts.snap_element != 'VERTEX':
- ts.snap_element = 'VERTEX'
+ if ts.snap_elements != {'VERTEX'}:
+ ts.snap_elements = {'VERTEX'}
return {'FINISHED'}
@@ -157,10 +156,10 @@ class SnapIncrement(Operator):
if ts.use_snap is False:
ts.use_snap = True
- ts.snap_element = 'INCREMENT'
+ ts.snap_elements = {'INCREMENT'}
- if ts.snap_element != 'INCREMENT':
- ts.snap_element = 'INCREMENT'
+ if ts.snap_elements != {'INCREMENT'}:
+ ts.snap_elements = {'INCREMENT'}
return {'FINISHED'}
@@ -185,7 +184,8 @@ class SnapTargetVariable(Operator):
bl_idname = "object.snaptargetvariable"
bl_label = "Snap Target Variable"
bl_options = {'REGISTER', 'UNDO'}
- variable = bpy.props.StringProperty()
+
+ variable: bpy.props.StringProperty()
@classmethod
def poll(cls, context):
diff --git a/space_view3d_pie_menus/pie_views_numpad_menu.py b/space_view3d_pie_menus/pie_views_numpad_menu.py
index 02cf5464..6c55bfe4 100644
--- a/space_view3d_pie_menus/pie_views_numpad_menu.py
+++ b/space_view3d_pie_menus/pie_views_numpad_menu.py
@@ -23,7 +23,7 @@ bl_info = {
"description": "Viewport Numpad Menus",
"author": "pitiwazou, meta-androcto",
"version": (0, 1, 1),
- "blender": (2, 77, 0),
+ "blender": (2, 80, 0),
"location": "Q key",
"warning": "",
"wiki_url": "",
@@ -108,17 +108,17 @@ class PieViewNumpad(Menu):
rd = scene.render
# 4 - LEFT
- pie.operator("view3d.viewnumpad", text="Left", icon='TRIA_LEFT').type = 'LEFT'
+ pie.operator("view3d.view_axis", text="Left", icon='TRIA_LEFT').type = 'LEFT'
# 6 - RIGHT
- pie.operator("view3d.viewnumpad", text="Right", icon='TRIA_RIGHT').type = 'RIGHT'
+ pie.operator("view3d.view_axis", text="Right", icon='TRIA_RIGHT').type = 'RIGHT'
# 2 - BOTTOM
- pie.operator("view3d.viewnumpad", text="Bottom", icon='TRIA_DOWN').type = 'BOTTOM'
+ pie.operator("view3d.view_axis", text="Bottom", icon='TRIA_DOWN').type = 'BOTTOM'
# 8 - TOP
- pie.operator("view3d.viewnumpad", text="Top", icon='TRIA_UP').type = 'TOP'
+ pie.operator("view3d.view_axis", text="Top", icon='TRIA_UP').type = 'TOP'
# 7 - TOP - LEFT
- pie.operator("view3d.viewnumpad", text="Front").type = 'FRONT'
+ pie.operator("view3d.view_axis", text="Front").type = 'FRONT'
# 9 - TOP - RIGHT
- pie.operator("view3d.viewnumpad", text="Back").type = 'BACK'
+ pie.operator("view3d.view_axis", text="Back").type = 'BACK'
# 1 - BOTTOM - LEFT
box = pie.split().column()
row = box.row(align=True)
@@ -131,8 +131,8 @@ class PieViewNumpad(Menu):
icon='LOCKED').data_path = "space_data.lock_camera"
row = box.row(align=True)
- row.operator("view3d.viewnumpad", text="View Cam", icon='VISIBLE_IPO_ON').type = 'CAMERA'
- row.operator("view3d.camera_to_view", text="Cam to view", icon='MAN_TRANS')
+ row.operator("view3d.view_camera", text="View Cam", icon='VISIBLE_IPO_ON')
+ row.operator("view3d.camera_to_view", text="Cam to view", icon='NONE')
icon_locked = 'LOCKED' if ob and ob.lock_rotation[0] is False else \
'UNLOCKED' if ob and ob.lock_rotation[0] is True else 'LOCKED'
@@ -143,7 +143,7 @@ class PieViewNumpad(Menu):
row = box.row(align=True)
row.prop(rd, "use_border", text="Border")
# 3 - BOTTOM - RIGHT
- pie.menu(PieViewallSelGlobEtc.bl_idname, text="View Menu", icon='BBOX')
+ pie.menu(PieViewallSelGlobEtc.bl_idname, text="View Menu", icon='NONE')
classes = (
diff --git a/space_view3d_spacebar_menu.py b/space_view3d_spacebar_menu.py
index 0831c0d5..c6a8c93f 100644
--- a/space_view3d_spacebar_menu.py
+++ b/space_view3d_spacebar_menu.py
@@ -64,10 +64,10 @@ class VIEW3D_MT_Space_Dynamic_Menu(Menu):
UseSeparator(self, context)
layout.menu("VIEW3D_MT_AddMenu", icon='OBJECT_DATAMODE')
layout.menu("VIEW3D_MT_View_Directions", icon='ZOOM_ALL')
- layout.menu("VIEW3D_MT_View_Navigation", icon='ROTATE')
- layout.menu("VIEW3D_MT_View_Toggle", icon='SPLITSCREEN')
+ layout.menu("VIEW3D_MT_View_Navigation", icon='PIVOT_BOUNDBOX')
+ layout.menu("VIEW3D_MT_View_Toggle", icon='WORKSPACE')
layout.operator("view3d.snap_cursor_to_center",
- text="Cursor to Center")
+ text="Cursor to World Origin")
layout.operator("view3d.snap_cursor_to_grid",
text="Cursor to Grid")
layout.menu("VIEW3D_MT_UndoS", icon='ARROW_LEFTRIGHT')
@@ -91,9 +91,9 @@ class VIEW3D_MT_Space_Dynamic_Menu(Menu):
UseSeparator(self, context)
layout.menu("VIEW3D_MT_TransformMenu", icon='MANIPUL')
layout.menu("VIEW3D_MT_MirrorMenu", icon='MOD_MIRROR')
- layout.menu("VIEW3D_MT_CursorMenu", icon='CURSOR')
+ layout.menu("VIEW3D_MT_CursorMenu", icon='PIVOT_CURSOR')
UseSeparator(self, context)
- layout.menu("VIEW3D_MT_ParentMenu", icon='ROTACTIVE')
+ layout.menu("VIEW3D_MT_ParentMenu", icon='PIVOT_ACTIVE')
layout.menu("VIEW3D_MT_GroupMenu", icon='GROUP')
UseSeparator(self, context)
layout.menu("VIEW3D_MT_object_specials", text="Specials", icon='SOLO_OFF')
@@ -121,16 +121,16 @@ class VIEW3D_MT_Space_Dynamic_Menu(Menu):
layout.menu("VIEW3D_MT_Select_Edit_Mesh", icon='RESTRICT_SELECT_OFF')
layout.menu("VIEW3D_MT_Edit_Multi", icon='VERTEXSEL')
UseSeparator(self, context)
- layout.menu("INFO_MT_mesh_add", text="Add Mesh", icon='OUTLINER_OB_MESH')
+ layout.menu("VIEW3D_MT_mesh_add", text="Add Mesh", icon='OUTLINER_OB_MESH')
layout.menu("VIEW3D_MT_Edit_Mesh", text="Mesh", icon='MESH_DATA')
UseSeparator(self, context)
layout.menu("VIEW3D_MT_TransformMenuEdit", icon='MANIPUL')
layout.menu("VIEW3D_MT_MirrorMenu", icon='MOD_MIRROR')
- layout.menu("VIEW3D_MT_EditCursorMenu", icon='CURSOR')
+ layout.menu("VIEW3D_MT_EditCursorMenu", icon='PIVOT_CURSOR')
UseSeparator(self, context)
layout.menu("VIEW3D_MT_UV_Map", icon='MOD_UVPROJECT')
layout.menu("VIEW3D_MT_edit_mesh_specials", icon='SOLO_OFF')
- layout.menu("VIEW3D_MT_edit_mesh_extrude", icon='ORTHO')
+ layout.menu("VIEW3D_MT_edit_mesh_extrude", icon='XRAY')
UseSeparator(self, context)
layout.operator_menu_enum("object.modifier_add", "type", icon='MODIFIER')
layout.operator_menu_enum("object.constraint_add",
@@ -230,9 +230,9 @@ class VIEW3D_MT_Space_Dynamic_Menu(Menu):
UseSeparator(self, context)
layout.menu("VIEW3D_MT_TransformMenu", icon='MANIPUL')
layout.menu("VIEW3D_MT_MirrorMenu", icon='MOD_MIRROR')
- layout.menu("VIEW3D_MT_CursorMenu", icon='CURSOR')
+ layout.menu("VIEW3D_MT_CursorMenu", icon='PIVOT_CURSOR')
UseSeparator(self, context)
- layout.menu("VIEW3D_MT_ParentMenu", icon='ROTACTIVE')
+ layout.menu("VIEW3D_MT_ParentMenu", icon='PIVOT_ACTIVE')
layout.menu("VIEW3D_MT_GroupMenu", icon='GROUP')
UseSeparator(self, context)
layout.menu("VIEW3D_MT_object_specials", text="Specials", icon='SOLO_OFF')
@@ -259,13 +259,13 @@ class VIEW3D_MT_Space_Dynamic_Menu(Menu):
layout.menu("VIEW3D_MT_Select_Edit_Curve",
icon='RESTRICT_SELECT_OFF')
UseSeparator(self, context)
- layout.menu("INFO_MT_curve_add", text="Add Curve",
+ layout.menu("VIEW3D_MT_curve_add", text="Add Curve",
icon='OUTLINER_OB_CURVE')
layout.menu("VIEW3D_MT_Edit_Curve", icon='CURVE_DATA')
UseSeparator(self, context)
layout.menu("VIEW3D_MT_TransformMenu", icon='MANIPUL')
layout.menu("VIEW3D_MT_MirrorMenu", icon='MOD_MIRROR')
- layout.menu("VIEW3D_MT_CursorMenu", icon='CURSOR')
+ layout.menu("VIEW3D_MT_CursorMenu", icon='PIVOT_CURSOR')
layout.menu("VIEW3D_MT_EditCurveCtrlpoints",
icon='CURVE_BEZCURVE')
layout.menu("VIEW3D_MT_EditCurveSpecials",
@@ -294,9 +294,9 @@ class VIEW3D_MT_Space_Dynamic_Menu(Menu):
UseSeparator(self, context)
layout.menu("VIEW3D_MT_TransformMenu", icon='MANIPUL')
layout.menu("VIEW3D_MT_MirrorMenu", icon='MOD_MIRROR')
- layout.menu("VIEW3D_MT_CursorMenu", icon='CURSOR')
+ layout.menu("VIEW3D_MT_CursorMenu", icon='PIVOT_CURSOR')
UseSeparator(self, context)
- layout.menu("VIEW3D_MT_ParentMenu", icon='ROTACTIVE')
+ layout.menu("VIEW3D_MT_ParentMenu", icon='PIVOT_ACTIVE')
layout.menu("VIEW3D_MT_GroupMenu", icon='GROUP')
UseSeparator(self, context)
layout.menu("VIEW3D_MT_object_specials", text="Specials", icon='SOLO_OFF')
@@ -321,11 +321,11 @@ class VIEW3D_MT_Space_Dynamic_Menu(Menu):
layout.menu("VIEW3D_MT_View_Menu", icon='ZOOM_ALL')
layout.menu("VIEW3D_MT_Select_Edit_Surface", icon='RESTRICT_SELECT_OFF')
UseSeparator(self, context)
- layout.menu("INFO_MT_surface_add", text="Add Surface",
+ layout.menu("VIEW3D_MT_surface_add", text="Add Surface",
icon='OUTLINER_OB_SURFACE')
layout.menu("VIEW3D_MT_TransformMenu", icon='MANIPUL')
layout.menu("VIEW3D_MT_MirrorMenu", icon='MOD_MIRROR')
- layout.menu("VIEW3D_MT_CursorMenu", icon='CURSOR')
+ layout.menu("VIEW3D_MT_CursorMenu", icon='PIVOT_CURSOR')
UseSeparator(self, context)
layout.prop_menu_enum(settings, "proportional_edit",
icon="PROP_CON")
@@ -357,9 +357,9 @@ class VIEW3D_MT_Space_Dynamic_Menu(Menu):
UseSeparator(self, context)
layout.menu("VIEW3D_MT_TransformMenu", icon='MANIPUL')
layout.menu("VIEW3D_MT_MirrorMenu", icon='MOD_MIRROR')
- layout.menu("VIEW3D_MT_CursorMenu", icon='CURSOR')
+ layout.menu("VIEW3D_MT_CursorMenu", icon='PIVOT_CURSOR')
UseSeparator(self, context)
- layout.menu("VIEW3D_MT_ParentMenu", icon='ROTACTIVE')
+ layout.menu("VIEW3D_MT_ParentMenu", icon='PIVOT_ACTIVE')
layout.menu("VIEW3D_MT_GroupMenu", icon='GROUP')
UseSeparator(self, context)
layout.menu("VIEW3D_MT_object_specials", text="Specials", icon='SOLO_OFF')
@@ -388,7 +388,7 @@ class VIEW3D_MT_Space_Dynamic_Menu(Menu):
icon='OUTLINER_OB_META')
layout.menu("VIEW3D_MT_TransformMenu", icon='MANIPUL')
layout.menu("VIEW3D_MT_MirrorMenu", icon='MOD_MIRROR')
- layout.menu("VIEW3D_MT_CursorMenu", icon='CURSOR')
+ layout.menu("VIEW3D_MT_CursorMenu", icon='PIVOT_CURSOR')
UseSeparator(self, context)
layout.prop_menu_enum(settings, "proportional_edit",
icon="PROP_CON")
@@ -418,9 +418,9 @@ class VIEW3D_MT_Space_Dynamic_Menu(Menu):
UseSeparator(self, context)
layout.menu("VIEW3D_MT_TransformMenu", icon='MANIPUL')
layout.menu("VIEW3D_MT_MirrorMenu", icon='MOD_MIRROR')
- layout.menu("VIEW3D_MT_CursorMenu", icon='CURSOR')
+ layout.menu("VIEW3D_MT_CursorMenu", icon='PIVOT_CURSOR')
UseSeparator(self, context)
- layout.menu("VIEW3D_MT_ParentMenu", icon='ROTACTIVE')
+ layout.menu("VIEW3D_MT_ParentMenu", icon='PIVOT_ACTIVE')
layout.menu("VIEW3D_MT_GroupMenu", icon='GROUP')
UseSeparator(self, context)
layout.menu("VIEW3D_MT_object_specials", text="Specials", icon='SOLO_OFF')
@@ -470,9 +470,9 @@ class VIEW3D_MT_Space_Dynamic_Menu(Menu):
layout.menu("VIEW3D_MT_Object", icon='VIEW3D')
UseSeparator(self, context)
layout.menu("VIEW3D_MT_TransformMenu", icon='MANIPUL')
- layout.menu("VIEW3D_MT_CursorMenuLite", icon='CURSOR')
+ layout.menu("VIEW3D_MT_CursorMenuLite", icon='PIVOT_CURSOR')
UseSeparator(self, context)
- layout.menu("VIEW3D_MT_ParentMenu", icon='ROTACTIVE')
+ layout.menu("VIEW3D_MT_ParentMenu", icon='PIVOT_ACTIVE')
layout.menu("VIEW3D_MT_GroupMenu", icon='GROUP')
UseSeparator(self, context)
layout.menu("VIEW3D_MT_object_specials", text="Specials", icon='SOLO_OFF')
@@ -489,7 +489,7 @@ class VIEW3D_MT_Space_Dynamic_Menu(Menu):
layout.operator("view3d.properties", icon='MENU_PANEL')
# Lamp Object Mode #
- if obj and obj.type == 'LAMP' and obj.mode in {'OBJECT'}:
+ if obj and obj.type == 'LIGHT' and obj.mode in {'OBJECT'}:
layout.operator_context = 'INVOKE_REGION_WIN'
layout.operator("wm.search_menu", text="Search", icon='VIEWZOOM')
@@ -501,9 +501,9 @@ class VIEW3D_MT_Space_Dynamic_Menu(Menu):
layout.menu("VIEW3D_MT_Object", icon='VIEW3D')
UseSeparator(self, context)
layout.menu("VIEW3D_MT_TransformMenuLite", icon='MANIPUL')
- layout.menu("VIEW3D_MT_CursorMenuLite", icon='CURSOR')
+ layout.menu("VIEW3D_MT_CursorMenuLite", icon='PIVOT_CURSOR')
UseSeparator(self, context)
- layout.menu("VIEW3D_MT_ParentMenu", icon='ROTACTIVE')
+ layout.menu("VIEW3D_MT_ParentMenu", icon='PIVOT_ACTIVE')
layout.menu("VIEW3D_MT_GroupMenu", icon='GROUP')
UseSeparator(self, context)
layout.menu("VIEW3D_MT_object_specials", text="Specials", icon='SOLO_OFF')
@@ -533,9 +533,9 @@ class VIEW3D_MT_Space_Dynamic_Menu(Menu):
UseSeparator(self, context)
layout.menu("VIEW3D_MT_TransformMenuArmature", icon='MANIPUL')
layout.menu("VIEW3D_MT_MirrorMenu", icon='MOD_MIRROR')
- layout.menu("VIEW3D_MT_CursorMenuLite", icon='CURSOR')
+ layout.menu("VIEW3D_MT_CursorMenuLite", icon='PIVOT_CURSOR')
UseSeparator(self, context)
- layout.menu("VIEW3D_MT_ParentMenu", icon='ROTACTIVE')
+ layout.menu("VIEW3D_MT_ParentMenu", icon='PIVOT_ACTIVE')
layout.menu("VIEW3D_MT_GroupMenu", icon='GROUP')
UseSeparator(self, context)
layout.menu("VIEW3D_MT_object_specials", text="Specials", icon='SOLO_OFF')
@@ -561,7 +561,7 @@ class VIEW3D_MT_Space_Dynamic_Menu(Menu):
layout.menu("VIEW3D_MT_Select_Edit_Armature",
icon='RESTRICT_SELECT_OFF')
UseSeparator(self, context)
- layout.menu("INFO_MT_armature_add", text="Add Armature",
+ layout.menu("VIEW3D_MT_armature_add", text="Add Armature",
icon='OUTLINER_OB_ARMATURE')
layout.menu("VIEW3D_MT_Edit_Armature", text="Armature",
icon='OUTLINER_DATA_ARMATURE')
@@ -570,8 +570,8 @@ class VIEW3D_MT_Space_Dynamic_Menu(Menu):
UseSeparator(self, context)
layout.menu("VIEW3D_MT_TransformMenuArmatureEdit", icon='MANIPUL')
layout.menu("VIEW3D_MT_MirrorMenu", icon='MOD_MIRROR')
- layout.menu("VIEW3D_MT_CursorMenuLite", icon='CURSOR')
- layout.menu("VIEW3D_MT_ParentMenu", icon='ROTACTIVE')
+ layout.menu("VIEW3D_MT_CursorMenuLite", icon='PIVOT_CURSOR')
+ layout.menu("VIEW3D_MT_ParentMenu", icon='PIVOT_ACTIVE')
layout.menu("VIEW3D_MT_armature_specials", icon='SOLO_OFF')
layout.menu("VIEW3D_MT_edit_armature_roll",
icon='BONE_DATA')
@@ -599,10 +599,10 @@ class VIEW3D_MT_Space_Dynamic_Menu(Menu):
layout.menu("VIEW3D_MT_TransformMenuArmaturePose", icon='MANIPUL')
layout.menu("VIEW3D_MT_pose_transform", icon='EMPTY_DATA')
UseSeparator(self, context)
- layout.menu("VIEW3D_MT_CursorMenuLite", icon='CURSOR')
+ layout.menu("VIEW3D_MT_CursorMenuLite", icon='PIVOT_CURSOR')
layout.menu("VIEW3D_MT_PoseCopy", icon='FILE')
- if arm.draw_type in {'BBONE', 'ENVELOPE'}:
+ if arm.display_type in {'BBONE', 'ENVELOPE'}:
layout.operator("transform.transform",
text="Scale Envelope Distance").mode = 'BONE_SIZE'
@@ -635,9 +635,9 @@ class VIEW3D_MT_Space_Dynamic_Menu(Menu):
UseSeparator(self, context)
layout.menu("VIEW3D_MT_TransformMenu", icon='MANIPUL')
layout.menu("VIEW3D_MT_MirrorMenu", icon='MOD_MIRROR')
- layout.menu("VIEW3D_MT_CursorMenu", icon='CURSOR')
+ layout.menu("VIEW3D_MT_CursorMenu", icon='PIVOT_CURSOR')
UseSeparator(self, context)
- layout.menu("VIEW3D_MT_ParentMenu", icon='ROTACTIVE')
+ layout.menu("VIEW3D_MT_ParentMenu", icon='PIVOT_ACTIVE')
layout.menu("VIEW3D_MT_GroupMenu", icon='GROUP')
UseSeparator(self, context)
layout.menu("VIEW3D_MT_object_specials", text="Specials", icon='SOLO_OFF')
@@ -666,7 +666,7 @@ class VIEW3D_MT_Space_Dynamic_Menu(Menu):
UseSeparator(self, context)
layout.menu("VIEW3D_MT_TransformMenu", icon='MANIPUL')
layout.menu("VIEW3D_MT_MirrorMenu", icon='MOD_MIRROR')
- layout.menu("VIEW3D_MT_CursorMenu", icon='CURSOR')
+ layout.menu("VIEW3D_MT_CursorMenu", icon='PIVOT_CURSOR')
UseSeparator(self, context)
layout.prop_menu_enum(settings, "proportional_edit",
icon="PROP_CON")
@@ -695,9 +695,9 @@ class VIEW3D_MT_Space_Dynamic_Menu(Menu):
UseSeparator(self, context)
layout.menu("VIEW3D_MT_TransformMenuLite", icon='MANIPUL')
layout.menu("VIEW3D_MT_MirrorMenu", icon='MOD_MIRROR')
- layout.menu("VIEW3D_MT_CursorMenuLite", icon='CURSOR')
+ layout.menu("VIEW3D_MT_CursorMenuLite", icon='PIVOT_CURSOR')
UseSeparator(self, context)
- layout.menu("VIEW3D_MT_ParentMenu", icon='ROTACTIVE')
+ layout.menu("VIEW3D_MT_ParentMenu", icon='PIVOT_ACTIVE')
layout.menu("VIEW3D_MT_GroupMenu", icon='GROUP')
UseSeparator(self, context)
layout.menu("VIEW3D_MT_object_specials", text="Specials", icon='SOLO_OFF')
@@ -726,9 +726,9 @@ class VIEW3D_MT_Space_Dynamic_Menu(Menu):
layout.menu("VIEW3D_MT_Object", icon='VIEW3D')
UseSeparator(self, context)
layout.menu("VIEW3D_MT_TransformMenuLite", icon='MANIPUL')
- layout.menu("VIEW3D_MT_CursorMenuLite", icon='CURSOR')
+ layout.menu("VIEW3D_MT_CursorMenuLite", icon='PIVOT_CURSOR')
UseSeparator(self, context)
- layout.menu("VIEW3D_MT_ParentMenu", icon='ROTACTIVE')
+ layout.menu("VIEW3D_MT_ParentMenu", icon='PIVOT_ACTIVE')
layout.menu("VIEW3D_MT_GroupMenu", icon='GROUP')
UseSeparator(self, context)
layout.operator_menu_enum("object.constraint_add",
@@ -754,7 +754,7 @@ class VIEW3D_MT_Space_Dynamic_Menu(Menu):
UseSeparator(self, context)
layout.menu("VIEW3D_MT_TransformMenu", icon='MANIPUL')
layout.menu("VIEW3D_MT_MirrorMenu", icon='MOD_MIRROR')
- layout.menu("VIEW3D_MT_CursorMenuLite", icon='CURSOR')
+ layout.menu("VIEW3D_MT_CursorMenuLite", icon='PIVOT_CURSOR')
UseSeparator(self, context)
layout.prop_menu_enum(settings, "proportional_edit",
icon="PROP_CON")
@@ -807,7 +807,6 @@ class VIEW3D_MT_Object(Menu):
layout.menu("VIEW3D_MT_object_track")
layout.menu("VIEW3D_MT_object_animation")
UseSeparator(self, context)
- layout.menu("VIEW3D_MT_object_game")
layout.menu("VIEW3D_MT_object_showhide")
UseSeparator(self, context)
layout.operator_menu_enum("object.convert", "target")
@@ -821,18 +820,18 @@ class VIEW3D_MT_AddMenu(Menu):
layout = self.layout
layout.operator_context = 'INVOKE_REGION_WIN'
- layout.menu("INFO_MT_mesh_add", text="Add Mesh",
+ layout.menu("VIEW3D_MT_mesh_add", text="Add Mesh",
icon='OUTLINER_OB_MESH')
- layout.menu("INFO_MT_curve_add", text="Add Curve",
+ layout.menu("VIEW3D_MT_curve_add", text="Add Curve",
icon='OUTLINER_OB_CURVE')
- layout.menu("INFO_MT_surface_add", text="Add Surface",
+ layout.menu("VIEW3D_MT_surface_add", text="Add Surface",
icon='OUTLINER_OB_SURFACE')
layout.operator_menu_enum("object.metaball_add", "type",
icon='OUTLINER_OB_META')
layout.operator("object.text_add", text="Add Text",
icon='OUTLINER_OB_FONT')
UseSeparator(self, context)
- layout.menu("INFO_MT_armature_add", text="Add Armature",
+ layout.menu("VIEW3D_MT_armature_add", text="Add Armature",
icon='OUTLINER_OB_ARMATURE')
layout.operator("object.add", text="Lattice",
icon='OUTLINER_OB_LATTICE').type = 'LATTICE'
@@ -842,8 +841,8 @@ class VIEW3D_MT_AddMenu(Menu):
UseSeparator(self, context)
layout.operator("object.camera_add", text="Camera",
icon='OUTLINER_OB_CAMERA')
- layout.operator_menu_enum("object.lamp_add", "type",
- icon="OUTLINER_OB_LAMP")
+ layout.operator_menu_enum("object.light_add", "type",
+ icon="OUTLINER_OB_LIGHT")
UseSeparator(self, context)
layout.operator_menu_enum("object.effector_add", "type",
text="Force Field",
@@ -851,18 +850,18 @@ class VIEW3D_MT_AddMenu(Menu):
layout.menu("VIEW3D_MT_object_quick_effects", text="Quick Effects", icon='PARTICLES')
UseSeparator(self, context)
- has_groups = (len(bpy.data.groups) > 0)
+ has_groups = (len(bpy.data.collections) > 0)
col_group = layout.column()
col_group.enabled = has_groups
- if not has_groups or len(bpy.data.groups) > 10:
+ if not has_groups or len(bpy.data.collections) > 10:
col_group.operator_context = 'INVOKE_REGION_WIN'
- col_group.operator("object.group_instance_add",
- text="Group Instance..." if has_groups else "No Groups in Data",
+ col_group.operator("object.collection_instance_add",
+ text="Collection Instance..." if has_groups else "No Groups in Data",
icon='GROUP_VERTEX')
else:
- col_group.operator_menu_enum("object.group_instance_add", "group",
- text="Group Instance", icon='GROUP_VERTEX')
+ col_group.operator_menu_enum("object.collection_instance_add", "collection",
+ text="Collection Instance", icon='GROUP_VERTEX')
# ********** Object Manipulator **********
@@ -975,18 +974,20 @@ class VIEW3D_MT_CursorMenu(Menu):
layout.operator("view3d.snap_cursor_to_selected",
text="Cursor to Selected")
layout.operator("view3d.snap_cursor_to_center",
- text="Cursor to Center")
+ text="Cursor to World Origin")
layout.operator("view3d.snap_cursor_to_grid",
text="Cursor to Grid")
layout.operator("view3d.snap_cursor_to_active",
text="Cursor to Active")
UseSeparator(self, context)
- layout.operator("view3d.snap_selected_to_cursor", text="Selection to Cursor").use_offset = False
- layout.operator("view3d.snap_selected_to_cursor", text="Selection to Cursor (Offset)").use_offset = True
+ layout.operator("view3d.snap_selected_to_cursor",
+ text="Selection to Cursor").use_offset = False
+ layout.operator("view3d.snap_selected_to_cursor",
+ text="Selection to Cursor (Keep Offset)").use_offset = True
layout.operator("view3d.snap_selected_to_grid",
text="Selection to Grid")
layout.operator("view3d.snap_cursor_selected_to_center",
- text="Selection and Cursor to Center")
+ text="Selection and Cursor to World Origin")
UseSeparator(self, context)
layout.menu("VIEW3D_MT_Pivot")
layout.operator("view3d.pivot_cursor",
@@ -1006,18 +1007,20 @@ class VIEW3D_MT_CursorMenuLite(Menu):
layout.operator("view3d.snap_cursor_to_selected",
text="Cursor to Selected")
layout.operator("view3d.snap_cursor_to_center",
- text="Cursor to Center")
+ text="Cursor to World Origin")
layout.operator("view3d.snap_cursor_to_grid",
text="Cursor to Grid")
layout.operator("view3d.snap_cursor_to_active",
text="Cursor to Active")
UseSeparator(self, context)
- layout.operator("view3d.snap_selected_to_cursor", text="Selection to Cursor").use_offset = False
- layout.operator("view3d.snap_selected_to_cursor", text="Selection to Cursor (Offset)").use_offset = True
+ layout.operator("view3d.snap_selected_to_cursor",
+ text="Selection to Cursor").use_offset = False
+ layout.operator("view3d.snap_selected_to_cursor",
+ text="Selection to Cursor (Keep Offset)").use_offset = True
layout.operator("view3d.snap_selected_to_grid",
text="Selection to Grid")
layout.operator("view3d.snap_cursor_selected_to_center",
- text="Selection and Cursor to Center")
+ text="Selection and Cursor to World Origin")
UseSeparator(self, context)
layout.menu("VIEW3D_MT_Pivot")
layout.operator("view3d.pivot_cursor",
@@ -1175,12 +1178,12 @@ class VIEW3D_MT_GroupMenu(Menu):
def draw(self, context):
layout = self.layout
- layout.operator("group.create")
- layout.operator("group.objects_add_active")
+ layout.operator("collection.create")
+ layout.operator("collection.objects_add_active")
UseSeparator(self, context)
- layout.operator("group.objects_remove")
- layout.operator("group.objects_remove_all")
- layout.operator("group.objects_remove_active")
+ layout.operator("collection.objects_remove")
+ layout.operator("collection.objects_remove_all")
+ layout.operator("collection.objects_remove_active")
# ********** Object Camera Options **********
@@ -1348,7 +1351,7 @@ class VIEW3D_MT_Edit_Multi(Menu):
prop = layout.operator("wm.context_set_value",
text="Vertex & Face Select",
- icon='ORTHO')
+ icon='XRAY')
prop.value = "(True, False, True)"
prop.data_path = "tool_settings.mesh_select_mode"
@@ -1412,7 +1415,7 @@ class VIEW3D_MT_EditCursorMenu(Menu):
layout.operator("view3d.snap_cursor_to_selected",
text="Cursor to Selected")
layout.operator("view3d.snap_cursor_to_center",
- text="Cursor to Center")
+ text="Cursor to World Origin")
layout.operator("view3d.snap_cursor_to_grid",
text="Cursor to Grid")
layout.operator("view3d.snap_cursor_to_active",
@@ -1420,8 +1423,10 @@ class VIEW3D_MT_EditCursorMenu(Menu):
layout.operator("view3d.snap_cursor_to_edge_intersection",
text="Cursor to Edge Intersection")
UseSeparator(self, context)
- layout.operator("view3d.snap_selected_to_cursor", text="Selection to Cursor").use_offset = False
- layout.operator("view3d.snap_selected_to_cursor", text="Selection to Cursor (Offset)").use_offset = True
+ layout.operator("view3d.snap_selected_to_cursor",
+ text="Selection to Cursor").use_offset = False
+ layout.operator("view3d.snap_selected_to_cursor",
+ text="Selection to Cursor (Keep Offset)").use_offset = True
layout.operator("view3d.snap_selected_to_grid",
text="Selection to Grid")
UseSeparator(self, context)
@@ -1648,7 +1653,7 @@ class VIEW3D_MT_Hide_Masks(Menu):
props = layout.operator("paint.mask_lasso_gesture", text="Lasso Mask")
UseSeparator(self, context)
- props = layout.operator("view3d.select_border", text="Box Mask", icon="BORDER_RECT")
+ props = layout.operator("view3d.select_box", text="Box Mask", icon="BORDER_RECT")
props = layout.operator("paint.hide_show", text="Box Hide")
props.action = 'HIDE'
props.area = 'INSIDE'
@@ -1890,7 +1895,7 @@ class VIEW3D_MT_TransformMenu(Menu):
layout = self.layout
layout.menu("VIEW3D_MT_ManipulatorMenu1")
UseSeparator(self, context)
- layout.operator("transform.translate", text="Grab/Move")
+ layout.operator("transform.translate", text="Move")
layout.operator("transform.rotate", text="Rotate")
layout.operator("transform.resize", text="Scale")
UseSeparator(self, context)
@@ -1920,7 +1925,7 @@ class VIEW3D_MT_TransformMenuEdit(Menu):
layout = self.layout
layout.menu("VIEW3D_MT_ManipulatorMenu1")
UseSeparator(self, context)
- layout.operator("transform.translate", text="Grab/Move")
+ layout.operator("transform.translate", text="Move")
layout.operator("transform.rotate", text="Rotate")
layout.operator("transform.resize", text="Scale")
UseSeparator(self, context)
@@ -1950,7 +1955,7 @@ class VIEW3D_MT_TransformMenuLite(Menu):
layout = self.layout
layout.menu("VIEW3D_MT_ManipulatorMenu1")
UseSeparator(self, context)
- layout.operator("transform.translate", text="Grab/Move")
+ layout.operator("transform.translate", text="Move")
layout.operator("transform.rotate", text="Rotate")
layout.operator("transform.resize", text="Scale")
UseSeparator(self, context)
@@ -1971,7 +1976,7 @@ class VIEW3D_MT_TransformMenuCamera(Menu):
layout.menu("VIEW3D_MT_ManipulatorMenu1")
layout.menu("VIEW3D_MT_object_clear")
layout.menu("VIEW3D_MT_object_apply")
- layout.operator("transform.translate", text="Grab/Move")
+ layout.operator("transform.translate", text="Move")
layout.operator("transform.rotate", text="Rotate")
layout.operator("transform.resize", text="Scale")
layout.operator("object.align")
@@ -1990,7 +1995,7 @@ class VIEW3D_MT_TransformMenuArmature(Menu):
layout.menu("VIEW3D_MT_ManipulatorMenu1")
UseSeparator(self, context)
- layout.operator("transform.translate", text="Grab/Move")
+ layout.operator("transform.translate", text="Move")
layout.operator("transform.rotate", text="Rotate")
layout.operator("transform.resize", text="Scale")
UseSeparator(self, context)
@@ -2016,7 +2021,7 @@ class VIEW3D_MT_TransformMenuArmatureEdit(Menu):
layout = self.layout
layout.menu("VIEW3D_MT_ManipulatorMenu1")
UseSeparator(self, context)
- layout.operator("transform.translate", text="Grab/Move")
+ layout.operator("transform.translate", text="Move")
layout.operator("transform.rotate", text="Rotate")
layout.operator("transform.resize", text="Scale")
UseSeparator(self, context)
@@ -2038,7 +2043,7 @@ class VIEW3D_MT_TransformMenuArmaturePose(Menu):
def draw(self, context):
layout = self.layout
layout.menu("VIEW3D_MT_ManipulatorMenu1")
- layout.operator("transform.translate", text="Grab/Move")
+ layout.operator("transform.translate", text="Move")
layout.operator("transform.rotate", text="Rotate")
layout.operator("transform.resize", text="Scale")
UseSeparator(self, context)
@@ -2052,9 +2057,9 @@ class VIEW3D_MT_TransformMenuArmaturePose(Menu):
layout.operator("pose.user_transforms_clear", text="Reset unkeyed")
obj = context.object
if obj.type == 'ARMATURE' and obj.mode in {'EDIT', 'POSE'}:
- if obj.data.draw_type == 'BBONE':
+ if obj.data.display_type == 'BBONE':
layout.operator("transform.transform", text="Scale BBone").mode = 'BONE_SIZE'
- elif obj.data.draw_type == 'ENVELOPE':
+ elif obj.data.display_type == 'ENVELOPE':
layout.operator("transform.transform", text="Scale Envelope Distance").mode = 'BONE_SIZE'
layout.operator("transform.transform", text="Scale Radius").mode = 'BONE_ENVELOPE'
@@ -2086,7 +2091,7 @@ class VIEW3D_MT_View_Border(Menu):
layout.operator_context = 'INVOKE_REGION_WIN'
layout.operator("view3d.clip_border", text="Clipping Border...")
layout.operator("view3d.zoom_border", text="Zoom Border...")
- layout.operator("view3d.render_border", text="Render Border...").camera_only = False
+ layout.operator("view3d.render_border", text="Render Border...")
class VIEW3D_MT_View_Toggle(Menu):
@@ -2223,8 +2228,8 @@ class VIEW3D_MT_Shade(Menu):
layout.operator("OBJECT_OT_shade_flat")
UseSeparator(self, context)
- layout.operator("view3d.display_wire_all", text="Wire all", icon='WIRE')
- layout.prop(context.object, "show_x_ray", text="X-Ray", icon="META_CUBE")
+ layout.operator("view3d.display_wire_all", text="Wire all", icon='SHADING_WIRE')
+ layout.prop(context.object, "show_in_front", text="X-Ray", icon="META_CUBE")
UseSeparator(self, context)
layout.prop(context.space_data.fx_settings, "use_ssao",
@@ -2267,7 +2272,7 @@ class VIEW3D_MT_Select_Object(Menu):
def draw(self, context):
layout = self.layout
layout.operator_context = 'INVOKE_REGION_WIN'
- layout.operator("view3d.select_border")
+ layout.operator("view3d.select_box")
layout.operator("view3d.select_circle")
UseSeparator(self, context)
layout.operator("object.select_all").action = 'TOGGLE'
@@ -2317,7 +2322,7 @@ class VIEW3D_MT_Select_Edit_Mesh(Menu):
def draw(self, context):
layout = self.layout
- layout.operator("view3d.select_border")
+ layout.operator("view3d.select_box")
layout.operator("view3d.select_circle")
UseSeparator(self, context)
layout.operator("mesh.select_all").action = 'TOGGLE'
@@ -2383,7 +2388,7 @@ class VIEW3D_MT_Select_Edit_Curve(Menu):
def draw(self, context):
layout = self.layout
- layout.operator("view3d.select_border")
+ layout.operator("view3d.select_box")
layout.operator("view3d.select_circle")
UseSeparator(self, context)
layout.operator("curve.select_all").action = 'TOGGLE'
@@ -2408,7 +2413,7 @@ class VIEW3D_MT_SelectArmatureMenu(Menu):
def draw(self, context):
layout = self.layout
- layout.operator("view3d.select_border")
+ layout.operator("view3d.select_box")
layout.operator("armature.select_all")
layout.operator("armature.select_inverse", text="Inverse")
layout.operator("armature.select_hierarchy",
@@ -2432,7 +2437,7 @@ class VIEW3D_MT_Select_Edit_Armature(Menu):
def draw(self, context):
layout = self.layout
- layout.operator("view3d.select_border")
+ layout.operator("view3d.select_box")
layout.operator("view3d.select_circle")
UseSeparator(self, context)
@@ -2475,7 +2480,7 @@ class VIEW3D_MT_Select_Pose(Menu):
def draw(self, context):
layout = self.layout
- layout.operator("view3d.select_border")
+ layout.operator("view3d.select_box")
layout.operator("view3d.select_circle")
UseSeparator(self, context)
layout.operator("pose.select_all").action = 'TOGGLE'
@@ -2556,7 +2561,7 @@ class VIEW3D_MT_Select_Edit_Surface(Menu):
def draw(self, context):
layout = self.layout
- layout.operator("view3d.select_border")
+ layout.operator("view3d.select_box")
layout.operator("view3d.select_circle")
UseSeparator(self, context)
layout.operator("curve.select_all").action = 'TOGGLE'
@@ -2577,7 +2582,7 @@ class VIEW3D_MT_SelectMetaball(Menu):
def draw(self, context):
layout = self.layout
- layout.operator("view3d.select_border")
+ layout.operator("view3d.select_box")
layout.operator("view3d.select_circle")
UseSeparator(self, context)
layout.operator("mball.select_all").action = 'TOGGLE'
@@ -2590,7 +2595,7 @@ class VIEW3D_MT_Select_Edit_Metaball(Menu):
def draw(self, context):
layout = self.layout
- layout.operator("view3d.select_border")
+ layout.operator("view3d.select_box")
layout.operator("view3d.select_circle")
layout.operator("mball.select_all").action = 'TOGGLE'
layout.operator("mball.select_all", text="Inverse").action = 'INVERT'
@@ -2615,7 +2620,7 @@ class VIEW3D_MT_Select_Particle(Menu):
def draw(self, context):
layout = self.layout
- layout.operator("view3d.select_border")
+ layout.operator("view3d.select_box")
layout.operator("view3d.select_circle")
UseSeparator(self, context)
@@ -2642,7 +2647,7 @@ class VIEW3D_MT_Select_Edit_Lattice(Menu):
def draw(self, context):
layout = self.layout
- layout.operator("view3d.select_border")
+ layout.operator("view3d.select_box")
layout.operator("view3d.select_circle")
UseSeparator(self, context)
layout.operator("lattice.select_mirror")
@@ -2662,7 +2667,7 @@ class VIEW3D_MT_Select_Gpencil(Menu):
def draw(self, context):
layout = self.layout
- layout.operator("gpencil.select_border")
+ layout.operator("gpencil.select_box")
layout.operator("gpencil.select_circle")
UseSeparator(self, context)
@@ -2700,7 +2705,7 @@ class VIEW3D_MT_Select_Paint_Mask(Menu):
def draw(self, context):
layout = self.layout
- layout.operator("view3d.select_border")
+ layout.operator("view3d.select_box")
layout.operator("view3d.select_circle")
layout.operator("paint.face_select_all").action = 'TOGGLE'
layout.operator("paint.face_select_all", text="Inverse").action = 'INVERT'
@@ -2712,7 +2717,7 @@ class VIEW3D_MT_Select_Paint_Mask_Vertex(Menu):
def draw(self, context):
layout = self.layout
- layout.operator("view3d.select_border")
+ layout.operator("view3d.select_box")
layout.operator("view3d.select_circle")
layout.operator("paint.vert_select_all").action = 'TOGGLE'
layout.operator("paint.vert_select_all", text="Inverse").action = 'INVERT'
@@ -2921,7 +2926,7 @@ class SetOriginToSelected(Operator):
# Code thanks to Isaac Weaver (wisaac) D1963
class SnapCursSelToCenter(Operator):
bl_idname = "view3d.snap_cursor_selected_to_center"
- bl_label = "Snap Cursor & Selection to Center"
+ bl_label = "Snap Cursor & Selection to World Origin"
bl_description = ("Snap 3D cursor and selected objects to the center \n"
"Works only in Object Mode")
diff --git a/system_blend_info.py b/system_blend_info.py
index 8b2a0939..65b1d9f9 100644
--- a/system_blend_info.py
+++ b/system_blend_info.py
@@ -102,10 +102,10 @@ class OBJECT_PT_blendinfo(bpy.types.Panel):
icon='CAMERA_DATA')
row = ob_cols[1].row()
- lamps = [o for o in objects.values() if o.type == 'LAMP']
+ lamps = [o for o in objects.values() if o.type == 'LIGHT']
num = len(lamps)
row.label(text=quantity_string(num, "Lamp", "Lamps"),
- icon='LAMP_DATA')
+ icon='LIGHT_DATA')
row = ob_cols[0].row()
armatures = [o for o in objects.values() if o.type == 'ARMATURE']
@@ -159,9 +159,9 @@ class OBJECT_PT_blendinfo(bpy.types.Panel):
icon='CAMERA_DATA')
row = db_cols[1].row()
- num = len(bpy.data.lamps)
+ num = len(bpy.data.lights)
row.label(text=quantity_string(num, "Lamp", "Lamps"),
- icon='LAMP_DATA')
+ icon='LIGHT_DATA')
row = db_cols[0].row()
num = len(bpy.data.armatures)
diff --git a/system_demo_mode/__init__.py b/system_demo_mode/__init__.py
index 000fcef1..b07da919 100644
--- a/system_demo_mode/__init__.py
+++ b/system_demo_mode/__init__.py
@@ -21,14 +21,15 @@
bl_info = {
"name": "Demo Mode",
"author": "Campbell Barton",
- "blender": (2, 57, 0),
+ "blender": (2, 80, 0),
"location": "Demo Menu",
"description": "Demo mode lets you select multiple blend files and loop over them.",
"warning": "",
"wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/"
"Scripts/System/Demo_Mode#Running_Demo_Mode",
"support": 'OFFICIAL',
- "category": "System"}
+ "category": "System",
+}
# To support reload properly, try to access a package var, if it's there, reload everything
if "bpy" in locals():
@@ -39,12 +40,12 @@ if "bpy" in locals():
import bpy
from bpy.props import (
- StringProperty,
- BoolProperty,
- IntProperty,
- FloatProperty,
- EnumProperty,
- )
+ StringProperty,
+ BoolProperty,
+ IntProperty,
+ FloatProperty,
+ EnumProperty,
+)
class DemoModeSetup(bpy.types.Operator):
@@ -57,85 +58,86 @@ class DemoModeSetup(bpy.types.Operator):
# to the class instance from the operator settings before calling.
# these are used to create the file list.
- directory = StringProperty(
- name="Search Path",
- description="Directory used for importing the file",
- maxlen=1024,
- subtype='DIR_PATH',
- )
- random_order = BoolProperty(
- name="Random Order",
- description="Select files randomly",
- default=False,
- )
- mode = EnumProperty(
- name="Method",
- items=(('AUTO', "Auto", ""),
- ('PLAY', "Play", ""),
- ('RENDER', "Render", ""),
- ),
- )
-
- run = BoolProperty(
- name="Run Immediately!",
- description="Run demo immediately",
- default=True,
- )
- exit = BoolProperty(
- name="Exit",
- description="Run once and exit",
- default=False,
- )
+ directory: StringProperty(
+ name="Search Path",
+ description="Directory used for importing the file",
+ maxlen=1024,
+ subtype='DIR_PATH',
+ )
+ random_order: BoolProperty(
+ name="Random Order",
+ description="Select files randomly",
+ default=False,
+ )
+ mode: EnumProperty(
+ name="Method",
+ items=(
+ ('AUTO', "Auto", ""),
+ ('PLAY', "Play", ""),
+ ('RENDER', "Render", ""),
+ )
+ )
+
+ run: BoolProperty(
+ name="Run Immediately!",
+ description="Run demo immediately",
+ default=True,
+ )
+ exit: BoolProperty(
+ name="Exit",
+ description="Run once and exit",
+ default=False,
+ )
# these are mapped directly to the config!
#
# anim
# ====
- anim_cycles = IntProperty(
- name="Cycles",
- description="Number of times to play the animation",
- min=1, max=1000,
- default=2,
- )
- anim_time_min = FloatProperty(
- name="Time Min",
- description="Minimum number of seconds to show the animation for "
- "(for small loops)",
- min=0.0, max=1000.0,
- soft_min=1.0, soft_max=1000.0,
- default=4.0,
- )
- anim_time_max = FloatProperty(
- name="Time Max",
- description="Maximum number of seconds to show the animation for "
- "(in case the end frame is very high for no reason)",
- min=0.0, max=100000000.0,
- soft_min=1.0, soft_max=100000000.0,
- default=8.0,
- )
- anim_screen_switch = FloatProperty(
- name="Screen Switch",
- description="Time between switching screens (in seconds) "
- "or 0 to disable",
- min=0.0, max=100000000.0,
- soft_min=1.0, soft_max=60.0,
- default=0.0,
- )
+ anim_cycles: IntProperty(
+ name="Cycles",
+ description="Number of times to play the animation",
+ min=1, max=1000,
+ default=2,
+ )
+ anim_time_min: FloatProperty(
+ name="Time Min",
+ description="Minimum number of seconds to show the animation for "
+ "(for small loops)",
+ min=0.0, max=1000.0,
+ soft_min=1.0, soft_max=1000.0,
+ default=4.0,
+ )
+ anim_time_max: FloatProperty(
+ name="Time Max",
+ description="Maximum number of seconds to show the animation for "
+ "(in case the end frame is very high for no reason)",
+ min=0.0, max=100000000.0,
+ soft_min=1.0, soft_max=100000000.0,
+ default=8.0,
+ )
+ anim_screen_switch: FloatProperty(
+ name="Screen Switch",
+ description="Time between switching screens (in seconds) "
+ "or 0 to disable",
+ min=0.0, max=100000000.0,
+ soft_min=1.0, soft_max=60.0,
+ default=0.0,
+ )
#
# render
# ======
- display_render = FloatProperty(
- name="Render Delay",
- description="Time to display the rendered image before moving on "
- "(in seconds)",
- min=0.0, max=60.0,
- default=4.0,
- )
- anim_render = BoolProperty(
- name="Render Anim",
- description="Render entire animation (render mode only)",
- default=False,
- )
+ display_render: FloatProperty(
+ name="Render Delay",
+ description="Time to display the rendered image before moving on "
+ "(in seconds)",
+ min=0.0, max=60.0,
+ default=4.0,
+ )
+ anim_render: BoolProperty(
+ name="Render Anim",
+ description="Render entire animation (render mode only)",
+ default=False,
+ )
def execute(self, context):
from . import config
@@ -168,13 +170,13 @@ class DemoModeSetup(bpy.types.Operator):
layout = self.layout
box = layout.box()
- box.label("Search *.blend recursively")
- box.label("Writes: demo.py config text")
+ box.label(text="Search *.blend recursively")
+ box.label(text="Writes: demo.py config text")
layout.prop(self, "run")
layout.prop(self, "exit")
- layout.label("Generate Settings:")
+ layout.label(text="Generate Settings:")
row = layout.row()
row.prop(self, "mode", expand=True)
layout.prop(self, "random_order")
@@ -184,7 +186,7 @@ class DemoModeSetup(bpy.types.Operator):
layout.separator()
sub = layout.column()
sub.active = (mode in {'AUTO', 'PLAY'})
- sub.label("Animate Settings:")
+ sub.label(text="Animate Settings:")
sub.prop(self, "anim_cycles")
sub.prop(self, "anim_time_min")
sub.prop(self, "anim_time_max")
@@ -193,7 +195,7 @@ class DemoModeSetup(bpy.types.Operator):
layout.separator()
sub = layout.column()
sub.active = (mode in {'AUTO', 'RENDER'})
- sub.label("Render Settings:")
+ sub.label(text="Render Settings:")
sub.prop(self, "display_render")
@@ -244,20 +246,27 @@ def menu_func(self, context):
layout.separator()
+classes = (
+ DemoModeSetup,
+ DemoModeRun,
+)
+
def register():
- bpy.utils.register_class(DemoModeSetup)
- bpy.utils.register_class(DemoModeRun)
+ from bpy.utils import register_class
+ for cls in classes:
+ register_class(cls)
- bpy.types.INFO_MT_file.prepend(menu_func)
+ bpy.types.TOPBAR_MT_file.prepend(menu_func)
extern_demo_mode_register()
def unregister():
- bpy.utils.unregister_class(DemoModeSetup)
- bpy.utils.unregister_class(DemoModeRun)
+ from bpy.utils import unregister_class
+ for cls in classes:
+ unregister_class(cls)
- bpy.types.INFO_MT_file.remove(menu_func)
+ bpy.types.TOPBAR_MT_file.remove(menu_func)
extern_demo_mode_unregister()
diff --git a/system_demo_mode/config.py b/system_demo_mode/config.py
index 24e25be5..b3a7aad4 100644
--- a/system_demo_mode/config.py
+++ b/system_demo_mode/config.py
@@ -65,7 +65,7 @@ def as_string(dirpath, random_order, exit, **kwargs):
"\n",
"exit = %r\n" % exit,
"\n",
- ]
+ ]
# All these work but use nicest formatting!
if 0: # works but not nice to edit.
diff --git a/system_demo_mode/demo_mode.py b/system_demo_mode/demo_mode.py
index 9b94378c..b365fdfc 100644
--- a/system_demo_mode/demo_mode.py
+++ b/system_demo_mode/demo_mode.py
@@ -44,23 +44,26 @@ DEMO_CFG = "demo.py"
# populate from script
global_config_files = []
-global_config = dict(anim_cycles=1,
- anim_render=False,
- anim_screen_switch=0.0,
- anim_time_max=60.0,
- anim_time_min=4.0,
- mode='AUTO',
- display_render=4.0)
+global_config = dict(
+ anim_cycles=1,
+ anim_render=False,
+ anim_screen_switch=0.0,
+ anim_time_max=60.0,
+ anim_time_min=4.0,
+ mode='AUTO',
+ display_render=4.0,
+)
# switch to the next file in 2 sec.
-global_config_fallback = dict(anim_cycles=1,
- anim_render=False,
- anim_screen_switch=0.0,
- anim_time_max=60.0,
- anim_time_min=4.0,
- mode='AUTO',
- display_render=4.0)
-
+global_config_fallback = dict(
+ anim_cycles=1,
+ anim_render=False,
+ anim_screen_switch=0.0,
+ anim_time_max=60.0,
+ anim_time_min=4.0,
+ mode='AUTO',
+ display_render=4.0,
+)
global_state = {
"init_time": 0.0,
@@ -154,7 +157,7 @@ def demo_mode_next_file(step=1):
def demo_mode_timer_add():
- global_state["timer"] = bpy.context.window_manager.event_timer_add(0.8, bpy.context.window)
+ global_state["timer"] = bpy.context.window_manager.event_timer_add(0.8, window=bpy.context.window)
def demo_mode_timer_remove():
@@ -253,9 +256,10 @@ def demo_mode_update():
demo_mode_next_file()
return
# above cycles and minimum display time
- if (time_total > global_config["anim_time_min"]) and \
- (global_state["anim_cycles"] > global_config["anim_cycles"]):
-
+ if (
+ (time_total > global_config["anim_time_min"]) and
+ (global_state["anim_cycles"] > global_config["anim_cycles"])
+ ):
# looped enough now.
demo_mode_next_file()
return
@@ -418,12 +422,12 @@ class DemoModeControl(bpy.types.Operator):
bl_idname = "wm.demo_mode_control"
bl_label = "Control"
- mode = bpy.props.EnumProperty(items=(
- ('PREV', "Prev", ""),
- ('PAUSE', "Pause", ""),
- ('NEXT', "Next", ""),
- ),
- name="Mode")
+ mode: bpy.props.EnumProperty(
+ items=(('PREV', "Prev", ""),
+ ('PAUSE', "Pause", ""),
+ ('NEXT', "Next", "")),
+ name="Mode"
+ )
def execute(self, context):
mode = self.mode
@@ -441,7 +445,7 @@ def menu_func(self, context):
layout = self.layout
layout.operator_context = 'EXEC_DEFAULT'
row = layout.row(align=True)
- row.label("Demo Mode:")
+ row.label(text="Demo Mode:")
if not DemoMode.enabled:
row.operator("wm.demo_mode", icon='PLAY', text="")
else:
diff --git a/system_property_chart.py b/system_property_chart.py
index 028786c9..3fc5ff13 100644
--- a/system_property_chart.py
+++ b/system_property_chart.py
@@ -179,8 +179,8 @@ def _property_chart_draw(self, context):
col.label(text="Properties")
row = col.row(align=True)
row.menu("SCENE_MT_properties_presets", text=bpy.types.SCENE_MT_properties_presets.bl_label)
- row.operator("scene.properties_preset_add", text="", icon="ZOOMIN")
- row.operator("scene.properties_preset_add", text="", icon="ZOOMOUT").remove_active = True
+ row.operator("scene.properties_preset_add", text="", icon='ADD')
+ row.operator("scene.properties_preset_add", text="", icon='REMOVE').remove_active = True
# edit the display props
col.prop(id_storage, self._PROP_STORAGE_ID, text="")
diff --git a/ui_layer_manager.py b/ui_layer_manager.py
index 018aad26..d3e20e69 100644
--- a/ui_layer_manager.py
+++ b/ui_layer_manager.py
@@ -277,11 +277,11 @@ class SCENE_OT_namedlayer_toggle_wire(Operator):
group_layers = scene.layergroups[group_idx].layers
layers = obj.layers
if True in {layer and group_layer for layer, group_layer in zip(layers, group_layers)}:
- obj.draw_type = display
+ obj.display_type = display
scene.layergroups[group_idx].use_wire = use_wire
else:
if obj.layers[layer_idx]:
- obj.draw_type = display
+ obj.display_type = display
scene.namedlayers.layers[layer_idx].use_wire = use_wire
return {'FINISHED'}
@@ -580,8 +580,8 @@ class SCENE_PT_namedlayer_groups(Panel):
row.template_list("SCENE_UL_namedlayer_groups", "", scene, "layergroups", scene, "layergroups_index")
col = row.column(align=True)
- col.operator("scene.namedlayer_group_add", icon='ZOOMIN', text="").layers = scene.layers
- col.operator("scene.namedlayer_group_remove", icon='ZOOMOUT', text="").group_idx = group_idx
+ col.operator("scene.namedlayer_group_add", icon='ADD', text="").layers = scene.layers
+ col.operator("scene.namedlayer_group_remove", icon='REMOVE', text="").group_idx = group_idx
if bool(scene.layergroups):
layout.prop(scene.layergroups[group_idx], "layers", text="", toggle=True)
diff --git a/ui_translate/__init__.py b/ui_translate/__init__.py
index 5ac0db95..4029bae1 100644
--- a/ui_translate/__init__.py
+++ b/ui_translate/__init__.py
@@ -21,11 +21,11 @@
bl_info = {
"name": "Manage UI translations",
"author": "Bastien Montagne",
- "version": (1, 1, 4),
- "blender": (2, 79, 0),
+ "version": (1, 1, 5),
+ "blender": (2, 80, 0),
"location": "Main \"File\" menu, text editor, any UI control",
- "description": "Allow to manage UI translations directly from Blender "
- "(update main po files, update scripts' translations, etc.)",
+ "description": "Allows managing UI translations directly from Blender "
+ "(update main .po files, update scripts' translations, etc.)",
"warning": "Still in development, not all features are fully implemented yet!",
"wiki_url": "http://wiki.blender.org/index.php/Dev:Doc/How_to/Translate_Blender",
"support": 'OFFICIAL',
@@ -42,15 +42,12 @@ if "bpy" in locals():
else:
import bpy
from . import (
- settings,
- edit_translation,
- update_svn,
- update_addon,
- update_ui,
- )
-
-
-import os
+ settings,
+ edit_translation,
+ update_svn,
+ update_addon,
+ update_ui,
+ )
classes = settings.classes + edit_translation.classes + update_svn.classes + update_addon.classes + update_ui.classes
@@ -59,18 +56,21 @@ classes = settings.classes + edit_translation.classes + update_svn.classes + upd
def register():
for cls in classes:
bpy.utils.register_class(cls)
+
bpy.types.WindowManager.i18n_update_svn_settings = \
- bpy.props.PointerProperty(type=update_ui.I18nUpdateTranslationSettings)
+ bpy.props.PointerProperty(type=update_ui.I18nUpdateTranslationSettings)
# Init addon's preferences (unfortunately, as we are using an external storage for the properties,
# the load/save user preferences process has no effect on them :( ).
if __name__ in bpy.context.user_preferences.addons:
pref = bpy.context.user_preferences.addons[__name__].preferences
+ import os
if os.path.isfile(pref.persistent_data_path):
pref._settings.load(pref.persistent_data_path, reset=True)
def unregister():
- del bpy.types.WindowManager.i18n_update_svn_settings
for cls in classes:
bpy.utils.unregister_class(cls)
+
+ del bpy.types.WindowManager.i18n_update_svn_settings
diff --git a/ui_translate/edit_translation.py b/ui_translate/edit_translation.py
index c66e2fac..8fdf676a 100644
--- a/ui_translate/edit_translation.py
+++ b/ui_translate/edit_translation.py
@@ -18,30 +18,24 @@
# <pep8 compliant>
+import os
+import shutil
if "bpy" in locals():
import importlib
importlib.reload(settings)
importlib.reload(utils_i18n)
else:
import bpy
+ from bpy.types import Operator
from bpy.props import (
- BoolProperty,
- CollectionProperty,
- EnumProperty,
- FloatProperty,
- FloatVectorProperty,
- IntProperty,
- PointerProperty,
- StringProperty,
- )
+ BoolProperty,
+ EnumProperty,
+ StringProperty,
+ )
from . import settings
from bl_i18n_utils import utils as utils_i18n
-import os
-import shutil
-
-
# A global cache for I18nMessages objects, as parsing po files takes a few seconds.
PO_CACHE = {}
@@ -52,21 +46,30 @@ def _get_messages(lang, fname):
return PO_CACHE[fname]
-class UI_OT_i18n_edittranslation_update_mo(bpy.types.Operator):
- """Try to "compile" given po file into relevant blender.mo file """ \
- """(WARNING: it will replace the official mo file in your user dir!)"""
+class UI_OT_i18n_edittranslation_update_mo(Operator):
+ """Try to "compile" given po file into relevant blender.mo file
+ (WARNING: it will replace the official mo file in your user dir!)"""
bl_idname = "ui.i18n_edittranslation_update_mo"
bl_label = "Edit Translation Update Mo"
- # "Parameters"
- lang = StringProperty(description="Current (translated) language",
- options={'SKIP_SAVE'})
- po_file = StringProperty(description="Path to the matching po file",
- subtype='FILE_PATH', options={'SKIP_SAVE'})
- clean_mo = BoolProperty(description="Clean up (remove) all local "
- "translation files, to be able to use "
- "all system's ones again",
- default=False, options={'SKIP_SAVE'})
+ # Operator Arguments
+ lang: StringProperty(
+ description="Current (translated) language",
+ options={'SKIP_SAVE'},
+ )
+
+ po_file: StringProperty(
+ description="Path to the matching po file",
+ subtype='FILE_PATH',
+ options={'SKIP_SAVE'},
+ )
+
+ clean_mo: BoolProperty(
+ description="Remove all local translation files, to be able to use the system ones again",
+ default=False,
+ options={'SKIP_SAVE'}
+ )
+ # /End Operator Arguments
def execute(self, context):
if self.clean_mo:
@@ -85,65 +88,169 @@ class UI_OT_i18n_edittranslation_update_mo(bpy.types.Operator):
return {'FINISHED'}
-class UI_OT_i18n_edittranslation(bpy.types.Operator):
- """Translate the label and tooltip of the property defined by given 'parameters'"""
+class UI_OT_i18n_edittranslation(Operator):
+ """Translate the label and tooltip of the given property"""
bl_idname = "ui.edittranslation"
bl_label = "Edit Translation"
- # "Parameters"
- but_label = StringProperty(description="Label of the control", options={'SKIP_SAVE'})
- rna_label = StringProperty(description="RNA-defined label of the control, if any", options={'SKIP_SAVE'})
- enum_label = StringProperty(description="Label of the enum item of the control, if any", options={'SKIP_SAVE'})
- but_tip = StringProperty(description="Tip of the control", options={'SKIP_SAVE'})
- rna_tip = StringProperty(description="RNA-defined tip of the control, if any", options={'SKIP_SAVE'})
- enum_tip = StringProperty(description="Tip of the enum item of the control, if any", options={'SKIP_SAVE'})
- rna_struct = StringProperty(description="Identifier of the RNA struct, if any", options={'SKIP_SAVE'})
- rna_prop = StringProperty(description="Identifier of the RNA property, if any", options={'SKIP_SAVE'})
- rna_enum = StringProperty(description="Identifier of the RNA enum item, if any", options={'SKIP_SAVE'})
- rna_ctxt = StringProperty(description="RNA context for label", options={'SKIP_SAVE'})
-
- lang = StringProperty(description="Current (translated) language", options={'SKIP_SAVE'})
- po_file = StringProperty(description="Path to the matching po file", subtype='FILE_PATH', options={'SKIP_SAVE'})
+ # Operator Arguments
+ but_label: StringProperty(
+ description="Label of the control",
+ options={'SKIP_SAVE'},
+ )
+
+ rna_label: StringProperty(
+ description="RNA-defined label of the control, if any",
+ options={'SKIP_SAVE'},
+ )
+
+ enum_label: StringProperty(
+ description="Label of the enum item of the control, if any",
+ options={'SKIP_SAVE'},
+ )
+
+ but_tip: StringProperty(
+ description="Tip of the control",
+ options={'SKIP_SAVE'},
+ )
+
+ rna_tip: StringProperty(
+ description="RNA-defined tip of the control, if any",
+ options={'SKIP_SAVE'},
+ )
+
+ enum_tip: StringProperty(
+ description="Tip of the enum item of the control, if any",
+ options={'SKIP_SAVE'},
+ )
+
+ rna_struct: StringProperty(
+ description="Identifier of the RNA struct, if any",
+ options={'SKIP_SAVE'},
+ )
+
+ rna_prop: StringProperty(
+ description="Identifier of the RNA property, if any",
+ options={'SKIP_SAVE'},
+ )
+
+ rna_enum: StringProperty(
+ description="Identifier of the RNA enum item, if any",
+ options={'SKIP_SAVE'},
+ )
+
+ rna_ctxt: StringProperty(
+ description="RNA context for label",
+ options={'SKIP_SAVE'},
+ )
+
+ lang: StringProperty(
+ description="Current (translated) language",
+ options={'SKIP_SAVE'},
+ )
+
+ po_file: StringProperty(
+ description="Path to the matching po file",
+ subtype='FILE_PATH',
+ options={'SKIP_SAVE'},
+ )
# Found in po file.
- org_but_label = StringProperty(description="Original label of the control", options={'SKIP_SAVE'})
- org_rna_label = StringProperty(description="Original RNA-defined label of the control, if any",
- options={'SKIP_SAVE'})
- org_enum_label = StringProperty(description="Original label of the enum item of the control, if any",
- options={'SKIP_SAVE'})
- org_but_tip = StringProperty(description="Original tip of the control", options={'SKIP_SAVE'})
- org_rna_tip = StringProperty(description="Original RNA-defined tip of the control, if any", options={'SKIP_SAVE'})
- org_enum_tip = StringProperty(description="Original tip of the enum item of the control, if any",
- options={'SKIP_SAVE'})
-
- flag_items = (('FUZZY', "Fuzzy", "Message is marked as fuzzy in po file"),
- ('ERROR', "Error", "Some error occurred with this message"),
- )
- but_label_flags = EnumProperty(items=flag_items, description="Flags about the label of the button",
- options={'SKIP_SAVE', 'ENUM_FLAG'})
- rna_label_flags = EnumProperty(items=flag_items, description="Flags about the RNA-defined label of the button",
- options={'SKIP_SAVE', 'ENUM_FLAG'})
- enum_label_flags = EnumProperty(items=flag_items, description="Flags about the RNA enum item label of the button",
- options={'SKIP_SAVE', 'ENUM_FLAG'})
- but_tip_flags = EnumProperty(items=flag_items, description="Flags about the tip of the button",
- options={'SKIP_SAVE', 'ENUM_FLAG'})
- rna_tip_flags = EnumProperty(items=flag_items, description="Flags about the RNA-defined tip of the button",
- options={'SKIP_SAVE', 'ENUM_FLAG'})
- enum_tip_flags = EnumProperty(items=flag_items, description="Flags about the RNA enum item tip of the button",
- options={'SKIP_SAVE', 'ENUM_FLAG'})
-
- stats_str = StringProperty(description="Stats from opened po", options={'SKIP_SAVE'})
- update_po = BoolProperty(description="Update po file, try to rebuild mo file, and refresh Blender UI",
- default=False, options={'SKIP_SAVE'})
- update_mo = BoolProperty(description="Try to rebuild mo file, and refresh Blender UI",
- default=False, options={'SKIP_SAVE'})
- clean_mo = BoolProperty(description="Clean up (remove) all local translation files, to be able to use "
- "all system's ones again",
- default=False, options={'SKIP_SAVE'})
+ org_but_label: StringProperty(
+ description="Original label of the control",
+ options={'SKIP_SAVE'},
+ )
+
+ org_rna_label: StringProperty(
+ description="Original RNA-defined label of the control, if any",
+ options={'SKIP_SAVE'},
+ )
+
+ org_enum_label: StringProperty(
+ description="Original label of the enum item of the control, if any",
+ options={'SKIP_SAVE'},
+ )
+
+ org_but_tip: StringProperty(
+ description="Original tip of the control",
+ options={'SKIP_SAVE'},
+ )
+
+ org_rna_tip: StringProperty(
+ description="Original RNA-defined tip of the control, if any", options={'SKIP_SAVE'}
+ )
+
+ org_enum_tip: StringProperty(
+ description="Original tip of the enum item of the control, if any",
+ options={'SKIP_SAVE'},
+ )
+
+ flag_items = (
+ ('FUZZY', "Fuzzy", "Message is marked as fuzzy in po file"),
+ ('ERROR', "Error", "Some error occurred with this message"),
+ )
+
+ but_label_flags: EnumProperty(
+ description="Flags about the label of the button",
+ items=flag_items,
+ options={'SKIP_SAVE', 'ENUM_FLAG'},
+ )
+
+ rna_label_flags: EnumProperty(
+ description="Flags about the RNA-defined label of the button",
+ items=flag_items,
+ options={'SKIP_SAVE', 'ENUM_FLAG'},
+ )
+
+ enum_label_flags: EnumProperty(
+ description="Flags about the RNA enum item label of the button",
+ items=flag_items,
+ options={'SKIP_SAVE', 'ENUM_FLAG'},
+ )
+
+ but_tip_flags: EnumProperty(
+ description="Flags about the tip of the button",
+ items=flag_items,
+ options={'SKIP_SAVE', 'ENUM_FLAG'},
+ )
+
+ rna_tip_flags: EnumProperty(
+ description="Flags about the RNA-defined tip of the button",
+ items=flag_items,
+ options={'SKIP_SAVE', 'ENUM_FLAG'},
+ )
+
+ enum_tip_flags: EnumProperty(
+ description="Flags about the RNA enum item tip of the button",
+ items=flag_items,
+ options={'SKIP_SAVE', 'ENUM_FLAG'},
+ )
+
+ stats_str: StringProperty(
+ description="Stats from opened po", options={'SKIP_SAVE'})
+
+ update_po: BoolProperty(
+ description="Update po file, try to rebuild mo file, and refresh Blender's UI",
+ default=False,
+ options={'SKIP_SAVE'},
+ )
+
+ update_mo: BoolProperty(
+ description="Try to rebuild mo file, and refresh Blender's UI",
+ default=False,
+ options={'SKIP_SAVE'},
+ )
+
+ clean_mo: BoolProperty(
+ description="Remove all local translation files, to be able to use the system ones again",
+ default=False,
+ options={'SKIP_SAVE'},
+ )
+ # /End Operator Arguments
def execute(self, context):
if not hasattr(self, "msgmap"):
- self.report('ERROR', "Looks like you did not invoke this operator first!")
+ self.report('ERROR', "invoke() needs to be called before execute()")
return {'CANCELLED'}
msgs = _get_messages(self.lang, self.po_file)
@@ -152,7 +259,6 @@ class UI_OT_i18n_edittranslation(bpy.types.Operator):
if 'ERROR' in getattr(self, mmap["msg_flags"]):
continue
k = mmap["key"]
-# print(k)
if k not in done_keys and len(k) == 1:
k = tuple(k)[0]
msgs.msgs[k].msgstr = getattr(self, mmap["msgstr"])
@@ -160,7 +266,7 @@ class UI_OT_i18n_edittranslation(bpy.types.Operator):
done_keys.add(k)
if self.update_po:
- # Try to overwrite po file, may fail if we have no good rights...
+ # Try to overwrite .po file, may fail if there are no permissions.
try:
msgs.write(kind='PO', dest=self.po_file)
except Exception as e:
@@ -175,19 +281,20 @@ class UI_OT_i18n_edittranslation(bpy.types.Operator):
return {'FINISHED'}
def invoke(self, context, event):
- self.msgmap = {"but_label": {"msgstr": "but_label", "msgid": "org_but_label",
- "msg_flags": "but_label_flags", "key": set()},
- "rna_label": {"msgstr": "rna_label", "msgid": "org_rna_label",
- "msg_flags": "rna_label_flags", "key": set()},
- "enum_label": {"msgstr": "enum_label", "msgid": "org_enum_label",
- "msg_flags": "enum_label_flags", "key": set()},
- "but_tip": {"msgstr": "but_tip", "msgid": "org_but_tip",
- "msg_flags": "but_tip_flags", "key": set()},
- "rna_tip": {"msgstr": "rna_tip", "msgid": "org_rna_tip",
- "msg_flags": "rna_tip_flags", "key": set()},
- "enum_tip": {"msgstr": "enum_tip", "msgid": "org_enum_tip",
- "msg_flags": "enum_tip_flags", "key": set()},
- }
+ self.msgmap = {
+ "but_label": {
+ "msgstr": "but_label", "msgid": "org_but_label", "msg_flags": "but_label_flags", "key": set()},
+ "rna_label": {
+ "msgstr": "rna_label", "msgid": "org_rna_label", "msg_flags": "rna_label_flags", "key": set()},
+ "enum_label": {
+ "msgstr": "enum_label", "msgid": "org_enum_label", "msg_flags": "enum_label_flags", "key": set()},
+ "but_tip": {
+ "msgstr": "but_tip", "msgid": "org_but_tip", "msg_flags": "but_tip_flags", "key": set()},
+ "rna_tip": {
+ "msgstr": "rna_tip", "msgid": "org_rna_tip", "msg_flags": "rna_tip_flags", "key": set()},
+ "enum_tip": {
+ "msgstr": "enum_tip", "msgid": "org_enum_tip", "msg_flags": "enum_tip_flags", "key": set()},
+ }
msgs = _get_messages(self.lang, self.po_file)
msgs.find_best_messages_matches(self, self.msgmap, self.rna_ctxt, self.rna_struct, self.rna_prop, self.rna_enum)
@@ -229,9 +336,8 @@ class UI_OT_i18n_edittranslation(bpy.types.Operator):
if self.org_but_label or self.org_rna_label or self.org_enum_label:
# XXX Can't use box, labels are not enough readable in them :/
box = layout.box()
- #box = layout
box.label(text="Labels:")
- split = box.split(percentage=0.15)
+ split = box.split(factor=0.15)
col1 = split.column()
col2 = split.column()
if self.org_but_label:
@@ -268,9 +374,8 @@ class UI_OT_i18n_edittranslation(bpy.types.Operator):
if self.org_but_tip or self.org_rna_tip or self.org_enum_tip:
# XXX Can't use box, labels are not enough readable in them :/
box = layout.box()
- #box = layout
box.label(text="Tool Tips:")
- split = box.split(percentage=0.15)
+ split = box.split(factor=0.15)
col1 = split.column()
col2 = split.column()
if self.org_but_tip:
diff --git a/ui_translate/settings.py b/ui_translate/settings.py
index 20f7c82e..97bddbc3 100644
--- a/ui_translate/settings.py
+++ b/ui_translate/settings.py
@@ -18,40 +18,46 @@
# <pep8 compliant>
+import os
+
if "bpy" in locals():
import importlib
importlib.reload(settings_i18n)
else:
import bpy
+ from bpy.types import (
+ Operator,
+ AddonPreferences,
+ )
from bpy.props import (
- BoolProperty,
- CollectionProperty,
- EnumProperty,
- FloatProperty,
- FloatVectorProperty,
- IntProperty,
- PointerProperty,
- StringProperty,
- )
+ BoolProperty,
+ StringProperty,
+ )
from bl_i18n_utils import settings as settings_i18n
-import os
-
-
settings = settings_i18n.I18nSettings()
-class UI_OT_i18n_settings_load(bpy.types.Operator):
+# Operators ###################################################################
+
+class UI_OT_i18n_settings_load(Operator):
"""Load translations' settings from a persistent JSon file"""
bl_idname = "ui.i18n_settings_load"
bl_label = "I18n Load Settings"
bl_option = {'REGISTER'}
- # "Parameters"
- filepath = StringProperty(description="Path to the saved settings file",
- subtype='FILE_PATH')
- filter_glob = StringProperty(default="*.json", options={'HIDDEN'})
+ # Operator Arguments
+ filepath: StringProperty(
+ subtype='FILE_PATH',
+ description="Path to the saved settings file",
+ )
+
+ filter_glob: StringProperty(
+ default="*.json",
+ options={'HIDDEN'}
+ )
+ # /End Operator Arguments
def invoke(self, context, event):
if not self.properties.is_property_set("filepath"):
@@ -67,16 +73,23 @@ class UI_OT_i18n_settings_load(bpy.types.Operator):
return {'FINISHED'}
-class UI_OT_i18n_settings_save(bpy.types.Operator):
+class UI_OT_i18n_settings_save(Operator):
"""Save translations' settings in a persistent JSon file"""
bl_idname = "ui.i18n_settings_save"
bl_label = "I18n Save Settings"
bl_option = {'REGISTER'}
- # "Parameters"
- filepath = StringProperty(description="Path to the saved settings file",
- subtype='FILE_PATH')
- filter_glob = StringProperty(default="*.json", options={'HIDDEN'})
+ # Operator Arguments
+ filepath: StringProperty(
+ description="Path to the saved settings file",
+ subtype='FILE_PATH',
+ )
+
+ filter_glob: StringProperty(
+ default="*.json",
+ options={'HIDDEN'},
+ )
+ # /End Operator Arguments
def invoke(self, context, event):
if not self.properties.is_property_set("filepath"):
@@ -92,18 +105,20 @@ class UI_OT_i18n_settings_save(bpy.types.Operator):
return {'FINISHED'}
+# Addon Preferences ###########################################################
+
def _setattr(self, name, val):
print(self, name, val)
setattr(self, name, val)
-class UI_AP_i18n_settings(bpy.types.AddonPreferences):
+class UI_AP_i18n_settings(AddonPreferences):
bl_idname = __name__.split(".")[0] # We want "top" module name!
bl_option = {'REGISTER'}
_settings = settings
- WARN_MSGID_NOT_CAPITALIZED = BoolProperty(
+ WARN_MSGID_NOT_CAPITALIZED: BoolProperty(
name="Warn Msgid Not Capitalized",
description="Warn about messages not starting by a capitalized letter (with a few allowed exceptions!)",
default=True,
@@ -111,7 +126,7 @@ class UI_AP_i18n_settings(bpy.types.AddonPreferences):
set=lambda self, val: _setattr(self._settings, "WARN_MSGID_NOT_CAPITALIZED", val),
)
- GETTEXT_MSGFMT_EXECUTABLE = StringProperty(
+ GETTEXT_MSGFMT_EXECUTABLE: StringProperty(
name="Gettext 'msgfmt' executable",
description="The gettext msgfmt 'compiler'. You’ll likely have to edit it if you’re under Windows",
subtype='FILE_PATH',
@@ -120,7 +135,7 @@ class UI_AP_i18n_settings(bpy.types.AddonPreferences):
set=lambda self, val: setattr(self._settings, "GETTEXT_MSGFMT_EXECUTABLE", val),
)
- FRIBIDI_LIB = StringProperty(
+ FRIBIDI_LIB: StringProperty(
name="Fribidi Library",
description="The FriBidi C compiled library (.so under Linux, .dll under windows...), you’ll likely have "
"to edit it if you’re under Windows, e.g. using the one included in svn's libraries repository",
@@ -130,7 +145,7 @@ class UI_AP_i18n_settings(bpy.types.AddonPreferences):
set=lambda self, val: setattr(self._settings, "FRIBIDI_LIB", val),
)
- SOURCE_DIR = StringProperty(
+ SOURCE_DIR: StringProperty(
name="Source Root",
description="The Blender source root path",
subtype='FILE_PATH',
@@ -139,7 +154,7 @@ class UI_AP_i18n_settings(bpy.types.AddonPreferences):
set=lambda self, val: setattr(self._settings, "SOURCE_DIR", val),
)
- I18N_DIR = StringProperty(
+ I18N_DIR: StringProperty(
name="Translation Root",
description="The bf-translation repository",
subtype='FILE_PATH',
@@ -148,7 +163,7 @@ class UI_AP_i18n_settings(bpy.types.AddonPreferences):
set=lambda self, val: setattr(self._settings, "I18N_DIR", val),
)
- SPELL_CACHE = StringProperty(
+ SPELL_CACHE: StringProperty(
name="Spell Cache",
description="A cache storing validated msgids, to avoid re-spellchecking them",
subtype='FILE_PATH',
@@ -157,7 +172,7 @@ class UI_AP_i18n_settings(bpy.types.AddonPreferences):
set=lambda self, val: setattr(self._settings, "SPELL_CACHE", val),
)
- PY_SYS_PATHS = StringProperty(
+ PY_SYS_PATHS: StringProperty(
name="Import Paths",
description="Additional paths to add to sys.path (';' separated)",
default="",
@@ -165,7 +180,7 @@ class UI_AP_i18n_settings(bpy.types.AddonPreferences):
set=lambda self, val: setattr(self._settings, "PY_SYS_PATHS", val),
)
- persistent_data_path = StringProperty(
+ persistent_data_path: StringProperty(
name="Persistent Data Path",
description="The name of a json file storing those settings (unfortunately, Blender's system "
"does not work here)",
@@ -187,7 +202,7 @@ class UI_AP_i18n_settings(bpy.types.AddonPreferences):
layout.prop(self, "PY_SYS_PATHS")
layout.separator()
- split = layout.split(0.75)
+ split = layout.split(factor=0.75)
col = split.column()
col.prop(self, "persistent_data_path")
row = col.row()
diff --git a/ui_translate/update_addon.py b/ui_translate/update_addon.py
index bac3ac45..38b0ac8a 100644
--- a/ui_translate/update_addon.py
+++ b/ui_translate/update_addon.py
@@ -25,16 +25,12 @@ if "bpy" in locals():
importlib.reload(bl_extract_messages)
else:
import bpy
+ from bpy.types import Operator
from bpy.props import (
- BoolProperty,
- CollectionProperty,
- EnumProperty,
- FloatProperty,
- FloatVectorProperty,
- IntProperty,
- PointerProperty,
- StringProperty,
- )
+ BoolProperty,
+ EnumProperty,
+ StringProperty,
+ )
from . import settings
from bl_i18n_utils import utils as utils_i18n
from bl_i18n_utils import bl_extract_messages
@@ -49,7 +45,8 @@ import subprocess
import tempfile
-##### Helpers #####
+# Helpers ###################################################################
+
def validate_module(op, context):
module_name = op.module_name
addon = getattr(context, "active_addon", None)
@@ -91,16 +88,27 @@ def enum_addons(self, context):
return _cached_enum_addons
-##### Operators #####
+# Operators ###################################################################
+
# This one is a helper one, as we sometimes need another invoke function (like e.g. file selection)...
-class UI_OT_i18n_addon_translation_invoke(bpy.types.Operator):
+class UI_OT_i18n_addon_translation_invoke(Operator):
"""Wrapper operator which will invoke given op after setting its module_name"""
bl_idname = "ui.i18n_addon_translation_invoke"
bl_label = "Update I18n Add-on"
bl_property = "module_name"
- module_name = EnumProperty(items=enum_addons, name="Add-on", description="Add-on to process", options=set())
- op_id = StringProperty(name="Operator Name", description="Name (id) of the operator to invoke")
+ # Operator Arguments
+ module_name: EnumProperty(
+ name="Add-on",
+ description="Add-on to process",
+ items=enum_addons,
+ options=set(),
+ )
+ op_id: StringProperty(
+ name="Operator Name",
+ description="Name (id) of the operator to invoke",
+ )
+ # /End Operator Arguments
def invoke(self, context, event):
global _cached_enum_addons
@@ -116,17 +124,24 @@ class UI_OT_i18n_addon_translation_invoke(bpy.types.Operator):
op = bpy.ops
for item in self.op_id.split('.'):
op = getattr(op, item, None)
- #print(self.op_id, item, op)
if op is None:
return {'CANCELLED'}
return op('INVOKE_DEFAULT', module_name=self.module_name)
-class UI_OT_i18n_addon_translation_update(bpy.types.Operator):
+
+class UI_OT_i18n_addon_translation_update(Operator):
"""Update given add-on's translation data (found as a py tuple in the add-on's source code)"""
bl_idname = "ui.i18n_addon_translation_update"
bl_label = "Update I18n Add-on"
- module_name = EnumProperty(items=enum_addons, name="Add-on", description="Add-on to process", options=set())
+ # Operator Arguments
+ module_name: EnumProperty(
+ name="Add-on",
+ description="Add-on to process",
+ items=enum_addons,
+ options=set()
+ )
+ # /End Operator Arguments
def execute(self, context):
global _cached_enum_addons
@@ -174,13 +189,23 @@ class UI_OT_i18n_addon_translation_update(bpy.types.Operator):
return {'FINISHED'}
-class UI_OT_i18n_addon_translation_import(bpy.types.Operator):
+class UI_OT_i18n_addon_translation_import(Operator):
"""Import given add-on's translation data from PO files"""
bl_idname = "ui.i18n_addon_translation_import"
bl_label = "I18n Add-on Import"
- module_name = EnumProperty(items=enum_addons, name="Add-on", description="Add-on to process", options=set())
- directory = StringProperty(maxlen=1024, subtype='FILE_PATH', options={'HIDDEN', 'SKIP_SAVE'})
+ # Operator Arguments
+ module_name: EnumProperty(
+ name="Add-on",
+ description="Add-on to process", options=set(),
+ items=enum_addons,
+ )
+
+ directory: StringProperty(
+ subtype='FILE_PATH', maxlen=1024,
+ options={'HIDDEN', 'SKIP_SAVE'}
+ )
+ # /End Operator Arguments
def _dst(self, trans, path, uid, kind):
if kind == 'PO':
@@ -253,16 +278,37 @@ class UI_OT_i18n_addon_translation_import(bpy.types.Operator):
return {'FINISHED'}
-class UI_OT_i18n_addon_translation_export(bpy.types.Operator):
+class UI_OT_i18n_addon_translation_export(Operator):
"""Export given add-on's translation data as PO files"""
+
bl_idname = "ui.i18n_addon_translation_export"
bl_label = "I18n Add-on Export"
- module_name = EnumProperty(items=enum_addons, name="Add-on", description="Add-on to process", options=set())
- use_export_pot = BoolProperty(name="Export POT", default=True, description="Export (generate) a POT file too")
- use_update_existing = BoolProperty(name="Update Existing", default=True,
- description="Update existing po files, if any, instead of overwriting them")
- directory = StringProperty(maxlen=1024, subtype='FILE_PATH', options={'HIDDEN', 'SKIP_SAVE'})
+ # Operator Arguments
+ module_name: EnumProperty(
+ name="Add-on",
+ description="Add-on to process",
+ items=enum_addons,
+ options=set()
+ )
+
+ use_export_pot: BoolProperty(
+ name="Export POT",
+ description="Export (generate) a POT file too",
+ default=True,
+ )
+
+ use_update_existing: BoolProperty(
+ name="Update Existing",
+ description="Update existing po files, if any, instead of overwriting them",
+ default=True,
+ )
+
+ directory: StringProperty(
+ subtype='FILE_PATH', maxlen=1024,
+ options={'HIDDEN', 'SKIP_SAVE'}
+ )
+ # /End Operator Arguments
def _dst(self, trans, path, uid, kind):
if kind == 'PO':
diff --git a/ui_translate/update_svn.py b/ui_translate/update_svn.py
index 81435046..28c1f46a 100644
--- a/ui_translate/update_svn.py
+++ b/ui_translate/update_svn.py
@@ -25,16 +25,11 @@ if "bpy" in locals():
importlib.reload(utils_languages_menu)
else:
import bpy
+ from bpy.types import Operator
from bpy.props import (
- BoolProperty,
- CollectionProperty,
- EnumProperty,
- FloatProperty,
- FloatVectorProperty,
- IntProperty,
- PointerProperty,
- StringProperty,
- )
+ BoolProperty,
+ EnumProperty,
+ )
from . import settings
from bl_i18n_utils import utils as utils_i18n
from bl_i18n_utils import utils_languages_menu
@@ -46,13 +41,20 @@ import subprocess
import tempfile
-##### Operators #####
-class UI_OT_i18n_updatetranslation_svn_branches(bpy.types.Operator):
+# Operators ###################################################################
+
+class UI_OT_i18n_updatetranslation_svn_branches(Operator):
"""Update i18n svn's branches (po files)"""
bl_idname = "ui.i18n_updatetranslation_svn_branches"
bl_label = "Update I18n Branches"
- use_skip_pot_gen = BoolProperty(name="Skip POT", default=False, description="Skip POT file generation")
+ # Operator Arguments
+ use_skip_pot_gen: BoolProperty(
+ name="Skip POT",
+ description="Skip POT file generation",
+ default=False,
+ )
+ # /End Operator Arguments
def execute(self, context):
if not hasattr(self, "settings"):
@@ -108,7 +110,7 @@ class UI_OT_i18n_updatetranslation_svn_branches(bpy.types.Operator):
return wm.invoke_props_dialog(self)
-class UI_OT_i18n_updatetranslation_svn_trunk(bpy.types.Operator):
+class UI_OT_i18n_updatetranslation_svn_trunk(Operator):
"""Update i18n svn's branches (po files)"""
bl_idname = "ui.i18n_updatetranslation_svn_trunk"
bl_label = "Update I18n Trunk"
@@ -166,14 +168,27 @@ class UI_OT_i18n_updatetranslation_svn_trunk(bpy.types.Operator):
return {'FINISHED'}
-class UI_OT_i18n_updatetranslation_svn_statistics(bpy.types.Operator):
- """Create or extend a 'i18n_info.txt' Text datablock containing statistics and checks about """
- """current branches and/or trunk"""
+class UI_OT_i18n_updatetranslation_svn_statistics(Operator):
+ """Create or extend a 'i18n_info.txt' Text datablock
+
+ It will contain statistics and checks about current branches and/or trunk.
+ """
bl_idname = "ui.i18n_updatetranslation_svn_statistics"
bl_label = "Update I18n Statistics"
- use_branches = BoolProperty(name="Check Branches", default=True, description="Check po files in branches")
- use_trunk = BoolProperty(name="Check Trunk", default=False, description="Check po files in trunk")
+ # Operator Arguments
+ use_branches: BoolProperty(
+ name="Check Branches",
+ description="Check po files in branches",
+ default=True,
+ )
+
+ use_trunk: BoolProperty(
+ name="Check Trunk",
+ description="Check po files in trunk",
+ default=False,
+ )
+ # /End Operator Arguments
report_name = "i18n_info.txt"
@@ -220,7 +235,6 @@ class UI_OT_i18n_updatetranslation_svn_statistics(bpy.types.Operator):
return {'FINISHED'}
-
def invoke(self, context, event):
wm = context.window_manager
return wm.invoke_props_dialog(self)
diff --git a/ui_translate/update_ui.py b/ui_translate/update_ui.py
index 91cee4ce..8fb79bef 100644
--- a/ui_translate/update_ui.py
+++ b/ui_translate/update_ui.py
@@ -18,74 +18,137 @@
# <pep8 compliant>
+import os
+
if "bpy" in locals():
import importlib
importlib.reload(settings)
importlib.reload(utils_i18n)
else:
import bpy
+ from bpy.types import (
+ Operator,
+ Panel,
+ PropertyGroup,
+ UIList,
+ )
from bpy.props import (
- BoolProperty,
- CollectionProperty,
- EnumProperty,
- FloatProperty,
- FloatVectorProperty,
- IntProperty,
- PointerProperty,
- StringProperty,
- )
+ BoolProperty,
+ IntProperty,
+ StringProperty,
+ CollectionProperty,
+ )
from . import settings
from bl_i18n_utils import utils as utils_i18n
from bpy.app.translations import pgettext_iface as iface_
-import os
+# Data ########################################################################
-##### Data #####
-class I18nUpdateTranslationLanguage(bpy.types.PropertyGroup):
- """Settings/info about a language"""
- uid = StringProperty(name="Language ID", default="", description="ISO code, like fr_FR")
- num_id = IntProperty(name="Numeric ID", default=0, min=0, description="Numeric ID (readonly!)")
- name = StringProperty(name="Language Name", default="",
- description="English language name/label (like \"French (Français)\")")
- use = BoolProperty(name="Use", default=True, description="Use this language in current operator")
- po_path = StringProperty(name="PO File Path", default="", subtype='FILE_PATH',
- description="Path to the relevant po file in branches")
- po_path_trunk = StringProperty(name="PO Trunk File Path", default="", subtype='FILE_PATH',
- description="Path to the relevant po file in trunk")
- mo_path_trunk = StringProperty(name="MO File Path", default="", subtype='FILE_PATH',
- description="Path to the relevant mo file")
- po_path_git = StringProperty(name="PO Git Master File Path", default="", subtype='FILE_PATH',
- description="Path to the relevant po file in Blender's translations git repository")
-
-
-class I18nUpdateTranslationSettings(bpy.types.PropertyGroup):
+class I18nUpdateTranslationLanguage(PropertyGroup):
+ """Settings/info about a language."""
+
+ uid: StringProperty(
+ name="Language ID",
+ description="ISO code (eg. \"fr_FR\")",
+ default="",
+ )
+
+ num_id: IntProperty(
+ name="Numeric ID",
+ description="Numeric ID (read only!)",
+ default=0, min=0,
+ )
+
+ name: StringProperty(
+ name="Language Name",
+ description="Language label (eg. \"French (Français)\")",
+ default="",
+ )
+
+ use: BoolProperty(
+ name="Use",
+ description="If this language should be used in the current operator",
+ default=True,
+ )
+
+ po_path: StringProperty(
+ name="PO File Path",
+ description="Path to the relevant po file in branches",
+ subtype='FILE_PATH',
+ default="",
+ )
+
+ po_path_trunk: StringProperty(
+ name="PO Trunk File Path",
+ description="Path to the relevant po file in trunk",
+ subtype='FILE_PATH',
+ default="",
+ )
+
+ mo_path_trunk: StringProperty(
+ name="MO File Path",
+ description="Path to the relevant mo file",
+ subtype='FILE_PATH',
+ default="",
+ )
+
+ po_path_git: StringProperty(
+ name="PO Git Master File Path",
+ description="Path to the relevant po file in Blender's translations git repository",
+ subtype='FILE_PATH',
+ default="",
+ )
+
+
+class I18nUpdateTranslationSettings(PropertyGroup):
"""Settings/info about a language"""
- langs = CollectionProperty(name="Languages", type=I18nUpdateTranslationLanguage,
- description="Languages to update in branches")
- active_lang = IntProperty(name="Active Language", default=0,
- description="Index of active language in langs collection")
- pot_path = StringProperty(name="POT File Path", default="", subtype='FILE_PATH',
- description="Path to the pot template file")
- is_init = BoolProperty(default=False, options={'HIDDEN'},
- description="Whether these settings have already been auto-set or not")
-
-
-##### UI #####
-class UI_UL_i18n_languages(bpy.types.UIList):
+
+ langs: CollectionProperty(
+ name="Languages",
+ type=I18nUpdateTranslationLanguage,
+ description="Languages to update in branches",
+ )
+
+ active_lang: IntProperty(
+ name="Active Language",
+ default=0,
+ description="Index of active language in langs collection",
+ )
+
+ pot_path: StringProperty(
+ name="POT File Path",
+ description="Path to the pot template file",
+ subtype='FILE_PATH',
+ default="",
+ )
+
+ is_init: BoolProperty(
+ description="Whether these settings have already been auto-set or not",
+ default=False,
+ options={'HIDDEN'},
+ )
+
+
+# UI ##########################################################################
+
+class UI_UL_i18n_languages(UIList):
+ """ """
+
def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
- #assert(isinstance(item, bpy.types.I18nUpdateTranslationLanguage))
if self.layout_type in {'DEFAULT', 'COMPACT'}:
- layout.label(item.name, icon_value=icon)
+ layout.label(text=item.name, icon_value=icon)
layout.prop(item, "use", text="")
elif self.layout_type in {'GRID'}:
layout.alignment = 'CENTER'
- layout.label(item.uid)
+ layout.label(text=item.uid)
layout.prop(item, "use", text="")
-class UI_PT_i18n_update_translations_settings(bpy.types.Panel):
+class UI_PT_i18n_update_translations_settings(Panel):
+ """ """
+
bl_label = "I18n Update Translation"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
@@ -102,7 +165,7 @@ class UI_PT_i18n_update_translations_settings(bpy.types.Panel):
layout.label(text="Could not init languages data!")
layout.label(text="Please edit the preferences of the UI Translate add-on")
else:
- split = layout.split(0.75)
+ split = layout.split(factor=0.75)
split.template_list("UI_UL_i18n_languages", "", i18n_sett, "langs", i18n_sett, "active_lang", rows=8)
col = split.column()
col.operator("ui.i18n_updatetranslation_svn_init_settings", text="Reset Settings")
@@ -132,7 +195,7 @@ class UI_PT_i18n_update_translations_settings(bpy.types.Panel):
layout.prop(i18n_sett, "pot_path")
layout.separator()
- layout.label("Add-ons:")
+ layout.label(text="Add-ons:")
row = layout.row()
op = row.operator("ui.i18n_addon_translation_invoke", text="Refresh I18n Data...")
op.op_id = "ui.i18n_addon_translation_update"
@@ -142,9 +205,11 @@ class UI_PT_i18n_update_translations_settings(bpy.types.Panel):
op.op_id = "ui.i18n_addon_translation_import"
-##### Operators #####
-class UI_OT_i18n_updatetranslation_svn_init_settings(bpy.types.Operator):
+# Operators ###################################################################
+
+class UI_OT_i18n_updatetranslation_svn_init_settings(Operator):
"""Init settings for i18n svn's update operators"""
+
bl_idname = "ui.i18n_updatetranslation_svn_init_settings"
bl_label = "Init I18n Update Settings"
bl_option = {'REGISTER'}
@@ -200,14 +265,25 @@ class UI_OT_i18n_updatetranslation_svn_init_settings(bpy.types.Operator):
return {'FINISHED'}
-class UI_OT_i18n_updatetranslation_svn_settings_select(bpy.types.Operator):
+class UI_OT_i18n_updatetranslation_svn_settings_select(Operator):
"""(De)select (or invert selection of) all languages for i18n svn's update operators"""
+
bl_idname = "ui.i18n_updatetranslation_svn_settings_select"
bl_label = "Init I18n Update Select Languages"
- use_select = BoolProperty(name="Select All", default=True, description="Select all if True, else deselect all")
- use_invert = BoolProperty(name="Invert Selection", default=False,
- description="Inverse selection (overrides 'Select All' when True)")
+ # Operator Arguments
+ use_select: BoolProperty(
+ name="Select All",
+ description="Select all if True, else deselect all",
+ default=True,
+ )
+
+ use_invert: BoolProperty(
+ name="Invert Selection",
+ description="Inverse selection (overrides 'Select All' when True)",
+ default=False,
+ )
+ # /End Operator Arguments
@classmethod
def poll(cls, context):
diff --git a/uv_texture_atlas.py b/uv_texture_atlas.py
index b655c83c..b3bc1ec6 100644
--- a/uv_texture_atlas.py
+++ b/uv_texture_atlas.py
@@ -51,7 +51,7 @@ def check_all_objects_visible(self, context):
group = scene.ms_lightmap_groups[scene.ms_lightmap_groups_index]
isAllObjectsVisible = True
bpy.ops.object.select_all(action='DESELECT')
- for thisObject in bpy.data.groups[group.name].objects:
+ for thisObject in bpy.data.collections[group.name].objects:
isThisObjectVisible = False
# scene.objects.active = thisObject
for thisLayerNumb in range(20):
@@ -68,7 +68,7 @@ def check_group_exist(self, context, use_report=True):
scene = context.scene
group = scene.ms_lightmap_groups[scene.ms_lightmap_groups_index]
- if group.name in bpy.data.groups:
+ if group.name in bpy.data.collections:
return True
else:
if use_report:
@@ -95,8 +95,8 @@ class TexAtl_Main(Panel):
row.template_list("UI_UL_list", "template_list_controls", scene,
"ms_lightmap_groups", scene, "ms_lightmap_groups_index", rows=2, maxrows=5)
col = row.column(align=True)
- col.operator("scene.ms_add_lightmap_group", icon='ZOOMIN', text="")
- col.operator("scene.ms_del_lightmap_group", icon='ZOOMOUT', text="")
+ col.operator("scene.ms_add_lightmap_group", icon='ADD', text="")
+ col.operator("scene.ms_del_lightmap_group", icon='REMOVE', text="")
row = self.layout.row(align=True)
@@ -127,15 +127,15 @@ class TexAtl_Main(Panel):
self.layout.prop(group, 'unwrap_type', text='Lightmap', expand=True)
row = self.layout.row()
row.operator(
- "object.ms_auto", text="Auto Unwrap", icon="LAMP_SPOT")
+ "object.ms_auto", text="Auto Unwrap", icon="LIGHT_SPOT")
row.prop(group, 'autoUnwrapPrecision', text='')
self.layout.label(text="Manual Unwrap:")
row = self.layout.row()
row.operator(
- "object.ms_run", text="StartManualUnwrap", icon="LAMP_SPOT")
+ "object.ms_run", text="StartManualUnwrap", icon="LIGHT_SPOT")
row.operator(
- "object.ms_run_remove", text="FinishManualUnwrap", icon="LAMP_SPOT")
+ "object.ms_run_remove", text="FinishManualUnwrap", icon="LIGHT_SPOT")
class TexAtl_RunAuto(Operator):
@@ -162,7 +162,7 @@ class TexAtl_RunAuto(Operator):
if bpy.ops.object.mode_set.poll():
bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
- if group.bake is True and bpy.data.groups[group.name].objects:
+ if group.bake is True and bpy.data.collections[group.name].objects:
# Check if objects are all on the visible Layers.
isAllObjVisible = check_all_objects_visible(self, context)
@@ -209,7 +209,7 @@ class TexAtl_RunStart(Operator):
if bpy.ops.object.mode_set.poll():
bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
- if group.bake is True and bpy.data.groups[group.name].objects:
+ if group.bake is True and bpy.data.collections[group.name].objects:
# Check if objects are all on the visible Layers.
isAllObjVisible = check_all_objects_visible(self, context)
@@ -257,7 +257,7 @@ class TexAtl_RunFinish(Operator):
if bpy.ops.object.mode_set.poll():
bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
- if group.bake is True and bpy.data.groups[group.name].objects:
+ if group.bake is True and bpy.data.collections[group.name].objects:
# Check if objects are all on the visible Layers.
isAllObjVisible = check_all_objects_visible(self, context)
@@ -354,9 +354,9 @@ class TexAtl_AddSelectedToGroup(Operator):
scene.ms_lightmap_groups_index].name
# Create a New Group if it was deleted.
- obj_group = bpy.data.groups.get(group_name)
+ obj_group = bpy.data.collections.get(group_name)
if obj_group is None:
- obj_group = bpy.data.groups.new(group_name)
+ obj_group = bpy.data.collections.new(group_name)
# Add objects to a group
if bpy.ops.object.mode_set.poll():
@@ -387,7 +387,7 @@ class TexAtl_SelectGroup(Operator):
bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
bpy.ops.object.select_all(action='DESELECT')
- obj_group = bpy.data.groups[group_name]
+ obj_group = bpy.data.collections[group_name]
for object in obj_group.objects:
object.select = True
return {'FINISHED'}
@@ -415,7 +415,7 @@ class TexAtl_RemoveFromGroup(Operator):
for group in scene.ms_lightmap_groups:
group_name = group.name
- obj_group = bpy.data.groups[group_name]
+ obj_group = bpy.data.collections[group_name]
for object in context.selected_objects:
scene.objects.active = object
@@ -451,7 +451,7 @@ class TexAtl_RemoveOtherUVs(Operator):
bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
# bpy.ops.object.select_all(action='DESELECT')
- obj_group = bpy.data.groups[group_name]
+ obj_group = bpy.data.collections[group_name]
# Remove other UVs of selected objects
for object in context.selected_objects:
@@ -482,7 +482,7 @@ class TexAtl_AddLightmapGroup(Operator):
def execute(self, context):
scene = context.scene
- obj_group = bpy.data.groups.new(self.name)
+ obj_group = bpy.data.collections.new(self.name)
item = scene.ms_lightmap_groups.add()
item.name = obj_group.name
@@ -513,7 +513,7 @@ class TexAtl_DelLightmapGroup(Operator):
group_name = scene.ms_lightmap_groups[idx].name
# Remove Group
- group = bpy.data.groups.get(group_name)
+ group = bpy.data.collections.get(group_name)
if group is not None:
# Unhide Objects if they are hidden
@@ -521,7 +521,7 @@ class TexAtl_DelLightmapGroup(Operator):
obj.hide_render = False
obj.hide = False
- bpy.data.groups.remove(group, do_unlink=True)
+ bpy.data.collections.remove(group, do_unlink=True)
# Remove Lightmap Group
scene.ms_lightmap_groups.remove(scene.ms_lightmap_groups_index)
@@ -553,7 +553,7 @@ class TexAtl_CreateLightmap(Operator):
image.generated_type = 'COLOR_GRID'
image.generated_width = self.resolutionX
image.generated_height = self.resolutionY
- obj_group = bpy.data.groups[self.group_name]
+ obj_group = bpy.data.collections[self.group_name]
# non MESH objects for removal list
NON_MESH_LIST = []
@@ -623,7 +623,7 @@ class TexAtl_MergeObjects(Operator):
# We do the MergeList because we will duplicate grouped objects
mergeList = []
- for object in bpy.data.groups[self.group_name].objects:
+ for object in bpy.data.collections[self.group_name].objects:
mergeList.append(object)
for object in mergeList:
@@ -746,7 +746,7 @@ class TexAtl_SeparateObjects(Operator):
bpy.ops.object.select_all(action='DESELECT')
ob_merged.hide = False
ob_merged.select = True
- groupSeparate = bpy.data.groups.new(ob_merged.name)
+ groupSeparate = bpy.data.collections.new(ob_merged.name)
groupSeparate.objects.link(ob_merged)
ob_merged.select = False