2026-03-11_4

This commit is contained in:
2026-03-17 15:34:28 -06:00
parent 9706bc055f
commit eef5547a2c
474 changed files with 113268 additions and 27500 deletions
+9 -9
View File
@@ -10,13 +10,13 @@ D:\Work\9 iClone\Amazon\
D:\Amazon\00_external-files\
N:\1. CHARACTERS\remapping\
[Recent]
P:\260217_Jarvis-Defense\Assets\CCiC\
P:\260217_Jarvis-Defense\Assets\Blends\Char\
C:\Users\Nathan\Downloads\
C:\Users\Nathan\Desktop\260217_Jarvis-Defense\
C:\Users\Nathan\Desktop\260217_Jarvis-Defense\Blends\animations\
C:\Users\Nathan\AppData\Local\Temp\
C:\Program Files\Blender Foundation\Blender 5.0\5.0\datafiles\assets\nodes\
C:\Users\Nathan\Desktop\260217_Jarvis-Defense\Assets\Blends\Char\
C:\Users\Nathan\Desktop\260217_Jarvis-Defense\Assets\Blends\
C:\Users\Nathan\Desktop\260217_Jarvis-Defense\Assets\Blends\Vehicles\
C:\Users\Nathan\Desktop\260217_Jarvis-Defense\Assets\
C:\Users\Nathan\Desktop\260217_Jarvis-Defense\Assets\Blends\Props\
C:\Users\Nathan\Desktop\
C:\Program Files\Blender Foundation\Blender 5.0\
P:\260217_Jarvis-Defense\Blends\animations\
P:\260217_Jarvis-Defense\Assets\Blends\
C:\Program Files (x86)\Steam\steamapps\common\Blender\5.0\scripts\startup\
F:\renders\
P:\260217_Jarvis-Defense\Assets\Blends\Vehicles\
+635 -1
View File
@@ -6,13 +6,647 @@
"git": "51a41a19"
},
"shared_storage": {
"location": "G:\\Flamenco\\jobs",
"location": "F:\\jobs",
"audience": "users",
"platform": "windows",
"shaman_enabled": true
},
"job_types": {
"job_types": [
{
"name": "BasedPlayblast",
"label": "BasedPlayblast",
"settings": [
{
"key": "frames",
"type": "string",
"description": "Frame range to playblast. Examples: '47', '1-30', '3, 5-10, 47-327'",
"eval": "f'{C.scene.frame_start}-{C.scene.frame_end}'",
"eval_info": {
"show_link_button": true,
"description": "Scene frame range"
},
"required": true
},
{
"key": "chunk_size",
"type": "int32",
"default": 5.0,
"description": "Number of frames to playblast in one Blender task",
"visible": "submission"
},
{
"key": "playblast_output_root",
"type": "string",
"default": "//",
"description": "Base directory of where playblast output is stored. Will have some job-specific parts appended to it",
"required": true,
"subtype": "dir_path",
"visible": "submission"
},
{
"key": "add_path_components",
"type": "int32",
"default": 0.0,
"description": "Number of path components of the current blend file to use in the playblast output path",
"propargs": {
"max": 32.0,
"min": 0.0
},
"required": true,
"visible": "submission"
},
{
"key": "playblast_output_path",
"type": "string",
"description": "Final file path of where playblast output will be saved",
"editable": false,
"eval": "str(Path(abspath(settings.playblast_output_root), last_n_dir_parts(settings.add_path_components), 'blast', jobname, jobname + '_######'))",
"subtype": "file_path"
},
{
"key": "resolution_percentage",
"type": "int32",
"default": 100.0,
"description": "Percentage of the render resolution to use for playblast",
"propargs": {
"max": 100.0,
"min": 1.0
},
"visible": "submission"
},
{
"key": "png_compression",
"type": "int32",
"default": 15.0,
"description": "PNG compression level (0-100, higher = smaller file)",
"propargs": {
"max": 100.0,
"min": 0.0
},
"visible": "submission"
},
{
"key": "keep_frames",
"type": "bool",
"default": false,
"description": "Keep the individual playblast frames after video creation",
"visible": "submission"
},
{
"key": "blendfile",
"type": "string",
"description": "Path of the Blend file to playblast",
"required": true,
"visible": "web"
},
{
"key": "fps",
"type": "float",
"eval": "C.scene.render.fps / C.scene.render.fps_base",
"visible": "hidden"
},
{
"key": "format",
"type": "string",
"default": "PNG",
"description": "Image format for playblast frames",
"required": true,
"visible": "web"
},
{
"key": "image_file_extension",
"type": "string",
"default": ".png",
"description": "File extension used when creating playblast images",
"required": true,
"visible": "hidden"
},
{
"key": "scene",
"type": "string",
"description": "Name of the scene to playblast.",
"eval": "C.scene.name",
"required": true,
"visible": "web"
}
],
"etag": "0cf19bc1871973734e485ebd4477e7688997cc3d",
"description": "Create a viewport preview/playblast and generate a video file"
},
{
"name": "BasedPlayblast-Optix-GPU",
"label": "BasedPlayblast OPTIX GPU",
"settings": [
{
"key": "frames",
"type": "string",
"description": "Frame range to playblast. Examples: '47', '1-30', '3, 5-10, 47-327'",
"eval": "f'{C.scene.frame_start}-{C.scene.frame_end}'",
"eval_info": {
"show_link_button": true,
"description": "Scene frame range"
},
"required": true
},
{
"key": "chunk_size",
"type": "int32",
"default": 5.0,
"description": "Number of frames to playblast in one Blender task",
"visible": "submission"
},
{
"key": "playblast_output_root",
"type": "string",
"default": "//",
"description": "Base directory of where playblast output is stored. Will have some job-specific parts appended to it",
"required": true,
"subtype": "dir_path",
"visible": "submission"
},
{
"key": "add_path_components",
"type": "int32",
"default": 0.0,
"description": "Number of path components of the current blend file to use in the playblast output path",
"propargs": {
"max": 32.0,
"min": 0.0
},
"required": true,
"visible": "submission"
},
{
"key": "playblast_output_path",
"type": "string",
"description": "Final file path of where playblast output will be saved",
"editable": false,
"eval": "str(Path(abspath(settings.playblast_output_root), last_n_dir_parts(settings.add_path_components), 'blast', jobname, jobname + '_######'))",
"subtype": "file_path"
},
{
"key": "resolution_percentage",
"type": "int32",
"default": 100.0,
"description": "Percentage of the render resolution to use for playblast",
"propargs": {
"max": 100.0,
"min": 1.0
},
"visible": "submission"
},
{
"key": "png_compression",
"type": "int32",
"default": 15.0,
"description": "PNG compression level (0-100, higher = smaller file)",
"propargs": {
"max": 100.0,
"min": 0.0
},
"visible": "submission"
},
{
"key": "keep_frames",
"type": "bool",
"default": false,
"description": "Keep the individual playblast frames after video creation",
"visible": "submission"
},
{
"key": "experimental_gp3",
"type": "bool",
"description": "Experimental Flag: Grease Pencil 3",
"label": "Experimental: GPv3",
"required": false
},
{
"key": "experimental_new_anim",
"type": "bool",
"description": "Experimental Flag: New Animation Data-block",
"label": "Experimental: Baklava",
"required": false
},
{
"key": "blender_args_before",
"type": "string",
"description": "CLI arguments for Blender, placed before the .blend filename",
"label": "Blender CLI args: Before",
"required": false
},
{
"key": "blender_args_after",
"type": "string",
"description": "CLI arguments for Blender, placed after the .blend filename",
"label": "After",
"required": false
},
{
"key": "blendfile",
"type": "string",
"description": "Path of the Blend file to playblast",
"required": true,
"visible": "web"
},
{
"key": "fps",
"type": "float",
"eval": "C.scene.render.fps / C.scene.render.fps_base",
"visible": "hidden"
},
{
"key": "format",
"type": "string",
"default": "PNG",
"description": "Image format for playblast frames",
"required": true,
"visible": "web"
},
{
"key": "image_file_extension",
"type": "string",
"default": ".png",
"description": "File extension used when creating playblast images",
"required": true,
"visible": "hidden"
},
{
"key": "scene",
"type": "string",
"description": "Name of the scene to playblast.",
"eval": "C.scene.name",
"required": true,
"visible": "web"
}
],
"etag": "fe38e5ae9b3e640119d0d55fc7cbf4f8f15f2982",
"description": "Create a viewport preview/playblast using Cycles OPTIX GPU rendering and generate a video file"
},
{
"name": "TalkingHeads Custom Render",
"label": "TalkingHeads Custom Render",
"settings": [
{
"key": "frames",
"type": "string",
"description": "Frame range to render. Examples: '47', '1-30', '3, 5-10, 47-327'",
"eval": "f'{C.scene.frame_start}-{C.scene.frame_end}'",
"eval_info": {
"show_link_button": true,
"description": "Scene frame range"
},
"required": true
},
{
"key": "chunk_size",
"type": "int32",
"default": 1.0,
"description": "Number of frames to render in one Blender render task",
"visible": "submission"
},
{
"key": "blendfile",
"type": "string",
"description": "Path of the Blend file to render",
"eval": "bpy.data.filepath",
"required": true,
"visible": "web"
},
{
"key": "render_output_root",
"type": "string",
"description": "Base path where renders are stored, typically the project's Renders folder. If empty, derived automatically.",
"eval": "__import__('os').path.normpath(__import__('os').path.join(((__import__('re').search(r'^(.*?)[\\/][Bb]lends[\\/]', bpy.data.filepath.replace('\\\\','/')) and __import__('re').search(r'^(.*?)[\\/][Bb]lends[\\/]', bpy.data.filepath.replace('\\\\','/')).group(1)) or __import__('os').path.dirname(bpy.data.filepath)), 'Renders'))",
"eval_info": {
"show_link_button": true,
"description": "Auto-detect the project's Renders folder"
},
"required": false,
"subtype": "dir_path",
"visible": "submission"
},
{
"key": "use_submodule",
"type": "bool",
"default": false,
"description": "Include a submodule folder under Renders. Turn off to omit submodule entirely.",
"label": "Use Submodule",
"required": false,
"visible": "submission"
},
{
"key": "submodule",
"type": "string",
"description": "Optional submodule under Renders (e.g. 'Waterspider B'). If empty, omitted.",
"eval": "(__import__('os').path.basename(__import__('os').path.dirname(bpy.data.filepath)) if settings.use_submodule else '')",
"eval_info": {
"show_link_button": true,
"description": "Auto-fill with the current .blend file's parent folder"
},
"required": false,
"visible": "submission"
},
{
"key": "render_output_path",
"type": "string",
"description": "Final file path of where render output will be saved",
"editable": false,
"eval": "((lambda Path, abspath, os_path, settings_obj, jobname: str(Path(abspath(settings_obj.render_output_root or '//')) / (((str(settings_obj.submodule or '').strip()) if (settings_obj.use_submodule and str(settings_obj.submodule or '').strip()) else ((os_path.basename(os_path.dirname(bpy.data.filepath))) if settings_obj.use_submodule else ''))) / jobname / (jobname + '_######')))(__import__('pathlib').Path, __import__('os').path.abspath, __import__('os').path, settings, (job.name if 'job' in dir() and job and job.name else __import__('os').path.splitext(__import__('os').path.basename(bpy.data.filepath))[0])))",
"subtype": "file_path"
},
{
"key": "fps",
"type": "float",
"eval": "C.scene.render.fps / C.scene.render.fps_base",
"visible": "hidden"
},
{
"key": "format",
"type": "string",
"eval": "C.scene.render.image_settings.file_format",
"required": true,
"visible": "web"
},
{
"key": "image_file_extension",
"type": "string",
"description": "File extension used when rendering images",
"eval": "C.scene.render.file_extension",
"required": true,
"visible": "hidden"
},
{
"key": "has_previews",
"type": "bool",
"description": "Whether Blender will render preview images.",
"eval": "C.scene.render.image_settings.use_preview",
"required": false,
"visible": "hidden"
},
{
"key": "scene",
"type": "string",
"description": "Name of the scene to render.",
"eval": "C.scene.name",
"required": true,
"visible": "web"
}
],
"etag": "5f716e7195e55712daee20c237d95f748e2c3d1e",
"description": "Render a sequence of frames, and create a preview video file"
},
{
"name": "TalkingHeads cycles-optix-gpu",
"label": "TalkingHeads Cycles OPTIX GPU",
"settings": [
{
"key": "frames",
"type": "string",
"description": "Frame range to render. Examples: '47', '1-30', '3, 5-10, 47-327'",
"eval": "f'{C.scene.frame_start}-{C.scene.frame_end}'",
"eval_info": {
"show_link_button": true,
"description": "Scene frame range"
},
"required": true
},
{
"key": "chunk_size",
"type": "int32",
"default": 1.0,
"description": "Number of frames to render in one Blender render task",
"visible": "submission"
},
{
"key": "render_output_root",
"type": "string",
"description": "Base path where renders are stored, typically the project's Renders folder. If empty, derived automatically.",
"eval": "__import__('os').path.normpath(__import__('os').path.join(((__import__('re').search(r'^(.*?)[\\/][Bb]lends[\\/]', bpy.data.filepath.replace('\\\\','/')) and __import__('re').search(r'^(.*?)[\\/][Bb]lends[\\/]', bpy.data.filepath.replace('\\\\','/')).group(1)) or __import__('os').path.dirname(bpy.data.filepath)), 'Renders'))",
"eval_info": {
"show_link_button": true,
"description": "Auto-detect the project's Renders folder"
},
"required": false,
"subtype": "dir_path",
"visible": "submission"
},
{
"key": "use_submodule",
"type": "bool",
"default": false,
"description": "Include a submodule folder under Renders. Turn off to omit submodule entirely.",
"label": "Use Submodule",
"required": false,
"visible": "submission"
},
{
"key": "submodule",
"type": "string",
"description": "Optional submodule under Renders (e.g. 'Waterspider B'). If empty, omitted.",
"eval": "(__import__('os').path.basename(__import__('os').path.dirname(bpy.data.filepath)) if settings.use_submodule else '')",
"eval_info": {
"show_link_button": true,
"description": "Auto-fill with the current .blend file's parent folder"
},
"required": false,
"visible": "submission"
},
{
"key": "render_output_path",
"type": "string",
"description": "Final file path of where render output will be saved",
"editable": false,
"eval": "((lambda Path, abspath, os_path, settings_obj, jobname: str(Path(abspath(settings_obj.render_output_root or '//')) / (((str(settings_obj.submodule or '').strip()) if (settings_obj.use_submodule and str(settings_obj.submodule or '').strip()) else ((os_path.basename(os_path.dirname(bpy.data.filepath))) if settings_obj.use_submodule else ''))) / jobname / (jobname + '_######')))(__import__('pathlib').Path, __import__('os').path.abspath, __import__('os').path, settings, (job.name if 'job' in dir() and job and job.name else __import__('os').path.splitext(__import__('os').path.basename(bpy.data.filepath))[0])))",
"subtype": "file_path"
},
{
"key": "experimental_gp3",
"type": "bool",
"description": "Experimental Flag: Grease Pencil 3",
"label": "Experimental: GPv3",
"required": false
},
{
"key": "experimental_new_anim",
"type": "bool",
"description": "Experimental Flag: New Animation Data-block",
"label": "Experimental: Baklava",
"required": false
},
{
"key": "blender_args_before",
"type": "string",
"description": "CLI arguments for Blender, placed before the .blend filename",
"label": "Blender CLI args: Before",
"required": false
},
{
"key": "blender_args_after",
"type": "string",
"description": "CLI arguments for Blender, placed after the .blend filename",
"label": "After",
"required": false
},
{
"key": "blendfile",
"type": "string",
"description": "Path of the Blend file to render",
"eval": "bpy.data.filepath",
"required": true,
"visible": "web"
},
{
"key": "fps",
"type": "float",
"eval": "C.scene.render.fps / C.scene.render.fps_base",
"visible": "hidden"
},
{
"key": "format",
"type": "string",
"eval": "C.scene.render.image_settings.file_format",
"required": true,
"visible": "web"
},
{
"key": "image_file_extension",
"type": "string",
"description": "File extension used when rendering images",
"eval": "C.scene.render.file_extension",
"required": true,
"visible": "hidden"
},
{
"key": "has_previews",
"type": "bool",
"description": "Whether Blender will render preview images.",
"eval": "C.scene.render.image_settings.use_preview",
"required": false,
"visible": "hidden"
}
],
"etag": "758c56b672b0970896f30b85ee5165a5c096aec4",
"description": "OPTIX GPU rendering + extra checkboxes for some experimental features + extra CLI args for Blender"
},
{
"name": "cycles-optix-gpu",
"label": "Cycles OPTIX GPU",
"settings": [
{
"key": "frames",
"type": "string",
"description": "Frame range to render. Examples: '47', '1-30', '3, 5-10, 47-327'",
"eval": "f'{C.scene.frame_start}-{C.scene.frame_end}'",
"eval_info": {
"show_link_button": true,
"description": "Scene frame range"
},
"required": true
},
{
"key": "chunk_size",
"type": "int32",
"default": 1.0,
"description": "Number of frames to render in one Blender render task",
"visible": "submission"
},
{
"key": "render_output_root",
"type": "string",
"description": "Base directory of where render output is stored. Will have some job-specific parts appended to it",
"required": true,
"subtype": "dir_path",
"visible": "submission"
},
{
"key": "add_path_components",
"type": "int32",
"default": 0.0,
"description": "Number of path components of the current blend file to use in the render output path",
"propargs": {
"max": 32.0,
"min": 0.0
},
"required": true,
"visible": "submission"
},
{
"key": "render_output_path",
"type": "string",
"description": "Final file path of where render output will be saved",
"editable": false,
"eval": "str(Path(abspath(settings.render_output_root), last_n_dir_parts(settings.add_path_components), jobname, '{timestamp}', '######'))",
"subtype": "file_path"
},
{
"key": "experimental_gp3",
"type": "bool",
"description": "Experimental Flag: Grease Pencil 3",
"label": "Experimental: GPv3",
"required": false
},
{
"key": "experimental_new_anim",
"type": "bool",
"description": "Experimental Flag: New Animation Data-block",
"label": "Experimental: Baklava",
"required": false
},
{
"key": "blender_args_before",
"type": "string",
"description": "CLI arguments for Blender, placed before the .blend filename",
"label": "Blender CLI args: Before",
"required": false
},
{
"key": "blender_args_after",
"type": "string",
"description": "CLI arguments for Blender, placed after the .blend filename",
"label": "After",
"required": false
},
{
"key": "blendfile",
"type": "string",
"description": "Path of the Blend file to render",
"required": true,
"visible": "web"
},
{
"key": "fps",
"type": "float",
"eval": "C.scene.render.fps / C.scene.render.fps_base",
"visible": "hidden"
},
{
"key": "format",
"type": "string",
"eval": "C.scene.render.image_settings.file_format",
"required": true,
"visible": "web"
},
{
"key": "image_file_extension",
"type": "string",
"description": "File extension used when rendering images",
"eval": "C.scene.render.file_extension",
"required": true,
"visible": "hidden"
},
{
"key": "has_previews",
"type": "bool",
"description": "Whether Blender will render preview images.",
"eval": "C.scene.render.image_settings.use_preview",
"required": false,
"visible": "hidden"
}
],
"etag": "ebed5e28846668474299cd2a8b55fb47b69b54d7",
"description": "OPTIX GPU rendering + extra checkboxes for some experimental features + extra CLI args for Blender"
},
{
"name": "echo-sleep-test",
"label": "Echo Sleep Test",
+2 -3
View File
@@ -12,6 +12,5 @@
{NVIDIA Corporation/NVIDIA GeForce RTX 4080 SUPER/PCIe/SSE2/4.6.0 NVIDIA 581.57}=SUPPORTED
{NVIDIA Corporation/NVIDIA GeForce RTX 4080 SUPER/NVIDIA 591.44}=SUPPORTED
{NVIDIA Corporation/NVIDIA GeForce RTX 4080 SUPER/NVIDIA 591.74}=SUPPORTED
{NVIDIA Corporation/NVIDIA GeForce RTX 3090/NVIDIA 591.86}=SUPPORTED
{NVIDIA Corporation/NVIDIA GeForce RTX 3090/NVIDIA 595.71}=SUPPORTED
{NVIDIA Corporation/NVIDIA GeForce RTX 3090/NVIDIA 595.79}=SUPPORTED
{NVIDIA Corporation/NVIDIA GeForce RTX 4080 SUPER/NVIDIA 595.79}=SUPPORTED
{NVIDIA Corporation/NVIDIA GeForce RTX 4080 SUPER/PCIe/SSE2/4.6.0 NVIDIA 595.79}=SUPPORTED
+33 -29
View File
@@ -1,30 +1,34 @@
C:\Users\Nathan\Downloads\Shot5c\Shot5c.blend
C:\Users\Nathan\Desktop\260217_Jarvis-Defense\Blends\animations\Shot_Q.blend
D:\NSFWAssets\char\Scrag_Boy\SquirrelFucker_v1.2.blend
C:\Users\Nathan\Downloads\Shot_4_g.blend
C:\Users\Nathan\Downloads\Shot_4_e.blend
C:\Users\Nathan\Downloads\Shot_4_d.blend
C:\Users\Nathan\Downloads\Shot_4_b.blend
C:\Users\Nathan\Downloads\Shot_4_f.blend
C:\Users\Nathan\Downloads\Shot_4_a.blend
P:\260217_Jarvis-Defense\Blends\animations\Shot_Q.blend
P:\260217_Jarvis-Defense\Assets\Blends\Char\Chase_v3.0.blend
P:\260217_Jarvis-Defense\Assets\Blends\Char\Russell_v4.0.blend
P:\260217_Jarvis-Defense\Blends\animations\Shot_4.blend
P:\260217_Jarvis-Defense\Assets\Blends\Char\Priest_v3.0.blend
P:\260217_Jarvis-Defense\Assets\Blends\Char\Priest_v2.0.blend
D:\Work\9 iClone\Jarvis Defense\Blender\Dec_v5.blend
C:\Users\Nathan\Desktop\Shot5c.blend
C:\Users\Nathan\Downloads\startup.blend
P:\260217_Jarvis-Defense\Blends\animations\Shot_L.blend
P:\260217_Jarvis-Defense\Assets\Blends\3075 Decade Dr.blend
P:\260217_Jarvis-Defense\Assets\Blends\Vehicles\Ford 250_v2.0.blend
C:\Users\Nathan\Downloads\Shot_4_c.blend
C:\Users\Nathan\Downloads\Shot_4.blend
C:\Users\Nathan\Desktop\260217_Jarvis-Defense\Blends\animations\Shot_4.blend
C:\Users\Nathan\Desktop\260217_Jarvis-Defense\Blends\animations\Jarvis-Defense_L.blend
C:\Users\Nathan\Desktop\260217_Jarvis-Defense\Assets\Blends\Vehicles\Ford 250.blend
C:\Users\Nathan\Desktop\260217_Jarvis-Defense\Blends\animations\shot 2 avoid.blend
C:\Users\Nathan\AppData\Local\Temp\2026-03-07_12-15_Shot_4.blend
C:\Users\Nathan\Desktop\260217_Jarvis-Defense\Blends\animations\erratic_movement_powerwalk.blend
C:\Users\Nathan\Desktop\260217_Jarvis-Defense\Assets\Blends\lipsync.blend
C:\Users\Nathan\Desktop\260217_Jarvis-Defense\Blends\animations\welcome_to_SLC.blend
C:\Users\Nathan\Desktop\260217_Jarvis-Defense\Blends\animations\welcome_to_staten_island.blend
C:\Users\Nathan\Desktop\260217_Jarvis-Defense\Assets\Blends\Char\Priest_v2.0.blend
C:\Users\Nathan\Desktop\260217_Jarvis-Defense\Assets\Blends\Char\Russell_v3.0.blend
C:\Users\Nathan\Desktop\260217_Jarvis-Defense\Assets\Blends\Char\Kayla_Russell_v4_gw.blend
C:\Users\Nathan\Desktop\260217_Jarvis-Defense\Blends\animations\Shot_1.blend
C:\Users\Nathan\Desktop\260217_Jarvis-Defense\Assets\Blends\3075 Decade Dr.blend
C:\Users\Nathan\Desktop\260217_Jarvis-Defense\Blends\animations\mocap_test_driver_v1.blend
C:\Users\Nathan\Desktop\260217_Jarvis-Defense\Assets\Blends\Char\Chase_v2.0.blend
C:\Users\Nathan\Desktop\260217_Jarvis-Defense\Assets\Blends\Char\Russell_v2.0.blend
C:\Users\Nathan\Desktop\260217_Jarvis-Defense\Assets\Blends\Vehicles\Pop Up Camper.blend
C:\Users\Nathan\Desktop\260217_Jarvis-Defense\Assets\Blends\Pop Up Camper.blend
C:\Users\Nathan\Desktop\260217_Jarvis-Defense\Assets\Blends\Props\Ruger Holster.blend
P:\260217_Jarvis-Defense\Blends\animations\shot 3.blend
P:\260217_Jarvis-Defense\Assets\Blends\Vehicles\Ford 250.blend
P:\260217_Jarvis-Defense\Assets\Blends\Vehicles\Ford 250_NL.blend
C:\Users\Nathan\AppData\Local\Temp\2026-03-10_11-52_Ford 250.blend
P:\260217_Jarvis-Defense\Blends\animations\Shot_1.blend
F:\jobs\Shot_jeds_long_take-uwhb\Shot_4.flamenco.blend
P:\260217_Jarvis-Defense\Blends\animations\Shot_2.blend
P:\260217_Jarvis-Defense\Assets\Blends\Char\Chase_v2.0.blend
\\NEXUS\proj\260217_Jarvis-Defense\Assets\Blends\Char\Chase_v3.0.blend
P:\260217_Jarvis-Defense\Assets\Blends\Char\Russell_v3.1.blend
P:\260217_Jarvis-Defense\Assets\Blends\Char\Russell_v3.0.blend
D:\Work\9 iClone\Jarvis Defense\Blender\Kayla_Russell_v4.blend
D:\Work\9 iClone\Jarvis Defense\Blender\Kayla_Russell_dress.blend
P:\260217_Jarvis-Defense\Blends\animations\Jarvis-Defense_L.blend
P:\260217_Jarvis-Defense\Blends\animations\shot 2 avoid.blend
P:\260217_Jarvis-Defense\Assets\Blends\Vehicles\Pop Up Camper.blend
C:\Users\Nathan\SynologyDrive\Desktop\260217_Jarvis-Defense\Blends\animations\Shot_4.blend
P:\260217_Jarvis-Defense\Assets\Blends\Char\Russell_v2.0.blend
P:\260217_Jarvis-Defense\Assets\Blends\Char\Russell_v1.0.blend
C:\Users\Nathan\SynologyDrive\Desktop\260217_Jarvis-Defense\Assets\Blends\Char\Russell_v2.0.blend
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -18,81 +18,45 @@ Created by Manuel Rais and Christophe Seux
along with this program. If not, see <http://www.gnu.org/licenses/>.
'''
bl_info = {
"name": "Bone Widget",
"author": "Manuel Rais, Christophe Seux, Bassam Kurdali, Wayne Dixon, Blender Defender, Max Nadolny, Markus Berg",
"version": (2, 1),
"blender": (4, 1, 0),
"location": "UI > Properties Panel",
"description": "Easily Create Bone Widgets",
"warning": "",
"wiki_url": "",
"tracker_url": "",
"category": "Rigging"}
if "bpy" in locals():
import importlib
bl_class_registry.BlClassRegistry.cleanup()
importlib.reload(prefs)
importlib.reload(operators)
importlib.reload(props)
importlib.reload(panels)
importlib.reload(prefs)
importlib.reload(menus)
else:
import bpy
from . import bl_class_registry
from . import operators
from . import props
from . import panels
from . import prefs
from . import menus
import bpy
def get_user_preferences(context):
if hasattr(context, "user_preferences"):
return context.user_preferences
return context.preferences
def check_version(major, minor, _):
"""
Check blender version
"""
if bpy.app.version[0] == major and bpy.app.version[1] == minor:
return 0
if bpy.app.version[0] > major:
return 1
if bpy.app.version[1] > minor:
return 1
return -1
def register():
operators.register()
props.register()
menus.register()
bl_class_registry.BlClassRegistry.register()
prefs.register()
# Apply preferences of the panel location.
context = bpy.context
pref = get_user_preferences(context).addons[__package__].preferences
# Only default panel location is available in < 2.80
if check_version(2, 80, 0) < 0:
pref.panel_category = "Rigging"
prefs.BoneWidgetPreferences.panel_category_update_fn(pref, context)
prefs.BoneWidget_preferences.panel_category_update_fn(pref, context)
panels.register()
def unregister():
operators.unregister()
props.unregister()
menus.unregister()
# TODO: Unregister by BlClassRegistry
bl_class_registry.BlClassRegistry.unregister()
prefs.unregister()
panels.unregister()
if __name__ == "__main__":
register()
@@ -0,0 +1,34 @@
schema_version = "1.0.0"
id = "bone_widget"
version = "2.3.3"
name = "Bone Widget"
tagline = "Easily Create Bone Widgets"
maintainer = "Wayne Dixon <waylowinternet@gmail.com>"
type = "add-on"
website = "https://github.com/waylow/boneWidget"
tags = ["Rigging"]
blender_version_min = "4.2.0"
license = [
"SPDX:GPL-3.0-or-later",
]
[permissions]
files = "Import/export json and image files from/to disk"
[build]
paths_exclude_pattern = [
"/.git/",
"__pycache__/",
"images/",
".*",
"*.blend",
"*.blend[0123456789]",
"*.md",
"*.zip",
]
@@ -0,0 +1,168 @@
from .props import ImportColorSet
class BoneWidgetImportData:
"""
Tracks import status for BoneWidgets, including successes, failures, and duplicates.
"""
def __init__(self):
self.new_imported_items: int = 0 # Count of new successfully imported items
self.total_num_imports: int = 0 # Total number of attempted imports
self.failed_imports: list[Widget | ColorSet] = []
self.skipped_imports: list[Widget | ColorSet] = []
self.imported_items: list[Widget | ColorSet] = []
self.duplicate_imports: list[Widget | ColorSet] = []
self.import_type: str | None = None # Type of import operation (None if undefined)
self.json_import_error: bool = False # Flag for JSON parsing errors
def imported(self) -> int:
"""Returns the number of newly imported items or total imported items."""
return self.new_imported_items or len(self.imported_items)
def skipped(self) -> int:
"""Returns the number of skipped imports."""
return len(self.skipped_imports)
def failed(self) -> int:
"""Returns the number of failed imports."""
return len(self.failed_imports)
def total(self) -> int:
"""Returns the total number of attempted imports."""
return self.total_num_imports
def reset_imports(self) -> None:
"""Resets import tracking data, clearing skipped, imported, and duplicate items."""
self.skipped_imports = []
self.imported_items = {}
self.duplicate_imports = {}
class Widget:
def __init__(self, name: str, widget_dict: dict):
self._name: str = name if name else "Unnamed Widget"
self._vertices: list[list[float]] = widget_dict.get("vertices", [[]]) # Ensure list structure
self._edges: list[list[int]] = widget_dict.get("edges", [[]])
self._faces: list[list[int]] = widget_dict.get("faces", [[]])
self._image: str = widget_dict.get("image", "") or "user_defined.png"
@property
def name(self) -> str:
"""Returns the widget name."""
return self._name
@name.setter
def name(self, new_name: str) -> None:
self._name = new_name
@property
def vertices(self) -> list[list[float]]:
"""Returns the list of vertices."""
return self._vertices
@property
def edges(self) -> list[list[int]]:
"""Returns the list of edges."""
return self._edges
@property
def faces(self) -> list[list[int]]:
"""Returns the list of faces."""
return self._faces
@property
def image(self) -> str:
"""Returns the image filename."""
return self._image
def __repr__(self):
return f"Widget({self.name})"
def __eq__(self, other: object) -> bool:
if not isinstance(other, Widget):
return False
return (
self.vertices == other.vertices and
self.edges == other.edges and
self.faces == other.faces and
self.image == other.image
)
def to_dict(self) -> dict[str, dict[str, list | str]]:
"""
Returns a dictionary with the widget's name as the key,
and its attributes as the value. Matches the structure of the
original widgets collection.
"""
return {
self.name: {
"vertices": self.vertices,
"edges": self.edges,
"faces": self.faces,
"image": self.image
}
}
class ColorSet:
def __init__(self, color_dict: dict[str, list[float]]):
self._name: str = color_dict.get("name", "Unnamed ColorSet")
self._normal: list[float] = color_dict.get("normal", [])
self._select: list[float] = color_dict.get("select", [])
self._active: list[float] = color_dict.get("active", [])
@property
def name(self) -> str:
"""Returns the color set name."""
return self._name
@name.setter
def name(self, new_name: str) -> None:
"""Sets a new color set name."""
self._name = new_name
@property
def normal(self) -> list[float]:
"""Returns the normal color values."""
return self._normal
@property
def select(self) -> list[float]:
"""Returns the select color values."""
return self._select
@property
def active(self) -> list[float]:
"""Returns the active color values."""
return self._active
def __repr__(self) -> str:
return f"ColorSet({self.name})"
def __eq__(self, other: object) -> bool:
if not isinstance(other, ColorSet):
return False
return (
self.normal == other.normal and
self.select == other.select and
self.active == other.active
)
@classmethod
def from_pg(cls, pg: ImportColorSet) -> "ColorSet":
"""
Creates a ColorSet instance from a Blender PropertyGroup -> ImportColorSet.
Args:
pg (ImportColorSet): The PropertyGroup holding color data.
Returns:
ColorSet: A new instance based on the property group.
"""
return cls({
"name": pg.name,
"normal": list(pg.normal),
"select": list(pg.select),
"active": list(pg.active)
})
@@ -0,0 +1 @@
@@ -0,0 +1,647 @@
import bpy
import os
import json
import numpy
import re
from .main_functions import get_preferences
from ..classes import BoneWidgetImportData, Widget, ColorSet
from .. import __package__
JSON_DEFAULT_WIDGETS = "widgets.json"
JSON_USER_WIDGETS = "user_widgets.json"
JSON_COLOR_PRESETS = "custom_color_sets.json"
widget_data = {}
def get_addon_dir():
return os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
def get_custom_dir():
pref = get_preferences(bpy.context)
if pref.use_default_location:
return bpy.utils.extension_path_user(
package=__package__, path="bone_widget_custom_data", create=True
)
else:
return pref.user_data_location
def get_default_image_dir(image_folder):
return os.path.abspath(os.path.join(get_addon_dir(), image_folder))
def get_custom_image_dir(image_folder):
return os.path.abspath(os.path.join(get_custom_dir(), image_folder))
def get_custom_color_preset_dir():
return os.path.abspath(os.path.join(get_custom_dir(), JSON_COLOR_PRESETS))
def get_widget_directory(file):
if file == JSON_DEFAULT_WIDGETS:
return os.path.join(get_addon_dir(), file)
elif file == JSON_USER_WIDGETS:
return os.path.join(get_custom_dir(), file)
def validate_json_data(data: dict, required_keys: tuple, can_be_empty: bool = True) -> bool:
required_keys = set(required_keys)
if not isinstance(data, dict):
return False
# Check if all required keys are present
if not required_keys.issubset(data.keys()):
return False
if not can_be_empty:
# Check if values are not empty
if any(not data[key] for key in required_keys):
return False
return True
def update_preview_collection():
from .functions.preview_functions import create_preview_collection
create_preview_collection()
def objectDataToDico(object, custom_image):
verts = []
depsgraph = bpy.context.evaluated_depsgraph_get()
mesh = object.evaluated_get(depsgraph).to_mesh()
for v in mesh.vertices:
verts.append(tuple(numpy.array(tuple(v.co)) *
(object.scale[0], object.scale[1], object.scale[2])))
polygons = []
for p in mesh.polygons:
polygons.append(tuple(p.vertices))
edges = []
for e in mesh.edges:
edges.append(e.key)
custom_image = custom_image if custom_image != "" else "user_defined.png"
wgts = {"vertices": verts, "edges": edges,
"faces": polygons, "image": custom_image}
return (wgts)
def read_widgets(filename=""):
global widget_data
wgts = {}
if not filename:
files = [JSON_DEFAULT_WIDGETS, JSON_USER_WIDGETS]
else:
files = [filename]
for file in files:
jsonFile = get_widget_directory(file)
if os.path.exists(jsonFile):
f = open(jsonFile, 'r')
wgts.update(json.load(f))
f.close()
if not filename: # if both files have been read
widget_data = wgts.copy()
return (wgts)
def get_widget_data(widget):
return widget_data[widget]
def write_widgets(wgts, file):
jsonFile = get_widget_directory(file)
# if os.path.exists(jsonFile):
f = open(jsonFile, 'w')
f.write(json.dumps(wgts))
f.close()
def add_remove_widgets(context, addOrRemove, items, widgets, widget_name="", custom_image=""):
wgts = {}
# file from where the widget should be read or written to
file = JSON_USER_WIDGETS
widget_items = []
for widget_item in items:
widget_items.append(widget_item[1])
activeShape = None
ob_name = None
return_message = ""
if addOrRemove == 'add':
wgts = read_widgets(file)
bw_widget_prefix = get_preferences(context).widget_prefix
for ob in widgets:
if not widget_name:
if ob.name.startswith(bw_widget_prefix):
ob_name = ob.name[len(bw_widget_prefix):]
else:
ob_name = ob.name
else:
ob_name = widget_name
if (ob_name) not in widget_items:
widget_items.append(ob_name)
wgts[ob_name] = objectDataToDico(ob, custom_image)
activeShape = ob_name
return_message = "Widget - " + ob_name + " has been added!"
elif addOrRemove == 'remove':
user_widgets = read_widgets(file)
if widgets in user_widgets:
wgts = user_widgets
else:
file = JSON_DEFAULT_WIDGETS
wgts = read_widgets(file)
del wgts[widgets]
if widgets in widget_items:
widget_index = widget_items.index(widgets)
activeShape = widget_items[widget_index +
1] if widget_index == 0 else widget_items[widget_index - 1]
widget_items.remove(widgets)
return_message = "Widget - " + widgets + " has been removed!"
if activeShape is not None:
write_widgets(wgts, file)
# update the preview panel
update_preview_collection()
# trigger an update and display widget
bpy.context.window_manager.widget_list = activeShape
return 'INFO', return_message
elif ob_name is not None:
return 'WARNING', "Widget - " + ob_name + " already exists!"
def export_widget_library(filepath):
wgts = read_widgets(JSON_USER_WIDGETS)
if wgts:
# variables needed for exporting widgets
dest_dir = os.path.dirname(filepath)
json_dir = get_custom_dir()
image_folder = 'custom_thumbnails'
custom_image_dir = get_custom_image_dir(image_folder)
filename = os.path.basename(filepath)
if not filename:
filename = "widget_library.zip"
elif not filename.endswith('.zip'):
filename += ".zip"
# start the zipping process
try:
from zipfile import ZipFile
with ZipFile(os.path.join(dest_dir, filename), "w") as zip:
# write the json file
file = os.path.join(json_dir, JSON_USER_WIDGETS)
arcname = os.path.basename(file)
zip.write(file, arcname=arcname)
# write the custom images if present
if os.path.exists(custom_image_dir):
from pathlib import Path
for filepath in Path(custom_image_dir).iterdir():
arcname = os.path.join(
image_folder, os.path.basename(filepath))
zip.write(filepath, arcname=arcname)
except Exception as e:
print("Error exporting widget library: ", e)
return 0
return len(wgts)
def import_widget_library(filepath, action=""):
required_data_keys = ("vertices", "faces", "edges", "image") # json data
wgts = {}
from zipfile import ZipFile
# dest_dir = os.path.abspath(os.path.join(get_addon_dir(), '..'))
dest_dir = bpy.app.tempdir
widget_import = BoneWidgetImportData()
widget_import.import_type = "widget"
if os.path.exists(filepath) and action:
try:
with ZipFile(filepath, 'r') as zip_file:
# extract images
for file in zip_file.namelist():
if file.startswith('custom_thumbnails/'):
zip_file.extract(file, dest_dir)
elif file.endswith('.json'): # extract data from the .json file
f = zip_file.read(file)
json_data = f.decode('utf8').replace("'", '"')
wgts = json.loads(json_data)
# validate wgts data type
if not isinstance(wgts, dict):
raise TypeError(
f"Expected a dictionary, but got {type(wgts).__name__}")
current_wgts = read_widgets(JSON_USER_WIDGETS)
# check for duplicate names
for name, data in sorted(wgts.items()): # sorting by keys
widget_import.total_num_imports += 1
# validate json data
if not validate_json_data(data, required_data_keys):
widget_import.failed_imports.append(Widget(name, data))
continue
if action == "ASK":
widget_import.skipped_imports.append(Widget(name, data))
elif action == "OVERWRITE":
widget_import.imported_items.append(Widget(name, data))
elif action == "SKIP":
# check for duplicates
data_match = data == current_wgts[name]
if data_match:
widget_import.skipped_imports.append(
Widget(name, data))
# widget_import.duplicate_imports.update({name : data})
elif name not in current_wgts:
widget_import.imported_items.append(Widget(name, data))
else:
widget_import.skipped_imports.append(
Widget(name, data))
else:
widget_import.failed_imports.append(Widget(name, data))
except TypeError as e: # Handle data type errors specifically
print(f"Error while importing widget library: {e}")
widget_import.json_import_error = True
except Exception as e:
print(f"Error while importing widget library: {e}")
for name, data in wgts.items():
widget_import.failed_imports.append(Widget(name, data))
widget_import.total_num_imports = widget_import.failed()
return widget_import
def update_widget_library(new_widgets: dict[str, dict[str, list | str]],
new_images: set[str], zip_filepath: str) -> None:
# store the currently selected widget
current_widget = bpy.context.window_manager.widget_list
wgts = read_widgets(JSON_USER_WIDGETS)
wgts.update(new_widgets)
write_widgets(wgts, JSON_USER_WIDGETS)
# extract any images needed from zip library
if new_images:
from zipfile import ZipFile
dest_dir = get_custom_dir()
if os.path.exists(zip_filepath):
try:
with ZipFile(zip_filepath, 'r') as zip_file:
for file in zip_file.namelist():
if file.startswith('custom_thumbnails/') and file.split("/")[1] in new_images:
zip_file.extract(file, dest_dir)
except Exception as e:
print("Failed to extract custom images: ", e)
else:
print("zip file path doesn't exist!! - ", zip_filepath)
# update the preview panel
update_preview_collection()
# trigger an update and display original but updated widget
bpy.context.window_manager.widget_list = current_widget
def update_custom_image(image_name):
current_widget = bpy.context.window_manager.widget_list
current_widget_data = get_widget_data(current_widget)
# swap out the image
current_widget_data['image'] = image_name
# update and write the new data
wgts = read_widgets(JSON_USER_WIDGETS)
if current_widget in wgts:
wgts[current_widget] = current_widget_data
write_widgets(wgts, JSON_USER_WIDGETS)
else:
wgts = read_widgets(JSON_DEFAULT_WIDGETS)
wgts[current_widget] = current_widget_data
write_widgets(wgts, JSON_DEFAULT_WIDGETS)
# update the preview panel
update_preview_collection()
# trigger an update and display original but updated widget
bpy.context.window_manager.widget_list = current_widget
def reset_default_images():
current_widget = bpy.context.window_manager.widget_list
wgts = read_widgets(JSON_DEFAULT_WIDGETS)
for name, data in wgts.items():
image = f"{name}.png"
data["image"] = image
write_widgets(wgts, JSON_DEFAULT_WIDGETS)
# update the preview panel
update_preview_collection()
# trigger an update and display original but updated widget
bpy.context.window_manager.widget_list = current_widget
################ COLOR PRESETS ################
def read_color_presets():
presets = {}
# Read the JSON file
json_file = get_custom_color_preset_dir()
if os.path.exists(json_file):
with open(json_file, "r") as file:
presets = json.load(file)
presets = {item["name"]: item for item in presets} # convert to dictionary
return presets
def update_color_presets(new_presets, zip_filepath):
for preset in new_presets:
add_color_set(bpy.context, preset)
# extract any images needed from zip library
# if new_images:
# from zipfile import ZipFile
# dest_dir = os.path.abspath(os.path.join(get_addon_dir(), '..'))
# if os.path.exists(zip_filepath):
# try:
# with ZipFile(zip_filepath, 'r') as zip_file:
# for file in zip_file.namelist():
# if file.startswith('custom_thumbnails/') and file.split("/")[1] in new_images:
# zip_file.extract(file, dest_dir)
# except:
# pass
def import_color_presets(filepath, action=""):
required_data_keys = ("name", "normal", "select", "active") # json data
presets = None
from zipfile import ZipFile
dest_dir = get_custom_dir()
presets_import = BoneWidgetImportData()
presets_import.import_type = "colorset"
if os.path.exists(filepath) and action:
try:
with ZipFile(filepath, 'r') as zip_file:
# extract images
for file in zip_file.namelist():
# if file.startswith('preset_thumbnails/'):
# zip_file.extract(file, dest_dir)
if file.endswith('.json'): # extract data from the .json file
f = zip_file.read(file)
json_data = f.decode('utf8').replace("'", '"')
presets = json.loads(json_data)
# validate presets data type
if not isinstance(presets, list):
raise TypeError(
f"Expected a list, but got {type(presets).__name__}")
current_presets = read_color_presets()
# check for duplicate presets
for preset in presets:
presets_import.total_num_imports += 1
# validate json data
if not validate_json_data(preset, required_data_keys, False):
presets_import.failed_imports.append(ColorSet(preset))
continue
name = preset['name']
if action == "ASK":
presets_import.skipped_imports.append(ColorSet(preset))
elif action == "OVERWRITE":
presets_import.imported_items.append(ColorSet(preset))
elif action == "SKIP":
# name and colors match or just colors match
if colors_match(preset, current_presets[name]):
presets_import.skipped_imports.append(ColorSet(preset))
elif not name in current_presets:
presets_import.imported_items.append(ColorSet(preset))
else:
presets_import.skipped_imports.append(ColorSet(preset))
else:
presets_import.failed_imports.append(ColorSet(preset))
except TypeError as e: # Handle data type errors specifically
print(f"Error while importing color presets: {e}")
presets_import.json_import_error = True
except Exception as e:
print(f"Error while importing color presets: {e}")
for preset in presets:
presets_import.failed_imports.append(ColorSet(preset))
presets_import.total_num_imports = presets_import.failed()
return presets_import
def colors_match(set1, set2):
if isinstance(set1, dict):
return set1['normal'] == set2['normal'] \
and set1['select'] == set2['select'] \
and set1['active'] == set2['active']
elif isinstance(set1, bpy.types.ThemeBoneColorSet):
return set1.normal == set2.normal \
and set1.select == set2.select \
and set1.active == set2.active
def scan_armature_color_presets(context, armature):
found_color_sets = set()
colorsets_import = BoneWidgetImportData()
colorsets_import.import_type = "colorset"
current_color_sets = context.window_manager.custom_color_presets
# edit bones
for bone in armature.bones:
if bone.color.is_custom:
is_unique_colorset = True
for color_set in current_color_sets:
if colors_match(bone.color.custom, color_set):
is_unique_colorset = False # not unique
break
color_data = (tuple(bone.color.custom.normal), tuple(
bone.color.custom.select), tuple(bone.color.custom.active))
if is_unique_colorset and not color_data in found_color_sets:
color_set = {attr: list(getattr(bone.color.custom, attr)[:3]) for attr in [
"normal", "active", "select"]}
color_set['name'] = bone.name
colorsets_import.skipped_imports.append(ColorSet(color_set))
found_color_sets.add(color_data)
# pose bones
pose_bone = context.object.pose.bones.get(bone.name)
if pose_bone.color.is_custom:
is_unique_colorset = True
for color_set in current_color_sets:
if colors_match(pose_bone.color.custom, color_set):
is_unique_colorset = False # not unique
break
color_data = (tuple(pose_bone.color.custom.normal), tuple(
pose_bone.color.custom.select), tuple(pose_bone.color.custom.active))
if is_unique_colorset and not color_data in found_color_sets:
color_set = {attr: list(getattr(pose_bone.color.custom, attr)[
:3]) for attr in ["normal", "active", "select"]}
color_set['name'] = bone.name
colorsets_import.skipped_imports.append(ColorSet(color_set))
found_color_sets.add(color_data)
return colorsets_import
def export_color_presets(filepath, context):
color_presets = len(context.window_manager.custom_color_presets)
if color_presets:
dest_dir = os.path.dirname(filepath)
json_dir = get_custom_dir()
# image_folder = 'preset_thumbnails'
# custom_image_dir = get_custom_image_dir(image_folder)
filename = os.path.basename(filepath)
if not filename:
filename = "color_presets.zip"
elif not filename.endswith('.zip'):
filename += ".zip"
# start the zipping process
try:
from zipfile import ZipFile
with ZipFile(os.path.join(dest_dir, filename), "w") as zip:
# write the json file
file = os.path.join(json_dir, JSON_COLOR_PRESETS)
arcname = os.path.basename(file)
zip.write(file, arcname=arcname)
except Exception as e:
print("Error exporting color presets: ", e)
return 0
return color_presets
def add_color_set_from_bone(context, bone, suffix_name):
new_item = context.window_manager.custom_color_presets.add()
color_set = bone.color.custom
new_name = bone.name + suffix_name # CHANGE LATER
# check if the name already ends with an incremented number
match = re.match(r"^(.*)\.(\d{3})$", new_name)
count = int(match.group(2)) if match else 1
base_name = match.group(1) if match else new_name
while any(item.name == new_name for item in context.window_manager.custom_color_presets):
new_name = f"{base_name}.{count:03d}"
count += 1
new_item.name = new_name
if not color_set: # new default color set
new_item.normal = (1.0, 0.0, 0.0)
new_item.select = (0.0, 1.0, 0.0)
new_item.active = (0.0, 0.0, 1.0)
else:
new_item.normal = color_set.normal
new_item.select = color_set.select
new_item.active = color_set.active
def add_color_set(context, color_set=None):
new_item = context.window_manager.custom_color_presets.add()
base_name = "Color Set" if not color_set else color_set.name
new_name = base_name
# check if the name already ends with an incremented number
match = re.match(r"^(.*)\.(\d{3})$", base_name)
count = int(match.group(2)) if match else 1
base_name = match.group(1) if match else new_name
while any(item.name == new_name for item in context.window_manager.custom_color_presets):
new_name = f"{base_name}.{count:03d}"
count += 1
new_item.name = new_name
if not color_set: # new default color set
new_item.normal = (1.0, 0.0, 0.0)
new_item.select = (0.0, 1.0, 0.0)
new_item.active = (0.0, 0.0, 1.0)
else:
new_item.normal = color_set.normal
new_item.select = color_set.select
new_item.active = color_set.active
def save_color_sets(context):
if not bpy.context.window_manager.turn_off_colorset_save:
bpy.context.window_manager.turn_off_colorset_save = True
color_sets = [{
"name": item.name,
"normal": list(item.normal),
"select": list(item.select),
"active": list(item.active)
} for item in context.window_manager.custom_color_presets]
filepath = get_custom_color_preset_dir()
with open(filepath, 'w') as f:
json.dump(color_sets, f, indent=4)
bpy.context.window_manager.turn_off_colorset_save = False
def load_color_presets():
filepath = get_custom_color_preset_dir()
if os.path.exists(filepath):
with open(filepath, 'r') as f:
color_sets = json.load(f)
bpy.context.window_manager.custom_color_presets.clear()
bpy.context.window_manager.turn_off_colorset_save = True
for item in color_sets:
new_item = bpy.context.window_manager.custom_color_presets.add()
new_item.name = item["name"]
new_item.normal = item["normal"]
new_item.select = item["select"]
new_item.active = item["active"]
bpy.context.window_manager.turn_off_colorset_save = False
@@ -0,0 +1,614 @@
import bpy
import numpy
from mathutils import Matrix, Vector
from .. import __package__
def get_collection(context):
# check user preferences for the name of the collection
if not get_preferences(context).use_rigify_defaults:
bw_collection_name = get_preferences(
context).bonewidget_collection_name
else:
bw_collection_name = "WGTS_" + context.active_object.name
collection = recursive_layer_collection(
context.scene.collection, bw_collection_name)
if collection: # if it already exists
return collection
collection = bpy.data.collections.get(bw_collection_name)
if collection: # if it exists but not linked to scene
context.scene.collection.children.link(collection)
return collection
else: # create a new collection
collection = bpy.data.collections.new(bw_collection_name)
context.scene.collection.children.link(collection)
# hide new collection
viewlayer_collection = context.view_layer.layer_collection.children[collection.name]
viewlayer_collection.hide_viewport = True
return collection
def recursive_layer_collection(layer_collection, collection_name):
found = None
if (layer_collection.name == collection_name):
return layer_collection
for layer in layer_collection.children:
found = recursive_layer_collection(layer, collection_name)
if found:
return found
def get_view_layer_collection(context, widget=None):
widget_collection = bpy.data.collections[bpy.data.objects[widget.name].users_collection[0].name]
# save current active layer_collection
saved_layer_collection = bpy.context.view_layer.layer_collection
# actually find the view_layer we want
layer_collection = recursive_layer_collection(
saved_layer_collection, widget_collection.name)
# make sure the collection (data level) is not hidden
widget_collection.hide_viewport = False
# change the active view layer
bpy.context.view_layer.active_layer_collection = layer_collection
# make sure it isn't excluded so it can be edited
layer_collection.exclude = False
# return the active view layer to what it was
bpy.context.view_layer.active_layer_collection = saved_layer_collection
return layer_collection
def match_bone_matrix(widget, match_bone):
if widget == None:
return
widget.matrix_local = match_bone.bone.matrix_local
widget.matrix_world = match_bone.id_data.matrix_world @ match_bone.bone.matrix_local
if match_bone.custom_shape_transform:
# if it has a transform override, apply this to the widget loc and rot
org_scale = widget.matrix_world.to_scale()
org_scale_mat = Matrix.Scale(1, 4, org_scale)
target_matrix = match_bone.custom_shape_transform.id_data.matrix_world @ match_bone.custom_shape_transform.bone.matrix_local
loc = target_matrix.to_translation()
loc_mat = Matrix.Translation(loc)
rot = target_matrix.to_euler().to_matrix()
widget.matrix_world = loc_mat @ rot.to_4x4() @ org_scale_mat
if match_bone.use_custom_shape_bone_size:
ob_scale = bpy.context.scene.objects[match_bone.id_data.name].scale
widget.scale = [match_bone.bone.length * ob_scale[0],
match_bone.bone.length * ob_scale[1], match_bone.bone.length * ob_scale[2]]
# if the user has added any custom transforms to the bone widget display - calculate this too
loc = match_bone.custom_shape_translation
rot = match_bone.custom_shape_rotation_euler
scale = match_bone.custom_shape_scale_xyz
widget.scale *= scale
widget.matrix_world = widget.matrix_world @ Matrix.LocRotScale(
loc, rot, widget.scale)
widget.data.update()
def from_widget_find_bone(widget):
match_bone = None
for ob in bpy.context.scene.objects:
if ob.type == "ARMATURE":
for bone in ob.pose.bones:
if bone.custom_shape == widget:
match_bone = bone
return match_bone
def create_widget(bone, widget, relative, size, slide, rotation, collection, use_face_data, wireframe_width):
if not get_preferences(bpy.context).use_rigify_defaults:
bw_widget_prefix = get_preferences(bpy.context).widget_prefix
else:
bw_widget_prefix = "WGT-" + bpy.context.active_object.name + "_"
matrix_bone = bone
# delete the existing shape
if bone.custom_shape:
bpy.data.objects.remove(
bpy.data.objects[bone.custom_shape.name], do_unlink=True)
# make the data name include the prefix
new_data = bpy.data.meshes.new(bw_widget_prefix + bone.name)
bone.use_custom_shape_bone_size = relative
# deal with face data
faces = widget['faces'] if use_face_data else []
# add the verts
new_data.from_pydata(numpy.array(
widget['vertices']) * size, widget['edges'], faces)
# Create transform matrices (slide vector and rotation)
widget_matrix = Matrix()
# make the slide value always relative to the bone length
if not relative: # TODO: shift this to user preference?
slide = Vector(slide) # turn slide into a vector
slide *= bone.length
trans = Matrix.Translation(slide)
rot = rotation.to_matrix().to_4x4()
# Translate then rotate the matrix
widget_matrix = widget_matrix @ trans
widget_matrix = widget_matrix @ rot
# transform the widget with this matrix
new_data.transform(widget_matrix)
new_data.update(calc_edges=True)
new_object = bpy.data.objects.new(bw_widget_prefix + bone.name, new_data)
new_object.data = new_data
new_object.name = bw_widget_prefix + bone.name
collection.objects.link(new_object)
new_object.matrix_world = bpy.context.active_object.matrix_world @ matrix_bone.bone.matrix_local
new_object.scale = [matrix_bone.bone.length,
matrix_bone.bone.length, matrix_bone.bone.length]
layer = bpy.context.view_layer
layer.update()
bone.custom_shape = new_object
# show faces if use face data is enabled
bone.bone.show_wire = not use_face_data
if bpy.app.version >= (4, 2, 0):
bone.custom_shape_wire_width = wireframe_width
def symmetrize_widget(bone, collection):
if not get_preferences(bpy.context).use_rigify_defaults:
bw_widget_prefix = get_preferences(bpy.context).widget_prefix
rigify_object_name = ''
else:
bw_widget_prefix = "WGT-"
rigify_object_name = bpy.context.active_object.name + "_"
mirror_bone = find_mirror_object(bone)
if not mirror_bone:
return
widget = bone.custom_shape
if not widget or not widget.data:
return
# clean up existing mirrored widget if it's different
mirror_widget = mirror_bone.custom_shape
if mirror_widget and mirror_widget != widget:
existing = bpy.context.scene.objects.get(mirror_widget.name)
if existing:
bpy.data.objects.remove(existing)
# create mirrored mesh data
new_data = widget.data.copy()
for vert in new_data.vertices:
vert.co.x *= -1 # mirror along X-axis
new_object = widget.copy()
new_object.data = new_data
new_object.name = bw_widget_prefix + rigify_object_name + mirror_bone.name
bpy.data.collections[collection.name].objects.link(new_object)
# use custom shape transform if available
transform_bone = mirror_bone.custom_shape_transform or mirror_bone
new_object.matrix_local = transform_bone.bone.matrix_local
new_object.scale = [transform_bone.bone.length] * 3
new_object.data.flip_normals()
bpy.context.view_layer.update()
mirror_bone.custom_shape = new_object
mirror_bone.bone.show_wire = bone.bone.show_wire
mirror_bone.use_custom_shape_bone_size = bone.use_custom_shape_bone_size
symmetrize_color = get_preferences(bpy.context).symmetrize_color
if bpy.app.version >= (4, 0, 0) and symmetrize_color:
# pose bone colors
mirror_bone.bone.color.custom.normal = bone.bone.color.custom.normal
mirror_bone.bone.color.custom.select = bone.bone.color.custom.select
mirror_bone.bone.color.custom.active = bone.bone.color.custom.active
mirror_bone.bone.color.palette = bone.bone.color.palette
# edit bone colors
mirror_bone.color.custom.normal = bone.color.custom.normal
mirror_bone.color.custom.select = bone.color.custom.select
mirror_bone.color.custom.active = bone.color.custom.active
mirror_bone.color.palette = bone.color.palette
if bpy.app.version >= (4, 2, 0):
mirror_bone.custom_shape_wire_width = bone.custom_shape_wire_width
def symmetrize_widget_helper(bone, collection, active_object, widgets_and_bones):
bw_symmetry_suffix = get_preferences(bpy.context).symmetry_suffix
bw_symmetry_suffix = bw_symmetry_suffix.split(";")
suffix_1 = bw_symmetry_suffix[0].replace(" ", "")
suffix_2 = bw_symmetry_suffix[1].replace(" ", "")
if active_object.name.endswith(suffix_1):
if bone.name.endswith(suffix_1) and widgets_and_bones[bone]:
symmetrize_widget(bone, collection)
elif active_object.name.endswith(suffix_2):
if bone.name.endswith(suffix_2) and widgets_and_bones[bone]:
symmetrize_widget(bone, collection)
def delete_unused_widgets():
if not get_preferences(bpy.context).use_rigify_defaults:
bw_collection_name = get_preferences(
bpy.context).bonewidget_collection_name
else:
bw_collection_name = 'WGTS_' + bpy.context.active_object.name
collection = recursive_layer_collection(
bpy.context.scene.collection, bw_collection_name)
widget_list = []
for ob in bpy.data.objects:
if ob.type == 'ARMATURE':
for bone in ob.pose.bones:
if bone.custom_shape:
widget_list.append(bone.custom_shape)
unwanted_list = [
ob for ob in collection.all_objects if ob not in widget_list]
for ob in unwanted_list:
bpy.data.objects.remove(bpy.data.objects[ob.name], do_unlink=True)
return
def edit_widget(active_bone):
widget = active_bone.custom_shape
collection = get_view_layer_collection(bpy.context, widget)
collection.hide_viewport = False
# hide all other objects in collection
for obj in collection.collection.all_objects:
if obj.name != widget.name:
obj.hide_set(True)
else:
obj.hide_set(False) # in case user manually hid it
armature = active_bone.id_data
bpy.ops.object.mode_set(mode='OBJECT')
bpy.context.active_object.select_set(False)
if bpy.context.space_data.local_view:
bpy.ops.view3d.localview()
# select object and make it active
widget.select_set(True)
bpy.context.view_layer.objects.active = widget
bpy.ops.object.mode_set(mode='EDIT')
bpy.context.tool_settings.mesh_select_mode = (
True, False, False) # enter vertex mode
def return_to_armature(widget):
bone = from_widget_find_bone(widget)
armature = bone.id_data
if bpy.context.active_object.mode == 'EDIT':
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.select_all(action='DESELECT')
collection = get_view_layer_collection(bpy.context, widget)
collection.hide_viewport = True
# unhide all objects in the collection
for obj in collection.collection.all_objects:
obj.hide_set(False)
if bpy.context.space_data.local_view:
bpy.ops.view3d.localview()
bpy.context.view_layer.objects.active = armature
armature.select_set(True)
bpy.ops.object.mode_set(mode='POSE')
if bpy.app.version < (5, 0, 0):
armature.data.bones[bone.name].select = True
armature.data.bones.active = armature.data.bones[bone.name]
def find_mirror_object(object):
bw_symmetry_suffix = get_preferences(bpy.context).symmetry_suffix
bw_symmetry_suffix = bw_symmetry_suffix.split(";")
suffix_1 = bw_symmetry_suffix[0].replace(" ", "")
suffix_2 = bw_symmetry_suffix[1].replace(" ", "")
if object.name.endswith(suffix_1):
suffix = suffix_2
suffix_length = len(suffix_1)
elif object.name.endswith(suffix_2):
suffix = suffix_1
suffix_length = len(suffix_2)
elif object.name.endswith(suffix_1.lower()):
suffix = suffix_2.lower()
suffix_length = len(suffix_1)
elif object.name.endswith(suffix_2.lower()):
suffix = suffix_1.lower()
suffix_length = len(suffix_2)
else: # what if the widget ends in .001?
print('Object suffix unknown, using blank')
suffix = ''
object_name = list(object.name)
object_base_name = object_name[:-suffix_length]
mirrored_object_name = "".join(object_base_name) + suffix
if object.id_data.type == 'ARMATURE':
return object.id_data.pose.bones.get(mirrored_object_name)
else:
return bpy.context.scene.objects.get(mirrored_object_name)
def find_match_bones():
bw_symmetry_suffix = get_preferences(bpy.context).symmetry_suffix
bw_symmetry_suffix = bw_symmetry_suffix.split(";")
suffix_1 = bw_symmetry_suffix[0].replace(" ", "")
suffix_2 = bw_symmetry_suffix[1].replace(" ", "")
widgets_and_bones = {}
if bpy.context.object.type == 'ARMATURE':
for bone in bpy.context.selected_pose_bones:
if bone.name.endswith(suffix_1) or bone.name.endswith(suffix_2):
widgets_and_bones[bone] = bone.custom_shape
mirror_bone = find_mirror_object(bone)
if mirror_bone:
widgets_and_bones[mirror_bone] = mirror_bone.custom_shape
armature = bpy.context.object
active_object = bpy.context.active_pose_bone
else:
for shape in bpy.context.selected_objects:
bone = from_widget_find_bone(shape)
if bone.name.endswith(("L", "R")):
widgets_and_bones[from_widget_find_bone(shape)] = shape
mirrorShape = find_mirror_object(shape)
if mirrorShape:
widgets_and_bones[mirrorShape] = mirrorShape
active_object = from_widget_find_bone(bpy.context.object)
armature = active_object.id_data
return (widgets_and_bones, active_object, armature)
def resync_widget_names():
if not get_preferences(bpy.context).use_rigify_defaults:
bw_collection_name = get_preferences(
bpy.context).bonewidget_collection_name
bw_widget_prefix = get_preferences(bpy.context).widget_prefix
else:
bw_collection_name = 'WGTS_' + bpy.context.active_object.name
bw_widget_prefix = 'WGT-' + bpy.context.active_object.name + '_'
widgets_and_bones = {}
if bpy.context.object.type == 'ARMATURE':
for bone in bpy.context.active_object.pose.bones:
if bone.custom_shape:
widgets_and_bones[bone] = bone.custom_shape
for k, v in widgets_and_bones.items():
if k.name != (bw_widget_prefix + k.name):
bpy.data.objects[v.name].name = str(bw_widget_prefix + k.name)
def clear_bone_widgets():
if bpy.context.object.type == 'ARMATURE':
for bone in bpy.context.selected_pose_bones:
if bone.custom_shape:
bone.custom_shape = None
bone.custom_shape_transform = None
def add_object_as_widget(context, collection):
selected_objects = bpy.context.selected_objects
if len(selected_objects) != 2:
print('Only a widget object and the pose bone(s)')
return {'FINISHED'}
allowed_object_types = ['MESH', 'CURVE']
widget_object = None
for ob in selected_objects:
if ob.type in allowed_object_types:
widget_object = ob
if widget_object:
active_bone = context.active_pose_bone
# deal with any existing shape
if active_bone.custom_shape:
bpy.data.objects.remove(
bpy.data.objects[active_bone.custom_shape.name], do_unlink=True)
# duplicate shape
widget = widget_object.copy()
widget.data = widget.data.copy()
# rename it
bw_widget_prefix = get_preferences(context).widget_prefix
widget_name = bw_widget_prefix + active_bone.name
widget.name = widget_name
widget.data.name = widget_name
# link it
collection.objects.link(widget)
# match transforms
widget.matrix_world = bpy.context.active_object.matrix_world @ active_bone.bone.matrix_local
widget.scale = [active_bone.bone.length,
active_bone.bone.length, active_bone.bone.length]
layer = bpy.context.view_layer
layer.update()
active_bone.custom_shape = widget
active_bone.bone.show_wire = True
# deselect original object
widget_object.select_set(False)
def set_bone_color(context, color, clear_both_modes=None):
if context.object.mode == "POSE":
if color == 'DEFAULT' and clear_both_modes != None:
for bone in context.selected_pose_bones:
bone.color.palette = 'DEFAULT'
if clear_both_modes:
bone.bone.color.palette = 'DEFAULT'
return
for bone in context.selected_pose_bones:
bone.color.palette = color # this will get the selected bone color
if color == "CUSTOM":
bone.color.custom.normal = context.scene.bw_settings.custom_pose_color_set.normal
bone.color.custom.select = context.scene.bw_settings.custom_pose_color_set.select
bone.color.custom.active = context.scene.bw_settings.custom_pose_color_set.active
# set the edit bone colors if applicable (while in pose mode)
if get_preferences(context).edit_bone_colors == 'DEFAULT':
bone.bone.color.palette = 'DEFAULT' # this will reset the edit bone color
elif get_preferences(context).edit_bone_colors == 'LINKED':
bone.bone.color.palette = color # set the edit bone colors
# Set the custom color to edit bones (if applicable)
if color == "CUSTOM":
bone.bone.color.custom.normal = context.scene.bw_settings.custom_pose_color_set.normal
bone.bone.color.custom.select = context.scene.bw_settings.custom_pose_color_set.select
bone.bone.color.custom.active = context.scene.bw_settings.custom_pose_color_set.active
elif context.object.mode == "EDIT":
if color == 'DEFAULT' and clear_both_modes != None:
for edit_bone in context.selected_bones:
edit_bone.color.palette = 'DEFAULT'
if clear_both_modes:
pose_bone = context.object.pose.bones.get(edit_bone.name)
pose_bone.color.palette = 'DEFAULT'
return
for edit_bone in context.selected_bones:
if get_preferences(context).edit_bone_colors == 'DEFAULT':
# this will get the edit bone color back to default
edit_bone.color.palette = 'DEFAULT'
elif get_preferences(context).edit_bone_colors == 'LINKED':
edit_bone.color.palette = color # set the edit mode color
# get the pose bone
pose_bone = context.object.pose.bones.get(edit_bone.name)
pose_bone.color.palette = color # set the pose mode color
if color == "CUSTOM":
# set edit bone custom colors
edit_bone.color.custom.normal = context.scene.bw_settings.custom_edit_color_set.normal
edit_bone.color.custom.select = context.scene.bw_settings.custom_edit_color_set.select
edit_bone.color.custom.active = context.scene.bw_settings.custom_edit_color_set.active
# set pose bone custom colors
pose_bone.color.custom.normal = context.scene.bw_settings.custom_edit_color_set.normal
pose_bone.color.custom.select = context.scene.bw_settings.custom_edit_color_set.select
pose_bone.color.custom.active = context.scene.bw_settings.custom_edit_color_set.active
elif get_preferences(context).edit_bone_colors == 'SEPARATE':
edit_bone.color.palette = color # set the edit mode color
if color == "CUSTOM":
# set edit bone custom colors
edit_bone.color.custom.normal = context.scene.bw_settings.custom_edit_color_set.normal
edit_bone.color.custom.select = context.scene.bw_settings.custom_edit_color_set.select
edit_bone.color.custom.active = context.scene.bw_settings.custom_edit_color_set.active
def copy_bone_color(context, bone):
live_update_current_state = context.scene.bw_settings.live_update_on
context.scene.bw_settings.live_update_on = False
if bone.color.is_custom:
if context.object.mode == 'POSE':
context.scene.bw_settings.custom_pose_color_set.normal = bone.color.custom.normal
context.scene.bw_settings.custom_pose_color_set.select = bone.color.custom.select
context.scene.bw_settings.custom_pose_color_set.active = bone.color.custom.active
else:
context.scene.bw_settings.custom_edit_color_set.normal = bone.color.custom.normal
context.scene.bw_settings.custom_edit_color_set.select = bone.color.custom.select
context.scene.bw_settings.custom_edit_color_set.active = bone.color.custom.active
elif bone.color.palette != "DEFAULT": # bone has a theme assigned
theme = bone.color.palette
theme_id = int(theme[-2:]) - 1
theme_color_set = bpy.context.preferences.themes[0].bone_color_sets[theme_id]
palette = context.scene.bw_settings.custom_pose_color_set if context.object.mode == 'POSE' \
else context.scene.bw_settings.custom_edit_color_set
palette.normal = theme_color_set.normal
palette.select = theme_color_set.select
palette.active = theme_color_set.active
context.scene.bw_settings.live_update_on = live_update_current_state
def update_bone_color(self, context):
if context.scene.bw_settings.live_update_on:
set_bone_color(context, "CUSTOM")
def advanced_options_toggled(self, context):
if self.advanced_options:
self.global_size_advanced = (self.global_size_simple,) * 3
self.slide_advanced[1] = self.slide_simple
else:
self.global_size_simple = self.global_size_advanced[1]
self.slide_simple = self.slide_advanced[1]
def bone_color_items(self, context):
items = [("DEFAULT", "Default Colors", "", "", 0)]
for i in range(1, 16):
items.append((f"THEME{i:02}", f"Theme {i:02}",
"", f"COLORSET_{i:02}_VEC", i))
return items
def bone_color_items_short(self, context):
items = []
for i in range(1, 16):
items.append((f"THEME{i:02}", f"Theme {i:02}",
"", f"COLORSET_{i:02}_VEC", i))
items.append(("CUSTOM", "Custom", "", "COLOR", 16))
return items
def live_update_toggle(self, context):
context.scene.bw_settings.live_update_on = self.live_update_toggle
def get_preferences(context):
return context.preferences.addons[__package__].preferences
@@ -0,0 +1,301 @@
import bpy
import bpy.utils.previews
from .json_functions import read_widgets, get_widget_data, get_default_image_dir, get_custom_image_dir, JSON_USER_WIDGETS
import os
from .. import __package__
from mathutils import Vector
preview_collections = {}
def create_preview_collection():
if preview_collections:
del bpy.types.WindowManager.widget_list
for pcoll in preview_collections.values():
bpy.utils.previews.remove(pcoll)
preview_collections.clear()
pcoll = bpy.utils.previews.new()
pcoll.widget_list = ()
preview_collections["widgets"] = pcoll
bpy.types.WindowManager.widget_list = bpy.props.EnumProperty(
items=generate_previews(), name="Shape", description="Shape", update=preview_update
)
def generate_previews():
enum_items = []
pcoll = preview_collections["widgets"]
if pcoll.widget_list:
return pcoll.widget_list
directory = get_default_image_dir('thumbnails')
custom_directory = get_custom_image_dir("custom_thumbnails")
if directory and os.path.exists(directory):
widget_data = {item[0]: item[1].get(
"image", "missing_image.png") for item in read_widgets().items()}
widget_names = sorted(widget_data.keys())
for i, name in enumerate(widget_names):
image = widget_data.get(name, "")
if image is not None:
filepath = os.path.join(directory, image)
# try in custom_thumbnails if above failed
if not os.path.exists(filepath):
filepath = os.path.join(custom_directory, image)
# if image still not found, let the user know
if not os.path.exists(filepath):
filepath = os.path.join(directory, "missing_image.png")
icon = pcoll.get(name)
if not icon:
thumb = pcoll.load(name, filepath, 'IMAGE')
else:
thumb = pcoll[name]
face_data_info = "Contains Face Data" if get_widget_data(
name).get("faces") else ""
enum_items.append((name, name, face_data_info, thumb.icon_id, i))
pcoll.widget_list = enum_items
return enum_items
def preview_update(self, context):
generate_previews()
def get_preview_default():
return bpy.context.preferences.addons[__package__].preferences.preview_default
def copy_custom_image(filepath, filename):
if os.path.exists(filepath):
image_directory = get_custom_image_dir('custom_thumbnails')
destination_path = os.path.join(image_directory, filename)
try:
# create custom thumbnail folder if not existing
if not os.path.exists(image_directory):
os.makedirs(image_directory)
import shutil
shutil.copyfile(filepath, destination_path)
return True
except:
pass
return False
def remove_custom_image(filename):
image_directory = get_custom_image_dir('custom_thumbnails')
destination_path = os.path.join(image_directory, filename)
if os.path.isfile(destination_path):
# make sure the image is only used once - else stop
count = 0
for v in read_widgets(JSON_USER_WIDGETS).values():
if v.get("image") == filename:
count += 1
if count > 1:
return False
try:
os.remove(destination_path)
return True
except:
pass
return False
#### Thumbnail Render Functions ####
def create_wireframe_copy(obj, use_color, color, thickness):
copy = obj.copy()
copy.data = obj.data.copy()
if not use_color:
copy.color = color
# Create a new Geometry Nodes modifier
geo_mod = copy.modifiers.new(name="BoneWidget_WireFrame", type='NODES')
# Create a new node group and assign it to the modifier
node_group = bpy.data.node_groups.new(
name="BONEWIDGET_GeometryGroup", type='GeometryNodeTree')
geo_mod.node_group = node_group
# Add input and output sockets
node_group.interface.new_socket(
name="Geometry", in_out="INPUT", socket_type="NodeSocketGeometry")
node_group.interface.new_socket(
name="Geometry", in_out="OUTPUT", socket_type="NodeSocketGeometry")
# Add Thickness input
thickness_socket = node_group.interface.new_socket(
name="Thickness", in_out="INPUT", socket_type="NodeSocketFloat")
thickness_socket.default_value = 0.5
thickness_socket.min_value = 0.01
thickness_socket.max_value = 2
# Create nodes
node_input = node_group.nodes.new('NodeGroupInput')
node_output = node_group.nodes.new('NodeGroupOutput')
node_uv_sphere = node_group.nodes.new('GeometryNodeMeshUVSphere')
node_mesh_to_curve = node_group.nodes.new('GeometryNodeMeshToCurve')
node_curve_circle = node_group.nodes.new(
'GeometryNodeCurvePrimitiveCircle')
node_instance_on_points = node_group.nodes.new(
'GeometryNodeInstanceOnPoints')
node_curve_to_mesh = node_group.nodes.new('GeometryNodeCurveToMesh')
node_join_geometry = node_group.nodes.new('GeometryNodeJoinGeometry')
# Set initial values (internal)
node_uv_sphere.inputs["Segments"].default_value = 8
node_uv_sphere.inputs["Rings"].default_value = 8
node_curve_circle.inputs["Resolution"].default_value = 8
# Position nodes for better visualization (optional)
node_input.location = (-400, 0)
node_uv_sphere.location = (-150, 100)
node_mesh_to_curve.location = (-150, -50)
node_curve_circle.location = (-150, -150)
node_instance_on_points.location = (100, 250)
node_curve_to_mesh.location = (100, -100)
node_join_geometry.location = (350, 0)
node_output.location = (550, 0)
# Connect nodes
node_group.links.new(
node_input.outputs["Geometry"], node_instance_on_points.inputs["Points"])
node_group.links.new(
node_input.outputs["Geometry"], node_mesh_to_curve.inputs["Mesh"])
node_group.links.new(
node_input.outputs["Thickness"], node_uv_sphere.inputs["Radius"])
node_group.links.new(
node_input.outputs["Thickness"], node_curve_circle.inputs["Radius"])
node_group.links.new(
node_uv_sphere.outputs["Mesh"], node_instance_on_points.inputs["Instance"])
node_group.links.new(
node_mesh_to_curve.outputs["Curve"], node_curve_to_mesh.inputs["Curve"])
node_group.links.new(
node_curve_circle.outputs["Curve"], node_curve_to_mesh.inputs["Profile Curve"])
node_group.links.new(
node_instance_on_points.outputs["Instances"], node_join_geometry.inputs["Geometry"])
node_group.links.new(
node_curve_to_mesh.outputs["Mesh"], node_join_geometry.inputs["Geometry"])
node_group.links.new(
node_join_geometry.outputs["Geometry"], node_output.inputs["Geometry"])
# scale this so it isn't so sensitive
geo_mod["Socket_2"] = (thickness / 10)
return copy
def setup_viewport(context):
area = context.area
space = context.space_data
region_3d = space.region_3d
original_view_matrix = region_3d.view_matrix.copy()
bpy.ops.view3d.view_selected()
return original_view_matrix
def restore_viewport_position(context, view_matrix, view_perspective):
if context.space_data.type == 'VIEW_3D':
region_3d = context.space_data.region_3d
# Restore viewport matrix position
region_3d.view_matrix = view_matrix
# Restore viewport perspective
region_3d.view_perspective = view_perspective
def render_widget_thumbnail(image_name, widget_object, image_directory):
if image_directory: # If True save to the current directory but...
if bpy.data.filepath: # Check the file has been saved
image_directory = os.path.dirname(bpy.data.filepath)
else:
# Fall back if it hasn't been saved
image_directory = os.path.expanduser("~")
# add '.png' to the name
image_name = image_name + '.png'
else: # if False use the add-on location
image_directory = get_custom_image_dir('custom_thumbnails')
destination_path = os.path.join(image_directory, image_name)
scene = bpy.context.scene
scene.render.engine = 'BLENDER_WORKBENCH'
scene.render.resolution_x, scene.render.resolution_y = (512, 512)
scene.render.resolution_percentage = 100
scene.render.image_settings.file_format = 'PNG'
scene.render.image_settings.color_mode = 'RGBA'
scene.view_settings.view_transform = 'Standard'
scene.render.film_transparent = True
scene.display.shading.light = 'FLAT'
scene.display.shading.color_type = 'OBJECT'
scene.render.filepath = image_directory
# Reframe Camera
camera = scene.camera
obj = widget_object
frame_object_with_padding(camera, obj, padding=0.1)
bpy.ops.render.render(write_still=False)
bpy.data.images['Render Result'].save_render(
filepath=bpy.path.abspath(destination_path))
return bpy.path.abspath(destination_path)
def add_camera_from_view(context):
name = "BoneWidget_Thumbnail_Camera"
region_3d = context.region_data
space = context.space_data
if region_3d is None or space.type != 'VIEW_3D':
print("This must be run from a 3D Viewport.")
return None
# Create camera data and object
cam_data = bpy.data.cameras.new(name)
cam_obj = bpy.data.objects.new(name, cam_data)
context.scene.collection.objects.link(cam_obj)
# Align camera to current viewport
cam_obj.matrix_world = region_3d.view_matrix.inverted()
# Make it the active camera
context.scene.camera = cam_obj
return cam_obj
def frame_object_with_padding(camera, obj, padding=0.1):
depsgraph = bpy.context.evaluated_depsgraph_get()
# Get bounding box corners in world space
coords = [obj.matrix_world @ Vector(corner) for corner in obj.bound_box]
# Find center of bounding box
center = sum(coords, Vector()) / len(coords)
# Scale each point away from the center to apply padding
scaled_coords = [(center + (co - center) * (1 + padding)) for co in coords]
# Flatten the list of Vectors into a list of floats
flat_coords = [v for co in scaled_coords for v in co]
# Use the camera fitting function
cam_location, _ = camera.camera_fit_coords(depsgraph, flat_coords)
camera.location = cam_location
@@ -0,0 +1,59 @@
from bpy.types import Menu
class BONEWIDGET_MT_bw_specials(Menu):
bl_label = "Bone Widget Specials"
def draw(self, context):
layout = self.layout
layout.operator("bonewidget.add_widgets", icon="ADD",
text="Add Widget to Library")
layout.operator("bonewidget.remove_widgets", icon="REMOVE",
text="Remove Widget from Library")
layout.separator()
layout.operator("bonewidget.add_custom_image", icon="FILE_IMAGE",
text="Add Custom Image to Widget")
layout.operator("bonewidget.render_widget_thumbnail", icon="RESTRICT_RENDER_OFF",
text="Render Object as Thumbnail")
layout.separator()
layout.operator("bonewidget.import_widget_library",
icon="IMPORT", text="Import Widget Library")
layout.operator("bonewidget.export_widget_library",
icon="EXPORT", text="Export Widget Library")
class BONEWIDGET_MT_bw_color_presets_specials(Menu):
bl_label = "Color Presets Specials"
def draw(self, context):
layout = self.layout
btn_text = "Add Preset from Theme" if "THEME" in context.scene.bw_settings.bone_widget_colors else "Add Preset from Palette"
layout.operator("bonewidget.add_color_set_from",
text=btn_text, icon="ADD")
layout.operator("bonewidget.add_preset_from_bone",
icon="ADD", text="Add Preset from Bone")
layout.operator("bonewidget.add_presets_from_armature",
icon="ADD", text="Add Preset from Armature")
layout.separator()
layout.operator("bonewidget.import_color_presets",
icon="IMPORT", text="Import Color Presets")
layout.operator("bonewidget.export_color_presets",
icon="EXPORT", text="Export Color Presets")
classes = (
BONEWIDGET_MT_bw_specials,
BONEWIDGET_MT_bw_color_presets_specials,
)
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 classes:
unregister_class(cls)
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,280 @@
import bpy
import bpy.utils.previews
from .props import PresetColorSetItem
from .functions.main_functions import (
recursive_layer_collection,
get_preferences,
)
from .functions.preview_functions import (
create_preview_collection,
preview_collections,
get_preview_default,
)
from .functions.json_functions import load_color_presets
from .menus import BONEWIDGET_MT_bw_specials
class BONEWIDGET_PT_bw_panel:
"""BoneWidget Addon UI"""
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = "Rigging"
bl_label = "Bone Widget"
class BONEWIDGET_PT_bw_panel_main(BONEWIDGET_PT_bw_panel, bpy.types.Panel):
bl_idname = 'BONEWIDGET_PT_bw_panel_main'
bl_label = "Bone Widget"
def draw(self, context):
if context.window_manager.load_presets_on_startup:
load_color_presets()
context.window_manager.load_presets_on_startup = False
# cache call to get preferences
preferences = get_preferences(context)
layout = self.layout
# preview toggle checkbox
row = layout.row(align=True)
row.prop(context.window_manager, "toggle_preview")
# preview view
if context.window_manager.toggle_preview:
row = layout.row(align=True)
preview_panel_size = preferences.preview_panel_size
preview_popup_size = preferences.preview_popup_size
row.template_icon_view(context.window_manager, "widget_list", show_labels=True,
scale=preview_panel_size, scale_popup=preview_popup_size)
# dropdown list
row = layout.row(align=True)
row.prop(context.window_manager, "widget_list", expand=False, text="")
row = layout.row(align=True)
row.menu("BONEWIDGET_MT_bw_specials", icon='DOWNARROW_HLT', text="")
row.operator("bonewidget.create_widget",
icon="OBJECT_DATAMODE", text="Create")
if context.mode == "POSE":
row.operator("bonewidget.edit_widget", icon="OUTLINER_DATA_MESH")
else:
row.operator("bonewidget.return_to_armature",
icon="LOOP_BACK", text='To bone')
layout.separator()
# Symmetry buttons etc
row = layout.row(align=True)
row.operator("bonewidget.symmetrize_shape",
icon='MOD_MIRROR', text="Symmetrize Shape")
icon = 'RESTRICT_COLOR_OFF'
if preferences.symmetrize_color:
icon = 'RESTRICT_COLOR_ON'
row.prop(preferences, "symmetrize_color",
icon=icon, text='', toggle=True)
row = layout.row()
row.operator("bonewidget.match_bone_transforms",
icon='GROUP_BONE', text="Match Bone Transforms")
row = layout.row()
row.operator("bonewidget.resync_widget_names",
icon='FILE_REFRESH', text="Resync Widget Names")
# Clear Bone Widget buttons etc
layout.separator()
layout.operator("bonewidget.clear_widgets",
icon='X', text="Clear Bone Widget")
layout.operator("bonewidget.delete_unused_widgets",
icon='TRASH', text="Delete Unused Widgets")
if context.mode == 'POSE':
layout.operator("bonewidget.add_as_widget",
text="Use Selected Object",
icon='RESTRICT_SELECT_OFF')
# if the bw collection exists, show the visibility toggle
if not preferences.use_rigify_defaults: # rigify
bw_collection_name = preferences.bonewidget_collection_name
elif context.active_object: # active object
bw_collection_name = 'WGTS_' + context.active_object.name
else: # this is needed because sometimes there is no active object
bw_collection_name = None
bw_collection = recursive_layer_collection(
context.view_layer.layer_collection, bw_collection_name)
if bw_collection is not None:
if bw_collection.hide_viewport:
icon = "HIDE_ON"
text = "Show Collection"
else:
icon = "HIDE_OFF"
text = "Hide Collection"
row = layout.row()
row.separator()
row = layout.row()
row.operator("bonewidget.toggle_collection_visibilty",
icon=icon, text=text)
# BONE COLORS
if bpy.app.version >= (4, 0, 0):
layout.separator()
row = layout.row(align=True)
row.operator("bonewidget.set_bone_color",
text="Set Bone Color", icon="BRUSHES_ALL")
row.scale_x = 3.0
icon_row = row.row()
icon_row.enabled = (context.object is not None and context.object.type == 'ARMATURE' and
context.object.mode in {'POSE', 'EDIT'})
icon_row.template_icon_view(
context.scene.bw_settings, "bone_widget_colors", show_labels=False, scale=1, scale_popup=1.8)
if context.scene.bw_settings.bone_widget_colors == "CUSTOM":
custom_pose_color = context.scene.bw_settings.custom_pose_color_set
custom_edit_color = context.scene.bw_settings.custom_edit_color_set
if context.object.mode == 'POSE': # display pose bone colors
row = layout.row(align=True)
row.prop(custom_pose_color, "normal", text="")
row.prop(custom_pose_color, "select", text="")
row.prop(custom_pose_color, "active", text="")
# edit bone colors
elif context.object.mode == "EDIT" and preferences.edit_bone_colors != 'DEFAULT':
row = layout.row(align=True)
row.prop(custom_edit_color, "normal", text="")
row.prop(custom_edit_color, "select", text="")
row.prop(custom_edit_color, "active", text="")
if context.object.mode == 'POSE' or (context.object.mode == 'EDIT' and
preferences.edit_bone_colors != 'DEFAULT'):
row.separator(factor=0.5)
row.prop(context.scene.bw_settings, "live_update_toggle",
text="", icon="UV_SYNC_SELECT")
row = layout.row()
row.operator("bonewidget.copy_bone_color",
text="Copy Bone Color", icon="COPYDOWN")
row = layout.row(align=True)
row.operator("bonewidget.clear_bone_color",
text="Clear Bone Color", icon="PANEL_CLOSE")
icon = 'BONE_DATA'
if preferences.clear_both_modes:
icon = 'GROUP_BONE'
row.prop(preferences, "clear_both_modes",
icon=icon, text='', toggle=True)
class BONEWIDGET_PT_bw_custom_color_presets(BONEWIDGET_PT_bw_panel, bpy.types.Panel):
bl_idname = "BONEWIDGET_PT_bw_custom_color_presets"
bl_parent_id = "BONEWIDGET_PT_bw_panel_main"
bl_label = "Custom Color Presets"
bl_options = {'DEFAULT_CLOSED'}
@classmethod
def poll(self, context):
return bpy.app.version >= (4, 0, 0)
def draw(self, context):
layout = self.layout
row = layout.row()
row.template_list("BONEWIDGET_UL_colorset_items", "", context.window_manager, "custom_color_presets",
context.window_manager, "colorset_list_index")
col = row.column(align=True)
col.operator("bonewidget.add_default_custom_colorset",
icon='ADD', text="")
col.operator("bonewidget.remove_custom_item", icon='REMOVE', text="")
col.separator()
col.menu("BONEWIDGET_MT_bw_color_presets_specials",
icon="DOWNARROW_HLT", text="")
col.separator()
col.operator("bonewidget.move_custom_item_up", icon="TRIA_UP", text="")
col.operator("bonewidget.move_custom_item_down",
icon="TRIA_DOWN", text="")
row = layout.row()
row.operator("bonewidget.add_colorset_to_bone",
text="Apply To Selected Bones")
class BONEWIDGET_UL_colorset_items(bpy.types.UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_property, index):
# set the size of each color set field
split = layout.split(factor=0.58)
split.prop(item, "name", text="", emboss=False)
row = split.row(align=True)
row.prop(item, "normal", text="")
row.prop(item, "select", text="")
row.prop(item, "active", text="")
classes = (
BONEWIDGET_PT_bw_panel_main,
BONEWIDGET_PT_bw_custom_color_presets,
BONEWIDGET_UL_colorset_items,
)
def register():
if not hasattr(bpy.types.WindowManager, "widget_list"):
create_preview_collection()
bpy.types.WindowManager.toggle_preview = bpy.props.BoolProperty(
name="Preview Panel",
default=get_preview_default(),
description="Show thumbnail previews"
)
bpy.utils.register_class(PresetColorSetItem)
bpy.types.WindowManager.custom_color_presets = bpy.props.CollectionProperty(
type=PresetColorSetItem)
bpy.types.WindowManager.colorset_list_index = bpy.props.IntProperty(
name="Index", default=0)
bpy.types.WindowManager.turn_off_colorset_save = bpy.props.BoolProperty(
name="Turn Off ColorSet Save",
description="Disable automatic saving of color sets",
default=False
)
bpy.types.WindowManager.load_presets_on_startup = bpy.props.BoolProperty(
name="Load Presets on Startup",
description="Load color presets when Blender starts",
default=True
)
from bpy.utils import register_class
for cls in classes:
try:
register_class(cls)
except:
pass
def unregister():
if hasattr(bpy.types.WindowManager, "widget_list"):
del bpy.types.WindowManager.widget_list
del bpy.types.WindowManager.toggle_preview
del bpy.types.WindowManager.custom_color_presets
del bpy.types.WindowManager.colorset_list_index
del bpy.types.WindowManager.turn_off_colorset_save
del bpy.types.WindowManager.load_presets_on_startup
bpy.utils.unregister_class(PresetColorSetItem)
for pcoll in preview_collections.values():
bpy.utils.previews.remove(pcoll)
preview_collections.clear()
from bpy.utils import unregister_class
for cls in classes:
try:
unregister_class(cls)
except:
pass
+203
View File
@@ -0,0 +1,203 @@
import bpy
from bpy.types import AddonPreferences
from bpy.props import StringProperty, BoolProperty, FloatProperty, EnumProperty
from .panels import BONEWIDGET_PT_bw_panel_main
from .operators import BONEWIDGET_OT_reset_default_images, BONEWIDGET_OT_user_data_filebrowser
class BoneWidget_preferences(AddonPreferences):
bl_idname = __package__
# Use Rigify Defaults
use_rigify_defaults: BoolProperty(
name="Use Rigify Defaults",
description="Use the same naming convention for widget creation (disable if you prefer your naming convention)",
default=True,
)
# widget prefix
widget_prefix: StringProperty(
name="Bone Widget prefix",
description="Choose a prefix for the widget objects",
default="WGT-",
)
# symmetry suffix
symmetry_suffix: StringProperty(
name="Bone Widget symmetry suffix",
description="Choose a naming convention for the symmetrical widgets, separate by semicolon.",
default="L; R",
)
# collection name
bonewidget_collection_name: StringProperty(
name="Bone Widget collection name",
description="Choose a name for the collection the widgets will appear",
default="WGTS",
)
def panel_category_update_fn(self, context):
has_panel = hasattr(bpy.types, BONEWIDGET_PT_bw_panel_main.bl_idname)
if has_panel:
try:
bpy.utils.unregister_class(BONEWIDGET_PT_bw_panel_main)
except:
pass
BONEWIDGET_PT_bw_panel_main.bl_category = self.panel_category
bpy.utils.register_class(BONEWIDGET_PT_bw_panel_main)
panel_category: StringProperty(
name="Panel Category",
description="Category to show Bone-Widgets panel",
default="Rigging",
update=panel_category_update_fn,
)
preview_panel_size: FloatProperty(
name="Preview Panel Size",
description="Size of the Preview Panel",
default=6.0,
min=1.0,
max=10.0,
precision=1,
)
preview_popup_size: FloatProperty(
name="Preview Popup Size",
description="Size of the Preview Popup Thumbnails",
default=3.5,
min=1.0,
max=10.0,
precision=1,
)
preview_default: BoolProperty(
name="Default Preview State",
description="Default state of preview panel",
default=True,
)
edit_bone_colors: EnumProperty(
name="Edit Bone Colors",
description="Behavior of Edit Bone colors",
items=[
('DEFAULT', "Default", "Set the Edit Bone color to the default colors"),
('LINKED', "Linked",
"Use the same colors for both the Edit bones and Pose bones"),
('SEPARATE', "Separate",
"Edit bones and Pose bones will have their own colors"),
],
default='DEFAULT'
)
clear_both_modes: bpy.props.BoolProperty(
name="Clear All Bone Color",
description='When enabled, bone colors from Edit mode and Pose mode will be cleared. When disabled, only the color from the current mode will be cleared',
default=True
)
symmetrize_color: bpy.props.BoolProperty(
name="Symmetrize Bone Colors",
description='When enabled, bone colors will be copied when you symmetrize a widget. When disabled, only the shape will be symmetrized',
default=True
)
use_default_location: bpy.props.BoolProperty(
name="Use default location",
description='When enabled, user widgets and color sets will be saved to extensions/.user/{repository_name}/bone_widget/bone_widget_custom_data',
default=True
)
user_data_location: StringProperty(
name="User Data Location",
description="Choose a location where you want to save custom data",
default="",
)
def draw(self, context):
layout = self.layout
box = layout.box()
box.label(text="Widget Naming Convention:")
box.prop(self, "use_rigify_defaults", text="Use Rigify Defaults")
box_row = box.row()
box_col = box_row.column()
box_col.prop(self, "widget_prefix", text="Widget Prefix")
box_col.prop(self, "bonewidget_collection_name",
text="Collection name")
box_row.enabled = not self.use_rigify_defaults
box_row = box.row()
box_row = box.row()
box_row.prop(self, "symmetry_suffix", text="Symmetry suffix")
row = layout.row()
box = layout.box()
box_col = box.column()
box_col.label(text="Set the category to show Bone-Widgets panel:")
box_col.prop(self, "panel_category")
# edit bone colors
row = layout.row()
box = layout.box()
box.label(text="Bone Color Behavior:")
row = box.row()
row.prop(self, "edit_bone_colors")
row = box.row()
row.label(text="Clearing Colors:")
row.prop(self, "clear_both_modes")
row = box.row()
row.label(text="Symmetrize Colors:")
row.prop(self, "symmetrize_color")
# preview area
row = layout.row()
box = layout.box()
box.label(text="Thumbnail Previews:")
box_row = box.row()
box_row.prop(self, "preview_default",
text="Display Previews by Default")
box_row = box.row()
box_col = box_row.column()
box_col.label(text="Preview Panel Size:")
box_row.prop(self, "preview_panel_size", text="")
box_row = box.row()
box_col = box_row.column()
box_col.label(text="Preview Popup Size:")
box_row.prop(self, "preview_popup_size", text="")
# custom data
row = layout.row()
box = layout.box()
box.label(text="Custom Data:")
box_row = box.row()
box_row.prop(self, "use_default_location", text="Use Default Location")
box_row = box.row()
box_col = box_row.column()
box_col.prop(self, "user_data_location", text="Custom Path")
box_row.operator("bonewidget.user_data_filebrowser",
icon="FILEBROWSER", text="")
box_row.enabled = not self.use_default_location
# reset button
layout.separator()
row = layout.row()
row = row.split(factor=.75)
row.label(text="Reset Default Widget Thumbnails")
row.operator("bonewidget.reset_default_images", icon="ERROR")
def register():
bpy.utils.register_class(BoneWidget_preferences)
def unregister():
bpy.utils.unregister_class(BoneWidget_preferences)
+197
View File
@@ -0,0 +1,197 @@
import bpy
from .functions.main_functions import (
update_bone_color,
bone_color_items_short,
live_update_toggle
)
from bpy.types import PropertyGroup
from bpy.props import BoolProperty, EnumProperty, PointerProperty
class CustomColorSet(bpy.types.PropertyGroup):
name: bpy.props.StringProperty(name="Name", default="Untitled")
normal: bpy.props.FloatVectorProperty(
name="Normal",
subtype='COLOR_GAMMA',
default=(0.0, 0.0, 0.0),
size=3,
min=0.0, max=1.0,
description="Color used for the surface of bones",
update=update_bone_color,
)
select: bpy.props.FloatVectorProperty(
name="Select",
subtype='COLOR_GAMMA',
default=(0.0, 0.0, 0.0),
size=3,
min=0.0, max=1.0,
description="Color used for selected bones",
update=update_bone_color,
)
active: bpy.props.FloatVectorProperty(
name="Active",
subtype='COLOR_GAMMA',
default=(0.0, 0.0, 0.0),
size=3,
min=0.0, max=1.0,
description="Color used for active bones",
update=update_bone_color,
)
class BW_Settings(PropertyGroup):
live_update_on: BoolProperty(
name="Live Update On",
description="Enable live widget updates",
default=False
)
live_update_toggle: BoolProperty(
name="Live Update Toggle",
description="Toggle live updates in the UI",
default=False,
update=live_update_toggle,
)
lock_colorset_color_changes: BoolProperty(
name="Lock ColorSet Color Changes",
description="Prevent modifying the current color set",
default=False
)
# Blender's bone color themes
bone_widget_colors: EnumProperty(
name="Colors",
description="Select a Bone Color",
items=bone_color_items_short, # get the themes minus the blank ones
default=1, # THEME01
)
# Nested Property Groups
custom_edit_color_set: PointerProperty(type=CustomColorSet)
custom_pose_color_set: PointerProperty(type=CustomColorSet)
save_timer = None
def debounce_save(context):
"""Schedule saving the color sets 1 second after the last change."""
from .functions.json_functions import save_color_sets
global save_timer
if save_timer is not None:
try:
bpy.app.timers.unregister(save_timer)
except ValueError:
pass
def delayed_save():
save_color_sets(context)
return None # stop the timer
save_timer = delayed_save
bpy.app.timers.register(save_timer, first_interval=1.0)
class PresetColorSetItem(bpy.types.PropertyGroup):
def update_colorset_list(self, context):
if not context.window_manager.turn_off_colorset_save and not context.scene.bw_settings.lock_colorset_color_changes:
debounce_save(context)
name: bpy.props.StringProperty(
name="Name", default="Untitled", update=update_colorset_list)
normal: bpy.props.FloatVectorProperty(
name="Normal",
subtype='COLOR_GAMMA',
default=(0.0, 0.0, 0.0),
size=3,
min=0.0, max=1.0,
description="Color used for the surface of bones",
update=update_colorset_list,
)
select: bpy.props.FloatVectorProperty(
name="Select",
subtype='COLOR_GAMMA',
default=(0.0, 0.0, 0.0),
size=3,
min=0.0, max=1.0,
description="Color used for selected bones",
update=update_colorset_list,
)
active: bpy.props.FloatVectorProperty(
name="Active",
subtype='COLOR_GAMMA',
default=(0.0, 0.0, 0.0),
size=3,
min=0.0, max=1.0,
description="Color used for active bones",
update=update_colorset_list,
)
class ImportColorSet(bpy.types.PropertyGroup):
normal: bpy.props.FloatVectorProperty(
name="Normal",
subtype='COLOR_GAMMA',
default=(0.0, 0.0, 0.0),
size=3,
min=0.0, max=1.0,
description="Color used for the surface of bones",
)
select: bpy.props.FloatVectorProperty(
name="Select",
subtype='COLOR_GAMMA',
default=(0.0, 0.0, 0.0),
size=3,
min=0.0, max=1.0,
description="Color used for selected bones",
)
active: bpy.props.FloatVectorProperty(
name="Active",
subtype='COLOR_GAMMA',
default=(0.0, 0.0, 0.0),
size=3,
min=0.0, max=1.0,
description="Color used for active bones",
)
def get_import_options():
return [
("OVERWRITE", "Add/Overwrite", "Add or Overwrite existing item"),
("SKIP", "Skip", "Skip item"),
("RENAME", "Rename", "Rename item"),
]
class ImportItemData(bpy.types.PropertyGroup):
name: bpy.props.StringProperty(
name="Unnamed",
description="The name of the imported item"
)
import_option: bpy.props.EnumProperty(
name="Options",
description="Choose an option",
items=get_import_options(),
default="SKIP"
)
def register():
bpy.utils.register_class(CustomColorSet)
bpy.utils.register_class(BW_Settings)
bpy.types.Scene.bw_settings = bpy.props.PointerProperty(type=BW_Settings)
def unregister():
del bpy.types.Scene.bw_settings
bpy.utils.unregister_class(BW_Settings)
bpy.utils.unregister_class(CustomColorSet)
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,979 @@
# SPDX-FileCopyrightText: 2016-2022 Blender Foundation
#
# SPDX-License-Identifier: GPL-2.0-or-later
# authors: dudecon, jambay
# This module contains the UI definition, display,
# and processing (create mesh) functions.
# The routines to generate the vertices for the wall
# are found in the "Blocks" module.
import bpy
from bpy.types import Operator
from bpy.props import (
BoolProperty,
FloatProperty,
StringProperty,
)
from .Blocks import (
NOTZERO, PI,
dims,
settings,
shelfSpecs,
stepSpecs,
createWall,
radialized,
slope,
openingSpecs,
bigBlock,
shelfExt,
stepMod,
stepLeft,
shelfBack,
stepOnly,
stepBack,
)
from bpy_extras import object_utils
from .interface import draw_transform_props
class add_mesh_wallb(Operator, object_utils.AddObjectHelper):
bl_idname = "mesh.wall_add"
bl_label = "Add a Masonry Wall"
bl_description = "Create a block (masonry) wall mesh"
bl_options = {'REGISTER', 'UNDO'}
# UI items - API for properties - User accessible variables...
# not all options are via UI, and some operations just don't work yet
Wall : BoolProperty(name = "Wall",
default = True,
description = "Wall")
#### change properties
name : StringProperty(name = "Name",
description = "Name")
change : BoolProperty(name = "Change",
default = False,
description = "change Wall")
# only create object when True
# False allows modifying several parameters without creating object
ConstructTog: BoolProperty(
name="Construct",
description="Generate the object",
default=True
)
# need to modify so radial makes a tower (normal);
# want "flat" setting to make disk (alternate)
# make the wall circular - if not sloped it's a flat disc
RadialTog: BoolProperty(
name="Radial",
description="Make masonry radial",
default=False
)
# curve the wall - if radial creates dome.
SlopeTog: BoolProperty(
name="Curved",
description="Make masonry sloped, or curved",
default=False
)
# need to review defaults and limits for all of these UI objects
# wall area/size
WallStart: FloatProperty(
name="Start",
description="Left side, or start angle",
default=-10.0,
min=-100, max=100.0
)
WallEnd: FloatProperty(
name="End",
description="Right side, or end angle",
default=10.0,
min=0.0, max=100.0
)
WallBottom: FloatProperty(
name="Bottom",
description="Lower height or radius",
default=0.0,
min=-100, max=100
)
WallTop: FloatProperty(
name="Top",
description="Upper height or radius",
default=15.0,
min=0.0, max=100.0
)
EdgeOffset: FloatProperty(
name="Edging",
description="Block staggering on wall sides",
default=0.6, min=0.0, max=100.0
)
# block sizing
Width: FloatProperty(
name="Width",
description="Average width of each block",
default=1.5,
min=0.01, max=100.0
)
WidthVariance: FloatProperty(
name="Variance",
description="Random variance of block width",
default=0.5,
min=0.0, max=100.0
)
WidthMinimum: FloatProperty(
name="Minimum",
description="Absolute minimum block width",
default=0.5,
min=0.01, max=100.0
)
Height: FloatProperty(
name="Height",
description="Average Height of each block",
default=0.7,
min=0.01, max=100.0
)
HeightVariance: FloatProperty(
name="Variance",
description="Random variance of block Height",
default=0.3,
min=0.0, max=100.0
)
HeightMinimum: FloatProperty(
name="Minimum",
description="Absolute minimum block Height",
default=0.25,
min=0.01, max=100.0
)
Depth: FloatProperty(
name="Depth",
description="Average Depth of each block",
default=2.0,
min=0.01, max=100.0
)
DepthVariance: FloatProperty(
name="Variance",
description="Random variance of block Depth",
default=0.1,
min=0.0, max=100.0
)
DepthMinimum: FloatProperty(
name="Minimum",
description="Absolute minimum block Depth",
default=1.0,
min=0.01, max=100.0
)
MergeBlock: BoolProperty(
name="Merge Blocks",
description="Make big blocks (merge closely adjoining blocks)",
default=False
)
# edging for blocks
Grout: FloatProperty(
name="Thickness",
description="Distance between blocks",
default=0.1,
min=-10.0, max=10.0
)
GroutVariance: FloatProperty(
name="Variance",
description="Random variance of block Grout",
default=0.03,
min=0.0, max=100.0
)
GroutDepth: FloatProperty(
name="Depth",
description="Grout Depth from the face of the blocks",
default=0.1,
min=0.0001, max=10.0
)
GroutDepthVariance: FloatProperty(
name="Variance",
description="Random variance of block Grout Depth",
default=0.03,
min=0.0, max=100.0
)
GroutEdge: BoolProperty(
name="Edging",
description="Grout perimiter",
default=False
)
# properties for openings
Opening1Tog: BoolProperty(
name="Opening(s)",
description="Make windows or doors",
default=True
)
Opening1Width: FloatProperty(
name="Width",
description="The Width of the first opening",
default=2.5,
min=0.01, max=100.0
)
Opening1Height: FloatProperty(
name="Height",
description="The Height of the first opening",
default=3.5,
min=0.01, max=100.0
)
Opening1X: FloatProperty(
name="Indent",
description="The x position or spacing of the first opening",
default=5.0,
min=-100, max=100.0
)
Opening1Z: FloatProperty(
name="Bottom",
description="The z position of the First opening",
default=5.0,
min=-100, max=100.0
)
Opening1Repeat: BoolProperty(
name="Repeat",
description="make multiple openings, with spacing X1",
default=False
)
Opening1TopArchTog: BoolProperty(
name="Top Arch",
description="Add an arch to the top of the first opening",
default=True
)
Opening1TopArch: FloatProperty(
name="Curve",
description="Height of the arch on the top of the opening",
default=2.5,
min=0.001, max=100.0
)
Opening1TopArchThickness: FloatProperty(
name="Thickness",
description="Thickness of the arch on the top of the opening",
default=0.75,
min=0.001, max=100.0
)
Opening1BtmArchTog: BoolProperty(
name="Bottom Arch",
description="Add an arch to the bottom of opening 1",
default=False
)
Opening1BtmArch: FloatProperty(
name="Curve",
description="Height of the arch on the bottom of the opening",
default=1.0,
min=0.01, max=100.0
)
Opening1BtmArchThickness: FloatProperty(
name="Thickness",
description="Thickness of the arch on the bottom of the opening",
default=0.5,
min=0.01, max=100.0
)
Opening1Bevel: FloatProperty(
name="Bevel",
description="Angle block face",
default=0.25,
min=-10.0, max=10.0
)
# openings on top of wall
CrenelTog: BoolProperty(
name="Crenels",
description="Make openings along top of wall",
default=False
)
CrenelXP: FloatProperty(
name="Width",
description="Gap width in wall based the percentage of wall width",
default=0.25,
min=0.10, max=1.0,
subtype="PERCENTAGE"
)
CrenelZP: FloatProperty(
name="Height",
description="Crenel Height as the percentage of wall height",
default=0.10,
min=0.10, max=1.0,
subtype="PERCENTAGE"
)
# narrow openings in wall.
# need to prevent overlap with arch openings - though inversion is an interesting effect.
SlotTog: BoolProperty(
name="Slots",
description="Make narrow openings in wall",
default=False
)
SlotRpt: BoolProperty(
name="Repeat",
description="Repeat slots along wall",
default=False
)
SlotWdg: BoolProperty(
name="Wedged (n/a)",
description="Bevel edges of slots",
default=False
)
SlotX: FloatProperty(
name="Indent",
description="The x position or spacing of slots",
default=0.0, min=-100, max=100.0
)
SlotGap: FloatProperty(
name="Opening",
description="The opening size of slots",
default=0.5, min=0.10, max=100.0
)
SlotV: BoolProperty(
name="Vertical",
description="Vertical slots",
default=True
)
SlotVH: FloatProperty(
name="Height",
description="Height of vertical slot",
default=3.5,
min=0.10, max=100.0
)
SlotVBtm: FloatProperty(
name="Bottom",
description="Z position for slot",
default=5.00,
min=-100.0, max=100.0
)
SlotH: BoolProperty(
name="Horizontal",
description="Horizontal slots",
default=False
)
SlotHW: FloatProperty(
name="Width",
description="Width of horizontal slot",
default=2.5,
min=0.10, max=100.0
)
# this should offset from VBtm... maybe make a % like crenels?
SlotHBtm: FloatProperty(
name="Bottom",
description="Z position for horizontal slot",
default=5.50,
min=-100.0, max=100.0
)
# properties for shelf (extend blocks in area)
ShelfTog: BoolProperty(
name="Shelf",
description="Add blocks in area by depth to make shelf/platform",
default=False
)
ShelfX: FloatProperty(
name="Left",
description="The x position of Shelf",
default=-5.00,
min=-100, max=100.0
)
ShelfZ: FloatProperty(
name="Bottom",
description="The z position of Shelf",
default=10.0,
min=-100, max=100.0
)
ShelfH: FloatProperty(
name="Height",
description="The Height of Shelf area",
default=1.0,
min=0.01, max=100.0
)
ShelfW: FloatProperty(
name="Width",
description="The Width of shelf area",
default=5.0,
min=0.01, max=100.0
)
ShelfD: FloatProperty(
name="Depth",
description="Depth of each block for shelf (from cursor + 1/2 wall depth)",
default=2.0,
min=0.01, max=100.0
)
ShelfBack: BoolProperty(
name="Backside",
description="Shelf on backside of wall",
default=False
)
# properties for steps (extend blocks in area, progressive width)
StepTog: BoolProperty(
name="Steps",
description="Add blocks in area by depth with progressive width to make steps",
default=False
)
StepX: FloatProperty(
name="Left",
description="The x position of steps",
default=-9.00,
min=-100, max=100.0
)
StepZ: FloatProperty(
name="Bottom",
description="The z position of steps",
default=0.0,
min=-100, max=100.0
)
StepH: FloatProperty(
name="Height",
description="The Height of step area",
default=10.0,
min=0.01, max=100.0
)
StepW: FloatProperty(
name="Width",
description="The Width of step area",
default=8.0,
min=0.01, max=100.0
)
StepD: FloatProperty(
name="Depth",
description="Depth of each block for steps (from cursor + 1/2 wall depth)",
default=1.0,
min=0.01, max=100.0
)
StepV: FloatProperty(
name="Riser",
description="Height of each step",
default=0.70,
min=0.01, max=100.0
)
StepT: FloatProperty(
name="Tread",
description="Width of each step",
default=1.0,
min=0.01, max=100.0
)
StepLeft: BoolProperty(
name="Direction",
description="If checked, flip steps direction towards the -X axis",
default=False
)
StepOnly: BoolProperty(
name="Steps Only",
description="Steps only, no supporting blocks",
default=False
)
StepBack: BoolProperty(
name="Backside",
description="Steps on backside of wall",
default=False
)
# Display the toolbox options
def draw(self, context):
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
# Wall shape modifiers
layout.prop(self, 'ConstructTog')
# Wall area (size/position)
header, panel = layout.panel("WALLFACTORY_PT_AREA", default_closed=False)
header.label(text="Wall Size")
if panel:
panel.prop(self, "RadialTog")
panel.prop(self, "SlopeTog")
col = panel.column(align=True)
col.prop(self, "WallStart")
col.prop(self, "WallEnd")
col = panel.column(align=True)
col.prop(self, "WallBottom")
col.prop(self, "WallTop")
panel.prop(self, "EdgeOffset")
# Wall block sizing
header, panel = layout.panel("WALLFACTORY_PT_BLOCKS", default_closed=False)
header.label(text="Block Size")
if panel:
panel.prop(self, "MergeBlock")
# add checkbox for "fixed" sizing (ignore variance) a.k.a. bricks
col = panel.column(align=True)
col.prop(self, "Width")
col.prop(self, "WidthVariance")
col.prop(self, "WidthMinimum")
col = panel.column(align=True)
col.prop(self, "Height")
col.prop(self, "HeightVariance")
col.prop(self, "HeightMinimum")
col = panel.column(align=True)
col.prop(self, "Depth")
col.prop(self, "DepthVariance")
col.prop(self, "DepthMinimum")
# grout settings
header, panel = layout.panel("WALLFACTORY_PT_GROUT", default_closed=True)
header.label(text="Grout")
if panel:
col = panel.column(align=True)
col.prop(self, "Grout")
col.prop(self, "GroutVariance")
col = panel.column(align=True)
col.prop(self, "GroutDepth")
col.prop(self, "GroutDepthVariance")
# Openings (doors, windows; arched)
header, panel = layout.panel("WALLFACTORY_PT_OPENINGS", default_closed=True)
header.use_property_split = False
header.prop(self, 'Opening1Tog', text='')
header.label(text="Openings")
if panel:
openings_col = panel.column()
openings_col.enabled = self.Opening1Tog
openings_col.use_property_split = True
col = openings_col.column(align=True)
col.prop(self, "Opening1Width")
col.prop(self, "Opening1Height")
col.prop(self, "Opening1X")
col.prop(self, "Opening1Z")
col.prop(self, "Opening1Bevel")
openings_col.prop(self, "Opening1Repeat", toggle=True)
openings_col.prop(self, "Opening1TopArchTog")
col = openings_col.column(align=True)
col.enabled = self.Opening1TopArchTog
col.prop(self, "Opening1TopArch")
col.prop(self, "Opening1TopArchThickness")
openings_col.prop(self, "Opening1BtmArchTog")
col = openings_col.column(align=True)
col.enabled = self.Opening1BtmArchTog
col.prop(self, "Opening1BtmArch")
col.prop(self, "Opening1BtmArchThickness")
# Slots (narrow openings)
header, panel = layout.panel("WALLFACTORY_PT_SLOTS", default_closed=True)
header.use_property_split = False
header.prop(self, 'SlotTog', text='')
header.label(text="Slots")
if panel:
panel.enabled = self.SlotTog
panel.prop(self, "SlotRpt")
col = panel.column(align=True)
col.prop(self, "SlotX")
col.prop(self, "SlotGap")
panel.prop(self, "SlotV")
col = panel.column(align=True)
col.enabled = self.SlotV
col.prop(self, "SlotVH")
col.prop(self, "SlotVBtm")
panel.prop(self, "SlotH")
col = panel.column(align=True)
col.enabled = self.SlotH
col.prop(self, "SlotHW")
col.prop(self, "SlotHBtm")
# Crenels, gaps in top of wall
header, panel = layout.panel("WALLFACTORY_PT_CRENELS", default_closed=True)
header.use_property_split = False
header.prop(self, 'CrenelTog', text='')
header.label(text="Crenels")
if panel:
panel.enabled = self.CrenelTog
col = panel.column(align=True)
col.prop(self, "CrenelXP")
col.prop(self, "CrenelZP")
# Shelfing (protrusions)
header, panel = layout.panel("WALLFACTORY_PT_SHELF", default_closed=True)
header.use_property_split = False
header.prop(self, 'ShelfTog', text='')
header.label(text="Shelf")
if panel:
panel.enabled = self.ShelfTog
col = panel.column(align=True)
col.prop(self, "ShelfX")
col.prop(self, "ShelfZ")
col = panel.column(align=True)
col.prop(self, "ShelfW")
col.prop(self, "ShelfH")
col.prop(self, "ShelfD")
panel.prop(self, "ShelfBack")
# Steps
header, panel = layout.panel("WALLFACTORY_PT_STEPS", default_closed=True)
header.use_property_split = False
header.prop(self, 'StepTog', text='')
header.label(text="Steps")
if panel:
panel.enabled = self.StepTog
col = panel.column(align=True)
col.prop(self, "StepX")
col.prop(self, "StepZ")
col = panel.column(align=True)
col.prop(self, "StepH")
col.prop(self, "StepW")
col.prop(self, "StepD")
col = panel.column(align=True)
col.prop(self, "StepV")
col.prop(self, "StepT")
col = panel.column(align=True)
panel.prop(self, "StepLeft")
panel.prop(self, "StepOnly")
panel.prop(self, "StepBack")
if self.change == False:
header, panel = layout.panel("WALLFACTORY_PT_TRANSFORM", default_closed=True)
header.label(text="Transform")
if panel:
draw_transform_props(self, panel)
# Respond to UI - get the properties set by user.
# Check and process UI settings to generate masonry
def execute(self, context):
global radialized
global slope
global openingSpecs
global bigBlock
global shelfExt
global stepMod
global stepLeft
global shelfBack
global stepOnly
global stepBack
# Create the wall when enabled (skip regen iterations when off)
if not self.ConstructTog:
return {'FINISHED'}
# turn off 'Enter Edit Mode'
use_enter_edit_mode = bpy.context.preferences.edit.use_enter_edit_mode
bpy.context.preferences.edit.use_enter_edit_mode = False
# enter the settings for the wall dimensions (area)
# start can't be zero - min/max don't matter [if max less than end] but zero don't workie.
# start can't exceed end.
if not self.WallStart or self.WallStart >= self.WallEnd:
self.WallStart = NOTZERO # Reset UI if input out of bounds...
dims['s'] = self.WallStart
dims['e'] = self.WallEnd
dims['b'] = self.WallBottom
dims['t'] = self.WallTop
settings['eoff'] = self.EdgeOffset
# retrieve the settings for the wall block properties
settings['w'] = self.Width
settings['wv'] = self.WidthVariance
settings['wm'] = self.WidthMinimum
if not radialized:
settings['sdv'] = settings['w']
else:
settings['sdv'] = 0.12
settings['h'] = self.Height
settings['hv'] = self.HeightVariance
settings['hm'] = self.HeightMinimum
settings['d'] = self.Depth
settings['dv'] = self.DepthVariance
settings['dm'] = self.DepthMinimum
if self.MergeBlock:
bigBlock = 1
else:
bigBlock = 0
settings['g'] = self.Grout
settings['gv'] = self.GroutVariance
settings['gd'] = self.GroutDepth
settings['gdv'] = self.GroutDepthVariance
if self.GroutEdge:
settings['ge'] = 1
else:
settings['ge'] = 0
# set wall shape modifiers
if self.RadialTog:
radialized = 1
# eliminate to allow user control for start/completion?
dims['s'] = 0.0 # complete radial
if dims['e'] > PI * 2:
dims['e'] = PI * 2 # max end for circle
if dims['b'] < settings['g']:
dims['b'] = settings['g'] # min bottom for grout extension
else:
radialized = 0
if self.SlopeTog:
slope = 1
else:
slope = 0
shelfExt = 0
shelfBack = 0
# Add shelf if enabled
if self.ShelfTog:
shelfExt = 1
shelfSpecs['h'] = self.ShelfH
shelfSpecs['w'] = self.ShelfW
shelfSpecs['d'] = self.ShelfD
shelfSpecs['x'] = self.ShelfX
shelfSpecs['z'] = self.ShelfZ
if self.ShelfBack:
shelfBack = 1
stepMod = 0
stepLeft = 0
stepOnly = 0
stepBack = 0
# Make steps if enabled
if self.StepTog:
stepMod = 1
stepSpecs['x'] = self.StepX
stepSpecs['z'] = self.StepZ
stepSpecs['h'] = self.StepH
stepSpecs['w'] = self.StepW
stepSpecs['d'] = self.StepD
stepSpecs['v'] = self.StepV
stepSpecs['t'] = self.StepT
if self.StepLeft:
stepLeft = 1
if self.StepOnly:
stepOnly = 1
if self.StepBack:
stepBack = 1
# enter the settings for the openings
# when openings overlap they create inverse stonework - interesting but not the desired effect :)
# if opening width == indent * 2 the edge blocks fail (row of blocks cross opening) - bug.
openingSpecs = []
openingIdx = 0 # track opening array references for multiple uses
# general openings with arch options - can be windows or doors.
if self.Opening1Tog:
# set defaults...
openingSpecs += [{'w': 0.5, 'h': 0.5, 'x': 0.8, 'z': 2.7, 'rp': 1,
'b': 0.0, 'v': 0, 'vl': 0, 't': 0, 'tl': 0}]
openingSpecs[openingIdx]['w'] = self.Opening1Width
openingSpecs[openingIdx]['h'] = self.Opening1Height
openingSpecs[openingIdx]['x'] = self.Opening1X
openingSpecs[openingIdx]['z'] = self.Opening1Z
openingSpecs[openingIdx]['rp'] = self.Opening1Repeat
if self.Opening1TopArchTog:
openingSpecs[openingIdx]['v'] = self.Opening1TopArch
openingSpecs[openingIdx]['t'] = self.Opening1TopArchThickness
if self.Opening1BtmArchTog:
openingSpecs[openingIdx]['vl'] = self.Opening1BtmArch
openingSpecs[openingIdx]['tl'] = self.Opening1BtmArchThickness
openingSpecs[openingIdx]['b'] = self.Opening1Bevel
openingIdx += 1 # count window/door/arch openings
# Slots (narrow openings)
if self.SlotTog:
if self.SlotV: # vertical slots
# set defaults...
openingSpecs += [{'w': 0.5, 'h': 0.5, 'x': 0.0, 'z': 2.7, 'rp': 0,
'b': 0.0, 'v': 0, 'vl': 0, 't': 0, 'tl': 0}]
openingSpecs[openingIdx]['w'] = self.SlotGap
openingSpecs[openingIdx]['h'] = self.SlotVH
openingSpecs[openingIdx]['x'] = self.SlotX
openingSpecs[openingIdx]['z'] = self.SlotVBtm
openingSpecs[openingIdx]['rp'] = self.SlotRpt
# make them pointy...
openingSpecs[openingIdx]['v'] = self.SlotGap
openingSpecs[openingIdx]['t'] = self.SlotGap / 2
openingSpecs[openingIdx]['vl'] = self.SlotGap
openingSpecs[openingIdx]['tl'] = self.SlotGap / 2
openingIdx += 1 # count vertical slot openings
# need to handle overlap of H and V slots...
if self.SlotH: # Horizontal slots
# set defaults...
openingSpecs += [{'w': 0.5, 'h': 0.5, 'x': 0.0, 'z': 2.7, 'rp': 0,
'b': 0.0, 'v': 0, 'vl': 0, 't': 0, 'tl': 0}]
openingSpecs[openingIdx]['w'] = self.SlotHW
openingSpecs[openingIdx]['h'] = self.SlotGap
openingSpecs[openingIdx]['x'] = self.SlotX
openingSpecs[openingIdx]['z'] = self.SlotHBtm
# horizontal repeat isn't same spacing as vertical...
openingSpecs[openingIdx]['rp'] = self.SlotRpt
# make them pointy...
openingIdx += 1 # count horizontal slot openings
# Crenellations (top row openings)
if self.CrenelTog:
# add bottom arch option?
# perhaps a repeat toggle...
# if crenel opening overlaps with arch opening it fills with blocks...
# set defaults...
openingSpecs += [{'w': 0.5, 'h': 0.5, 'x': 0.0, 'z': 2.7, 'rp': 1,
'b': 0.0, 'v': 0, 'vl': 0, 't': 0, 'tl': 0}]
wallW = self.WallEnd - self.WallStart
crenelW = wallW * self.CrenelXP # Width % opening.
wallH = self.WallTop - self.WallBottom
crenelH = wallH * self.CrenelZP # % proportional height.
openingSpecs[openingIdx]['w'] = crenelW
openingSpecs[openingIdx]['h'] = crenelH
# calculate the spacing between openings.
# this isn't the absolute start (left),
# it's opening center offset relative to cursor (space between openings)...
openingSpecs[openingIdx]['x'] = crenelW * 2 - 1 # assume standard spacing
if not radialized: # normal wall?
# set indent 0 (center) if opening is 50% or more of wall width, no repeat.
if crenelW * 2 >= wallW:
openingSpecs[openingIdx]['x'] = 0
openingSpecs[openingIdx]['rp'] = 0
# set bottom of opening (center of hole)
openingSpecs[openingIdx]['z'] = self.WallTop - (crenelH / 2)
openingIdx += 1 # count crenel openings
# Process the user settings to generate a wall
# generate the list of vertices for the wall...
verts_array, faces_array = createWall(
radialized, slope, openingSpecs, bigBlock,
shelfExt, shelfBack, stepMod, stepLeft, stepOnly,
stepBack
)
if bpy.context.mode == "OBJECT":
if context.selected_objects != [] and context.active_object and \
(context.active_object.data is not None) and ('Wall' in context.active_object.data.keys()) and \
(self.change == True):
obj = context.active_object
oldmesh = obj.data
oldmeshname = obj.data.name
mesh = bpy.data.meshes.new("Wall")
mesh.from_pydata(verts_array, [], faces_array)
obj.data = mesh
for material in oldmesh.materials:
obj.data.materials.append(material)
bpy.data.meshes.remove(oldmesh)
obj.data.name = oldmeshname
else:
mesh = bpy.data.meshes.new("Wall")
mesh.from_pydata(verts_array, [], faces_array)
obj = object_utils.object_data_add(context, mesh, operator=self)
mesh.update()
obj.data["Wall"] = True
obj.data["change"] = False
for prm in WallParameters():
obj.data[prm] = getattr(self, prm)
if bpy.context.mode == "EDIT_MESH":
active_object = context.active_object
name_active_object = active_object.name
bpy.ops.object.mode_set(mode='OBJECT')
mesh = bpy.data.meshes.new("TMP")
mesh.from_pydata(verts_array, [], faces_array)
obj = object_utils.object_data_add(context, mesh, operator=self)
obj.select_set(True)
active_object.select_set(True)
bpy.context.view_layer.objects.active = active_object
bpy.ops.object.join()
context.active_object.name = name_active_object
bpy.ops.object.mode_set(mode='EDIT')
if use_enter_edit_mode:
bpy.ops.object.mode_set(mode = 'EDIT')
# restore pre operator state
bpy.context.preferences.edit.use_enter_edit_mode = use_enter_edit_mode
return {'FINISHED'}
def WallParameters():
WallParameters = [
"ConstructTog",
"RadialTog",
"SlopeTog",
"WallStart",
"WallEnd",
"WallBottom",
"WallTop",
"EdgeOffset",
"Width",
"WidthVariance",
"WidthMinimum",
"Height",
"HeightVariance",
"HeightMinimum",
"Depth",
"DepthVariance",
"DepthMinimum",
"MergeBlock",
"Grout",
"GroutVariance",
"GroutDepth",
"GroutDepthVariance",
"GroutEdge",
"Opening1Tog",
"Opening1Width",
"Opening1Height",
"Opening1X",
"Opening1Z",
"Opening1Repeat",
"Opening1TopArchTog",
"Opening1TopArch",
"Opening1TopArchThickness",
"Opening1BtmArchTog",
"Opening1BtmArch",
"Opening1BtmArchThickness",
"CrenelTog",
"CrenelXP",
"CrenelZP",
"SlotTog",
"SlotRpt",
"SlotWdg",
"SlotX",
"SlotGap",
"SlotV",
"SlotVH",
"SlotVBtm",
"SlotH",
"SlotHW",
"SlotHBtm",
"ShelfTog",
"ShelfX",
"ShelfZ",
"ShelfH",
"ShelfW",
"ShelfD",
"ShelfBack",
"StepTog",
"StepX",
"StepZ",
"StepH",
"StepW",
"StepD",
"StepV",
"StepT",
"StepLeft",
"StepOnly",
"StepBack",
]
return WallParameters
@@ -0,0 +1,460 @@
# SPDX-FileCopyrightText: 2011-2023 Blender Foundation
#
# SPDX-License-Identifier: GPL-2.0-or-later
# Contributed to by:
# Pontiac, Fourmadmen, varkenvarken, tuga3d, meta-androcto, metalliandy #
# dreampainter, cotejrp1, liero, Kayo Phoenix, sugiany, dommetysk, Jambay #
# Phymec, Anthony D'Agostino, Pablo Vazquez, Richard Wilks, lijenstina, #
# Sjaak-de-Draak, Phil Cote, cotejrp1, xyz presets by elfnor, revolt_randy, #
# Vladimir Spivak (cwolf3d), Jonathan Lampel #
# Note: Blocks has to be loaded before the WallFactory or the script
# will not work properly after (F8) reload
if "bpy" in locals():
import importlib
importlib.reload(add_mesh_star)
importlib.reload(add_mesh_twisted_torus)
importlib.reload(add_mesh_gemstones)
importlib.reload(add_mesh_gears)
importlib.reload(add_mesh_3d_function_surface)
importlib.reload(add_mesh_round_cube)
importlib.reload(add_mesh_supertoroid)
importlib.reload(add_mesh_pyramid)
importlib.reload(add_mesh_torusknot)
importlib.reload(add_mesh_honeycomb)
importlib.reload(add_mesh_teapot)
importlib.reload(add_mesh_pipe_joint)
importlib.reload(add_mesh_solid)
importlib.reload(add_mesh_round_brilliant)
importlib.reload(add_mesh_menger_sponge)
importlib.reload(add_mesh_vertex)
importlib.reload(add_empty_as_parent)
importlib.reload(add_mesh_beam_builder)
importlib.reload(Blocks)
importlib.reload(Wallfactory)
importlib.reload(add_mesh_triangles)
importlib.reload(preferences)
else:
from . import add_mesh_star
from . import add_mesh_twisted_torus
from . import add_mesh_gemstones
from . import add_mesh_gears
from . import add_mesh_3d_function_surface
from . import add_mesh_round_cube
from . import add_mesh_supertoroid
from . import add_mesh_pyramid
from . import add_mesh_torusknot
from . import add_mesh_honeycomb
from . import add_mesh_teapot
from . import add_mesh_pipe_joint
from . import add_mesh_solid
from . import add_mesh_round_brilliant
from . import add_mesh_menger_sponge
from . import add_mesh_vertex
from . import add_empty_as_parent
from . import add_mesh_beam_builder
from . import Blocks
from . import Wallfactory
from . import add_mesh_triangles
from . import preferences
from .add_mesh_rocks import __init__
from .add_mesh_rocks import rockgen
import bpy
from bpy.types import Menu
class VIEW3D_MT_mesh_vert_add(Menu):
# Define the "Single Vert" menu
bl_idname = "VIEW3D_MT_mesh_vert_add"
bl_label = "Single Vert"
def draw(self, context):
layout = self.layout
layout.operator_context = 'INVOKE_REGION_WIN'
layout.operator("mesh.primitive_vert_add",
text="Add Single Vert")
layout.separator()
layout.operator("mesh.primitive_emptyvert_add",
text="Object Origin Only")
layout.operator("mesh.primitive_symmetrical_vert_add",
text="Origin & Vert Mirrored")
layout.operator("mesh.primitive_symmetrical_empty_add",
text="Object Origin Mirrored")
class VIEW3D_MT_mesh_gears_add(Menu):
# Define the "Gears" menu
bl_idname = "VIEW3D_MT_mesh_gears_add"
bl_label = "Gears"
def draw(self, context):
layout = self.layout
layout.operator_context = 'INVOKE_REGION_WIN'
oper = layout.operator("mesh.primitive_gear", text="Gear")
oper.change = False
oper = layout.operator("mesh.primitive_worm_gear", text="Worm")
oper.change = False
class VIEW3D_MT_mesh_gemstones_add(Menu):
# Define the "Gemstones" menu
bl_idname = "VIEW3D_MT_mesh_gemstones_add"
bl_label = "Gemstones"
def draw(self, context):
layout = self.layout
layout.operator_context = 'INVOKE_REGION_WIN'
oper = layout.operator("mesh.primitive_brilliant_add", text="Brilliant")
oper.change = False
oper = layout.operator("mesh.primitive_diamond_add", text="Diamond")
oper.change = False
oper = layout.operator("mesh.primitive_gem_add", text="Gem")
oper.change = False
class VIEW3D_MT_mesh_math_add(Menu):
# Define the "Math Function" menu
bl_idname = "VIEW3D_MT_mesh_math_add"
bl_label = "Math Functions"
def draw(self, context):
layout = self.layout
layout.operator_context = 'INVOKE_REGION_WIN'
layout.operator("mesh.primitive_z_function_surface",
text="Z Math Surface")
layout.operator("mesh.primitive_xyz_function_surface",
text="XYZ Math Surface")
self.layout.operator("mesh.primitive_solid_add", text="Regular Solid")
self.layout.operator("mesh.make_triangle", text="Triangle")
class VIEW3D_MT_mesh_extras_add(Menu):
# Define the "Extra Objects" menu
bl_idname = "VIEW3D_MT_mesh_extras_add"
bl_label = "Extras"
def draw(self, context):
layout = self.layout
layout.operator_context = 'INVOKE_REGION_WIN'
oper = layout.operator("mesh.add_mesh_rock", text="Rock Generator")
oper = layout.operator("mesh.add_beam", text="Beam Builder")
oper.change = False
oper = layout.operator("mesh.wall_add", text="Wall Factory")
oper.change = False
layout.separator()
oper = layout.operator("mesh.primitive_star_add", text="Simple Star")
oper.change = False
oper = layout.operator("mesh.primitive_steppyramid_add", text="Step Pyramid")
oper.change = False
oper = layout.operator("mesh.honeycomb_add", text="Honeycomb")
oper.change = False
oper = layout.operator("mesh.primitive_teapot_add", text="Teapot+")
oper = layout.operator("mesh.menger_sponge_add", text="Menger Sponge")
class VIEW3D_MT_mesh_torus_add(Menu):
# Define the "Torus Objects" menu
bl_idname = "VIEW3D_MT_mesh_torus_add"
bl_label = "Torus Objects"
def draw(self, context):
layout = self.layout
layout.operator_context = 'INVOKE_REGION_WIN'
oper = layout.operator("mesh.primitive_twisted_torus_add", text="Twisted Torus")
oper.change = False
oper = layout.operator("mesh.primitive_supertoroid_add", text="Supertoroid")
oper.change = False
oper = layout.operator("mesh.primitive_torusknot_add", text="Torus Knot")
oper.change = False
class VIEW3D_MT_mesh_pipe_joints_add(Menu):
# Define the "Pipe Joints" menu
bl_idname = "VIEW3D_MT_mesh_pipe_joints_add"
bl_label = "Pipe Joints"
def draw(self, context):
layout = self.layout
layout.operator_context = 'INVOKE_REGION_WIN'
oper = layout.operator("mesh.primitive_elbow_joint_add", text="Elbow")
oper.change = False
oper = layout.operator("mesh.primitive_tee_joint_add", text="T-Joint")
oper.change = False
oper = layout.operator("mesh.primitive_wye_joint_add", text="Y-Joint")
oper.change = False
oper = layout.operator("mesh.primitive_cross_joint_add", text="Cross-Joint")
oper.change = False
oper = layout.operator("mesh.primitive_n_joint_add", text="N-Joint")
oper.change = False
# Register all operators and panels
# Define "Extras" menu
def menu_func(self, context):
layout = self.layout
layout.operator_context = 'INVOKE_REGION_WIN'
prefs = bpy.context.preferences.addons[__package__].preferences
if prefs.show_round_cube:
oper = layout.operator("mesh.primitive_round_cube_add", text="Round Cube", icon='SPHERE')
oper.change = False
layout.separator()
if prefs.show_single_vert:
layout.menu("VIEW3D_MT_mesh_vert_add", text="Single Vert", icon='DECORATE')
if prefs.show_torus_objects:
layout.menu("VIEW3D_MT_mesh_torus_add", text="Torus Objects", icon='MESH_TORUS')
if prefs.show_math_functions:
layout.menu("VIEW3D_MT_mesh_math_add", text="Math Functions", icon='GRAPH')
if prefs.show_gears:
layout.menu("VIEW3D_MT_mesh_gears_add", text="Gears", icon='PREFERENCES')
if prefs.show_pipe_joints:
layout.menu("VIEW3D_MT_mesh_pipe_joints_add", text="Pipe Joints", icon='IPO_CONSTANT')
if prefs.show_gemstones:
layout.menu("VIEW3D_MT_mesh_gemstones_add", text="Gemstones", icon="MESH_ICOSPHERE")
if prefs.show_extras:
layout.menu("VIEW3D_MT_mesh_extras_add", text="Extras", icon="PACKAGE")
if prefs.show_parent_to_empty:
layout.separator()
layout.operator("object.parent_to_empty", text="Parent to Empty", icon="OUTLINER_OB_EMPTY")
def Extras_contex_menu(self, context):
bl_label = 'Change'
obj = context.object
layout = self.layout
if obj is None or obj.data is None:
return
if 'Gear' in obj.data.keys():
props = layout.operator("mesh.primitive_gear", text="Change Gear")
props.change = True
for prm in add_mesh_gears.GearParameters():
setattr(props, prm, obj.data[prm])
layout.separator()
if 'WormGear' in obj.data.keys():
props = layout.operator("mesh.primitive_worm_gear", text="Change WormGear")
props.change = True
for prm in add_mesh_gears.WormGearParameters():
setattr(props, prm, obj.data[prm])
layout.separator()
if 'Beam' in obj.data.keys():
props = layout.operator("mesh.add_beam", text="Change Beam")
props.change = True
for prm in add_mesh_beam_builder.BeamParameters():
setattr(props, prm, obj.data[prm])
layout.separator()
if 'Wall' in obj.data.keys():
props = layout.operator("mesh.wall_add", text="Change Wall")
props.change = True
for prm in Wallfactory.WallParameters():
setattr(props, prm, obj.data[prm])
layout.separator()
if 'ElbowJoint' in obj.data.keys():
props = layout.operator("mesh.primitive_elbow_joint_add", text="Change ElbowJoint")
props.change = True
for prm in add_mesh_pipe_joint.ElbowJointParameters():
setattr(props, prm, obj.data[prm])
layout.separator()
if 'TeeJoint' in obj.data.keys():
props = layout.operator("mesh.primitive_tee_joint_add", text="Change TeeJoint")
props.change = True
for prm in add_mesh_pipe_joint.TeeJointParameters():
setattr(props, prm, obj.data[prm])
layout.separator()
if 'WyeJoint' in obj.data.keys():
props = layout.operator("mesh.primitive_wye_joint_add", text="Change WyeJoint")
props.change = True
for prm in add_mesh_pipe_joint.WyeJointParameters():
setattr(props, prm, obj.data[prm])
layout.separator()
if 'CrossJoint' in obj.data.keys():
props = layout.operator("mesh.primitive_cross_joint_add", text="Change CrossJoint")
props.change = True
for prm in add_mesh_pipe_joint.CrossJointParameters():
setattr(props, prm, obj.data[prm])
layout.separator()
if 'NJoint' in obj.data.keys():
props = layout.operator("mesh.primitive_n_joint_add", text="Change NJoint")
props.change = True
for prm in add_mesh_pipe_joint.NJointParameters():
setattr(props, prm, obj.data[prm])
layout.separator()
if 'Diamond' in obj.data.keys():
props = layout.operator("mesh.primitive_diamond_add", text="Change Diamond")
props.change = True
for prm in add_mesh_gemstones.DiamondParameters():
setattr(props, prm, obj.data[prm])
layout.separator()
if 'Gem' in obj.data.keys():
props = layout.operator("mesh.primitive_gem_add", text="Change Gem")
props.change = True
for prm in add_mesh_gemstones.GemParameters():
setattr(props, prm, obj.data[prm])
layout.separator()
if 'Brilliant' in obj.data.keys():
props = layout.operator("mesh.primitive_brilliant_add", text="Change Brilliant")
props.change = True
for prm in add_mesh_round_brilliant.BrilliantParameters():
setattr(props, prm, obj.data[prm])
layout.separator()
if 'Roundcube' in obj.data.keys():
props = layout.operator("mesh.primitive_round_cube_add", text="Change Roundcube")
props.change = True
for prm in add_mesh_round_cube.RoundCubeParameters():
setattr(props, prm, obj.data[prm])
layout.separator()
if 'TorusKnot' in obj.data.keys():
props = layout.operator("mesh.primitive_torusknot_add", text="Change TorusKnot")
props.change = True
for prm in add_mesh_torusknot.TorusKnotParameters():
setattr(props, prm, obj.data[prm])
layout.separator()
if 'SuperToroid' in obj.data.keys():
props = layout.operator("mesh.primitive_supertoroid_add", text="Change SuperToroid")
props.change = True
for prm in add_mesh_supertoroid.SuperToroidParameters():
setattr(props, prm, obj.data[prm])
layout.separator()
if 'TwistedTorus' in obj.data.keys():
props = layout.operator("mesh.primitive_twisted_torus_add", text="Change TwistedTorus")
props.change = True
for prm in add_mesh_twisted_torus.TwistedTorusParameters():
setattr(props, prm, obj.data[prm])
layout.separator()
if 'Star' in obj.data.keys():
props = layout.operator("mesh.primitive_star_add", text="Change Star")
props.change = True
for prm in add_mesh_star.StarParameters():
setattr(props, prm, obj.data[prm])
layout.separator()
if 'Pyramid' in obj.data.keys():
props = layout.operator("mesh.primitive_steppyramid_add", text="Change Pyramid")
props.change = True
for prm in add_mesh_pyramid.PyramidParameters():
setattr(props, prm, obj.data[prm])
layout.separator()
if 'HoneyComb' in obj.data.keys():
props = layout.operator("mesh.honeycomb_add", text="Change HoneyComb")
props.change = True
for prm in add_mesh_honeycomb.HoneyCombParameters():
setattr(props, prm, obj.data[prm])
layout.separator()
# Register
classes = [
VIEW3D_MT_mesh_vert_add,
VIEW3D_MT_mesh_gears_add,
VIEW3D_MT_mesh_gemstones_add,
VIEW3D_MT_mesh_math_add,
VIEW3D_MT_mesh_extras_add,
VIEW3D_MT_mesh_torus_add,
VIEW3D_MT_mesh_pipe_joints_add,
add_mesh_star.AddStar,
add_mesh_twisted_torus.AddTwistedTorus,
add_mesh_gemstones.AddDiamond,
add_mesh_gemstones.AddGem,
add_mesh_gears.AddGear,
add_mesh_gears.AddWormGear,
add_mesh_3d_function_surface.AddZFunctionSurface,
add_mesh_3d_function_surface.AddXYZFunctionSurface,
add_mesh_round_cube.AddRoundCube,
add_mesh_supertoroid.add_supertoroid,
add_mesh_pyramid.AddPyramid,
add_mesh_torusknot.AddTorusKnot,
add_mesh_honeycomb.add_mesh_honeycomb,
add_mesh_teapot.AddTeapot,
add_mesh_pipe_joint.AddElbowJoint,
add_mesh_pipe_joint.AddTeeJoint,
add_mesh_pipe_joint.AddWyeJoint,
add_mesh_pipe_joint.AddCrossJoint,
add_mesh_pipe_joint.AddNJoint,
add_mesh_solid.Solids,
add_mesh_round_brilliant.MESH_OT_primitive_brilliant_add,
add_mesh_menger_sponge.AddMengerSponge,
add_mesh_vertex.AddVert,
add_mesh_vertex.AddEmptyVert,
add_mesh_vertex.AddSymmetricalEmpty,
add_mesh_vertex.AddSymmetricalVert,
add_empty_as_parent.P2E,
add_empty_as_parent.PreFix,
add_mesh_beam_builder.addBeam,
Wallfactory.add_mesh_wallb,
add_mesh_triangles.MakeTriangle,
preferences.AddMeshExtraObjectsPreferences,
]
def register():
import os
from bpy.utils import register_class
for cls in classes:
register_class(cls)
add_mesh_rocks.register()
# Add "Extras" menu to the "Add Mesh" menu and context menu.
bpy.types.VIEW3D_MT_mesh_add.append(menu_func)
bpy.types.VIEW3D_MT_object_context_menu.prepend(Extras_contex_menu)
# Part of 4.3 may be back-ported to 4.2.
if register_preset_path := getattr(bpy.utils, "register_preset_path", None):
register_preset_path(os.path.join(os.path.dirname(__file__)))
def unregister():
import os
# Remove "Extras" menu from the "Add Mesh" menu and context menu.
bpy.types.VIEW3D_MT_object_context_menu.remove(Extras_contex_menu)
bpy.types.VIEW3D_MT_mesh_add.remove(menu_func)
from bpy.utils import unregister_class
for cls in reversed(classes):
unregister_class(cls)
add_mesh_rocks.unregister()
# Part of 4.3 may be back-ported to 4.2.
if unregister_preset_path := getattr(bpy.utils, "unregister_preset_path", None):
unregister_preset_path(os.path.join(os.path.dirname(__file__)))
if __name__ == "__main__":
register()
@@ -0,0 +1,133 @@
# SPDX-FileCopyrightText: 2015-2022 Blender Foundation
#
# SPDX-License-Identifier: GPL-2.0-or-later
# Original Author Liero
import bpy
from bpy.types import Operator
from bpy.props import (
StringProperty,
BoolProperty,
EnumProperty,
)
def centro(sel):
x = sum([obj.location[0] for obj in sel]) / len(sel)
y = sum([obj.location[1] for obj in sel]) / len(sel)
z = sum([obj.location[2] for obj in sel]) / len(sel)
return (x, y, z)
class P2E(Operator):
bl_idname = "object.parent_to_empty"
bl_label = "Parent to Empty"
bl_description = "Parent selected objects to a new Empty"
bl_options = {"REGISTER", "UNDO"}
nombre: StringProperty(
name="",
default='OBJECTS',
description='Give the empty / group a name'
)
grupo: BoolProperty(
name="Create Group",
default=False,
description="Also add objects to a group"
)
locat: EnumProperty(
name='',
items=[('CURSOR', 'Cursor', 'Cursor'), ('ACTIVE', 'Active', 'Active'),
('CENTER', 'Center', 'Selection Center')],
description='Empty location',
default='CENTER'
)
renom: BoolProperty(
name="Add Prefix",
default=False,
description="Add prefix to objects name"
)
@classmethod
def poll(cls, context):
objs = context.selected_objects
return (len(objs) > 0)
def draw(self, context):
layout = self.layout
layout.prop(self, "nombre")
column = layout.column(align=True)
column.prop(self, "locat")
column.prop(self, "grupo")
column.prop(self, "renom")
def execute(self, context):
objs = context.selected_objects
act = context.object
sce = context.scene
try:
bpy.ops.object.mode_set()
except:
pass
if self.locat == 'CURSOR':
loc = sce.cursor.location
elif self.locat == 'ACTIVE':
loc = act.location
else:
loc = centro(objs)
bpy.ops.object.add(type='EMPTY', location=loc)
context.object.name = self.nombre
context.object.show_name = True
context.object.show_in_front = True
if self.grupo:
bpy.ops.collection.create(name=self.nombre)
bpy.ops.collection.objects_add_active()
for o in objs:
o.select_set(True)
if not o.parent:
bpy.ops.object.parent_set(type='OBJECT')
if self.grupo:
bpy.ops.collection.objects_add_active()
o.select_set(False)
for o in objs:
if self.renom:
o.name = self.nombre + '_' + o.name
return {'FINISHED'}
class PreFix(Operator):
bl_idname = "object.toggle_prefix"
bl_label = "Toggle Sufix"
bl_description = "Toggle parent name as sufix for c"
bl_options = {"REGISTER", "UNDO"}
@classmethod
def poll(cls, context):
act = context.object
return (act and act.type == 'EMPTY')
def execute(self, context):
act = context.object
objs = act.children
prefix = act.name + '_'
remove = False
for o in objs:
if o.name.startswith(prefix):
remove = True
break
if remove is True:
for o in objs:
if o.name.startswith(prefix):
o.name = o.name.partition(prefix)[2]
else:
for o in objs:
o.name = prefix + o.name
return {'FINISHED'}
@@ -0,0 +1,612 @@
# SPDX-FileCopyrightText: 2010-2022 Blender Foundation
#
# SPDX-License-Identifier: GPL-2.0-or-later
# Original by Buerbaum Martin (Pontiac), Elod Csirmaz
import bpy
import math
import numpy
from mathutils import *
from math import *
from bpy.types import Operator
from bpy.props import (
StringProperty,
IntProperty,
FloatProperty,
BoolProperty,
)
# List of safe functions for eval()
safe_list = ['acos', 'asin', 'atan', 'atan2', 'ceil', 'cos', 'cosh',
'degrees', 'e', 'exp', 'fabs', 'floor', 'fmod', 'frexp', 'hypot',
'ldexp', 'log', 'log10', 'modf', 'pi', 'pow', 'radians',
'sin', 'sinh', 'sqrt', 'tan', 'tanh', 'gcd']
# Use the list to filter the local namespace
safe_dict = dict((k, globals().get(k, None)) for k in safe_list)
safe_dict['math'] = math
safe_dict['numpy'] = safe_dict['np'] = numpy
safe_dict['lcm'] = numpy.lcm
safe_dict['max'] = max
safe_dict['min'] = min
# Stores the values of a list of properties and the
# operator id in a property group ('recall_op') inside the object
# Could (in theory) be used for non-objects.
# Note: Replaces any existing property group with the same name!
# ob ... Object to store the properties in
# op ... The operator that should be used
# op_args ... A dictionary with valid Blender
# properties (operator arguments/parameters)
# Create a new mesh (object) from verts/edges/faces
# verts/edges/faces ... List of vertices/edges/faces for the
# new mesh (as used in from_pydata)
# name ... Name of the new mesh (& object)
def create_mesh_object(context, verts, edges, faces, name):
# Create new mesh
mesh = bpy.data.meshes.new(name)
# Make a mesh from a list of verts/edges/faces
mesh.from_pydata(verts, edges, faces)
# Update mesh geometry after adding stuff
mesh.update()
from bpy_extras import object_utils
return object_utils.object_data_add(context, mesh, operator=None)
# A very simple "bridge" tool
def createFaces(vertIdx1, vertIdx2, closed=False, flipped=False):
faces = []
if not vertIdx1 or not vertIdx2:
return None
if len(vertIdx1) < 2 and len(vertIdx2) < 2:
return None
fan = False
if (len(vertIdx1) != len(vertIdx2)):
if (len(vertIdx1) == 1 and len(vertIdx2) > 1):
fan = True
else:
return None
total = len(vertIdx2)
if closed:
# Bridge the start with the end
if flipped:
face = [
vertIdx1[0],
vertIdx2[0],
vertIdx2[total - 1]]
if not fan:
face.append(vertIdx1[total - 1])
faces.append(face)
else:
face = [vertIdx2[0], vertIdx1[0]]
if not fan:
face.append(vertIdx1[total - 1])
face.append(vertIdx2[total - 1])
faces.append(face)
# Bridge the rest of the faces
for num in range(total - 1):
if flipped:
if fan:
face = [vertIdx2[num], vertIdx1[0], vertIdx2[num + 1]]
else:
face = [vertIdx2[num], vertIdx1[num],
vertIdx1[num + 1], vertIdx2[num + 1]]
faces.append(face)
else:
if fan:
face = [vertIdx1[0], vertIdx2[num], vertIdx2[num + 1]]
else:
face = [vertIdx1[num], vertIdx2[num],
vertIdx2[num + 1], vertIdx1[num + 1]]
faces.append(face)
return faces
class AddZFunctionSurface(Operator):
bl_idname = "mesh.primitive_z_function_surface"
bl_label = "Add Z Function Surface"
bl_description = "Add a surface defined defined by a function z=f(x,y)"
bl_options = {'REGISTER', 'UNDO', 'PRESET'}
equation: StringProperty(
name="Z Equation",
description="Equation for z=f(x,y)",
default="1 - ( x**2 + y**2 )"
)
div_x: IntProperty(
name="X Subdivisions",
description="Number of vertices in x direction",
default=16,
min=3,
max=256
)
div_y: IntProperty(
name="Y Subdivisions",
description="Number of vertices in y direction",
default=16,
min=3,
max=256
)
size_x: FloatProperty(
name="X Size",
description="Size of the x axis",
default=2.0,
min=0.01,
max=100.0,
unit="LENGTH"
)
size_y: FloatProperty(
name="Y Size",
description="Size of the y axis",
default=2.0,
min=0.01,
max=100.0,
unit="LENGTH"
)
def draw(self, context):
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
layout.separator()
layout.prop(self, 'equation')
col = layout.column(align=True)
col.prop(self, 'div_x', text='Subdivisions X')
col.prop(self, 'div_y', text='Y')
col = layout.column(align=True)
col.prop(self, 'size_x', text='Size X')
col.prop(self, 'size_y', text='Y')
def execute(self, context):
equation = self.equation
div_x = self.div_x
div_y = self.div_y
size_x = self.size_x
size_y = self.size_y
verts = []
faces = []
delta_x = size_x / (div_x - 1)
delta_y = size_y / (div_y - 1)
start_x = -(size_x / 2.0)
start_y = -(size_y / 2.0)
edgeloop_prev = []
if equation:
try:
expr_args = (
compile(equation, __file__, 'eval'),
{"__builtins__": None},
safe_dict)
except:
import traceback
# WARNING is used to prevent the constant pop-up spam
self.report({'WARNING'},
"Error parsing expression: {} "
"(Check the console for more info)".format(equation))
print("\n[Add Z Function Surface]:\n\n", traceback.format_exc(limit=1))
return {'CANCELLED'}
for row_x in range(div_x):
edgeloop_cur = []
x = start_x + row_x * delta_x
for row_y in range(div_y):
y = start_y + row_y * delta_y
z = 0.0
safe_dict['x'] = x
safe_dict['y'] = y
# Try to evaluate the equation.
try:
z = float(eval(*expr_args))
except:
import traceback
self.report({'WARNING'},
"Error evaluating expression: {} "
"(Check the console for more info)".format(equation))
print("\n[Add Z Function Surface]:\n\n", traceback.format_exc(limit=1))
return {'CANCELLED'}
edgeloop_cur.append(len(verts))
verts.append((x, y, z))
if len(edgeloop_prev) > 0:
faces_row = createFaces(edgeloop_prev, edgeloop_cur)
faces.extend(faces_row)
edgeloop_prev = edgeloop_cur
base = create_mesh_object(context, verts, [], faces, "Z Function")
else:
self.report({'WARNING'}, "Z Equation - No expression is given")
return {'CANCELLED'}
return {'FINISHED'}
def xyz_function_surface_faces(self, x_eq, y_eq, z_eq,
range_u_min, range_u_max, range_u_step, wrap_u,
range_v_min, range_v_max, range_v_step, wrap_v,
a_eq, b_eq, c_eq, f_eq, g_eq, h_eq, n, close_v):
verts = []
faces = []
# Distance of each step in Blender Units
uStep = (range_u_max - range_u_min) / range_u_step
vStep = (range_v_max - range_v_min) / range_v_step
# Number of steps in the vertex creation loops.
# Number of steps is the number of faces
# => Number of points is +1 unless wrapped.
uRange = range_u_step + 1
vRange = range_v_step + 1
if wrap_u:
uRange = uRange - 1
if wrap_v:
vRange = vRange - 1
try:
expr_args_x = (
compile(x_eq, __file__.replace(".py", "_x.py"), 'eval'),
{"__builtins__": None},
safe_dict)
expr_args_y = (
compile(y_eq, __file__.replace(".py", "_y.py"), 'eval'),
{"__builtins__": None},
safe_dict)
expr_args_z = (
compile(z_eq, __file__.replace(".py", "_z.py"), 'eval'),
{"__builtins__": None},
safe_dict)
expr_args_a = (
compile(a_eq, __file__.replace(".py", "_a.py"), 'eval'),
{"__builtins__": None},
safe_dict)
expr_args_b = (
compile(b_eq, __file__.replace(".py", "_b.py"), 'eval'),
{"__builtins__": None},
safe_dict)
expr_args_c = (
compile(c_eq, __file__.replace(".py", "_c.py"), 'eval'),
{"__builtins__": None},
safe_dict)
expr_args_f = (
compile(f_eq, __file__.replace(".py", "_f.py"), 'eval'),
{"__builtins__": None},
safe_dict)
expr_args_g = (
compile(g_eq, __file__.replace(".py", "_g.py"), 'eval'),
{"__builtins__": None},
safe_dict)
expr_args_h = (
compile(h_eq, __file__.replace(".py", "_h.py"), 'eval'),
{"__builtins__": None},
safe_dict)
except:
import traceback
self.report({'WARNING'}, "Error parsing expression(s) - "
"Check the console for more info")
print("\n[Add X, Y, Z Function Surface]:\n\n", traceback.format_exc(limit=1))
return [], []
for vN in range(vRange):
v = range_v_min + (vN * vStep)
for uN in range(uRange):
u = range_u_min + (uN * uStep)
safe_dict['u'] = u
safe_dict['v'] = v
safe_dict['n'] = n
# Try to evaluate the equations.
try:
safe_dict['a'] = float(eval(*expr_args_a))
safe_dict['b'] = float(eval(*expr_args_b))
safe_dict['c'] = float(eval(*expr_args_c))
safe_dict['f'] = float(eval(*expr_args_f))
safe_dict['g'] = float(eval(*expr_args_g))
safe_dict['h'] = float(eval(*expr_args_h))
verts.append((
float(eval(*expr_args_x)),
float(eval(*expr_args_y)),
float(eval(*expr_args_z))))
except:
import traceback
self.report({'WARNING'}, "Error evaluating expression(s) - "
"Check the console for more info")
print("\n[Add X, Y, Z Function Surface]:\n\n", traceback.format_exc(limit=1))
return [], []
for vN in range(range_v_step):
vNext = vN + 1
if wrap_v and (vNext >= vRange):
vNext = 0
for uN in range(range_u_step):
uNext = uN + 1
if wrap_u and (uNext >= uRange):
uNext = 0
faces.append([(vNext * uRange) + uNext,
(vNext * uRange) + uN,
(vN * uRange) + uN,
(vN * uRange) + uNext])
if close_v and wrap_u and (not wrap_v):
for uN in range(1, range_u_step - 1):
faces.append([
range_u_step - 1,
range_u_step - 1 - uN,
range_u_step - 2 - uN])
faces.append([
range_v_step * uRange,
range_v_step * uRange + uN,
range_v_step * uRange + uN + 1])
return verts, faces
# Original Script "Parametric.py" by Ed Mackey.
# -> http://www.blinken.com/blender-plugins.php
# Partly converted for Blender 2.5 by tuga3d.
#
# Sphere:
# x = sin(2*pi*u)*sin(pi*v)
# y = cos(2*pi*u)*sin(pi*v)
# z = cos(pi*v)
# u_min = v_min = 0
# u_max = v_max = 1
#
# "Snail shell"
# x = 1.2**v*(sin(u)**2 *sin(v))
# y = 1.2**v*(sin(u)*cos(u))
# z = 1.2**v*(sin(u)**2 *cos(v))
# u_min = 0
# u_max = pi
# v_min = -pi/4,
# v max = 5*pi/2
class AddXYZFunctionSurface(Operator):
bl_idname = "mesh.primitive_xyz_function_surface"
bl_label = "Add XYZ Function Surface"
bl_description = ("Add a surface defined defined by 3 functions:\n"
"x=F1(u,v), y=F2(u,v) and z=F3(u,v)")
bl_options = {'REGISTER', 'UNDO', 'PRESET'}
x_eq: StringProperty(
name="X Equation",
description="Equation for x=F(u,v). "
"Also available: n, a, b, c, f, g, h",
default="cos(v)*(1+cos(u))*sin(v/8)"
)
y_eq: StringProperty(
name="Y Equation",
description="Equation for y=F(u,v). "
"Also available: n, a, b, c, f, g, h",
default="sin(u)*sin(v/8)+cos(v/8)*1.5"
)
z_eq: StringProperty(
name="Z Equation",
description="Equation for z=F(u,v). "
"Also available: n, a, b, c, f, g, h",
default="sin(v)*(1+cos(u))*sin(v/8)"
)
range_u_min: FloatProperty(
name="U Min",
description="Minimum U value. Lower boundary of U range",
min=-100.00,
max=0.00,
default=0.00
)
range_u_max: FloatProperty(
name="U Max",
description="Maximum U value. Upper boundary of U range",
min=0.00,
max=100.00,
default=2 * pi
)
range_u_step: IntProperty(
name="U Step",
description="U Subdivisions",
min=1,
max=1024,
default=32
)
wrap_u: BoolProperty(
name="U Wrap",
description="U Wrap around",
default=True
)
range_v_min: FloatProperty(
name="V Min",
description="Minimum V value. Lower boundary of V range",
min=-100.00,
max=0.00,
default=0.00
)
range_v_max: FloatProperty(
name="V Max",
description="Maximum V value. Upper boundary of V range",
min=0.00,
max=100.00,
default=4 * pi
)
range_v_step: IntProperty(
name="V Step",
description="V Subdivisions",
min=1,
max=1024,
default=128
)
wrap_v: BoolProperty(
name="V Wrap",
description="V Wrap around",
default=False
)
close_v: BoolProperty(
name="Close V",
description="Create faces for first and last "
"V values (only if U is wrapped)",
default=False
)
n_eq: IntProperty(
name="Number of Objects (n=0..N-1)",
description="The parameter n will be the index "
"of the current object, 0 to N-1",
min=1,
max=100,
default=1
)
a_eq: StringProperty(
name="A Helper Function",
description="Equation for a=F(u,v). Also available: n",
default="0"
)
b_eq: StringProperty(
name="B Helper Function",
description="Equation for b=F(u,v). Also available: n",
default="0"
)
c_eq: StringProperty(
name="C Helper Function",
description="Equation for c=F(u,v). Also available: n",
default="0"
)
f_eq: StringProperty(
name="F Helper Function",
description="Equation for f=F(u,v). Also available: n, a, b, c",
default="0"
)
g_eq: StringProperty(
name="G Helper Function",
description="Equation for g=F(u,v). Also available: n, a, b, c",
default="0"
)
h_eq: StringProperty(
name="H Helper Function",
description="Equation for h=F(u,v). Also available: n, a, b, c",
default="0"
)
show_wire : BoolProperty(
name="Show Wireframe",
default=True,
description="Add the objects wireframe over solid drawing"
)
edit_mode : BoolProperty(
name="Show in Edit Mode",
default=True,
description="Show in Edit Mode"
)
def draw(self, context):
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
layout.separator()
col = layout.column()
col.prop(self, 'x_eq', text='Equation X')
col.prop(self, 'y_eq', text='Y')
col.prop(self, 'z_eq', text='Z')
layout.separator()
col = layout.column(align=True)
col.prop(self, 'range_u_min', text='U Min')
col.prop(self, 'range_u_max', text='Max')
col.prop(self, 'range_u_step', text='Step')
col.prop(self, 'wrap_u', text='Wrap')
layout.separator()
col = layout.column(align=True)
col.prop(self, 'range_v_min', text='V Min')
col.prop(self, 'range_v_max', text='Max')
col.prop(self, 'range_v_step', text='Step')
col.prop(self, 'wrap_v', text='Wrap')
col.prop(self, 'close_v', text='Close')
layout.separator()
col = layout.column()
col.prop(self, 'n_eq', text='Objects')
col.prop(self, 'a_eq', text='Helper Function A')
col.prop(self, 'b_eq', text='B')
col.prop(self, 'c_eq', text='C')
col.prop(self, 'f_eq', text='F')
col.prop(self, 'g_eq', text='G')
col.prop(self, 'h_eq', text='H')
layout.separator()
row = layout.row(heading='Show')
row.prop(self, 'show_wire', text='Wireframe')
layout.prop(self, 'edit_mode', text='In Edit Mode')
def execute(self, context):
for n in range(0, self.n_eq):
verts, faces = xyz_function_surface_faces(
self,
self.x_eq,
self.y_eq,
self.z_eq,
self.range_u_min,
self.range_u_max,
self.range_u_step,
self.wrap_u,
self.range_v_min,
self.range_v_max,
self.range_v_step,
self.wrap_v,
self.a_eq,
self.b_eq,
self.c_eq,
self.f_eq,
self.g_eq,
self.h_eq,
n,
self.close_v
)
if not verts:
return {'CANCELLED'}
obj = create_mesh_object(context, verts, [], faces, "XYZ Function")
if self.show_wire:
context.active_object.show_wire = True
else:
context.active_object.show_wire = False
if self.edit_mode:
bpy.ops.object.mode_set(mode = 'EDIT')
else:
bpy.ops.object.mode_set(mode = 'OBJECT')
return {'FINISHED'}
@@ -0,0 +1,822 @@
# SPDX-FileCopyrightText: 2016-2022 Blender Foundation
#
# SPDX-License-Identifier: GPL-2.0-or-later
# Author: revolt_randy, Jambay
# Create "Beam" primitives. Based on original script by revolt_randy
import bpy
from bpy.types import Operator
from bpy.props import (
BoolProperty,
EnumProperty,
FloatProperty,
IntProperty,
StringProperty,
)
from bpy_extras import object_utils
from .interface import draw_transform_props
# #####################
# Create vertices for end of mesh
#
# y_off - verts y-axis origin
#
# returns:
# endVs - x,y,z list
def beamEndVs(sRef, y_off):
thick = sRef.beamW * 2
if sRef.Type == '2': # swap width and height for C shape
bEndX2 = sRef.beamZ / 2
bEndXInr = ((sRef.beamZ - thick) / 2)
bEndZ2 = sRef.beamX / 2
bEndZInr = ((sRef.beamX - thick) / 2)
else:
bEndX2 = sRef.beamX / 2
bEndXInr = ((sRef.beamX - thick) / 2)
bEndZ2 = sRef.beamZ / 2
bEndZInr = ((sRef.beamZ - thick) / 2)
endVs = []
# outer ...
endVs.append((bEndX2, y_off, bEndZ2))
endVs.append((-bEndX2, y_off, bEndZ2))
endVs.append((-bEndX2, y_off, -bEndZ2))
endVs.append((bEndX2, y_off, -bEndZ2))
# innner ...
endVs.append((bEndXInr, y_off, bEndZInr))
endVs.append((-bEndXInr, y_off, bEndZInr))
endVs.append((-bEndXInr, y_off, -bEndZInr))
endVs.append((bEndXInr, y_off, -bEndZInr))
return endVs
# #####################
# Create End Faces
#
# verts_list - list of vertices
#
# returns:
# beamFs, a list of tuples defining the end faces.
def beamEndFaces(verts_list):
beamFs = []
num_of_verts = int(len(verts_list) / 2)
# Create list of faces
for index in range(num_of_verts):
faces_temp = []
if index == (num_of_verts - 1):
faces_temp.append(verts_list[index])
faces_temp.append(verts_list[index - index])
faces_temp.append(verts_list[index + 1])
faces_temp.append(verts_list[index * 2 + 1])
else:
faces_temp.append(verts_list[index])
faces_temp.append(verts_list[index + 1])
faces_temp.append(verts_list[index + num_of_verts + 1])
faces_temp.append(verts_list[index + num_of_verts])
beamFs.append(tuple(faces_temp))
return beamFs
# #####################
# Bridge vertices to create side faces.
#
# front_verts - front face vertices
# back_verts - back face vertices
# front & back must be ordered in same direction
# with respect to y-axis
#
# returns:
# sideFaces, a list of the bridged faces
def beamSides(front_verts, back_verts):
sideFaces = []
num_of_faces = (len(front_verts))
# add first value to end of lists for looping
front_verts.append(front_verts[0])
back_verts.append(back_verts[0])
# Build the faces
for index in range(num_of_faces):
facestemp = (front_verts[index], front_verts[index + 1], back_verts[index + 1], back_verts[index])
sideFaces.append(facestemp)
return sideFaces
# #####################
# Creates a box beam
#
# returns:
# beamVs - x, y, z, location of each vertice
# beamFs - vertices that make up each face
def create_beam(sRef):
frontVs = []
frontFs = []
backVs = []
y_off = sRef.beamY / 2 # offset from center for vertices
frontVs = beamEndVs(sRef, y_off)
backVs = beamEndVs(sRef, -y_off)
# Combine vertices
beamVs = frontVs + backVs
# Create front face
numofverts = len(frontVs)
verts_front_list = []
for index in range(numofverts):
verts_front_list.append(index)
frontFs = beamEndFaces(verts_front_list)
# Create back face
faces_back_temp = []
verts_back_list = []
numofverts = len(backVs)
for index in range(numofverts):
verts_back_list.append(index + numofverts)
faces_back_temp = beamEndFaces(verts_back_list)
# Create side faces
faces_side_temp = []
# Object has thickness, create list of outside vertices
numofverts = len(verts_front_list)
halfVerts = int(numofverts / 2)
frontVs = verts_front_list[0:halfVerts]
backVs = verts_back_list[0:halfVerts]
faces_side_temp = beamSides(frontVs, backVs)
# Create list of inside vertices
frontVs = verts_front_list[halfVerts:numofverts]
backVs = verts_back_list[halfVerts:numofverts]
faces_side_temp += beamSides(frontVs, backVs)
# Combine all faces
beamFs = frontFs + faces_back_temp + faces_side_temp
return beamVs, beamFs
# #####################
# Taper/angle faces of beam.
# inner vert toward outer vert
# based on percentage of taper.
#
# returns:
# adVert - the calculated vertex
def beamSlant(sRef, outV, inV):
bTaper = 100 - sRef.edgeA
# calculate variance & adjust vertex
deltaV = ((inV - outV) / 100)
adVert = outV + (deltaV * bTaper)
return adVert
# #####################
# Modify location to shape beam.
#
# verts - tuples for one end of beam
#
# returns:
# verts - modified tuples for beam shape.
def beamSquareEnds(sRef, verts):
# match 5th & 6th z locations to 1st & 2nd
vert_orig = verts[0]
vert_temp = verts[4]
vert_x = beamSlant(sRef, vert_orig[0], vert_temp[0])
verts[4] = (vert_x, vert_temp[1], vert_orig[2])
vert_orig = verts[1]
vert_temp = verts[5]
vert_x = beamSlant(sRef, vert_orig[0], vert_temp[0])
verts[5] = (vert_x, vert_temp[1], vert_orig[2])
return verts
# #####################
#
# Create U shaped beam
# Shared with C shape - see beamEndVs
# for sizing and rotate in addBeamObj.
#
# returns:
# beamVs - vertice x, y, z, locations
# beamFs - face vertices
def create_u_beam(sRef):
# offset vertices from center
y_off = sRef.beamY / 2
frontVtemp = []
frontFtemp = []
frontVlist = []
backVtemp = []
backFtemp = []
backVlist = []
sideFs = []
frontVtemp = beamEndVs(sRef, y_off) # Box beam
frontVtemp = beamSquareEnds(sRef, frontVtemp) # U shape
backVtemp = beamEndVs(sRef, -y_off)
backVtemp = beamSquareEnds(sRef, backVtemp)
beamVs = frontVtemp + backVtemp
# Create front face
for index in range(len(frontVtemp)): # Build vert list
frontVlist.append(index)
frontFtemp = beamEndFaces(frontVlist)
frontFtemp = frontFtemp[1:4] # Remove 1st face
# Create back face
numofverts = len(backVtemp)
for index in range(numofverts): # Build vertex list
backVlist.append(index + numofverts)
backFtemp = beamEndFaces(backVlist)
backFtemp = backFtemp[1:4] # Remove face
# Create list vertices for outside faces
numofverts = int(len(frontVlist))
halfVerts = int(numofverts / 2)
frontVtemp = frontVlist[0:halfVerts]
backVtemp = backVlist[0:halfVerts]
sideFs = beamSides(frontVtemp, backVtemp)
sideFs = sideFs[1:] # Remove face
# Create inside verts
frontVtemp = frontVlist[halfVerts:numofverts]
backVtemp = backVlist[halfVerts:numofverts]
sideFs += beamSides(frontVtemp, backVtemp)
sideFs = sideFs[0:3] + sideFs[4:] # Remove face
# fill in faces
sideFs.append((0, 4, 12, 8))
sideFs.append((5, 1, 9, 13))
beamFs = frontFtemp + backFtemp + sideFs # Combine faces
return beamVs, beamFs
# #####################
# returns:
# verts_final - x, y, z, location of each vertice
# faces_final - vertices that make up each face
def create_L_beam(sRef):
thick = sRef.beamW
# offset vertices from center
x_off = sRef.beamX / 2
y_off = sRef.beamY / 2
z_off = sRef.beamZ / 2
# Create temporarylists to hold vertices locations
verts_front_temp = []
verts_back_temp = []
# Create front vertices by calculation
verts_front_temp = [
(-x_off, -y_off, z_off),
(-(x_off - thick), -y_off, z_off),
(-(x_off - thick), -y_off, -(z_off - thick)),
(x_off, -y_off, -(z_off - thick)),
(x_off, -y_off, -z_off),
(-x_off, -y_off, -z_off)
]
# Adjust taper
vert_outside = verts_front_temp[0]
vert_inside = verts_front_temp[1]
vert_taper = beamSlant(sRef, vert_outside[0], vert_inside[0])
verts_front_temp[1] = [vert_taper, vert_inside[1], vert_inside[2]]
vert_outside = verts_front_temp[4]
vert_inside = verts_front_temp[3]
vert_taper = beamSlant(sRef, vert_outside[2], vert_inside[2])
verts_front_temp[3] = [vert_inside[0], vert_inside[1], vert_taper]
# Create back vertices by calculation
verts_back_temp = [
(-x_off, y_off, z_off),
(-(x_off - thick), y_off, z_off),
(-(x_off - thick), y_off, -(z_off - thick)),
(x_off, y_off, -(z_off - thick)),
(x_off, y_off, -z_off),
(-x_off, y_off, -z_off)
]
# Adjust taper
vert_outside = verts_back_temp[0]
vert_inside = verts_back_temp[1]
vert_taper = beamSlant(sRef, vert_outside[0], vert_inside[0])
verts_back_temp[1] = [vert_taper, vert_inside[1], vert_inside[2]]
vert_outside = verts_back_temp[4]
vert_inside = verts_back_temp[3]
vert_taper = beamSlant(sRef, vert_outside[2], vert_inside[2])
verts_back_temp[3] = [vert_inside[0], vert_inside[1], vert_taper]
verts_final = verts_front_temp + verts_back_temp
# define end faces, only 4 so just coded
faces_front_temp = []
faces_back_temp = []
faces_side_temp = []
faces_front_temp = [(0, 1, 2, 5), (2, 3, 4, 5)]
faces_back_temp = [(6, 7, 8, 11), (8, 9, 10, 11)]
verts_front_list = []
verts_back_list = []
num_of_verts = len(verts_front_temp)
# build lists of back and front verts for beamSides function
for index in range(num_of_verts):
verts_front_list.append(index)
for index in range(num_of_verts):
verts_back_list.append(index + 6)
faces_side_temp = beamSides(verts_front_list, verts_back_list)
faces_final = faces_front_temp + faces_back_temp + faces_side_temp
return verts_final, faces_final
# #####################
# returns:
# verts_final - a list of tuples of the x, y, z, location of each vertice
# faces_final - a list of tuples of the vertices that make up each face
def create_T_beam(sRef):
thick = sRef.beamW
# Get offset of vertices from center
x_off = sRef.beamX / 2
y_off = sRef.beamY / 2
z_off = sRef.beamZ / 2
thick_off = thick / 2
# Create temporarylists to hold vertices locations
verts_front_temp = []
verts_back_temp = []
# Create front vertices
verts_front_temp = [
(-x_off, -y_off, z_off),
(-thick_off, -y_off, z_off),
(thick_off, -y_off, z_off),
(x_off, -y_off, z_off),
(x_off, -y_off, z_off - thick),
(thick_off, -y_off, z_off - thick),
(thick_off, -y_off, -z_off),
(-thick_off, -y_off, -z_off),
(-thick_off, -y_off, z_off - thick),
(-x_off, -y_off, z_off - thick)
]
# Adjust taper
vert_outside = verts_front_temp[0]
vert_inside = verts_front_temp[9]
vert_taper = (beamSlant(sRef, vert_outside[2], vert_inside[2]))
verts_front_temp[9] = [vert_inside[0], vert_inside[1], vert_taper]
vert_outside = verts_front_temp[3]
vert_inside = verts_front_temp[4]
verts_front_temp[4] = [vert_inside[0], vert_inside[1], vert_taper]
# Adjust taper of bottom of beam, so 0 the center
# now becomes vert_outside, and vert_inside is calculated
# 1/2 way towards center
vert_outside = (0, -y_off, -z_off)
vert_inside = verts_front_temp[6]
vert_taper = (beamSlant(sRef, vert_outside[0], vert_inside[0]))
verts_front_temp[6] = [vert_taper, vert_inside[1], vert_inside[2]]
vert_outside = (0, -y_off, -z_off)
vert_inside = verts_front_temp[7]
vert_taper = beamSlant(sRef, vert_outside[0], vert_inside[0])
verts_front_temp[7] = [vert_taper, vert_inside[1], vert_inside[2]]
# Create fack vertices by calculation
verts_back_temp = [
(-x_off, y_off, z_off),
(-thick_off, y_off, z_off),
(thick_off, y_off, z_off),
(x_off, y_off, z_off),
(x_off, y_off, z_off - thick),
(thick_off, y_off, z_off - thick),
(thick_off, y_off, -z_off),
(-thick_off, y_off, -z_off),
(-thick_off, y_off, z_off - thick),
(-x_off, y_off, z_off - thick)
]
# Adjust taper
vert_outside = verts_back_temp[0]
vert_inside = verts_back_temp[9]
vert_taper = (beamSlant(sRef, vert_outside[2], vert_inside[2]))
verts_back_temp[9] = [vert_inside[0], vert_inside[1], vert_taper]
vert_outside = verts_back_temp[3]
vert_inside = verts_back_temp[4]
vert_taper = (beamSlant(sRef, vert_outside[2], vert_inside[2]))
verts_back_temp[4] = [vert_inside[0], vert_inside[1], vert_taper]
# Adjust taper of bottom of beam, so 0 the center
# now becomes vert_outside, and vert_inside is calculated
# 1/2 way towards center
vert_outside = (0, -y_off, -z_off)
vert_inside = verts_back_temp[6]
vert_taper = (beamSlant(sRef, vert_outside[0], vert_inside[0]))
verts_back_temp[6] = [vert_taper, vert_inside[1], vert_inside[2]]
vert_outside = (0, -y_off, -z_off)
vert_inside = verts_back_temp[7]
vert_taper = (beamSlant(sRef, vert_outside[0], vert_inside[0]))
verts_back_temp[7] = [vert_taper, vert_inside[1], vert_inside[2]]
verts_final = verts_front_temp + verts_back_temp
# define end faces, only 8 so just coded
faces_front_temp = []
faces_back_temp = []
faces_side_temp = []
faces_front_temp = [(0, 1, 8, 9), (1, 2, 5, 8),
(2, 3, 4, 5), (5, 6, 7, 8)]
faces_back_temp = [(10, 11, 18, 19), (11, 12, 15, 18),
(12, 13, 14, 15), (15, 16, 17, 18)]
verts_front_list = []
verts_back_list = []
num_of_verts = len(verts_front_temp)
# build lists of back and front verts for beamSides function
for index in range(num_of_verts):
verts_front_list.append(index)
for index in range(num_of_verts):
verts_back_list.append(index + 10)
faces_side_temp = beamSides(verts_front_list, verts_back_list)
faces_final = faces_front_temp + faces_back_temp + faces_side_temp
return verts_final, faces_final
# #####################
# returns:
# verts_final - a list of tuples of the x, y, z, location of each vertice
# faces_final - a list of tuples of the vertices that make up each face
def create_I_beam(sRef):
thick = sRef.beamW
# Get offset of vertices from center
x_off = sRef.beamX / 2
y_off = sRef.beamY / 2
z_off = sRef.beamZ / 2
thick_off = thick / 2
# Create temporarylists to hold vertices locations
verts_front_temp = []
verts_back_temp = []
# Create front vertices by calculation
verts_front_temp = [
(-x_off, -y_off, z_off),
(-thick_off, -y_off, z_off),
(thick_off, -y_off, z_off),
(x_off, -y_off, z_off),
(x_off, -y_off, z_off - thick),
(thick_off, -y_off, z_off - thick),
(thick_off, -y_off, -z_off + thick),
(x_off, -y_off, -z_off + thick),
(x_off, -y_off, -z_off),
(thick_off, -y_off, -z_off),
(-thick_off, -y_off, -z_off),
(-x_off, -y_off, -z_off),
(-x_off, -y_off, -z_off + thick),
(-thick_off, -y_off, -z_off + thick),
(-thick_off, -y_off, z_off - thick),
(-x_off, -y_off, z_off - thick)
]
# Adjust taper
vert_outside = verts_front_temp[0]
vert_inside = verts_front_temp[15]
vert_taper = (beamSlant(sRef, vert_outside[2], vert_inside[2]))
verts_front_temp[15] = [vert_inside[0], vert_inside[1], vert_taper]
vert_outside = verts_front_temp[3]
vert_inside = verts_front_temp[4]
vert_taper = (beamSlant(sRef, vert_outside[2], vert_inside[2]))
verts_front_temp[4] = [vert_inside[0], vert_inside[1], vert_taper]
vert_outside = verts_front_temp[8]
vert_inside = verts_front_temp[7]
vert_taper = (beamSlant(sRef, vert_outside[2], vert_inside[2]))
verts_front_temp[7] = [vert_inside[0], vert_inside[1], vert_taper]
vert_outside = verts_front_temp[11]
vert_inside = verts_front_temp[12]
vert_taper = (beamSlant(sRef, vert_outside[2], vert_inside[2]))
verts_front_temp[12] = [vert_inside[0], vert_inside[1], vert_taper]
# Create back vertices by calculation
verts_back_temp = [
(-x_off, y_off, z_off),
(-thick_off, y_off, z_off),
(thick_off, y_off, z_off),
(x_off, y_off, z_off),
(x_off, y_off, z_off - thick),
(thick_off, y_off, z_off - thick),
(thick_off, y_off, -z_off + thick),
(x_off, y_off, -z_off + thick),
(x_off, y_off, -z_off),
(thick_off, y_off, -z_off),
(-thick_off, y_off, -z_off),
(-x_off, y_off, -z_off),
(-x_off, y_off, -z_off + thick),
(-thick_off, y_off, -z_off + thick),
(-thick_off, y_off, z_off - thick),
(-x_off, y_off, z_off - thick)
]
# Adjust taper
vert_outside = verts_back_temp[0]
vert_inside = verts_back_temp[15]
vert_taper = (beamSlant(sRef, vert_outside[2], vert_inside[2]))
verts_back_temp[15] = [vert_inside[0], vert_inside[1], vert_taper]
vert_outside = verts_back_temp[3]
vert_inside = verts_back_temp[4]
vert_taper = (beamSlant(sRef, vert_outside[2], vert_inside[2]))
verts_back_temp[4] = [vert_inside[0], vert_inside[1], vert_taper]
vert_outside = verts_back_temp[8]
vert_inside = verts_back_temp[7]
vert_taper = (beamSlant(sRef, vert_outside[2], vert_inside[2]))
verts_back_temp[7] = [vert_inside[0], vert_inside[1], vert_taper]
vert_outside = verts_back_temp[11]
vert_inside = verts_back_temp[12]
vert_taper = (beamSlant(sRef, vert_outside[2], vert_inside[2]))
verts_back_temp[12] = [vert_inside[0], vert_inside[1], vert_taper]
verts_final = verts_front_temp + verts_back_temp
# define end faces, only 7 per end, so just coded
faces_front_temp = []
faces_back_temp = []
faces_side_temp = []
faces_front_temp = [(0, 1, 14, 15), (1, 2, 5, 14),
(2, 3, 4, 5), (6, 7, 8, 9),
(6, 9, 10, 13), (12, 13, 10, 11),
(5, 6, 13, 14)]
faces_back_temp = [(16, 17, 30, 31), (17, 18, 21, 30),
(18, 19, 20, 21), (22, 23, 24, 25),
(22, 25, 26, 29), (28, 29, 26, 27),
(21, 22, 29, 30)]
verts_front_list = []
verts_back_list = []
num_of_verts = len(verts_front_temp)
# build lists of back and front verts for beamSides function
for index in range(num_of_verts):
verts_front_list.append(index)
for index in range(num_of_verts):
verts_back_list.append(index + 16)
faces_side_temp = beamSides(verts_front_list, verts_back_list)
faces_final = faces_front_temp + faces_back_temp + faces_side_temp
return verts_final, faces_final
# ######################
#
# Generate beam mesh.
def addBeamMesh(sRef, context):
verts = []
faces = []
# type of beam to add
if sRef.Type == '0':
verts, faces = create_beam(sRef)
elif sRef.Type == '1':
verts, faces = create_u_beam(sRef)
elif sRef.Type == '2':
verts, faces = create_u_beam(sRef)
elif sRef.Type == '3':
verts, faces = create_L_beam(sRef)
elif sRef.Type == '4':
verts, faces = create_I_beam(sRef)
elif sRef.Type == '5':
verts, faces = create_T_beam(sRef)
else: # unknown type, use default.
verts, faces = create_beam(sRef)
beamMesh = bpy.data.meshes.new("Beam")
beamMesh.from_pydata(verts, [], faces)
beamMesh.update(calc_edges=True)
return beamMesh
# ######################
# Create a beam primitive.
#
# UI functions and object creation.
class addBeam(Operator, object_utils.AddObjectHelper):
bl_idname = "mesh.add_beam"
bl_label = "Beam Builder"
bl_description = "Create beam meshes of various profiles"
bl_options = {'REGISTER', 'UNDO', 'PRESET'}
Beam : BoolProperty(name = "Beam",
default = True,
description = "Beam")
change : BoolProperty(name = "Change",
default = False,
description = "change Beam")
Type: EnumProperty(
name="Beam Type",
items=(
('0', "Box Profile", "Square Beam"),
("1", "U Profile", "U Profile Beam"),
("2", "C Profile", "C Profile Beam"),
("3", "L Profile", "L Profile Beam"),
("4", "I Profile", "I Profile Beam"),
("5", "T Profile", "T Profile Beam")
),
description="Beam form"
)
beamZ: FloatProperty(
name="Height",
min=0.01,
#max=100,
default=1
)
beamX: FloatProperty(
name="Width",
min=0.01,
#max=100,
default=.5
)
beamY: FloatProperty(
name="Depth",
min=0.01,
#max=100,
default=2
)
beamW: FloatProperty(
name="Thickness",
min=0.01,
#max=1,
default=0.1
)
edgeA: IntProperty(
name="Taper",
min=0,
#max=100,
default=0,
description="Angle beam edges"
)
def draw(self, context):
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
layout.separator()
layout.prop(self, "Type")
layout.separator()
layout.prop(self, "beamZ")
layout.prop(self, "beamX")
layout.prop(self, "beamY")
layout.prop(self, "beamW")
if self.Type != '0':
layout.prop(self, "edgeA")
if self.change == False:
# generic transform props
layout.separator()
draw_transform_props(self, layout)
def execute(self, context):
# turn off 'Enter Edit Mode'
use_enter_edit_mode = bpy.context.preferences.edit.use_enter_edit_mode
bpy.context.preferences.edit.use_enter_edit_mode = False
if bpy.context.mode == "OBJECT":
if context.selected_objects != [] and context.active_object and \
(context.active_object.data is not None) and ('Beam' in context.active_object.data.keys()) and \
(self.change == True):
obj = context.active_object
oldmesh = obj.data
oldmeshname = obj.data.name
mesh = addBeamMesh(self, context)
obj.data = mesh
for material in oldmesh.materials:
obj.data.materials.append(material)
bpy.data.meshes.remove(oldmesh)
obj.data.name = oldmeshname
else:
mesh = addBeamMesh(self, context)
obj = object_utils.object_data_add(context, mesh, operator=self)
if self.Type == '2': # Rotate C shape
bpy.ops.transform.rotate(value=1.570796, constraint_axis=[False, True, False])
bpy.ops.object.transform_apply(location=False, rotation=True, scale=False)
obj.data["Beam"] = True
obj.data["change"] = False
for prm in BeamParameters():
obj.data[prm] = getattr(self, prm)
if bpy.context.mode == "EDIT_MESH":
active_object = context.active_object
name_active_object = active_object.name
bpy.ops.object.mode_set(mode='OBJECT')
mesh = addBeamMesh(self, context)
obj = object_utils.object_data_add(context, mesh, operator=self)
obj.select_set(True)
active_object.select_set(True)
bpy.context.view_layer.objects.active = active_object
bpy.ops.object.join()
context.active_object.name = name_active_object
bpy.ops.object.mode_set(mode='EDIT')
if use_enter_edit_mode:
bpy.ops.object.mode_set(mode = 'EDIT')
# restore pre operator state
bpy.context.preferences.edit.use_enter_edit_mode = use_enter_edit_mode
return {'FINISHED'}
def BeamParameters():
BeamParameters = [
"Type",
"beamZ",
"beamX",
"beamY",
"beamW",
"edgeA",
]
return BeamParameters
@@ -0,0 +1,987 @@
# SPDX-FileCopyrightText: 2009-2010 Michel J. Anders (varkenvarken)
#
# SPDX-License-Identifier: GPL-2.0-or-later
import bpy
from bpy.types import Operator
from math import (
atan, asin, cos,
sin, tan, pi,
radians,
)
from bpy.props import (
FloatProperty,
IntProperty,
BoolProperty,
StringProperty,
FloatVectorProperty
)
from mathutils import (
Vector,
Matrix,
)
from bpy_extras import object_utils
from .interface import draw_transform_props
# A very simple "bridge" tool.
# Connects two equally long vertex rows with faces.
# Returns a list of the new faces (list of lists)
#
# vertIdx1 ... First vertex list (list of vertex indices)
# vertIdx2 ... Second vertex list (list of vertex indices)
# closed ... Creates a loop (first & last are closed)
# flipped ... Invert the normal of the face(s)
#
# Note: You can set vertIdx1 to a single vertex index to create
# a fan/star of faces
# Note: If both vertex idx list are the same length they have
# to have at least 2 vertices
def createFaces(vertIdx1, vertIdx2, closed=False, flipped=False):
faces = []
if not vertIdx1 or not vertIdx2:
return None
if len(vertIdx1) < 2 and len(vertIdx2) < 2:
return None
fan = False
if (len(vertIdx1) != len(vertIdx2)):
if (len(vertIdx1) == 1 and len(vertIdx2) > 1):
fan = True
else:
return None
total = len(vertIdx2)
if closed:
# Bridge the start with the end.
if flipped:
face = [
vertIdx1[0],
vertIdx2[0],
vertIdx2[total - 1]]
if not fan:
face.append(vertIdx1[total - 1])
faces.append(face)
else:
face = [vertIdx2[0], vertIdx1[0]]
if not fan:
face.append(vertIdx1[total - 1])
face.append(vertIdx2[total - 1])
faces.append(face)
# Bridge the rest of the faces.
for num in range(total - 1):
if flipped:
if fan:
face = [vertIdx2[num], vertIdx1[0], vertIdx2[num + 1]]
else:
face = [vertIdx2[num], vertIdx1[num],
vertIdx1[num + 1], vertIdx2[num + 1]]
faces.append(face)
else:
if fan:
face = [vertIdx1[0], vertIdx2[num], vertIdx2[num + 1]]
else:
face = [vertIdx1[num], vertIdx2[num],
vertIdx2[num + 1], vertIdx1[num + 1]]
faces.append(face)
return faces
# Calculate the vertex coordinates for a single
# section of a gear tooth.
# Returns 4 lists of vertex coords (list of tuples):
# *-*---*---* (1.) verts_inner_base
# | | | |
# *-*---*---* (2.) verts_outer_base
# | | |
# *---*---* (3.) verts_middle_tooth
# \ | /
# *-*-* (4.) verts_tip_tooth
#
# a
# t
# d
# radius
# Ad
# De
# base
# p_angle
# rack
# crown
def add_tooth(a, t, d, radius, Ad, De, base, p_angle, rack=0, crown=0.0):
A = [a, a + t / 4, a + t / 2, a + 3 * t / 4]
C = [cos(i) for i in A]
S = [sin(i) for i in A]
Ra = radius + Ad
Rd = radius - De
Rb = Rd - base
# Pressure angle calc
O = Ad * tan(p_angle)
if Ra != 0:
p_angle = atan(O / Ra)
else:
p_angle = atan(O)
if radius < 0:
p_angle = -p_angle
if rack:
S = [sin(t / 4) * I for I in range(-2, 3)]
Sp = [0, sin(-t / 4 + p_angle), 0, sin(t / 4 - p_angle)]
verts_inner_base = [(Rb, radius * S[I], d) for I in range(4)]
verts_outer_base = [(Rd, radius * S[I], d) for I in range(4)]
verts_middle_tooth = [(radius, radius * S[I], d) for I in range(1, 4)]
verts_tip_tooth = [(Ra, radius * Sp[I], d) for I in range(1, 4)]
else:
Cp = [
0,
cos(a + t / 4 + p_angle),
cos(a + t / 2),
cos(a + 3 * t / 4 - p_angle)]
Sp = [0,
sin(a + t / 4 + p_angle),
sin(a + t / 2),
sin(a + 3 * t / 4 - p_angle)]
verts_inner_base = [(Rb * C[I], Rb * S[I], d)
for I in range(4)]
verts_outer_base = [(Rd * C[I], Rd * S[I], d)
for I in range(4)]
verts_middle_tooth = [(radius * C[I], radius * S[I], d + crown / 3)
for I in range(1, 4)]
verts_tip_tooth = [(Ra * Cp[I], Ra * Sp[I], d + crown)
for I in range(1, 4)]
return (verts_inner_base, verts_outer_base,
verts_middle_tooth, verts_tip_tooth)
# EXPERIMENTAL Calculate the vertex coordinates for a single
# section of a gearspoke.
# Returns them as a list of tuples
#
# a
# t
# d
# radius
# De
# base
# s
# w
# l
# gap
# width
#
# @todo Finish this.
def add_spoke(a, t, d, radius, De, base, s, w, l, gap=0, width=19):
Rd = radius - De
Rb = Rd - base
verts = []
edgefaces = []
edgefaces2 = []
sf = []
if not gap:
for N in range(width, 1, -2):
edgefaces.append(len(verts))
ts = t / 4
tm = a + 2 * ts
te = asin(w / Rb)
td = te - ts
t4 = ts + td * (width - N) / (width - 3.0)
A = [tm + (i - int(N / 2)) * t4 for i in range(N)]
C = [cos(i) for i in A]
S = [sin(i) for i in A]
verts.extend((Rb * I, Rb * J, d) for (I, J) in zip(C, S))
edgefaces2.append(len(verts) - 1)
Rb = Rb - s
n = 0
for N in range(width, 3, -2):
sf.extend([(i + n, i + 1 + n, i + 2 + n, i + N + n)
for i in range(0, N - 1, 2)])
sf.extend([(i + 2 + n, i + N + n, i + N + 1 + n, i + N + 2 + n)
for i in range(0, N - 3, 2)])
n = n + N
return verts, edgefaces, edgefaces2, sf
# Create gear geometry.
# Returns:
# * A list of vertices (list of tuples)
# * A list of faces (list of lists)
# * A list (group) of vertices of the tip (list of vertex indices)
# * A list (group) of vertices of the valley (list of vertex indices)
#
# teethNum ... Number of teeth on the gear
# radius ... Radius of the gear, negative for crown gear
# Ad ... Addendum, extent of tooth above radius
# De ... Dedendum, extent of tooth below radius
# base ... Base, extent of gear below radius
# p_angle ... Pressure angle. Skewness of tooth tip. (radiant)
# width ... Width, thickness of gear
# skew ... Skew of teeth. (radiant)
# conangle ... Conical angle of gear. (radiant)
# rack
# crown ... Inward pointing extend of crown teeth
#
# inner radius = radius - (De + base)
def add_gear(teethNum, radius, Ad, De, base, p_angle,
width=1, skew=0, conangle=0, rack=0, crown=0.0):
if teethNum < 2:
return None, None, None, None
t = 2 * pi / teethNum
if rack:
teethNum = 1
#print(radius, width, conangle)
if radius != 0:
scale = (radius - 2 * width * tan(conangle)) / radius
else:
scale = radius - 2 * width * tan(conangle)
verts = []
faces = []
vgroup_top = [] # Vertex group of top/tip? vertices.
vgroup_valley = [] # Vertex group of valley vertices
verts_bridge_prev = []
for toothCnt in range(teethNum):
a = toothCnt * t
verts_bridge_start = []
verts_bridge_end = []
verts_outside_top = []
verts_outside_bottom = []
for (s, d, c, top) \
in [(0, -width, 1, True), (skew, width, scale, False)]:
verts1, verts2, verts3, verts4 = add_tooth(a + s, t, d,
radius * c, Ad * c, De * c, base * c, p_angle,
rack, crown)
vertsIdx1 = list(range(len(verts), len(verts) + len(verts1)))
verts.extend(verts1)
vertsIdx2 = list(range(len(verts), len(verts) + len(verts2)))
verts.extend(verts2)
vertsIdx3 = list(range(len(verts), len(verts) + len(verts3)))
verts.extend(verts3)
vertsIdx4 = list(range(len(verts), len(verts) + len(verts4)))
verts.extend(verts4)
verts_outside = []
verts_outside.extend(vertsIdx2[:2])
verts_outside.append(vertsIdx3[0])
verts_outside.extend(vertsIdx4)
verts_outside.append(vertsIdx3[-1])
verts_outside.append(vertsIdx2[-1])
if top:
# verts_inside_top = vertsIdx1
verts_outside_top = verts_outside
verts_bridge_start.append(vertsIdx1[0])
verts_bridge_start.append(vertsIdx2[0])
verts_bridge_end.append(vertsIdx1[-1])
verts_bridge_end.append(vertsIdx2[-1])
else:
# verts_inside_bottom = vertsIdx1
verts_outside_bottom = verts_outside
verts_bridge_start.append(vertsIdx2[0])
verts_bridge_start.append(vertsIdx1[0])
verts_bridge_end.append(vertsIdx2[-1])
verts_bridge_end.append(vertsIdx1[-1])
# Valley = first 2 vertices of outer base:
vgroup_valley.extend(vertsIdx2[:1])
# Top/tip vertices:
vgroup_top.extend(vertsIdx4)
faces_tooth_middle_top = createFaces(vertsIdx2[1:], vertsIdx3,
flipped=top)
faces_tooth_outer_top = createFaces(vertsIdx3, vertsIdx4,
flipped=top)
faces_base_top = createFaces(vertsIdx1, vertsIdx2, flipped=top)
faces.extend(faces_base_top)
faces.extend(faces_tooth_middle_top)
faces.extend(faces_tooth_outer_top)
# faces_inside = createFaces(verts_inside_top, verts_inside_bottom)
# faces.extend(faces_inside)
faces_outside = createFaces(verts_outside_top, verts_outside_bottom,
flipped=True)
faces.extend(faces_outside)
if toothCnt == 0:
verts_bridge_first = verts_bridge_start
# Bridge one tooth to the next
if verts_bridge_prev:
faces_bridge = createFaces(verts_bridge_prev, verts_bridge_start)
faces.extend(faces_bridge)
# Remember "end" vertices for next tooth.
verts_bridge_prev = verts_bridge_end
# Bridge the first to the last tooth.
faces_bridge_f_l = createFaces(verts_bridge_prev, verts_bridge_first)
faces.extend(faces_bridge_f_l)
return verts, faces, vgroup_top, vgroup_valley
# Create spokes geometry
# Returns:
# * A list of vertices (list of tuples)
# * A list of faces (list of lists)
#
# teethNum ... Number of teeth on the gear.
# radius ... Radius of the gear, negative for crown gear
# De ... Dedendum, extent of tooth below radius
# base ... Base, extent of gear below radius
# width ... Width, thickness of gear
# conangle ... Conical angle of gear. (radiant)
# rack
# spoke
# spbevel
# spwidth
# splength
# spresol
#
# @todo Finish this
# @todo Create a function that takes a "Gear" and creates a
# matching "Gear Spokes" object
def add_spokes(teethNum, radius, De, base, width=1, conangle=0, rack=0,
spoke=3, spbevel=0.1, spwidth=0.2, splength=1.0, spresol=9):
if teethNum < 2:
return None, None, None, None
if spoke < 2:
return None, None, None, None
t = 2 * pi / teethNum
if rack:
teethNum = 1
scale = (radius - 2 * width * tan(conangle)) / radius
verts = []
faces = []
c = scale # debug
fl = len(verts)
for toothCnt in range(teethNum):
a = toothCnt * t
s = 0 # For test
if toothCnt % spoke == 0:
for d in (-width, width):
sv, edgefaces, edgefaces2, sf = add_spoke(a + s, t, d,
radius * c, De * c, base * c,
spbevel, spwidth, splength, 0, spresol)
verts.extend(sv)
faces.extend([j + fl for j in i] for i in sf)
fl += len(sv)
d1 = fl - len(sv)
d2 = fl - 2 * len(sv)
faces.extend([(i + d2, j + d2, j + d1, i + d1)
for (i, j) in zip(edgefaces[:-1], edgefaces[1:])])
faces.extend([(i + d2, j + d2, j + d1, i + d1)
for (i, j) in zip(edgefaces2[:-1], edgefaces2[1:])])
else:
for d in (-width, width):
sv, edgefaces, edgefaces2, sf = add_spoke(a + s, t, d,
radius * c, De * c, base * c,
spbevel, spwidth, splength, 1, spresol)
verts.extend(sv)
fl += len(sv)
d1 = fl - len(sv)
d2 = fl - 2 * len(sv)
faces.extend([[i + d2, i + 1 + d2, i + 1 + d1, i + d1]
for (i) in range(0, 3)])
faces.extend([[i + d2, i + 1 + d2, i + 1 + d1, i + d1]
for (i) in range(5, 8)])
return verts, faces
# Create worm geometry.
# Returns:
# * A list of vertices
# * A list of faces
# * A list (group) of vertices of the tip
# * A list (group) of vertices of the valley
#
# teethNum ... Number of teeth on the worm
# radius ... Radius of the gear, negative for crown gear
# Ad ... Addendum, extent of tooth above radius
# De ... Dedendum, extent of tooth below radius
# p_angle ... Pressure angle. Skewness of tooth tip. (radiant)
# width ... Width, thickness of gear
# crown ... Inward pointing extend of crown teeth
#
# @todo: Fix teethNum. Some numbers are not possible yet
# @todo: Create start & end geometry (closing faces)
def add_worm(teethNum, rowNum, radius, Ad, De, p_angle,
width=1, skew=radians(11.25), crown=0.0):
worm = teethNum
teethNum = 24
t = 2 * pi / teethNum
verts = []
faces = []
vgroup_top = [] # Vertex group of top/tip? vertices.
vgroup_valley = [] # Vertex group of valley vertices
# width = width / 2.0
edgeloop_prev = []
for Row in range(rowNum):
edgeloop = []
for toothCnt in range(teethNum):
a = toothCnt * t
s = Row * skew
d = Row * width
c = 1
isTooth = False
if toothCnt % (teethNum / worm) != 0:
# Flat
verts1, verts2, verts3, verts4 = add_tooth(a + s, t, d,
radius - De, 0.0, 0.0, 0, p_angle)
# Ignore other verts than the "other base".
verts1 = verts3 = verts4 = []
else:
# Tooth
isTooth = True
verts1, verts2, verts3, verts4 = add_tooth(a + s, t, d,
radius * c, Ad * c, De * c, 0 * c, p_angle, 0, crown)
# Remove various unneeded verts (if we are "inside" the tooth)
del(verts2[2]) # Central vertex in the base of the tooth.
del(verts3[1]) # Central vertex in the middle of the tooth.
vertsIdx2 = list(range(len(verts), len(verts) + len(verts2)))
verts.extend(verts2)
vertsIdx3 = list(range(len(verts), len(verts) + len(verts3)))
verts.extend(verts3)
vertsIdx4 = list(range(len(verts), len(verts) + len(verts4)))
verts.extend(verts4)
if isTooth:
verts_current = []
verts_current.extend(vertsIdx2[:2])
verts_current.append(vertsIdx3[0])
verts_current.extend(vertsIdx4)
verts_current.append(vertsIdx3[-1])
verts_current.append(vertsIdx2[-1])
# Valley = first 2 vertices of outer base:
vgroup_valley.extend(vertsIdx2[:1])
# Top/tip vertices:
vgroup_top.extend(vertsIdx4)
else:
# Flat
verts_current = vertsIdx2
# Valley - all of them.
vgroup_valley.extend(vertsIdx2)
edgeloop.extend(verts_current)
# Create faces between rings/rows.
if edgeloop_prev:
faces_row = createFaces(edgeloop, edgeloop_prev, closed=True)
faces.extend(faces_row)
# Remember last ring/row of vertices for next ring/row iteration.
edgeloop_prev = edgeloop
return verts, faces, vgroup_top, vgroup_valley
def AddGearMesh(self, context):
verts, faces, verts_tip, verts_valley = add_gear(
self.number_of_teeth,
self.radius,
self.addendum,
self.dedendum,
self.base,
self.angle,
width=self.width,
skew=self.skew,
conangle=self.conangle,
crown=self.crown
)
mesh = bpy.data.meshes.new("Gear")
mesh.from_pydata(verts, [], faces)
return mesh, verts_tip, verts_valley
class AddGear(Operator, object_utils.AddObjectHelper):
bl_idname = "mesh.primitive_gear"
bl_label = "Add Gear"
bl_description = "Construct a gear mesh"
bl_options = {'REGISTER', 'UNDO', 'PRESET'}
Gear : BoolProperty(name = "Gear",
default = True,
description = "Gear")
#### change properties
name : StringProperty(name = "Name",
description = "Name")
change : BoolProperty(name = "Change",
default = False,
description = "change Gear")
number_of_teeth: IntProperty(name="Teeth",
description="Number of teeth on the gear",
min=2,
soft_max=1000,
default=12
)
radius: FloatProperty(name="Radius",
description="Radius of the gear, negative for crown gear",
soft_min=-1000.0,
soft_max=1000.0,
unit='LENGTH',
default=1.0
)
addendum: FloatProperty(name="Addendum",
description="Addendum, extent of tooth above radius",
soft_min=-1000.0,
soft_max=1000.0,
unit='LENGTH',
default=0.1
)
dedendum: FloatProperty(name="Dedendum",
description="Dedendum, extent of tooth below radius",
soft_min=-1000.0,
soft_max=1000.0,
unit='LENGTH',
default=0.1
)
angle: FloatProperty(name="Pressure Angle",
description="Pressure angle, skewness of tooth tip",
soft_min=radians(-45.0),
soft_max=radians(45.0),
unit='ROTATION',
default=radians(20.0)
)
base: FloatProperty(name="Base",
description="Base, extent of gear below radius",
soft_min=-1000.0,
soft_max=1000.0,
unit='LENGTH',
default=0.2
)
width: FloatProperty(name="Width",
description="Width, thickness of gear",
soft_min=-1000.0,
soft_max=1000.0,
unit='LENGTH',
default=0.2
)
skew: FloatProperty(name="Skewness",
description="Skew of teeth",
soft_min=radians(-360.0),
soft_max=radians(360.0),
unit='ROTATION',
default=radians(0.0)
)
conangle: FloatProperty(name="Conical Angle",
description="Conical angle of gear",
soft_min=radians(-360.0),
soft_max=radians(360.0),
unit='ROTATION',
default=radians(0.0)
)
crown: FloatProperty(name="Crown",
description="Inward pointing extend of crown teeth",
soft_min=-1000.0,
soft_max=1000.0,
unit='LENGTH',
default=0.0
)
def draw(self, context):
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
layout.separator()
layout.prop(self, 'number_of_teeth')
layout.separator()
layout.prop(self, 'radius')
layout.prop(self, 'width')
layout.prop(self, 'base')
layout.separator()
layout.prop(self, 'dedendum')
layout.prop(self, 'addendum')
layout.separator()
layout.prop(self, 'angle')
layout.prop(self, 'skew')
layout.prop(self, 'conangle')
layout.prop(self, 'crown')
if self.change == False:
layout.separator()
draw_transform_props(self, layout)
@classmethod
def poll(cls, context):
return context.scene is not None
def execute(self, context):
# turn off 'Enter Edit Mode'
use_enter_edit_mode = bpy.context.preferences.edit.use_enter_edit_mode
bpy.context.preferences.edit.use_enter_edit_mode = False
if bpy.context.mode == "OBJECT":
if context.selected_objects != [] and context.active_object and \
(context.active_object.data is not None) and ('Gear' in context.active_object.data.keys()) and \
(self.change == True):
obj = context.active_object
oldmesh = obj.data
oldmeshname = obj.data.name
mesh, verts_tip, verts_valley = AddGearMesh(self, context)
obj.data = mesh
try:
bpy.ops.object.vertex_group_remove(all=True)
except:
pass
for material in oldmesh.materials:
obj.data.materials.append(material)
bpy.data.meshes.remove(oldmesh)
obj.data.name = oldmeshname
else:
mesh, verts_tip, verts_valley = AddGearMesh(self, context)
obj = object_utils.object_data_add(context, mesh, operator=self)
# Create vertex groups from stored vertices.
tipGroup = obj.vertex_groups.new(name='Tips')
tipGroup.add(verts_tip, 1.0, 'ADD')
valleyGroup = obj.vertex_groups.new(name='Valleys')
valleyGroup.add(verts_valley, 1.0, 'ADD')
obj.data["Gear"] = True
obj.data["change"] = False
for prm in GearParameters():
obj.data[prm] = getattr(self, prm)
if bpy.context.mode == "EDIT_MESH":
active_object = context.active_object
name_active_object = active_object.name
bpy.ops.object.mode_set(mode='OBJECT')
mesh, verts_tip, verts_valley = AddGearMesh(self, context)
obj = object_utils.object_data_add(context, mesh, operator=self)
# Create vertex groups from stored vertices.
tipGroup = obj.vertex_groups.new(name='Tips')
tipGroup.add(verts_tip, 1.0, 'ADD')
valleyGroup = obj.vertex_groups.new(name='Valleys')
valleyGroup.add(verts_valley, 1.0, 'ADD')
obj.select_set(True)
active_object.select_set(True)
bpy.context.view_layer.objects.active = active_object
bpy.ops.object.join()
context.active_object.name = name_active_object
bpy.ops.object.mode_set(mode='EDIT')
if use_enter_edit_mode:
bpy.ops.object.mode_set(mode = 'EDIT')
# restore pre operator state
bpy.context.preferences.edit.use_enter_edit_mode = use_enter_edit_mode
return {'FINISHED'}
def invoke(self, context, event):
self.execute(context)
return {'FINISHED'}
def GearParameters():
GearParameters = [
"number_of_teeth",
"radius",
"addendum",
"dedendum",
"base",
"angle",
"width",
"skew",
"conangle",
"crown",
]
return GearParameters
def AddWormGearMesh(self, context):
verts, faces, verts_tip, verts_valley = add_worm(
self.number_of_teeth,
self.number_of_rows,
self.radius,
self.addendum,
self.dedendum,
self.angle,
width=self.row_height,
skew=self.skew,
crown=self.crown
)
mesh = bpy.data.meshes.new("Worm Gear")
mesh.from_pydata(verts, [], faces)
return mesh, verts_tip, verts_valley
class AddWormGear(Operator, object_utils.AddObjectHelper):
bl_idname = "mesh.primitive_worm_gear"
bl_label = "Add Worm Gear"
bl_description = "Construct a worm gear mesh"
bl_options = {'REGISTER', 'UNDO', 'PRESET'}
WormGear : BoolProperty(name = "WormGear",
default = True,
description = "WormGear")
#### change properties
name : StringProperty(name = "Name",
description = "Name")
change : BoolProperty(name = "Change",
default = False,
description = "change WormGear")
number_of_teeth: IntProperty(
name="Teeth",
description="Number of teeth on the gear",
min=1,
soft_max=1000,
default=12
)
number_of_rows: IntProperty(
name="Rows",
description="Number of rows on the worm gear",
min=0,
soft_max=1000,
default=32
)
radius: FloatProperty(
name="Radius",
description="Radius of the gear, negative for crown gear",
soft_min=-1000.0,
soft_max=1000.0,
unit='LENGTH',
default=1.0
)
addendum: FloatProperty(
name="Addendum",
description="Addendum, extent of tooth above radius",
soft_min=-1000.0,
soft_max=1000.0,
unit='LENGTH',
default=0.1
)
dedendum: FloatProperty(
name="Dedendum",
description="Dedendum, extent of tooth below radius",
soft_min=-1000.0,
soft_max=1000.0,
unit='LENGTH',
default=0.1
)
angle: FloatProperty(
name="Pressure Angle",
description="Pressure angle, skewness of tooth tip",
soft_min=radians(-45.0),
soft_max=radians(45.0),
default=radians(20.0),
unit='ROTATION'
)
row_height: FloatProperty(
name="Row Height",
description="Height of each Row",
soft_min=-1000.0,
soft_max=1000.0,
unit='LENGTH',
default=0.2
)
skew: FloatProperty(
name="Skewness per Row",
description="Skew of each row",
soft_min=radians(-360.0),
soft_max=radians(360.0),
default=radians(11.25),
unit='ROTATION'
)
crown: FloatProperty(
name="Crown",
description="Inward pointing extend of crown teeth",
soft_min=-1000.0,
soft_max=1000.0,
unit='LENGTH',
default=0.0
)
def draw(self, context):
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
layout.separator()
layout.prop(self, "number_of_teeth")
layout.prop(self, "number_of_rows")
layout.separator()
layout.prop(self, "radius")
layout.prop(self, "row_height")
layout.separator()
layout.prop(self, "addendum")
layout.prop(self, "dedendum")
layout.separator()
layout.prop(self, "angle")
layout.prop(self, "skew")
layout.prop(self, "crown")
if self.change == False:
layout.separator()
draw_transform_props(self, layout)
def execute(self, context):
# turn off 'Enter Edit Mode'
use_enter_edit_mode = bpy.context.preferences.edit.use_enter_edit_mode
bpy.context.preferences.edit.use_enter_edit_mode = False
if bpy.context.mode == "OBJECT":
if context.selected_objects != [] and context.active_object and \
(context.active_object.data is not None) and ('WormGear' in context.active_object.data.keys()) and \
(self.change == True):
obj = context.active_object
oldmesh = obj.data
oldmeshname = obj.data.name
mesh, verts_tip, verts_valley = AddWormGearMesh(self, context)
obj.data = mesh
try:
bpy.ops.object.vertex_group_remove(all=True)
except:
pass
for material in oldmesh.materials:
obj.data.materials.append(material)
bpy.data.meshes.remove(oldmesh)
obj.data.name = oldmeshname
else:
mesh, verts_tip, verts_valley = AddWormGearMesh(self, context)
obj = object_utils.object_data_add(context, mesh, operator=self)
# Create vertex groups from stored vertices.
tipGroup = obj.vertex_groups.new(name = 'Tips')
tipGroup.add(verts_tip, 1.0, 'ADD')
valleyGroup = obj.vertex_groups.new(name = 'Valleys')
valleyGroup.add(verts_valley, 1.0, 'ADD')
obj.data["WormGear"] = True
obj.data["change"] = False
for prm in WormGearParameters():
obj.data[prm] = getattr(self, prm)
if bpy.context.mode == "EDIT_MESH":
active_object = context.active_object
name_active_object = active_object.name
bpy.ops.object.mode_set(mode='OBJECT')
mesh, verts_tip, verts_valley = AddWormGearMesh(self, context)
obj = object_utils.object_data_add(context, mesh, operator=self)
# Create vertex groups from stored vertices.
tipGroup = obj.vertex_groups.new(name = 'Tips')
tipGroup.add(verts_tip, 1.0, 'ADD')
valleyGroup = obj.vertex_groups.new(name = 'Valleys')
valleyGroup.add(verts_valley, 1.0, 'ADD')
obj.select_set(True)
active_object.select_set(True)
bpy.context.view_layer.objects.active = active_object
bpy.ops.object.join()
context.active_object.name = name_active_object
bpy.ops.object.mode_set(mode='EDIT')
if use_enter_edit_mode:
bpy.ops.object.mode_set(mode = 'EDIT')
# restore pre operator state
bpy.context.preferences.edit.use_enter_edit_mode = use_enter_edit_mode
return {'FINISHED'}
def WormGearParameters():
WormGearParameters = [
"number_of_teeth",
"number_of_rows",
"radius",
"addendum",
"dedendum",
"angle",
"row_height",
"skew",
"crown",
]
return WormGearParameters
@@ -0,0 +1,516 @@
# SPDX-FileCopyrightText: 2010-2022 Blender Foundation
#
# SPDX-License-Identifier: GPL-2.0-or-later
# Author: Pontiac, Fourmadmen, Dreampainter
import bpy
from bpy.types import Operator
from mathutils import (
Vector,
Quaternion,
)
from math import cos, sin, pi
from bpy.props import (
FloatProperty,
IntProperty,
BoolProperty,
StringProperty,
)
from bpy_extras import object_utils
from .interface import draw_transform_props
# Create a new mesh (object) from verts/edges/faces.
# verts/edges/faces ... List of vertices/edges/faces for the
# new mesh (as used in from_pydata)
# name ... Name of the new mesh (& object)
def create_mesh_object(context, self, verts, edges, faces, name):
# Create new mesh
mesh = bpy.data.meshes.new(name)
# Make a mesh from a list of verts/edges/faces.
mesh.from_pydata(verts, edges, faces)
# Update mesh geometry after adding stuff.
mesh.update()
from bpy_extras import object_utils
return object_utils.object_data_add(context, mesh, operator=self)
# A very simple "bridge" tool.
def createFaces(vertIdx1, vertIdx2, closed=False, flipped=False):
faces = []
if not vertIdx1 or not vertIdx2:
return None
if len(vertIdx1) < 2 and len(vertIdx2) < 2:
return None
fan = False
if (len(vertIdx1) != len(vertIdx2)):
if (len(vertIdx1) == 1 and len(vertIdx2) > 1):
fan = True
else:
return None
total = len(vertIdx2)
if closed:
# Bridge the start with the end
if flipped:
face = [
vertIdx1[0],
vertIdx2[0],
vertIdx2[total - 1]]
if not fan:
face.append(vertIdx1[total - 1])
faces.append(face)
else:
face = [vertIdx2[0], vertIdx1[0]]
if not fan:
face.append(vertIdx1[total - 1])
face.append(vertIdx2[total - 1])
faces.append(face)
# Bridge the rest of the faces
for num in range(total - 1):
if flipped:
if fan:
face = [vertIdx2[num], vertIdx1[0], vertIdx2[num + 1]]
else:
face = [vertIdx2[num], vertIdx1[num],
vertIdx1[num + 1], vertIdx2[num + 1]]
faces.append(face)
else:
if fan:
face = [vertIdx1[0], vertIdx2[num], vertIdx2[num + 1]]
else:
face = [vertIdx1[num], vertIdx2[num],
vertIdx2[num + 1], vertIdx1[num + 1]]
faces.append(face)
return faces
# @todo Clean up vertex&face creation process a bit.
def add_gem(r1, r2, seg, h1, h2):
"""
r1 = pavilion radius
r2 = crown radius
seg = number of segments
h1 = pavilion height
h2 = crown height
Generates the vertices and faces of the gem
"""
verts = []
a = 2.0 * pi / seg # Angle between segments
offset = a / 2.0 # Middle between segments
r3 = ((r1 + r2) / 2.0) / cos(offset) # Middle of crown
r4 = (r1 / 2.0) / cos(offset) # Middle of pavilion
h3 = h2 / 2.0 # Middle of crown height
h4 = -h1 / 2.0 # Middle of pavilion height
# Tip
vert_tip = len(verts)
verts.append(Vector((0.0, 0.0, -h1)))
# Middle vertex of the flat side (crown)
vert_flat = len(verts)
verts.append(Vector((0.0, 0.0, h2)))
edgeloop_flat = []
for i in range(seg):
s1 = sin(i * a)
s2 = sin(offset + i * a)
c1 = cos(i * a)
c2 = cos(offset + i * a)
verts.append((r4 * s1, r4 * c1, h4)) # Middle of pavilion
verts.append((r1 * s2, r1 * c2, 0.0)) # Pavilion
verts.append((r3 * s1, r3 * c1, h3)) # Middle crown
edgeloop_flat.append(len(verts))
verts.append((r2 * s2, r2 * c2, h2)) # Crown
faces = []
for index in range(seg):
i = index * 4
j = ((index + 1) % seg) * 4
faces.append([j + 2, vert_tip, i + 2, i + 3]) # Tip -> Middle of pav
faces.append([j + 2, i + 3, j + 3]) # Middle of pav -> pav
faces.append([j + 3, i + 3, j + 4]) # Pav -> Middle crown
faces.append([j + 4, i + 3, i + 4, i + 5]) # Crown quads
faces.append([j + 4, i + 5, j + 5]) # Middle crown -> crown
faces_flat = createFaces([vert_flat], edgeloop_flat, closed=True, flipped=True)
faces.extend(faces_flat)
return verts, faces
def add_diamond(segments, girdle_radius, table_radius,
crown_height, pavilion_height):
PI_2 = pi * 2.0
z_axis = (0.0, 0.0, -1.0)
verts = []
faces = []
height_flat = crown_height
height_middle = 0.0
height_tip = -pavilion_height
# Middle vertex of the flat side (crown)
vert_flat = len(verts)
verts.append(Vector((0.0, 0.0, height_flat)))
# Tip
vert_tip = len(verts)
verts.append(Vector((0.0, 0.0, height_tip)))
verts_flat = []
verts_girdle = []
for index in range(segments):
quat = Quaternion(z_axis, (index / segments) * PI_2)
# angle = PI_2 * index / segments # UNUSED
# Row for flat side
verts_flat.append(len(verts))
vec = quat @ Vector((table_radius, 0.0, height_flat))
verts.append(vec)
# Row for the middle/girdle
verts_girdle.append(len(verts))
vec = quat @ Vector((girdle_radius, 0.0, height_middle))
verts.append(vec)
# Flat face
faces_flat = createFaces([vert_flat], verts_flat, closed=True,
flipped=True)
# Side face
faces_side = createFaces(verts_girdle, verts_flat, closed=True)
# Tip faces
faces_tip = createFaces([vert_tip], verts_girdle, closed=True)
faces.extend(faces_tip)
faces.extend(faces_side)
faces.extend(faces_flat)
return verts, faces
class AddDiamond(Operator, object_utils.AddObjectHelper):
bl_idname = "mesh.primitive_diamond_add"
bl_label = "Add Diamond"
bl_description = "Construct a diamond mesh"
bl_options = {'REGISTER', 'UNDO', 'PRESET'}
Diamond : BoolProperty(name = "Diamond",
default = True,
description = "Diamond")
#### change properties
name : StringProperty(name = "Name",
description = "Name")
change : BoolProperty(name = "Change",
default = False,
description = "change Diamond")
segments: IntProperty(
name="Segments",
description="Number of segments for the diamond",
min=3,
max=256,
default=32
)
girdle_radius: FloatProperty(
name="Girdle Radius",
description="Girdle radius of the diamond",
min=0.01,
max=9999.0,
default=1.0
)
table_radius: FloatProperty(
name="Table Radius",
description="Girdle radius of the diamond",
min=0.01,
max=9999.0,
default=0.6
)
crown_height: FloatProperty(
name="Crown Height",
description="Crown height of the diamond",
min=0.01,
max=9999.0,
default=0.35
)
pavilion_height: FloatProperty(
name="Pavilion Height",
description="Pavilion height of the diamond",
min=0.01,
max=9999.0,
default=0.8
)
def draw(self, context):
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
layout.separator()
layout.prop(self, "segments")
col = layout.column(align=True)
col.prop(self, "girdle_radius", text='Radius Girdle')
col.prop(self, "table_radius", text='Table')
col = layout.column(align=True)
col.prop(self, "crown_height", text='Height Crown')
col.prop(self, "pavilion_height", text='Pavilion')
if self.change == False:
layout.separator()
draw_transform_props(self, layout)
def execute(self, context):
# turn off 'Enter Edit Mode'
use_enter_edit_mode = bpy.context.preferences.edit.use_enter_edit_mode
bpy.context.preferences.edit.use_enter_edit_mode = False
if bpy.context.mode == "OBJECT":
if context.selected_objects != [] and context.active_object and \
(context.active_object.data is not None) and ('Diamond' in context.active_object.data.keys()) and \
(self.change == True):
obj = context.active_object
oldmesh = obj.data
oldmeshname = obj.data.name
verts, faces = add_diamond(self.segments,
self.girdle_radius,
self.table_radius,
self.crown_height,
self.pavilion_height)
mesh = bpy.data.meshes.new("TMP")
mesh.from_pydata(verts, [], faces)
mesh.update()
obj.data = mesh
for material in oldmesh.materials:
obj.data.materials.append(material)
bpy.data.meshes.remove(oldmesh)
obj.data.name = oldmeshname
else:
verts, faces = add_diamond(self.segments,
self.girdle_radius,
self.table_radius,
self.crown_height,
self.pavilion_height)
obj = create_mesh_object(context, self, verts, [], faces, "Diamond")
obj.data["Diamond"] = True
obj.data["change"] = False
for prm in DiamondParameters():
obj.data[prm] = getattr(self, prm)
if bpy.context.mode == "EDIT_MESH":
active_object = context.active_object
name_active_object = active_object.name
bpy.ops.object.mode_set(mode='OBJECT')
verts, faces = add_diamond(self.segments,
self.girdle_radius,
self.table_radius,
self.crown_height,
self.pavilion_height)
obj = create_mesh_object(context, self, verts, [], faces, "TMP")
obj.select_set(True)
active_object.select_set(True)
bpy.context.view_layer.objects.active = active_object
bpy.ops.object.join()
context.active_object.name = name_active_object
bpy.ops.object.mode_set(mode='EDIT')
if use_enter_edit_mode:
bpy.ops.object.mode_set(mode = 'EDIT')
# restore pre operator state
bpy.context.preferences.edit.use_enter_edit_mode = use_enter_edit_mode
return {'FINISHED'}
def DiamondParameters():
DiamondParameters = [
"segments",
"girdle_radius",
"table_radius",
"crown_height",
"pavilion_height",
]
return DiamondParameters
class AddGem(Operator, object_utils.AddObjectHelper):
bl_idname = "mesh.primitive_gem_add"
bl_label = "Add Gem"
bl_description = "Construct an offset faceted gem mesh"
bl_options = {'REGISTER', 'UNDO', 'PRESET'}
Gem : BoolProperty(name = "Gem",
default = True,
description = "Gem")
#### change properties
name : StringProperty(name = "Name",
description = "Name")
change : BoolProperty(name = "Change",
default = False,
description = "change Gem")
segments: IntProperty(
name="Segments",
description="Longitudial segmentation",
min=3,
max=265,
default=8
)
pavilion_radius: FloatProperty(
name="Radius",
description="Radius of the gem",
min=0.01,
max=9999.0,
default=1.0
)
crown_radius: FloatProperty(
name="Table Radius",
description="Radius of the table(top)",
min=0.01,
max=9999.0,
default=0.6
)
crown_height: FloatProperty(
name="Table height",
description="Height of the top half",
min=0.01,
max=9999.0,
default=0.35
)
pavilion_height: FloatProperty(
name="Pavilion height",
description="Height of bottom half",
min=0.01,
max=9999.0,
default=0.8
)
def draw(self, context):
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
layout.separator()
layout.prop(self, "segments")
col = layout.column(align=True)
col.prop(self, "crown_radius", text='Radius Crown')
col.prop(self, "pavilion_radius", text='Pavilion')
col = layout.column(align=True)
col.prop(self, "crown_height", text='Height Crown')
col.prop(self, "pavilion_height", text='Pavilion')
if self.change == False:
layout.separator()
draw_transform_props(self, layout)
def execute(self, context):
# turn off 'Enter Edit Mode'
use_enter_edit_mode = bpy.context.preferences.edit.use_enter_edit_mode
bpy.context.preferences.edit.use_enter_edit_mode = False
if bpy.context.mode == "OBJECT":
if context.selected_objects != [] and context.active_object and \
(context.active_object.data is not None) and ('Gem' in context.active_object.data.keys()) and \
(self.change == True):
obj = context.active_object
oldmesh = obj.data
oldmeshname = obj.data.name
verts, faces = add_gem(
self.pavilion_radius,
self.crown_radius,
self.segments,
self.pavilion_height,
self.crown_height)
mesh = bpy.data.meshes.new("TMP")
mesh.from_pydata(verts, [], faces)
mesh.update()
obj.data = mesh
for material in oldmesh.materials:
obj.data.materials.append(material)
bpy.data.meshes.remove(oldmesh)
obj.data.name = oldmeshname
else:
verts, faces = add_gem(
self.pavilion_radius,
self.crown_radius,
self.segments,
self.pavilion_height,
self.crown_height)
obj = create_mesh_object(context, self, verts, [], faces, "Gem")
obj.data["Gem"] = True
obj.data["change"] = False
for prm in GemParameters():
obj.data[prm] = getattr(self, prm)
if bpy.context.mode == "EDIT_MESH":
active_object = context.active_object
name_active_object = active_object.name
bpy.ops.object.mode_set(mode='OBJECT')
verts, faces = add_gem(
self.pavilion_radius,
self.crown_radius,
self.segments,
self.pavilion_height,
self.crown_height)
obj = create_mesh_object(context, self, verts, [], faces, "TMP")
obj.select_set(True)
active_object.select_set(True)
bpy.context.view_layer.objects.active = active_object
bpy.ops.object.join()
context.active_object.name = name_active_object
bpy.ops.object.mode_set(mode='EDIT')
if use_enter_edit_mode:
bpy.ops.object.mode_set(mode = 'EDIT')
# restore pre operator state
bpy.context.preferences.edit.use_enter_edit_mode = use_enter_edit_mode
return {'FINISHED'}
def GemParameters():
GemParameters = [
"segments",
"pavilion_radius",
"crown_radius",
"crown_height",
"pavilion_height",
]
return GemParameters
@@ -0,0 +1,341 @@
# SPDX-FileCopyrightText: 2012-2022 Blender Foundation
#
# SPDX-License-Identifier: GPL-2.0-or-later
# Author: Kayo Phoenix
import bpy
from bpy_extras import object_utils
from math import (
pi, sin,
cos,
)
from bpy.props import (
IntProperty,
BoolProperty,
BoolVectorProperty,
FloatProperty,
FloatVectorProperty,
StringProperty,
)
from .interface import draw_transform_props
class honeycomb_geometry():
def __init__(self, rows, cols, D, E):
self.rows = rows
self.cols = cols
self.D = D
self.E = E
self.hE = 0.5 * self.E
self.R = 0.5 * self.D
self.a = sin(pi / 3)
self.d = self.a * self.D
self.hd = 0.5 * self.d
self.e = self.hE / self.a
self.he = 0.5 * self.e
self.r = self.R - self.e
self.hr = 0.5 * self.r
self.H = self.R * (1.5 * self.rows + 0.5) + self.e
if self.rows > 1:
self.W = self.d * (self.cols + 0.5) + self.E
else:
self.W = self.d * self.cols + self.E
self.hH = 0.5 * self.H
self.hW = 0.5 * self.W
self.sy = -self.hH + self.he + self.R
self.sx = -self.hW + self.hE + self.hd
self.gx = self.hd
self.dy = 1.5 * self.R
self.dx = self.d
def vert(self, row, col):
# full cell
if row >= 0 and row < self.rows and col >= 0 and col < self.cols:
return [0, 1, 2, 3, 4, 5]
# right down corner
if row == -1 and col == self.cols - 1:
return [1, 2]
if row == 0 and self.rows > 1 and col == self.cols:
return [1, 2, 3]
# left down corner
if row == -1 and col == -1:
return [0, 1]
if self.rows % 2:
# left up corner
if row == self.rows and col == -1:
return [4, 5]
# right up corner
if row == self.rows and col == self.cols - 1:
return [3, 4]
if row == self.rows - 1 and self.rows > 1 and col == self.cols:
return [2, 3, 4]
else:
# left up corner
if row == self.rows and col == 0:
return [4, 5]
if row == self.rows - 1 and self.rows > 1 and col == -1:
return [0, 4, 5]
# right up corner
if row == self.rows and col == self.cols:
return [3, 4]
# horizontal lines
if col >= 0 and col < self.cols:
if row == -1:
return [0, 1, 2]
if row == self.rows:
return [3, 4, 5]
# vertical lines
if row >= 0 and row < self.rows:
if col == -1:
if row % 2:
return [0, 1, 4, 5]
else:
return [0, 5]
if col == self.cols:
if row % 2 or self.rows == 1:
return [2, 3]
else:
return [1, 2, 3, 4]
return []
def cell(self, row, col, idx):
cp = [self.sx + self.dx * col, self.sy + self.dy * row, 0] # central point
if row % 2:
cp[0] += self.gx
co = [] # vertices coords
vi = self.vert(row, col)
ap = {}
for i in vi:
a = pi / 6 + i * pi / 3 # angle
ap[i] = idx + len(co)
co.append((cp[0] + cos(a) * self.r, cp[1] + sin(a) * self.r, cp[2]))
return co, ap
def generate(self):
ar = 1
ac = 1
cells = []
verts = []
faces = []
for row in range(-ar, self.rows + ar):
level = []
for col in range(-ac, self.cols + ac):
co, ap = self.cell(row, col, len(verts))
verts += co
level.append(ap)
cells.append(level)
# bottom row
row = 0
for col in range(1, len(cells[row]) - 1):
s = cells[row][col]
l = cells[row][col - 1]
u = cells[row + 1][col]
faces.append((s[1], u[5], u[4], s[2]))
faces.append((s[2], u[4], l[0]))
# top row
row = len(cells) - 1
cs = 0
if row % 2:
cs += 1
for col in range(1 + cs, len(cells[row]) - 1):
s = cells[row][col]
l = cells[row][col - 1]
d = cells[row - 1][col - cs]
faces.append((s[3], l[5], d[1]))
faces.append([s[3], d[1], d[0], s[4]])
# middle rows
for row in range(1, len(cells) - 1):
cs = 0
if row % 2:
cs += 1
for col in range(1, len(cells[row]) - 1):
s = cells[row][col]
l = cells[row][col - 1]
u = cells[row + 1][col - cs]
d = cells[row - 1][col - cs]
faces.append((s[1], u[5], u[4], s[2]))
faces.append((s[2], u[4], l[0]))
faces.append([s[2], l[0], l[5], s[3]])
faces.append((s[3], l[5], d[1]))
faces.append([s[3], d[1], d[0], s[4]])
# right column
row = 0
col = len(cells[row]) - 1
for row in range(1, len(cells) - 1):
cs = 0
if row % 2:
cs += 1
s = cells[row][col]
l = cells[row][col - 1]
u = cells[row + 1][col - cs]
d = cells[row - 1][col - cs]
if row % 2 and row < len(cells) - 2:
faces.append((s[1], u[5], u[4], s[2]))
faces.append((s[2], u[4], l[0]))
faces.append([s[2], l[0], l[5], s[3]])
faces.append((s[3], l[5], d[1]))
if row % 2 and row > 1:
faces.append([s[3], d[1], d[0], s[4]])
# final fix
if not self.rows % 2:
row = len(cells) - 1
s = cells[row][col]
l = cells[row][col - 1]
d = cells[row - 1][col - 1]
faces.append((s[3], l[5], d[1]))
faces.append([s[3], d[1], d[0], s[4]])
return verts, faces
def edge_max(diam):
return diam * sin(pi / 3)
class add_mesh_honeycomb(bpy.types.Operator, object_utils.AddObjectHelper):
bl_idname = "mesh.honeycomb_add"
bl_label = "Add Honeycomb"
bl_description = "Simple honeycomb mesh generator"
bl_options = {'REGISTER', 'UNDO'}
def fix_edge(self, context):
m = edge_max(self.diam)
if self.edge > m:
self.edge = m
HoneyComb : BoolProperty(name = "HoneyComb",
default = True,
description = "HoneyComb")
change : BoolProperty(name = "Change",
default = False,
description = "change HoneyComb")
rows: IntProperty(
name="Rows",
default=2,
min=1, max=100,
description='Number of the rows'
)
cols: IntProperty(
name='Columns',
default=2,
min=1, max=100,
description='Number of the columns'
)
diam: FloatProperty(
name='Cell Diameter',
default=1.0,
min=0.0, update=fix_edge,
description='Diameter of the cell'
)
edge: FloatProperty(
name='Edge Width',
default=0.1,
min=0.0, update=fix_edge,
description='Width of the edge'
)
def draw(self, context):
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
layout.prop(self, 'rows', expand=True)
layout.prop(self, 'cols', expand=True)
layout.prop(self, 'diam', expand=True)
layout.prop(self, 'edge', expand=True)
if self.change == False:
layout.separator()
draw_transform_props(self, layout)
@classmethod
def poll(cls, context):
return context.scene is not None
def execute(self, context):
# turn off 'Enter Edit Mode'
use_enter_edit_mode = bpy.context.preferences.edit.use_enter_edit_mode
bpy.context.preferences.edit.use_enter_edit_mode = False
if bpy.context.mode == "OBJECT":
if context.selected_objects != [] and context.active_object and \
(context.active_object.data is not None) and ('HoneyComb' in context.active_object.data.keys()) and \
(self.change == True):
obj = context.active_object
oldmesh = obj.data
oldmeshname = obj.data.name
comb = honeycomb_geometry(self.rows, self.cols, self.diam, self.edge)
verts, faces = comb.generate()
mesh = bpy.data.meshes.new('HoneyComb')
mesh.from_pydata(verts, [], faces)
obj.data = mesh
for material in oldmesh.materials:
obj.data.materials.append(material)
bpy.data.meshes.remove(oldmesh)
obj.data.name = oldmeshname
else:
comb = honeycomb_geometry(self.rows, self.cols, self.diam, self.edge)
verts, faces = comb.generate()
mesh = bpy.data.meshes.new('HoneyComb')
mesh.from_pydata(verts, [], faces)
obj = object_utils.object_data_add(context, mesh, operator=self)
obj.data["HoneyComb"] = True
obj.data["change"] = False
for prm in HoneyCombParameters():
obj.data[prm] = getattr(self, prm)
if bpy.context.mode == "EDIT_MESH":
active_object = context.active_object
name_active_object = active_object.name
bpy.ops.object.mode_set(mode='OBJECT')
comb = honeycomb_geometry(self.rows, self.cols, self.diam, self.edge)
verts, faces = comb.generate()
mesh = bpy.data.meshes.new('HoneyComb')
mesh.from_pydata(verts, [], faces)
obj = object_utils.object_data_add(context, mesh, operator=self)
obj.select_set(True)
active_object.select_set(True)
bpy.context.view_layer.objects.active = active_object
bpy.ops.object.join()
context.active_object.name = name_active_object
bpy.ops.object.mode_set(mode='EDIT')
if use_enter_edit_mode:
bpy.ops.object.mode_set(mode = 'EDIT')
# restore pre operator state
bpy.context.preferences.edit.use_enter_edit_mode = use_enter_edit_mode
return {'FINISHED'}
def HoneyCombParameters():
HoneyCombParameters = [
"rows",
"cols",
"diam",
"edge",
]
return HoneyCombParameters
@@ -0,0 +1,198 @@
# SPDX-FileCopyrightText: 2015 Sugiany
#
# SPDX-License-Identifier: MIT
import bpy
from bpy_extras.object_utils import (
AddObjectHelper,
object_data_add,
)
from bpy.props import (
IntProperty,
BoolProperty,
BoolVectorProperty,
FloatVectorProperty,
FloatProperty,
)
import mathutils
import copy
from .interface import draw_transform_props
class MengerSponge(object):
FACE_INDICES = [
[3, 7, 4, 0],
[5, 6, 2, 1],
[1, 2, 3, 0],
[7, 6, 5, 4],
[4, 5, 1, 0],
[2, 6, 7, 3],
]
def __init__(self, level):
self.__level = level
self.__max_point_number = 3 ** level
self.__vertices_map = {}
self.__indices = []
self.__face_visibility = {}
self.__faces = []
for x in range(3):
for y in range(3):
for z in range(3):
self.__face_visibility[(x, y, z)] = [
x == 0 or x == 2 and (y == 1 or z == 1),
x == 2 or x == 0 and (y == 1 or z == 1),
y == 0 or y == 2 and (x == 1 or z == 1),
y == 2 or y == 0 and (x == 1 or z == 1),
z == 0 or z == 2 and (y == 1 or x == 1),
z == 2 or z == 0 and (y == 1 or x == 1),
]
def create(self, width, height):
m = self.__max_point_number
points = [
(0, 0, 0),
(m, 0, 0),
(m, 0, m),
(0, 0, m),
(0, m, 0),
(m, m, 0),
(m, m, m),
(0, m, m),
]
self.__make_sub_sponge(points, None, self.__level)
vertices = self.__make_vertices(width, height)
return vertices, self.__faces
def __get_vindex(self, p):
if p in self.__vertices_map:
return self.__vertices_map[p]
index = len(self.__vertices_map)
self.__vertices_map[p] = index
return index
def __make_vertices(self, width, height):
vertices = [None] * len(self.__vertices_map)
w2 = width / 2
h2 = height / 2
w_step = width / self.__max_point_number
h_step = height / self.__max_point_number
for p, i in sorted(self.__vertices_map.items(), key=lambda x: x[1]):
vertices[i] = mathutils.Vector([
p[0] * w_step - w2,
p[1] * w_step - w2,
p[2] * h_step - h2,
])
return vertices
def __make_sub_sponge(self, cur_points, face_vis, depth):
if depth <= 0:
if not face_vis:
face_vis = [True] * 6
cur_point_indices = []
for p in cur_points:
cur_point_indices.append(self.__get_vindex(p))
for i, vis in enumerate(face_vis):
if vis:
f = []
for vi in self.FACE_INDICES[i]:
f.append(cur_point_indices[vi])
self.__faces.append(f)
return
base = cur_points[0]
width = (cur_points[1][0] - base[0]) / 3
local_vert_map = {}
for z in range(4):
for y in range(4):
for x in range(4):
local_vert_map[(x, y, z)] = (
width * x + base[0],
width * y + base[1],
width * z + base[2],
)
for x in range(3):
for y in range(3):
for z in range(3):
if [x, y, z].count(1) > 1:
continue
next_points = [
local_vert_map[(x, y, z)],
local_vert_map[(x + 1, y, z)],
local_vert_map[(x + 1, y, z + 1)],
local_vert_map[(x, y, z + 1)],
local_vert_map[(x, y + 1, z)],
local_vert_map[(x + 1, y + 1, z)],
local_vert_map[(x + 1, y + 1, z + 1)],
local_vert_map[(x, y + 1, z + 1)],
]
visibility = copy.copy(self.__face_visibility[(x, y, z)])
if face_vis:
visibility[0] = visibility[0] and (face_vis[0] or x != 0)
visibility[1] = visibility[1] and (face_vis[1] or x != 2)
visibility[2] = visibility[2] and (face_vis[2] or y != 0)
visibility[3] = visibility[3] and (face_vis[3] or y != 2)
visibility[4] = visibility[4] and (face_vis[4] or z != 0)
visibility[5] = visibility[5] and (face_vis[5] or z != 2)
self.__make_sub_sponge(
next_points,
visibility,
depth - 1)
class AddMengerSponge(bpy.types.Operator, AddObjectHelper):
bl_idname = "mesh.menger_sponge_add"
bl_label = "Menger Sponge"
bl_description = "Construct a menger sponge mesh"
bl_options = {'REGISTER', 'UNDO'}
level: IntProperty(
name="Level",
description="Sponge Level",
min=0, max=4,
default=1,
)
radius: FloatProperty(
name="Width",
description="Sponge Radius",
min=0.01, max=100.0,
default=1.0,
)
layers: BoolVectorProperty(
name="Layers",
size=20,
subtype='LAYER',
options={'HIDDEN', 'SKIP_SAVE'},
)
def draw(self, context):
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
layout.prop(self, 'level')
layout.prop(self, 'radius')
layout.separator()
draw_transform_props(self, layout)
def execute(self, context):
sponger = MengerSponge(self.level)
vertices, faces = sponger.create(self.radius * 2, self.radius * 2)
del sponger
mesh = bpy.data.meshes.new(name='Sponge')
mesh.from_pydata(vertices, [], faces)
uvs = [(0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (1.0, 0.0)]
mesh.uv_layers.new()
for i, uvloop in enumerate(mesh.uv_layers.active.data):
uvloop.uv = uvs[i % 4]
object_data_add(context, mesh, operator=self)
return {'FINISHED'}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,223 @@
# SPDX-FileCopyrightText: 2011-2022 Blender Foundation
#
# SPDX-License-Identifier: GPL-2.0-or-later
# Author: Phil Cote, cotejrp1, (http://www.blenderaddons.com)
import bpy
import bmesh
from bpy.props import (
FloatProperty,
IntProperty,
StringProperty,
BoolProperty,
)
from math import pi
from mathutils import (
Quaternion,
Vector,
)
from bpy_extras import object_utils
from .interface import draw_transform_props
def create_step(width, base_level, step_height, num_sides):
axis = [0, 0, -1]
PI2 = pi * 2
rad = width / 2
quat_angles = [(cur_side / num_sides) * PI2
for cur_side in range(num_sides)]
quaternions = [Quaternion(axis, quat_angle)
for quat_angle in quat_angles]
init_vectors = [Vector([rad, 0, base_level])] * len(quaternions)
quat_vector_pairs = list(zip(quaternions, init_vectors))
vectors = [quaternion @ vec for quaternion, vec in quat_vector_pairs]
bottom_list = [(vec.x, vec.y, vec.z) for vec in vectors]
top_list = [(vec.x, vec.y, vec.z + step_height) for vec in vectors]
full_list = bottom_list + top_list
return full_list
def split_list(l, n):
"""
split the blocks up. Credit to oremj for this one.
http://stackoverflow.com/questions/312443/how-do-you-split-a-list-into-evenly-sized-chunks-in-python
"""
n *= 2
returned_list = [l[i: i + n] for i in range(0, len(l), n)]
return returned_list
def get_connector_pairs(lst, n_sides):
# chop off the verts that get used for the base and top
lst = lst[n_sides:]
lst = lst[:-n_sides]
lst = split_list(lst, n_sides)
return lst
def pyramid_mesh(self, context):
all_verts = []
height_offset = 0
cur_width = self.width
for i in range(self.num_steps):
verts_loc = create_step(cur_width, height_offset, self.height,
self.num_sides)
height_offset += self.height
cur_width -= self.reduce_by
all_verts.extend(verts_loc)
mesh = bpy.data.meshes.new("Pyramid")
bm = bmesh.new()
for v_co in all_verts:
bm.verts.new(v_co)
def add_faces(n, block_vert_sets):
for bvs in block_vert_sets:
for i in range(self.num_sides - 1):
bm.faces.new([bvs[i], bvs[i + n], bvs[i + n + 1], bvs[i + 1]])
bm.faces.new([bvs[n - 1], bvs[(n * 2) - 1], bvs[n], bvs[0]])
# get the base and cap faces done.
bm.faces.new(bm.verts[0:self.num_sides])
bm.faces.new(reversed(bm.verts[-self.num_sides:])) # otherwise normal faces intern... T44619.
# side faces
block_vert_sets = split_list(bm.verts, self.num_sides)
add_faces(self.num_sides, block_vert_sets)
# connector faces between faces and faces of the block above it.
connector_pairs = get_connector_pairs(bm.verts, self.num_sides)
add_faces(self.num_sides, connector_pairs)
bm.to_mesh(mesh)
mesh.update()
return mesh
class AddPyramid(bpy.types.Operator, object_utils.AddObjectHelper):
bl_idname = "mesh.primitive_steppyramid_add"
bl_label = "Pyramid"
bl_description = "Construct a step pyramid mesh"
bl_options = {'REGISTER', 'UNDO', 'PRESET'}
Pyramid : BoolProperty(name = "Pyramid",
default = True,
description = "Pyramid")
change : BoolProperty(name = "Change",
default = False,
description = "change Pyramid")
num_sides: IntProperty(
name="Sides",
description="How many sides each step will have",
min=3,
default=4
)
num_steps: IntProperty(
name="Steps",
description="How many steps for the overall pyramid",
min=1,
default=10
)
width: FloatProperty(
name="Width",
description="Initial base step width",
min=0.01,
default=2
)
height: FloatProperty(
name="Height",
description="How tall each step will be",
min=0.01,
default=0.1
)
reduce_by: FloatProperty(
name="Taper",
description="How much to reduce each succeeding step by",
min=.01,
default=.20
)
def draw(self, context):
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
layout.separator()
layout.prop(self, 'num_sides')
layout.prop(self, 'num_steps')
layout.prop(self, 'width')
layout.prop(self, 'height')
layout.prop(self, 'reduce_by')
if self.change == False:
layout.separator()
draw_transform_props(self, layout)
def execute(self, context):
# turn off 'Enter Edit Mode'
use_enter_edit_mode = bpy.context.preferences.edit.use_enter_edit_mode
bpy.context.preferences.edit.use_enter_edit_mode = False
if bpy.context.mode == "OBJECT":
if context.selected_objects != [] and context.active_object and \
(context.active_object.data is not None) and ('Pyramid' in context.active_object.data.keys()) and \
(self.change == True):
obj = context.active_object
oldmesh = obj.data
oldmeshname = obj.data.name
obj.data = pyramid_mesh(self, context)
for material in oldmesh.materials:
obj.data.materials.append(material)
bpy.data.meshes.remove(oldmesh)
obj.data.name = oldmeshname
else:
mesh = pyramid_mesh(self, context)
obj = object_utils.object_data_add(context, mesh, operator=self)
obj.data["Pyramid"] = True
obj.data["change"] = False
for prm in PyramidParameters():
obj.data[prm] = getattr(self, prm)
if bpy.context.mode == "EDIT_MESH":
active_object = context.active_object
name_active_object = active_object.name
bpy.ops.object.mode_set(mode='OBJECT')
mesh = pyramid_mesh(self, context)
obj = object_utils.object_data_add(context, mesh, operator=self)
obj.select_set(True)
active_object.select_set(True)
bpy.context.view_layer.objects.active = active_object
bpy.ops.object.join()
context.active_object.name = name_active_object
bpy.ops.object.mode_set(mode='EDIT')
if use_enter_edit_mode:
bpy.ops.object.mode_set(mode = 'EDIT')
# restore pre operator state
bpy.context.preferences.edit.use_enter_edit_mode = use_enter_edit_mode
return {'FINISHED'}
def PyramidParameters():
PyramidParameters = [
"num_sides",
"num_steps",
"width",
"height",
"reduce_by",
]
return PyramidParameters
@@ -0,0 +1,36 @@
# SPDX-FileCopyrightText: 2019-2022 Blender Foundation
#
# SPDX-License-Identifier: GPL-2.0-or-later
# Paul "BrikBot" Marshall
# Created: July 1, 2011
# Last Modified: September 26, 2013
# Homepage (blog): http://post.darkarsenic.com/
# //blog.darkarsenic.com/
# Thanks to Meta-Androco, RickyBlender, Ace Dragon, and PKHG for ideas
# and testing.
#
# Coded in IDLE, tested in Blender 2.68a. NumPy Recommended.
# Search for "@todo" to quickly find sections that need work.
if "bpy" in locals():
import importlib
importlib.reload(rockgen)
else:
from . import rockgen
import bpy
# Register:
def register():
rockgen.register()
def unregister():
rockgen.unregister()
if __name__ == "__main__":
register()
@@ -0,0 +1,403 @@
<?xml version="1.0" ?>
<!DOCTYPE settings [
<!ELEMENT settings (default,preset*)>
<!ELEMENT default (title,size,shape,material,random)>
<!ELEMENT preset (title,size,shape,material,random)>
<!ELEMENT title (#PCDATA)>
<!ELEMENT size (scale+,skew+,use_scale_dis,scale_fac)>
<!ELEMENT scale (axis,lower,upper)>
<!ELEMENT axis (#PCDATA)>
<!ELEMENT lower (#PCDATA)>
<!ELEMENT upper (#PCDATA)>
<!ELEMENT skew (axis,value)>
<!ELEMENT value (#PCDATA)>
<!ELEMENT use_scale_dis (#PCDATA)>
<!ELEMENT scale_fac (#PCDATA)>
<!ELEMENT shape (deform,rough,detail,display_detail,smooth_fac,smooth_it)>
<!ELEMENT deform (#PCDATA)>
<!ELEMENT rough (#PCDATA)>
<!ELEMENT detail (#PCDATA)>
<!ELEMENT display_detail (#PCDATA)>
<!ELEMENT smooth_fac (#PCDATA)>
<!ELEMENT smooth_it (#PCDATA)>
<!ELEMENT material (mat_enable,mat_color,mat_bright,mat_rough,mat_spec,mat_hard,mat_use_trans,mat_alpha,mat_cloudy,mat_IOR,mat_mossy)>
<!ELEMENT mat_enable (#PCDATA)>
<!ELEMENT mat_color (#PCDATA)>
<!ELEMENT mat_bright (#PCDATA)>
<!ELEMENT mat_rough (#PCDATA)>
<!ELEMENT mat_spec (#PCDATA)>
<!ELEMENT mat_hard (#PCDATA)>
<!ELEMENT mat_use_trans (#PCDATA)>
<!ELEMENT mat_alpha (#PCDATA)>
<!ELEMENT mat_cloudy (#PCDATA)>
<!ELEMENT mat_IOR (#PCDATA)>
<!ELEMENT mat_mossy (#PCDATA)>
<!ELEMENT random (use_random_seed,user_seed)>
<!ELEMENT use_generate (#PCDATA)>
<!ELEMENT use_random_seed (#PCDATA)>
<!ELEMENT user_seed (#PCDATA)>
<!ATTLIST preset id ID #REQUIRED>
]>
<settings>
<default>
<title>Default</title>
<size>
<scale>
<axis>X</axis>
<lower>1.0</lower>
<upper>1.0</upper>
</scale>
<scale>
<axis>Y</axis>
<lower>1.0</lower>
<upper>1.0</upper>
</scale>
<scale>
<axis>Z</axis>
<lower>1.0</lower>
<upper>1.0</upper>
</scale>
<skew>
<axis>X</axis>
<value>0.0</value>
</skew>
<skew>
<axis>Y</axis>
<value>0.0</value>
</skew>
<skew>
<axis>Z</axis>
<value>0.0</value>
</skew>
<use_scale_dis>False</use_scale_dis>
<scale_fac>[1.0, 1.0, 1.0]</scale_fac>
</size>
<shape>
<deform>5.0</deform>
<rough>2.5</rough>
<detail>3</detail>
<display_detail>2</display_detail>
<smooth_fac>0.0</smooth_fac>
<smooth_it>0</smooth_it>
</shape>
<material>
<mat_enable>False</mat_enable>
<mat_color>[0.5, 0.5, 0.5]</mat_color>
<mat_bright>0.85</mat_bright>
<mat_rough>1.0</mat_rough>
<mat_spec>0.2</mat_spec>
<mat_hard>50</mat_hard>
<mat_use_trans>False</mat_use_trans>
<mat_alpha>0.0</mat_alpha>
<mat_cloudy>0.0</mat_cloudy>
<mat_IOR>1.0</mat_IOR>
<mat_mossy>0.0</mat_mossy>
</material>
<random>
<use_generate>True</use_generate>
<use_random_seed>True</use_random_seed>
<user_seed>1</user_seed>
</random>
</default>
<preset id="1">
<title>River Rock</title>
<size>
<scale>
<axis>X</axis>
<lower>0.5</lower>
<upper>1.25</upper>
</scale>
<scale>
<axis>Y</axis>
<lower>0.5</lower>
<upper>1.25</upper>
</scale>
<scale>
<axis>Z</axis>
<lower>0.5</lower>
<upper>1.25</upper>
</scale>
<skew>
<axis>X</axis>
<value>-0.5</value>
</skew>
<skew>
<axis>Y</axis>
<value>-0.5</value>
</skew>
<skew>
<axis>Z</axis>
<value>-0.5</value>
</skew>
<use_scale_dis>False</use_scale_dis>
<scale_fac>[1.0, 1.0, 1.0]</scale_fac>
</size>
<shape>
<deform>3.0</deform>
<rough>2.0</rough>
<detail>2</detail>
<display_detail>2</display_detail>
<smooth_fac>2.0</smooth_fac>
<smooth_it>2</smooth_it>
</shape>
<material>
<mat_enable>True</mat_enable>
<mat_color>[0.5, 0.5, 0.5]</mat_color>
<mat_bright>0.85</mat_bright>
<mat_rough>0.125</mat_rough>
<mat_spec>0.5</mat_spec>
<mat_hard>50</mat_hard>
<mat_use_trans>False</mat_use_trans>
<mat_alpha>0.0</mat_alpha>
<mat_cloudy>0.0</mat_cloudy>
<mat_IOR>1.0</mat_IOR>
<mat_mossy>0.0</mat_mossy>
</material>
<random>
<use_generate>True</use_generate>
<use_random_seed>True</use_random_seed>
<user_seed>1</user_seed>
</random>
</preset>
<preset id="2">
<title>Asteroid</title>
<size>
<scale>
<axis>X</axis>
<lower>1.0</lower>
<upper>5.0</upper>
</scale>
<scale>
<axis>Y</axis>
<lower>1.0</lower>
<upper>5.0</upper>
</scale>
<scale>
<axis>Z</axis>
<lower>1.0</lower>
<upper>5.0</upper>
</scale>
<skew>
<axis>X</axis>
<value>0.0</value>
</skew>
<skew>
<axis>Y</axis>
<value>0.0</value>
</skew>
<skew>
<axis>Z</axis>
<value>0.0</value>
</skew>
<use_scale_dis>False</use_scale_dis>
<scale_fac>[1.0, 1.0, 1.0]</scale_fac>
</size>
<shape>
<deform>7.5</deform>
<rough>3.0</rough>
<detail>4</detail>
<display_detail>3</display_detail>
<smooth_fac>0.0</smooth_fac>
<smooth_it>0</smooth_it>
</shape>
<material>
<mat_enable>True</mat_enable>
<mat_color>[0.3, 0.25, 0.2]</mat_color>
<mat_bright>0.85</mat_bright>
<mat_rough>1.5</mat_rough>
<mat_spec>0.25</mat_spec>
<mat_hard>30</mat_hard>
<mat_use_trans>False</mat_use_trans>
<mat_alpha>0.0</mat_alpha>
<mat_cloudy>0.0</mat_cloudy>
<mat_IOR>1.0</mat_IOR>
<mat_mossy>0.0</mat_mossy>
</material>
<random>
<use_generate>True</use_generate>
<use_random_seed>True</use_random_seed>
<user_seed>1</user_seed>
</random>
</preset>
<preset id="3">
<title>Sandstone</title>
<size>
<scale>
<axis>X</axis>
<lower>1.0</lower>
<upper>1.0</upper>
</scale>
<scale>
<axis>Y</axis>
<lower>1.0</lower>
<upper>1.0</upper>
</scale>
<scale>
<axis>Z</axis>
<lower>1.0</lower>
<upper>1.0</upper>
</scale>
<skew>
<axis>X</axis>
<value>0.0</value>
</skew>
<skew>
<axis>Y</axis>
<value>0.0</value>
</skew>
<skew>
<axis>Z</axis>
<value>0.0</value>
</skew>
<use_scale_dis>True</use_scale_dis>
<scale_fac>[5.0, 5.0, 0.1]</scale_fac>
</size>
<shape>
<deform>0.5</deform>
<rough>1.0</rough>
<detail>3</detail>
<display_detail>3</display_detail>
<smooth_fac>2.0</smooth_fac>
<smooth_it>2</smooth_it>
</shape>
<material>
<mat_enable>True</mat_enable>
<mat_color>[0.5, 0.4, 0.35]</mat_color>
<mat_bright>0.85</mat_bright>
<mat_rough>0.1</mat_rough>
<mat_spec>0.2</mat_spec>
<mat_hard>50</mat_hard>
<mat_use_trans>False</mat_use_trans>
<mat_alpha>0.0</mat_alpha>
<mat_cloudy>0.0</mat_cloudy>
<mat_IOR>1.0</mat_IOR>
<mat_mossy>0.0</mat_mossy>
</material>
<random>
<use_generate>True</use_generate>
<use_random_seed>True</use_random_seed>
<user_seed>1</user_seed>
</random>
</preset>
<preset id="4">
<title>Ice</title>
<size>
<scale>
<axis>X</axis>
<lower>0.0</lower>
<upper>2.0</upper>
</scale>
<scale>
<axis>Y</axis>
<lower>0.0</lower>
<upper>2.0</upper>
</scale>
<scale>
<axis>Z</axis>
<lower>0.0</lower>
<upper>2.0</upper>
</scale>
<skew>
<axis>X</axis>
<value>0.0</value>
</skew>
<skew>
<axis>Y</axis>
<value>0.0</value>
</skew>
<skew>
<axis>Z</axis>
<value>0.0</value>
</skew>
<use_scale_dis>False</use_scale_dis>
<scale_fac>[1.0, 1.0, 1.0]</scale_fac>
</size>
<shape>
<deform>5.0</deform>
<rough>1.0</rough>
<detail>3</detail>
<display_detail>2</display_detail>
<smooth_fac>2.0</smooth_fac>
<smooth_it>1</smooth_it>
</shape>
<material>
<mat_enable>True</mat_enable>
<mat_color>[0.9, 0.95, 1.0]</mat_color>
<mat_bright>0.85</mat_bright>
<mat_rough>0.25</mat_rough>
<mat_spec>0.2</mat_spec>
<mat_hard>50</mat_hard>
<mat_use_trans>True</mat_use_trans>
<mat_alpha>0.9</mat_alpha>
<mat_cloudy>0.1</mat_cloudy>
<mat_IOR>1.31</mat_IOR>
<mat_mossy>0.0</mat_mossy>
</material>
<random>
<use_generate>True</use_generate>
<use_random_seed>True</use_random_seed>
<user_seed>1</user_seed>
</random>
</preset>
<preset id="5">
<title>Fake Ocean</title>
<size>
<scale>
<axis>X</axis>
<lower>10.0</lower>
<upper>10.0</upper>
</scale>
<scale>
<axis>Y</axis>
<lower>10.0</lower>
<upper>10.0</upper>
</scale>
<scale>
<axis>Z</axis>
<lower>0.0</lower>
<upper>0.0</upper>
</scale>
<skew>
<axis>X</axis>
<value>0.0</value>
</skew>
<skew>
<axis>Y</axis>
<value>0.0</value>
</skew>
<skew>
<axis>Z</axis>
<value>0.0</value>
</skew>
<use_scale_dis>False</use_scale_dis>
<scale_fac>[1.0, 1.0, 1.0]</scale_fac>
</size>
<shape>
<deform>7.5</deform>
<rough>3.0</rough>
<detail>4</detail>
<display_detail>3</display_detail>
<smooth_fac>0.0</smooth_fac>
<smooth_it>0</smooth_it>
</shape>
<material>
<mat_enable>True</mat_enable>
<mat_color>[0.1, 0.12, 0.125]</mat_color>
<mat_bright>0.85</mat_bright>
<mat_rough>1.5</mat_rough>
<mat_spec>0.25</mat_spec>
<mat_hard>30</mat_hard>
<mat_use_trans>True</mat_use_trans>
<mat_alpha>0.5</mat_alpha>
<mat_cloudy>0.5</mat_cloudy>
<mat_IOR>1.333</mat_IOR>
<mat_mossy>0.0</mat_mossy>
</material>
<random>
<use_generate>True</use_generate>
<use_random_seed>True</use_random_seed>
<user_seed>1</user_seed>
</random>
</preset>
</settings>
@@ -0,0 +1,403 @@
<?xml version="1.0" ?>
<!DOCTYPE settings [
<!ELEMENT settings (default,preset*)>
<!ELEMENT default (title,size,shape,material,random)>
<!ELEMENT preset (title,size,shape,material,random)>
<!ELEMENT title (#PCDATA)>
<!ELEMENT size (scale+,skew+,use_scale_dis,scale_fac)>
<!ELEMENT scale (axis,lower,upper)>
<!ELEMENT axis (#PCDATA)>
<!ELEMENT lower (#PCDATA)>
<!ELEMENT upper (#PCDATA)>
<!ELEMENT skew (axis,value)>
<!ELEMENT value (#PCDATA)>
<!ELEMENT use_scale_dis (#PCDATA)>
<!ELEMENT scale_fac (#PCDATA)>
<!ELEMENT shape (deform,rough,detail,display_detail,smooth_fac,smooth_it)>
<!ELEMENT deform (#PCDATA)>
<!ELEMENT rough (#PCDATA)>
<!ELEMENT detail (#PCDATA)>
<!ELEMENT display_detail (#PCDATA)>
<!ELEMENT smooth_fac (#PCDATA)>
<!ELEMENT smooth_it (#PCDATA)>
<!ELEMENT material (mat_enable,mat_color,mat_bright,mat_rough,mat_spec,mat_hard,mat_use_trans,mat_alpha,mat_cloudy,mat_IOR,mat_mossy)>
<!ELEMENT mat_enable (#PCDATA)>
<!ELEMENT mat_color (#PCDATA)>
<!ELEMENT mat_bright (#PCDATA)>
<!ELEMENT mat_rough (#PCDATA)>
<!ELEMENT mat_spec (#PCDATA)>
<!ELEMENT mat_hard (#PCDATA)>
<!ELEMENT mat_use_trans (#PCDATA)>
<!ELEMENT mat_alpha (#PCDATA)>
<!ELEMENT mat_cloudy (#PCDATA)>
<!ELEMENT mat_IOR (#PCDATA)>
<!ELEMENT mat_mossy (#PCDATA)>
<!ELEMENT random (use_random_seed,user_seed)>
<!ELEMENT use_generate (#PCDATA)>
<!ELEMENT use_random_seed (#PCDATA)>
<!ELEMENT user_seed (#PCDATA)>
<!ATTLIST preset id ID #REQUIRED>
]>
<settings>
<default>
<title>Default</title>
<size>
<scale>
<axis>X</axis>
<lower>1.0</lower>
<upper>1.0</upper>
</scale>
<scale>
<axis>Y</axis>
<lower>1.0</lower>
<upper>1.0</upper>
</scale>
<scale>
<axis>Z</axis>
<lower>1.0</lower>
<upper>1.0</upper>
</scale>
<skew>
<axis>X</axis>
<value>0.0</value>
</skew>
<skew>
<axis>Y</axis>
<value>0.0</value>
</skew>
<skew>
<axis>Z</axis>
<value>0.0</value>
</skew>
<use_scale_dis>False</use_scale_dis>
<scale_fac>[1.0, 1.0, 1.0]</scale_fac>
</size>
<shape>
<deform>5.0</deform>
<rough>2.5</rough>
<detail>3</detail>
<display_detail>2</display_detail>
<smooth_fac>0.0</smooth_fac>
<smooth_it>0</smooth_it>
</shape>
<material>
<mat_enable>False</mat_enable>
<mat_color>[0.5, 0.5, 0.5]</mat_color>
<mat_bright>0.85</mat_bright>
<mat_rough>1.0</mat_rough>
<mat_spec>0.2</mat_spec>
<mat_hard>50</mat_hard>
<mat_use_trans>False</mat_use_trans>
<mat_alpha>0.0</mat_alpha>
<mat_cloudy>0.0</mat_cloudy>
<mat_IOR>1.0</mat_IOR>
<mat_mossy>0.0</mat_mossy>
</material>
<random>
<use_generate>True</use_generate>
<use_random_seed>True</use_random_seed>
<user_seed>1</user_seed>
</random>
</default>
<preset id="1">
<title>River Rock</title>
<size>
<scale>
<axis>X</axis>
<lower>0.5</lower>
<upper>1.25</upper>
</scale>
<scale>
<axis>Y</axis>
<lower>0.5</lower>
<upper>1.25</upper>
</scale>
<scale>
<axis>Z</axis>
<lower>0.5</lower>
<upper>1.25</upper>
</scale>
<skew>
<axis>X</axis>
<value>-0.5</value>
</skew>
<skew>
<axis>Y</axis>
<value>-0.5</value>
</skew>
<skew>
<axis>Z</axis>
<value>-0.5</value>
</skew>
<use_scale_dis>False</use_scale_dis>
<scale_fac>[1.0, 1.0, 1.0]</scale_fac>
</size>
<shape>
<deform>3.0</deform>
<rough>2.0</rough>
<detail>2</detail>
<display_detail>2</display_detail>
<smooth_fac>2.0</smooth_fac>
<smooth_it>2</smooth_it>
</shape>
<material>
<mat_enable>True</mat_enable>
<mat_color>[0.5, 0.5, 0.5]</mat_color>
<mat_bright>0.85</mat_bright>
<mat_rough>0.125</mat_rough>
<mat_spec>0.5</mat_spec>
<mat_hard>50</mat_hard>
<mat_use_trans>False</mat_use_trans>
<mat_alpha>0.0</mat_alpha>
<mat_cloudy>0.0</mat_cloudy>
<mat_IOR>1.0</mat_IOR>
<mat_mossy>0.0</mat_mossy>
</material>
<random>
<use_generate>True</use_generate>
<use_random_seed>True</use_random_seed>
<user_seed>1</user_seed>
</random>
</preset>
<preset id="2">
<title>Asteroid</title>
<size>
<scale>
<axis>X</axis>
<lower>1.0</lower>
<upper>5.0</upper>
</scale>
<scale>
<axis>Y</axis>
<lower>1.0</lower>
<upper>5.0</upper>
</scale>
<scale>
<axis>Z</axis>
<lower>1.0</lower>
<upper>5.0</upper>
</scale>
<skew>
<axis>X</axis>
<value>0.0</value>
</skew>
<skew>
<axis>Y</axis>
<value>0.0</value>
</skew>
<skew>
<axis>Z</axis>
<value>0.0</value>
</skew>
<use_scale_dis>False</use_scale_dis>
<scale_fac>[1.0, 1.0, 1.0]</scale_fac>
</size>
<shape>
<deform>7.5</deform>
<rough>3.0</rough>
<detail>4</detail>
<display_detail>3</display_detail>
<smooth_fac>0.0</smooth_fac>
<smooth_it>0</smooth_it>
</shape>
<material>
<mat_enable>True</mat_enable>
<mat_color>[0.3, 0.25, 0.2]</mat_color>
<mat_bright>0.85</mat_bright>
<mat_rough>1.5</mat_rough>
<mat_spec>0.25</mat_spec>
<mat_hard>30</mat_hard>
<mat_use_trans>False</mat_use_trans>
<mat_alpha>0.0</mat_alpha>
<mat_cloudy>0.0</mat_cloudy>
<mat_IOR>1.0</mat_IOR>
<mat_mossy>0.0</mat_mossy>
</material>
<random>
<use_generate>True</use_generate>
<use_random_seed>True</use_random_seed>
<user_seed>1</user_seed>
</random>
</preset>
<preset id="3">
<title>Sandstone</title>
<size>
<scale>
<axis>X</axis>
<lower>1.0</lower>
<upper>1.0</upper>
</scale>
<scale>
<axis>Y</axis>
<lower>1.0</lower>
<upper>1.0</upper>
</scale>
<scale>
<axis>Z</axis>
<lower>1.0</lower>
<upper>1.0</upper>
</scale>
<skew>
<axis>X</axis>
<value>0.0</value>
</skew>
<skew>
<axis>Y</axis>
<value>0.0</value>
</skew>
<skew>
<axis>Z</axis>
<value>0.0</value>
</skew>
<use_scale_dis>True</use_scale_dis>
<scale_fac>[5.0, 5.0, 0.1]</scale_fac>
</size>
<shape>
<deform>0.5</deform>
<rough>1.0</rough>
<detail>3</detail>
<display_detail>3</display_detail>
<smooth_fac>2.0</smooth_fac>
<smooth_it>2</smooth_it>
</shape>
<material>
<mat_enable>True</mat_enable>
<mat_color>[0.5, 0.4, 0.35]</mat_color>
<mat_bright>0.85</mat_bright>
<mat_rough>0.1</mat_rough>
<mat_spec>0.2</mat_spec>
<mat_hard>50</mat_hard>
<mat_use_trans>False</mat_use_trans>
<mat_alpha>0.0</mat_alpha>
<mat_cloudy>0.0</mat_cloudy>
<mat_IOR>1.0</mat_IOR>
<mat_mossy>0.0</mat_mossy>
</material>
<random>
<use_generate>True</use_generate>
<use_random_seed>True</use_random_seed>
<user_seed>1</user_seed>
</random>
</preset>
<preset id="4">
<title>Ice</title>
<size>
<scale>
<axis>X</axis>
<lower>0.0</lower>
<upper>2.0</upper>
</scale>
<scale>
<axis>Y</axis>
<lower>0.0</lower>
<upper>2.0</upper>
</scale>
<scale>
<axis>Z</axis>
<lower>0.0</lower>
<upper>2.0</upper>
</scale>
<skew>
<axis>X</axis>
<value>0.0</value>
</skew>
<skew>
<axis>Y</axis>
<value>0.0</value>
</skew>
<skew>
<axis>Z</axis>
<value>0.0</value>
</skew>
<use_scale_dis>False</use_scale_dis>
<scale_fac>[1.0, 1.0, 1.0]</scale_fac>
</size>
<shape>
<deform>5.0</deform>
<rough>1.0</rough>
<detail>3</detail>
<display_detail>2</display_detail>
<smooth_fac>2.0</smooth_fac>
<smooth_it>1</smooth_it>
</shape>
<material>
<mat_enable>True</mat_enable>
<mat_color>[0.9, 0.95, 1.0]</mat_color>
<mat_bright>0.85</mat_bright>
<mat_rough>0.25</mat_rough>
<mat_spec>0.2</mat_spec>
<mat_hard>50</mat_hard>
<mat_use_trans>True</mat_use_trans>
<mat_alpha>0.9</mat_alpha>
<mat_cloudy>0.1</mat_cloudy>
<mat_IOR>1.31</mat_IOR>
<mat_mossy>0.0</mat_mossy>
</material>
<random>
<use_generate>True</use_generate>
<use_random_seed>True</use_random_seed>
<user_seed>1</user_seed>
</random>
</preset>
<preset id="5">
<title>Fake Ocean</title>
<size>
<scale>
<axis>X</axis>
<lower>10.0</lower>
<upper>10.0</upper>
</scale>
<scale>
<axis>Y</axis>
<lower>10.0</lower>
<upper>10.0</upper>
</scale>
<scale>
<axis>Z</axis>
<lower>0.0</lower>
<upper>0.0</upper>
</scale>
<skew>
<axis>X</axis>
<value>0.0</value>
</skew>
<skew>
<axis>Y</axis>
<value>0.0</value>
</skew>
<skew>
<axis>Z</axis>
<value>0.0</value>
</skew>
<use_scale_dis>False</use_scale_dis>
<scale_fac>[1.0, 1.0, 1.0]</scale_fac>
</size>
<shape>
<deform>7.5</deform>
<rough>3.0</rough>
<detail>4</detail>
<display_detail>3</display_detail>
<smooth_fac>0.0</smooth_fac>
<smooth_it>0</smooth_it>
</shape>
<material>
<mat_enable>True</mat_enable>
<mat_color>[0.1, 0.12, 0.125]</mat_color>
<mat_bright>0.85</mat_bright>
<mat_rough>1.5</mat_rough>
<mat_spec>0.25</mat_spec>
<mat_hard>30</mat_hard>
<mat_use_trans>True</mat_use_trans>
<mat_alpha>0.5</mat_alpha>
<mat_cloudy>0.5</mat_cloudy>
<mat_IOR>1.333</mat_IOR>
<mat_mossy>0.0</mat_mossy>
</material>
<random>
<use_generate>True</use_generate>
<use_random_seed>True</use_random_seed>
<user_seed>1</user_seed>
</random>
</preset>
</settings>
@@ -0,0 +1,161 @@
# SPDX-FileCopyrightText: 2019-2022 Blender Foundation
#
# SPDX-License-Identifier: GPL-2.0-or-later
# This try block allows for the script to psudo-intelligently select the
# appropriate random to use. If Numpy's random is present it will use that.
# If Numpy's random is not present, it will through a "module not found"
# exception and instead use the slower built-in random that Python has.
try:
from numpy.random import random_integers as randint
from numpy.random import normal as gauss
from numpy.random import (
beta,
uniform,
)
except:
from random import (
randint,
gauss,
uniform,
)
from random import betavariate as beta
from .utils import skewedGauss
def randomizeTexture(texture, level=1):
'''
Set the values for a texture from parameters.
param: texture - bpy.data.texture to modify.
level - designated tweaked settings to use
-> Below 10 is a displacement texture
-> Between 10 and 20 is a base material texture
'''
noises = ['BLENDER_ORIGINAL', 'ORIGINAL_PERLIN', 'IMPROVED_PERLIN',
'VORONOI_F1', 'VORONOI_F2', 'VORONOI_F3', 'VORONOI_F4',
'VORONOI_F2_F1', 'VORONOI_CRACKLE']
if texture.type == 'CLOUDS':
if randint(0, 1) == 0:
texture.noise_type = 'SOFT_NOISE'
else:
texture.noise_type = 'HARD_NOISE'
if level != 11:
tempInt = randint(0, 6)
else:
tempInt = randint(0, 8)
texture.noise_basis = noises[tempInt]
texture.noise_depth = 8
if level == 0:
texture.noise_scale = gauss(0.625, 1 / 24)
elif level == 2:
texture.noise_scale = 0.15
elif level == 11:
texture.noise_scale = gauss(0.5, 1 / 24)
if texture.noise_basis in ['BLENDER_ORIGINAL', 'ORIGINAL_PERLIN',
'IMPROVED_PERLIN', 'VORONOI_F1']:
texture.intensity = gauss(1, 1 / 6)
texture.contrast = gauss(4, 1 / 3)
elif texture.noise_basis in ['VORONOI_F2', 'VORONOI_F3', 'VORONOI_F4']:
texture.intensity = gauss(0.25, 1 / 12)
texture.contrast = gauss(2, 1 / 6)
elif texture.noise_basis == 'VORONOI_F2_F1':
texture.intensity = gauss(0.5, 1 / 6)
texture.contrast = gauss(2, 1 / 6)
elif texture.noise_basis == 'VORONOI_CRACKLE':
texture.intensity = gauss(0.5, 1 / 6)
texture.contrast = gauss(2, 1 / 6)
elif texture.type == 'MUSGRAVE':
# musgraveType = ['MULTIFRACTAL', 'RIDGED_MULTIFRACTAL',
# 'HYBRID_MULTIFRACTAL', 'FBM', 'HETERO_TERRAIN']
texture.musgrave_type = 'MULTIFRACTAL'
texture.dimension_max = abs(gauss(0, 0.6)) + 0.2
texture.lacunarity = beta(3, 8) * 8.2 + 1.8
if level == 0:
texture.noise_scale = gauss(0.625, 1 / 24)
texture.noise_intensity = 0.2
texture.octaves = 1.0
elif level == 2:
texture.intensity = gauss(1, 1 / 6)
texture.contrast = 0.2
texture.noise_scale = 0.15
texture.octaves = 8.0
elif level == 10:
texture.intensity = gauss(0.25, 1 / 12)
texture.contrast = gauss(1.5, 1 / 6)
texture.noise_scale = 0.5
texture.octaves = 8.0
elif level == 12:
texture.octaves = uniform(1, 3)
elif level > 12:
texture.octaves = uniform(2, 8)
else:
texture.intensity = gauss(1, 1 / 6)
texture.contrast = 0.2
texture.octaves = 8.0
elif texture.type == 'DISTORTED_NOISE':
tempInt = randint(0, 8)
texture.noise_distortion = noises[tempInt]
tempInt = randint(0, 8)
texture.noise_basis = noises[tempInt]
texture.distortion = skewedGauss(2.0, 2.6666, (0.0, 10.0), False)
if level == 0:
texture.noise_scale = gauss(0.625, 1 / 24)
elif level == 2:
texture.noise_scale = 0.15
elif level >= 12:
texture.noise_scale = gauss(0.2, 1 / 48)
elif texture.type == 'STUCCI':
stucciTypes = ['PLASTIC', 'WALL_IN', 'WALL_OUT']
if randint(0, 1) == 0:
texture.noise_type = 'SOFT_NOISE'
else:
texture.noise_type = 'HARD_NOISE'
tempInt = randint(0, 2)
texture.stucci_type = stucciTypes[tempInt]
if level == 0:
tempInt = randint(0, 6)
texture.noise_basis = noises[tempInt]
texture.noise_scale = gauss(0.625, 1 / 24)
elif level == 2:
tempInt = randint(0, 6)
texture.noise_basis = noises[tempInt]
texture.noise_scale = 0.15
elif level >= 12:
tempInt = randint(0, 6)
texture.noise_basis = noises[tempInt]
texture.noise_scale = gauss(0.2, 1 / 30)
else:
tempInt = randint(0, 6)
texture.noise_basis = noises[tempInt]
elif texture.type == 'VORONOI':
metrics = ['DISTANCE', 'DISTANCE_SQUARED', 'MANHATTAN', 'CHEBYCHEV',
'MINKOVSKY_HALF', 'MINKOVSKY_FOUR', 'MINKOVSKY']
# Settings for first displacement level:
if level == 0:
tempInt = randint(0, 1)
texture.distance_metric = metrics[tempInt]
texture.noise_scale = gauss(0.625, 1 / 24)
texture.contrast = 0.5
texture.intensity = 0.7
elif level == 2:
texture.noise_scale = 0.15
tempInt = randint(0, 6)
texture.distance_metric = metrics[tempInt]
elif level >= 12:
tempInt = randint(0, 1)
texture.distance_metric = metrics[tempInt]
texture.noise_scale = gauss(0.125, 1 / 48)
texture.contrast = 0.5
texture.intensity = 0.7
else:
tempInt = randint(0, 6)
texture.distance_metric = metrics[tempInt]
return
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,144 @@
# SPDX-FileCopyrightText: 2019-2022 Blender Foundation
#
# SPDX-License-Identifier: GPL-2.0-or-later
# Paul "BrikBot" Marshall
# Created: July 1, 2011
# Last Modified: November 17, 2011
# Homepage (blog): http://post.darkarsenic.com/
# //blog.darkarsenic.com/
# Thanks to Meta-Androco, RickyBlender, Ace Dragon, and PKHG for ideas
# and testing.
#
# Coded in IDLE, tested in Blender 2.59. NumPy Recommended.
# Search for "@todo" to quickly find sections that need work.
import inspect
import shutil
from . import utils
from xml.dom import minidom
basePath = inspect.getfile(inspect.currentframe())[0:-len("settings.py")]
path = basePath + "add_mesh_rocks.xml"
try:
source = minidom.parse(path)
# print("Rock generator settings file found:\n" + path)
except:
print("Rock generator settings file not found. Creating settings file.")
shutil.copy(basePath + "factory.xml", path)
source = minidom.parse(path)
xmlDefault = source.getElementsByTagName('default')[0]
xmlPresets = source.getElementsByTagName('preset')
default = []
presets = []
# ----- Gets and Sets -----#
def getDefault():
global default
return default
def getPresetLists():
global presets
return presets
def getPreset(ID=0):
global presets
return presets[ID]
# ---------- Core ----------#
def parse():
global xmlDefault
global xmlPresets
global default
global presets
# Parse default values
default = parseNode(xmlDefault)
# Parse preset values
for setting in xmlPresets:
presets.append(parseNode(setting))
return '{FINISHED}'
# Takes a node and parses it for data. Relies on that setting.xml has
# a valid format as specified by the DTD.
# For some reason minidom places an empty child node for every other node.
def parseNode(setting, title=True):
loc = 1
if title:
# Preset name (xmlPreset.childNodes[1]):
title = setting.childNodes[loc].childNodes[0].data
loc += 2
# Preset size values (xmlPreset.childNodes[3]):
scaleX = [float(setting.childNodes[loc].childNodes[1].childNodes[3].childNodes[0].data),
float(setting.childNodes[loc].childNodes[1].childNodes[5].childNodes[0].data)]
scaleY = [float(setting.childNodes[loc].childNodes[3].childNodes[3].childNodes[0].data),
float(setting.childNodes[loc].childNodes[3].childNodes[5].childNodes[0].data)]
scaleZ = [float(setting.childNodes[loc].childNodes[5].childNodes[3].childNodes[0].data),
float(setting.childNodes[loc].childNodes[5].childNodes[5].childNodes[0].data)]
skewX = float(setting.childNodes[loc].childNodes[7].childNodes[3].childNodes[0].data)
skewY = float(setting.childNodes[loc].childNodes[9].childNodes[3].childNodes[0].data)
skewZ = float(setting.childNodes[loc].childNodes[11].childNodes[3].childNodes[0].data)
if setting.childNodes[loc].childNodes[13].childNodes[0].data == 'False':
use_scale_dis = False
else:
use_scale_dis = True
scale_fac = utils.toList(setting.childNodes[loc].childNodes[15].childNodes[0].data)
loc += 2
# Presst shape values (xmlPreset.childNodes[5]):
deform = float(setting.childNodes[loc].childNodes[1].childNodes[0].data)
rough = float(setting.childNodes[loc].childNodes[3].childNodes[0].data)
detail = int(setting.childNodes[loc].childNodes[5].childNodes[0].data)
display_detail = int(setting.childNodes[loc].childNodes[7].childNodes[0].data)
smooth_fac = float(setting.childNodes[loc].childNodes[9].childNodes[0].data)
smooth_it = int(setting.childNodes[loc].childNodes[11].childNodes[0].data)
loc += 2
# Preset material values (xmlPreset.childNodes[7]):
loc += 2
# Preset random values (xmlPreset.childNodes[9]):
if setting.childNodes[loc].childNodes[1].childNodes[0].data == 'True':
use_generate = True
else:
use_generate = False
if setting.childNodes[loc].childNodes[3].childNodes[0].data == 'False':
use_random_seed = False
else:
use_random_seed = True
user_seed = int(setting.childNodes[loc].childNodes[5].childNodes[0].data)
if title:
parsed = [title, scaleX, scaleY, scaleZ, skewX, skewY, skewZ,
use_scale_dis, scale_fac, deform, rough, detail,
display_detail, smooth_fac, smooth_it,
use_generate, use_random_seed, user_seed]
else:
parsed = [scaleX, scaleY, scaleZ, skewX, skewY, skewZ, use_scale_dis,
scale_fac, deform, rough, detail, display_detail, smooth_fac,
smooth_it, use_generate, use_random_seed, user_seed]
return parsed
def save():
return '{FINISHED}'
def _print():
for i in presets:
print(i)
return '{FINISHED}'
@@ -0,0 +1,141 @@
# SPDX-FileCopyrightText: 2019-2022 Blender Foundation
#
# SPDX-License-Identifier: GPL-2.0-or-later
# Converts a formatted string to a float tuple:
# IN - '(0.5, 0.2)' -> CONVERT -> OUT - (0.5, 0.2)
def toTuple(stringIn):
sTemp = str(stringIn)[1:len(str(stringIn)) - 1].split(', ')
fTemp = []
for i in sTemp:
fTemp.append(float(i))
return tuple(fTemp)
# Converts a formatted string to a float tuple:
# IN - '[0.5, 0.2]' -> CONVERT -> OUT - [0.5, 0.2]
def toList(stringIn):
sTemp = str(stringIn)[1:len(str(stringIn)) - 1].split(', ')
fTemp = []
for i in sTemp:
fTemp.append(float(i))
return fTemp
# Converts each item of a list into a float:
def toFloats(inList):
outList = []
for i in inList:
outList.append(float(i))
return outList
# Converts each item of a list into an integer:
def toInts(inList):
outList = []
for i in inList:
outList.append(int(i))
return outList
# Sets all faces smooth. Done this way since I can't
# find a simple way without using bpy.ops:
def smooth(mesh):
import bmesh
bm = bmesh.new()
bm.from_mesh(mesh)
for f in bm.faces:
f.smooth = True
bm.to_mesh(mesh)
return mesh
# This try block allows for the script to psudo-intelligently select the
# appropriate random to use. If Numpy's random is present it will use that.
# If Numpy's random is not present, it will through a "module not found"
# exception and instead use the slower built-in random that Python has.
try:
# from numpy.random import random_integers as randint
from numpy.random import normal as gauss
# from numpy.random import (beta,
# uniform,
# seed,
# weibull)
# print("Rock Generator: Numpy found.")
numpy = True
except:
from random import (
# randint,
gauss,
# uniform,
# seed
)
# from random import betavariate as beta
# from random import weibullvariate as weibull
print("Rock Generator: Numpy not found. Using Python's random.")
numpy = False
# Artificially skews a normal (gaussian) distribution. This will not create
# a continuous distribution curve but instead acts as a piecewise finction.
# This linearly scales the output on one side to fit the bounds.
#
# Example output histograms:
#
# Upper skewed: Lower skewed:
# | ▄ | _
# | █ | █
# | █_ | █
# | ██ | _█
# | _██ | ██
# | _▄███_ | ██ _
# | ▄██████ | ▄██▄█▄_
# | _█▄███████ | ███████
# | _██████████_ | ████████▄▄█_ _
# | _▄▄████████████ | ████████████▄█_
# | _▄_ ▄███████████████▄_ | _▄███████████████▄▄_
# ------------------------- -----------------------
# |mu |mu
# Histograms were generated in R (http://www.r-project.org/) based on the
# calculations below and manually duplicated here.
#
# param: mu - mu is the mean of the distribution.
# sigma - sigma is the standard deviation of the distribution.
# bounds - bounds[0] is the lower bound and bounds[1]
# is the upper bound.
# upperSkewed - if the distribution is upper skewed.
# return: out - Rondomly generated value from the skewed distribution.
#
# @todo: Because NumPy's random value generators are faster when called
# a bunch of times at once, maybe allow this to generate and return
# multiple values at once?
def skewedGauss(mu, sigma, bounds, upperSkewed=True):
raw = gauss(mu, sigma)
# Quicker to check an extra condition than do unnecessary math. . . .
if raw < mu and not upperSkewed:
out = ((mu - bounds[0]) / (3 * sigma)) * raw + ((mu * (bounds[0] - (mu - 3 * sigma))) / (3 * sigma))
elif raw > mu and upperSkewed:
out = ((mu - bounds[1]) / (3 * -sigma)) * raw + ((mu * (bounds[1] - (mu + 3 * sigma))) / (3 * -sigma))
else:
out = raw
return out
# @todo create a def for generating an alpha and beta for a beta distribution
# given a mu, sigma, and an upper and lower bound. This proved faster in
# profiling in addition to providing a much better distribution curve
# provided multiple iterations happen within this function; otherwise it was
# slower.
# This might be a scratch because of the bounds placed on mu and sigma:
#
# For alpha > 1 and beta > 1:
# mu^2 - mu^3 mu^3 - mu^2 + mu
# ----------- < sigma < ----------------
# 1 + mu 2 - mu
#
# def generateBeta(mu, sigma, scale, repitions=1):
# results = []
#
# return results
@@ -0,0 +1,500 @@
# SPDX-FileCopyrightText: 2015-2023 Blender Foundation
#
# SPDX-License-Identifier: GPL-2.0-or-later
import bpy
from math import (
pi, sin,
cos, tan,
)
from bpy.types import Operator
from mathutils import (
Vector,
Euler,
)
from bpy.props import (
IntProperty,
FloatProperty,
BoolProperty,
StringProperty,
)
from bpy_extras import object_utils
from .interface import draw_transform_props
# mesh generating function, returns mesh
def add_mesh_Brilliant(context, s, table_w, crown_h, girdle_t, pavi_d, bezel_f,
pavi_f, culet, girdle_real, keep_lga, g_real_smooth):
# # possible user inputs ( output 100% = 2 blender units )
# s # no. of girdle facets (steps) default: 16
# table_w # table width default: 0.530
# crown_h # crown height default: 0.162
# girdle_t # girdle thickness default: 0.017
# pavi_d # pavilion depth default: 0.431
# bezel_f # bezel factor default: 0.250
# pavi_f # pavilion factor default: 0.400
# culet # culet size default: 0.000
# girdle_real # type of girdle flat/real default: True
# g_real_smooth # smooth or flat shading default: False
# keep_lga # when culet > 0, keep lga default: False
# variables / shortcuts
if s % 2: # prevent odd number of steps (messes up mesh)
s = s - 1
if not girdle_real:
g_real_smooth = False
ang = 2 * pi / s # angle step size
Verts = [] # collect all vertices
Faces = [] # collect all faces
ca = cos(ang)
ca2 = cos(ang / 2)
sa4 = sin(ang / 4)
ta4 = tan(ang / 4)
ta8 = tan(ang / 8)
def fa(*vs): # shortcut Faces.append
v = []
for u in vs:
v.append(u)
Faces.append(v)
def va(vx, vz, iang, sang, n): # shortcut Verts.append
for i in range(n):
v = Vector((vx, 0, vz))
ai = sang + iang * i
E_rot = Euler((0, 0, ai), 'XYZ')
v.rotate(E_rot)
Verts.append((v.x, v.y, v.z))
# upper girdle angle
uga = (1 - bezel_f) * crown_h * 2 / (ca2 -
(table_w + (1 - table_w) * bezel_f) * ca2 / ca)
# lower girdle angle
if keep_lga:
if pavi_f > 0 and pavi_f < 1:
lga = (1 - pavi_f) * pavi_d * 2 / (ca2 - pavi_f * ca2 / ca)
elif pavi_f == 1:
lga = 0
else:
lga = 2 * pavi_d * ca
else:
lga = (1 - pavi_f) * pavi_d * 2 / (ca2 -
(culet + (1 - culet) * pavi_f) * ca2 / ca)
# append girdle vertices
va(1, 0, ang, 0, s)
va(1, 2 * girdle_t, ang, 0, s)
# append real girdle vertices
if girdle_real:
dnu = uga * (1 - ca2)
dfu = uga * (ta8 + ta4) * sa4
dnl = lga * (1 - ca2)
dfl = lga * (ta8 + ta4) * sa4
if abs(dnu) + abs(dnl) > 2 * girdle_t or dnu < 0 or dnl < 0:
girdle_real = False
else:
va(1, dnl, ang, ang / 2, s)
va(1, 2 * girdle_t - dnu, ang, ang / 2, s)
va(1, dfl, ang / 2, ang / 4, 2 * s)
va(1, 2 * girdle_t - dfu, ang / 2, ang / 4, 2 * s)
# make girdle faces
l1 = len(Verts) # 2*s / 8*s
for i in range(l1):
if girdle_real:
if i < s:
fa(i, i + s, 2 * i + 6 * s, 2 * i + 4 * s)
if i == 0:
fa(i, s, l1 - 1, 6 * s - 1)
else:
fa(i, i + s, 2 * i + 6 * s - 1, 2 * i + 4 * s - 1)
elif i > 2 * s - 1 and i < 3 * s:
fa(i, i + s, 2 * (i + s), 2 * i)
fa(i, i + s, 2 * (i + s) + 1, 2 * i + 1)
else:
if i < s - 1:
fa(i, i + s, i + s + 1, i + 1)
elif i == s - 1:
fa(i, i + s, s, 0)
# append upper girdle facet vertices
va((table_w + (1 - table_w) * bezel_f) / ca, (1 - bezel_f) * 2 * crown_h +
2 * girdle_t, 2 * ang, ang, int(s / 2))
# make upper girdle facet faces
l2 = len(Verts) # 2.5*s / 8.5*s
for i in range(l2):
if i > s and i < 2 * s - 1 and i % 2 != 0:
if girdle_real:
fa(i, 2 * (i + 2 * s), i + 2 * s, 2 * (i + 2 * s) + 1, i + 1,
int(7.5 * s) + int((i - 1) / 2))
fa(i, 2 * (i + 2 * s) - 1, i + 2 * s - 1, 2 * (i + 2 * s - 1),
i - 1, int(7.5 * s) + int((i - 1) / 2))
else:
fa(i, i + 1, int((i + 3 * s) / 2))
fa(i, i - 1, int((i + 3 * s) / 2))
elif i == s:
if girdle_real:
fa(i, l1 - 1, 4 * s - 1, l1 - 2, 2 * i - 1, l2 - 1)
fa(2 * i - 2, l1 - 4, 4 * s - 2, l1 - 3, 2 * i - 1, l2 - 1)
else:
fa(i, 2 * i - 1, l2 - 1)
fa(2 * i - 1, 2 * i - 2, l2 - 1)
# append table vertices
va(table_w, (crown_h + girdle_t) * 2, 2 * ang, 0, int(s / 2))
# make bezel facet faces and star facet faces
l3 = len(Verts) # 3*s / 9*s
for i in range(l3):
if i > l2 - 1 and i < l3 - 1:
fa(i, i + 1, i - int(s / 2))
fa(i + 1, i - int(s / 2), 2 * (i - l2) + 2 + s, i - int(s / 2) + 1)
elif i == l3 - 1:
fa(i, l2, l2 - 1)
fa(s, l2 - 1, l2, l2 - int(s / 2))
# make table facet face
tf = []
for i in range(l3):
if i > l2 - 1:
tf.append(i)
fa(*tf)
# append lower girdle facet vertices
if keep_lga:
va(pavi_f / ca, (pavi_f - 1) * pavi_d * 2, 2 * ang, ang, int(s / 2))
else:
va((pavi_f * (1 - culet) + culet) / ca, (pavi_f - 1) * pavi_d * 2, 2 * ang,
ang, int(s / 2))
# make lower girdle facet faces
l4 = len(Verts) # 3.5*s / 9.5*s
for i in range(l4):
if i > 0 and i < s - 1 and i % 2 == 0:
if girdle_real:
fa(i, 2 * (i + 2 * s), i + 2 * s, 2 * (i + 2 * s) + 1, i + 1,
int(i / 2) + 9 * s)
fa(i, 2 * (i + 2 * s) - 1, i + 2 * s - 1, 2 * (i + 2 * s - 1),
i - 1, int(i / 2) + 9 * s - 1)
else:
fa(i, i + 1, int(i / 2) + l4 - int(s / 2))
fa(i, i - 1, int(i / 2) + l4 - int(s / 2) - 1)
elif i == 0:
if girdle_real:
fa(0, 4 * s, 2 * s, 4 * s + 1, 1, 9 * s)
fa(0, 6 * s - 1, 3 * s - 1, 6 * s - 2, s - 1, l4 - 1)
else:
fa(0, 1, l4 - int(s / 2))
fa(0, s - 1, l4 - 1)
# append culet vertice(s)
if culet == 0:
va(0, pavi_d * (-2), 0, 0, 1)
else:
if keep_lga:
va(culet * pavi_f / ca, pavi_d * (-2) + culet * pavi_f * 2 * pavi_d,
2 * ang, ang, int(s / 2))
else:
va(culet / ca, pavi_d * (-2), 2 * ang, ang, int(s / 2))
# make pavilion facet face
l5 = len(Verts) # 4*s / 10*s //if !culet: 3.5*s+1 / 9.5*s+1
for i in range(l5):
if i > 0 and i < s - 1 and i % 2 == 0:
if culet:
fa(i, l3 + int(i / 2), l3 + int((s + i) / 2),
l3 + int((s + i) / 2) - 1, l3 + int(i / 2) - 1)
else:
fa(i, l3 + int(i / 2), l5 - 1, l3 + int(i / 2) - 1)
elif i == 0:
if culet:
fa(i, l3, l4, l5 - 1, l4 - 1)
else:
fa(i, l3, l5 - 1, l4 - 1)
# make culet facet face
if culet:
cf = []
for i in range(l5):
if i > l4 - 1:
cf.append(i)
fa(*cf)
# create actual mesh and object based on Verts and Faces given
dmesh = bpy.data.meshes.new("dmesh")
dmesh.from_pydata(Verts, [], Faces)
dmesh.update()
return dmesh
# object generating function, returns final object
def addBrilliant(context, self, s, table_w, crown_h, girdle_t, pavi_d, bezel_f,
pavi_f, culet, girdle_real, keep_lga, g_real_smooth):
# deactivate possible active Objects
bpy.context.view_layer.objects.active = None
# create actual mesh and object based on Verts and Faces given
dmesh = add_mesh_Brilliant(context, s, table_w, crown_h, girdle_t, pavi_d, bezel_f,
pavi_f, culet, girdle_real, keep_lga, g_real_smooth)
# Create object and link it into scene.
dobj = object_utils.object_data_add(context, dmesh, operator=self, name="dobj")
# activate and select object
bpy.context.view_layer.objects.active = dobj
dobj.select_set(True)
obj = bpy.context.active_object
# flip all face normals outside
bpy.ops.object.mode_set(mode='EDIT', toggle=False)
sel_mode = bpy.context.tool_settings.mesh_select_mode
bpy.context.tool_settings.mesh_select_mode = [False, False, True]
bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
for i, face in enumerate(obj.data.polygons):
face.select = True
bpy.ops.object.mode_set(mode='EDIT', toggle=False)
bpy.ops.mesh.normals_make_consistent(inside=False)
bpy.context.tool_settings.mesh_select_mode = sel_mode
bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
# make girdle smooth for complex girdle
if girdle_real and g_real_smooth:
bpy.ops.object.mode_set(mode='EDIT', toggle=False)
bpy.ops.mesh.select_all(action='DESELECT') # deselect all mesh data
bpy.ops.object.mode_set(mode='OBJECT')
pls = []
dp = obj.data.polygons[:4 * s] # only consider faces of girdle
ov = obj.data.vertices
for i, p in enumerate(dp):
pls.extend(p.vertices) # list all verts of girdle
for i, e in enumerate(obj.data.edges): # select edges to mark sharp
if e.vertices[0] in pls and e.vertices[1] in pls and abs(
ov[e.vertices[0]].co.x - ov[e.vertices[1]].co.x):
obj.data.edges[i].select = True
continue
obj.data.edges[i].select = False
bpy.ops.object.mode_set(mode='EDIT', toggle=False)
bpy.ops.mesh.mark_sharp()
bpy.context.tool_settings.mesh_select_mode = [False, False, True]
bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
bpy.ops.object.select_all(action='DESELECT')
for i, face in enumerate(obj.data.polygons):
if i < 4 * s:
face.select = True
continue
face.select = False
bpy.ops.object.mode_set(mode='EDIT', toggle=False)
bpy.ops.mesh.faces_shade_smooth()
edge_split_modifier = context.object.modifiers.new("", 'EDGE_SPLIT')
bpy.context.tool_settings.mesh_select_mode = sel_mode
bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
bpy.ops.object.modifier_apply(modifier=edge_split_modifier.name)
return dobj
# add new operator for object
class MESH_OT_primitive_brilliant_add(Operator, object_utils.AddObjectHelper):
bl_idname = "mesh.primitive_brilliant_add"
bl_label = "Brilliant"
bl_description = "Construct a custom brilliant mesh"
bl_options = {'REGISTER', 'UNDO', 'PRESET'}
Brilliant : BoolProperty(name = "Brilliant",
default = True,
description = "Brilliant")
change : BoolProperty(name = "Change",
default = False,
description = "change Brilliant")
s: IntProperty(
name="Segments",
description="Longitudial segmentation",
step=1,
min=6,
max=128,
default=16
)
table_w: FloatProperty(
name="Table Width",
description="Width of table",
min=0.001,
max=1.0,
default=0.53,
subtype='FACTOR'
)
crown_h: FloatProperty(
name="Crown Height",
description="Height of crown",
min=0.0,
max=1.0,
default=0.162,
subtype='FACTOR'
)
girdle_t: FloatProperty(
name="Girdle Height",
description="Height of girdle",
min=0.0,
max=0.5,
default=0.017,
subtype='FACTOR'
)
girdle_real: BoolProperty(
name="Real Girdle",
description="More beautiful girdle; has more polygons",
default=True
)
g_real_smooth: BoolProperty(
name="Smooth Girdle",
description="smooth shading for girdle, only available for real girdle",
default=False
)
pavi_d: FloatProperty(
name="Pavilion Depth",
description="Height of pavilion",
min=0.0,
max=1.0,
default=0.431,
subtype='FACTOR'
)
bezel_f: FloatProperty(
name="Upper Facet Factor",
description="Determines the form of bezel and upper girdle facets",
min=0.0,
max=1.0,
default=0.250,
subtype='FACTOR'
)
pavi_f: FloatProperty(
name="Lower Facet Factor",
description="Determines the form of pavilion and lower girdle facets",
min=0.001,
max=1.0,
default=0.400,
subtype='FACTOR'
)
culet: FloatProperty(
name="Culet Size",
description="0: no culet (default)",
min=0.0,
max=0.999,
default=0.0,
subtype='FACTOR'
)
keep_lga: BoolProperty(
name="Retain Lower Angle",
description="If culet > 0, retains angle of pavilion facets",
default=False
)
def draw(self, context):
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
layout.separator()
layout.prop(self, "s")
layout.prop(self, "table_w")
col = layout.column(align=True)
col.prop(self, "crown_h", text='Height Crown')
col.prop(self, "girdle_t", text='Girdle')
col.prop(self, "pavi_d", text='Pavilion')
layout.prop(self, "girdle_real")
layout.prop(self, "g_real_smooth")
col = layout.column(align=True)
col.prop(self, "bezel_f", text='Facet Upper')
col.prop(self, "pavi_f", text='Lower')
layout.prop(self, "culet")
layout.prop(self, "keep_lga")
if self.change == False:
layout.separator()
draw_transform_props(self, layout)
# call mesh/object generator function with user inputs
def execute(self, context):
# turn off 'Enter Edit Mode'
use_enter_edit_mode = bpy.context.preferences.edit.use_enter_edit_mode
bpy.context.preferences.edit.use_enter_edit_mode = False
if bpy.context.mode == "OBJECT":
if context.selected_objects != [] and context.active_object and \
(context.active_object.data is not None) and ('Brilliant' in context.active_object.data.keys()) and \
(self.change == True):
obj = context.active_object
oldmesh = obj.data
oldmeshname = obj.data.name
mesh = add_mesh_Brilliant(context, self.s, self.table_w, self.crown_h,
self.girdle_t, self.pavi_d, self.bezel_f,
self.pavi_f, self.culet, self.girdle_real,
self.keep_lga, self.g_real_smooth
)
obj.data = mesh
for material in oldmesh.materials:
obj.data.materials.append(material)
bpy.data.meshes.remove(oldmesh)
obj.data.name = oldmeshname
else:
obj = addBrilliant(context, self, self.s, self.table_w, self.crown_h,
self.girdle_t, self.pavi_d, self.bezel_f,
self.pavi_f, self.culet, self.girdle_real,
self.keep_lga, self.g_real_smooth
)
obj.data["Brilliant"] = True
obj.data["change"] = False
for prm in BrilliantParameters():
obj.data[prm] = getattr(self, prm)
if bpy.context.mode == "EDIT_MESH":
active_object = context.active_object
name_active_object = active_object.name
bpy.ops.object.mode_set(mode='OBJECT')
obj = addBrilliant(context, self, self.s, self.table_w, self.crown_h,
self.girdle_t, self.pavi_d, self.bezel_f,
self.pavi_f, self.culet, self.girdle_real,
self.keep_lga, self.g_real_smooth
)
obj.select_set(True)
active_object.select_set(True)
bpy.context.view_layer.objects.active = active_object
bpy.ops.object.join()
context.active_object.name = name_active_object
bpy.ops.object.mode_set(mode='EDIT')
if use_enter_edit_mode:
bpy.ops.object.mode_set(mode = 'EDIT')
# restore pre operator state
bpy.context.preferences.edit.use_enter_edit_mode = use_enter_edit_mode
return {'FINISHED'}
def BrilliantParameters():
BrilliantParameters = [
"s",
"table_w",
"crown_h",
"girdle_t",
"girdle_real",
"g_real_smooth",
"pavi_d",
"bezel_f",
"pavi_f",
"culet",
"keep_lga",
]
return BrilliantParameters
@@ -0,0 +1,521 @@
# SPDX-FileCopyrightText: 2015-2022 Blender Foundation
#
# SPDX-License-Identifier: GPL-2.0-or-later
# Author: Alain Ducharme (phymec)
import bpy
from bpy_extras import object_utils
from itertools import permutations
from math import (
copysign, pi,
sqrt,
)
from bpy.types import Operator
from bpy.props import (
BoolProperty,
EnumProperty,
FloatProperty,
FloatVectorProperty,
IntProperty,
StringProperty,
)
from .interface import draw_transform_props
def round_cube(radius=1.0, arcdiv=4, lindiv=0., size=(0., 0., 0.),
div_type='CORNERS', odd_axis_align=False, info_only=False):
# subdiv bitmasks
CORNERS, EDGES, ALL = 0, 1, 2
try:
subdiv = ('CORNERS', 'EDGES', 'ALL').index(div_type)
except ValueError:
subdiv = CORNERS # fallback
radius = max(radius, 0.)
if not radius:
# No sphere
arcdiv = 1
odd_axis_align = False
if arcdiv <= 0:
arcdiv = max(round(pi * radius * lindiv * 0.5), 1)
arcdiv = max(round(arcdiv), 1)
if lindiv <= 0. and radius:
lindiv = 1. / (pi / (arcdiv * 2.) * radius)
lindiv = max(lindiv, 0.)
if not lindiv:
subdiv = CORNERS
odd = arcdiv % 2 # even = arcdiv % 2 ^ 1
step_size = 2. / arcdiv
odd_aligned = 0
vi = -1.
steps = arcdiv + 1
if odd_axis_align and odd:
odd_aligned = 1
vi += 0.5 * step_size
steps = arcdiv
axis_aligned = not odd or odd_aligned
if arcdiv == 1 and not odd_aligned and subdiv == EDGES:
subdiv = CORNERS
half_chord = 0. # ~ spherical cap base radius
sagitta = 0. # ~ spherical cap height
if not axis_aligned:
half_chord = sqrt(3.) * radius / (3. * arcdiv)
id2 = 1. / (arcdiv * arcdiv)
sagitta = radius - radius * sqrt(id2 * id2 / 3. - id2 + 1.)
# Extrusion per axis
exyz = [0. if s < 2. * (radius - sagitta) else (s - 2. * (radius - sagitta)) * 0.5 for s in size]
ex, ey, ez = exyz
dxyz = [0, 0, 0] # extrusion divisions per axis
dssxyz = [0., 0., 0.] # extrusion division step sizes per axis
for i in range(3):
sc = 2. * (exyz[i] + half_chord)
dxyz[i] = round(sc * lindiv) if subdiv else 0
if dxyz[i]:
dssxyz[i] = sc / dxyz[i]
dxyz[i] -= 1
else:
dssxyz[i] = sc
if info_only:
ec = sum(1 for n in exyz if n)
if subdiv:
fxyz = [d + (e and axis_aligned) for d, e in zip(dxyz, exyz)]
dvc = arcdiv * 4 * sum(fxyz)
if subdiv == ALL:
dvc += sum(p1 * p2 for p1, p2 in permutations(fxyz, 2))
elif subdiv == EDGES and axis_aligned:
# (0, 0, 2, 4) * sum(dxyz) + (0, 0, 2, 6)
dvc += ec * ec // 2 * sum(dxyz) + ec * (ec - 1)
else:
dvc = (arcdiv * 4) * ec + ec * (ec - 1) if axis_aligned else 0
vert_count = int(6 * arcdiv * arcdiv + (0 if odd_aligned else 2) + dvc)
if not radius and not max(size) > 0:
vert_count = 1
return arcdiv, lindiv, vert_count
if not radius and not max(size) > 0:
# Single vertex
return [(0, 0, 0)], []
# uv lookup table
uvlt = []
v = vi
for j in range(1, steps + 1):
v2 = v * v
uvlt.append((v, v2, radius * sqrt(18. - 6. * v2) / 6.))
v = vi + j * step_size # v += step_size # instead of accumulating errors
# clear fp errors / signs at axis
if abs(v) < 1e-10:
v = 0.0
# Sides built left to right bottom up
# xp yp zp xd yd zd
sides = ((0, 2, 1, (-1, 1, 1)), # Y+ Front
(1, 2, 0, (-1, -1, 1)), # X- Left
(0, 2, 1, (1, -1, 1)), # Y- Back
(1, 2, 0, (1, 1, 1)), # X+ Right
(0, 1, 2, (-1, 1, -1)), # Z- Bottom
(0, 1, 2, (-1, -1, 1))) # Z+ Top
# side vertex index table (for sphere)
svit = [[[] for i in range(steps)] for i in range(6)]
# Extend svit rows for extrusion
yer = zer = 0
if ey:
yer = axis_aligned + (dxyz[1] if subdiv else 0)
svit[4].extend([[] for i in range(yer)])
svit[5].extend([[] for i in range(yer)])
if ez:
zer = axis_aligned + (dxyz[2] if subdiv else 0)
for side in range(4):
svit[side].extend([[] for i in range(zer)])
# Extend svit rows for odd_aligned
if odd_aligned:
for side in range(4):
svit[side].append([])
hemi = steps // 2
# Create vertices and svit without dups
vert = [0., 0., 0.]
verts = []
if arcdiv == 1 and not odd_aligned and subdiv == ALL:
# Special case: Grid Cuboid
for side, (xp, yp, zp, dir) in enumerate(sides):
svitc = svit[side]
rows = len(svitc)
if rows < dxyz[yp] + 2:
svitc.extend([[] for i in range(dxyz[yp] + 2 - rows)])
vert[zp] = (half_chord + exyz[zp]) * dir[zp]
for j in range(dxyz[yp] + 2):
vert[yp] = (j * dssxyz[yp] - half_chord - exyz[yp]) * dir[yp]
for i in range(dxyz[xp] + 2):
vert[xp] = (i * dssxyz[xp] - half_chord - exyz[xp]) * dir[xp]
if (side == 5) or ((i < dxyz[xp] + 1 and j < dxyz[yp] + 1) and (side < 4 or (i and j))):
svitc[j].append(len(verts))
verts.append(tuple(vert))
else:
for side, (xp, yp, zp, dir) in enumerate(sides):
svitc = svit[side]
exr = exyz[xp]
eyr = exyz[yp]
ri = 0 # row index
rij = zer if side < 4 else yer
if side == 5:
span = range(steps)
elif side < 4 or odd_aligned:
span = range(arcdiv)
else:
span = range(1, arcdiv)
ri = 1
for j in span: # rows
v, v2, mv2 = uvlt[j]
tv2mh = 1. / 3. * v2 - 0.5
hv2 = 0.5 * v2
if j == hemi and rij:
# Jump over non-edge row indices
ri += rij
for i in span: # columns
u, u2, mu2 = uvlt[i]
vert[xp] = u * mv2
vert[yp] = v * mu2
vert[zp] = radius * sqrt(u2 * tv2mh - hv2 + 1.)
vert[0] = (vert[0] + copysign(ex, vert[0])) * dir[0]
vert[1] = (vert[1] + copysign(ey, vert[1])) * dir[1]
vert[2] = (vert[2] + copysign(ez, vert[2])) * dir[2]
rv = tuple(vert)
if exr and i == hemi:
rx = vert[xp] # save rotated x
vert[xp] = rxi = (-exr - half_chord) * dir[xp]
if axis_aligned:
svitc[ri].append(len(verts))
verts.append(tuple(vert))
if subdiv:
offsetx = dssxyz[xp] * dir[xp]
for k in range(dxyz[xp]):
vert[xp] += offsetx
svitc[ri].append(len(verts))
verts.append(tuple(vert))
if eyr and j == hemi and axis_aligned:
vert[xp] = rxi
vert[yp] = -eyr * dir[yp]
svitc[hemi].append(len(verts))
verts.append(tuple(vert))
if subdiv:
offsety = dssxyz[yp] * dir[yp]
ry = vert[yp]
for k in range(dxyz[yp]):
vert[yp] += offsety
svitc[hemi + axis_aligned + k].append(len(verts))
verts.append(tuple(vert))
vert[yp] = ry
for k in range(dxyz[xp]):
vert[xp] += offsetx
svitc[hemi].append(len(verts))
verts.append(tuple(vert))
if subdiv & ALL:
for l in range(dxyz[yp]):
vert[yp] += offsety
svitc[hemi + axis_aligned + l].append(len(verts))
verts.append(tuple(vert))
vert[yp] = ry
vert[xp] = rx # restore
if eyr and j == hemi:
vert[yp] = (-eyr - half_chord) * dir[yp]
if axis_aligned:
svitc[hemi].append(len(verts))
verts.append(tuple(vert))
if subdiv:
offsety = dssxyz[yp] * dir[yp]
for k in range(dxyz[yp]):
vert[yp] += offsety
if exr and i == hemi and not axis_aligned and subdiv & ALL:
vert[xp] = rxi
for l in range(dxyz[xp]):
vert[xp] += offsetx
svitc[hemi + k].append(len(verts))
verts.append(tuple(vert))
vert[xp] = rx
svitc[hemi + axis_aligned + k].append(len(verts))
verts.append(tuple(vert))
svitc[ri].append(len(verts))
verts.append(rv)
ri += 1
# Complete svit edges (shared vertices)
# Sides' right edge
for side, rows in enumerate(svit[:4]):
for j, row in enumerate(rows[:-1]):
svit[3 if not side else side - 1][j].append(row[0])
# Sides' top edge
svit[0][-1].extend(svit[5][0])
svit[2][-1].extend(svit[5][-1][::-1])
for row in svit[5]:
svit[3][-1].insert(0, row[0])
svit[1][-1].append(row[-1])
if odd_aligned:
for side in svit[:4]:
side[-1].append(-1)
# Bottom edges
if odd_aligned:
svit[4].insert(0, [-1] + svit[2][0][-2::-1] + [-1])
for i, col in enumerate(svit[3][0][:-1]):
svit[4][i + 1].insert(0, col)
svit[4][i + 1].append(svit[1][0][-i - 2])
svit[4].append([-1] + svit[0][0][:-1] + [-1])
else:
svit[4][0].extend(svit[2][0][::-1])
for i, col in enumerate(svit[3][0][1:-1]):
svit[4][i + 1].insert(0, col)
svit[4][i + 1].append(svit[1][0][-i - 2])
svit[4][-1].extend(svit[0][0])
# Build faces
faces = []
if not axis_aligned:
hemi -= 1
for side, rows in enumerate(svit):
xp, yp = sides[side][:2]
oa4 = odd_aligned and side == 4
if oa4: # special case
hemi += 1
for j, row in enumerate(rows[:-1]):
tri = odd_aligned and (oa4 and not j or rows[j + 1][-1] < 0)
for i, vi in enumerate(row[:-1]):
# odd_aligned triangle corners
if vi < 0:
if not j and not i:
faces.append((row[i + 1], rows[j + 1][i + 1], rows[j + 1][i]))
elif oa4 and not i and j == len(rows) - 2:
faces.append((vi, row[i + 1], rows[j + 1][i + 1]))
elif tri and i == len(row) - 2:
if j:
faces.append((vi, row[i + 1], rows[j + 1][i]))
else:
if oa4 or arcdiv > 1:
faces.append((vi, rows[j + 1][i + 1], rows[j + 1][i]))
else:
faces.append((vi, row[i + 1], rows[j + 1][i]))
# subdiv = EDGES (not ALL)
elif subdiv and len(rows[j + 1]) < len(row) and (i >= hemi):
if (i == hemi):
faces.append((vi, row[i + 1 + dxyz[xp]], rows[j + 1 + dxyz[yp]][i + 1 + dxyz[xp]],
rows[j + 1 + dxyz[yp]][i]))
elif i > hemi + dxyz[xp]:
faces.append((vi, row[i + 1], rows[j + 1][i + 1 - dxyz[xp]], rows[j + 1][i - dxyz[xp]]))
elif subdiv and len(rows[j + 1]) > len(row) and (i >= hemi):
if (i > hemi):
faces.append((vi, row[i + 1], rows[j + 1][i + 1 + dxyz[xp]], rows[j + 1][i + dxyz[xp]]))
elif subdiv and len(row) < len(rows[0]) and i == hemi:
pass
else:
# Most faces...
faces.append((vi, row[i + 1], rows[j + 1][i + 1], rows[j + 1][i]))
if oa4:
hemi -= 1
return verts, faces
class AddRoundCube(Operator, object_utils.AddObjectHelper):
bl_idname = "mesh.primitive_round_cube_add"
bl_label = "Add Round Cube"
bl_description = ("Create mesh primitives: Quadspheres, "
"Capsules, Rounded Cuboids, 3D Grids etc")
bl_options = {"REGISTER", "UNDO", "PRESET"}
sanity_check_verts = 200000
vert_count = 0
Roundcube : BoolProperty(name = "Roundcube",
default = True,
description = "Roundcube")
change : BoolProperty(name = "Change",
default = False,
description = "change Roundcube")
radius: FloatProperty(
name="Radius",
description="Radius of vertices for sphere, capsule or cuboid bevel",
default=1, min=0.0, soft_min=0.01, step=10
)
size: FloatVectorProperty(
name="Size",
description="Size",
subtype='XYZ',
default=(0.0, 0.0, 0.0),
)
arc_div: IntProperty(
name="Arc Divisions",
description="Arc curve divisions, per quadrant, 0=derive from Linear",
default=8, min=1
)
lin_div: FloatProperty(
name="Linear Divisions",
description="Linear unit divisions (Edges/Faces), 0=derive from Arc",
default=0.0, min=0.0, step=100, precision=1
)
no_limit: BoolProperty(
name='No Vertex Limit',
description='Do not limit to ' + str(sanity_check_verts) + ' vertices (sanity check)',
options={'HIDDEN'},
default=False
)
div_type: EnumProperty(
name='Type',
description='Division type',
items=(
('CORNERS', 'Corners', 'Sphere / Corners'),
('EDGES', 'Edges', 'Sphere / Corners and extruded edges (size)'),
('ALL', 'All', 'Sphere / Corners, extruded edges and faces (size)')),
default='CORNERS',
)
odd_axis_align: BoolProperty(
name='Odd Axis Align',
description='Align odd arc divisions with axes (Note: triangle corners!)',
)
def execute(self, context):
# turn off 'Enter Edit Mode'
use_enter_edit_mode = bpy.context.preferences.edit.use_enter_edit_mode
bpy.context.preferences.edit.use_enter_edit_mode = False
if self.arc_div <= 0 and self.lin_div <= 0:
self.report({'ERROR'},
"Either Arc Divisions or Linear Divisions must be greater than zero")
return {'CANCELLED'}
if not self.no_limit and self.vert_count > self.sanity_check_verts:
self.report({'ERROR'}, 'More than ' + str(self.sanity_check_verts) +
' vertices! Check "No Limit" to proceed')
return {'CANCELLED'}
if bpy.context.mode == "OBJECT":
if context.selected_objects != [] and context.active_object and \
(context.active_object.data is not None) and ('Roundcube' in context.active_object.data.keys()) and \
(self.change == True):
obj = context.active_object
oldmesh = obj.data
oldmeshname = obj.data.name
verts, faces = round_cube(self.radius, self.arc_div, self.lin_div,
self.size, self.div_type, self.odd_axis_align)
mesh = bpy.data.meshes.new('Roundcube')
mesh.from_pydata(verts, [], faces)
obj.data = mesh
for material in oldmesh.materials:
obj.data.materials.append(material)
bpy.data.meshes.remove(oldmesh)
obj.data.name = oldmeshname
else:
verts, faces = round_cube(self.radius, self.arc_div, self.lin_div,
self.size, self.div_type, self.odd_axis_align)
mesh = bpy.data.meshes.new('Roundcube')
mesh.from_pydata(verts, [], faces)
obj = object_utils.object_data_add(context, mesh, operator=self)
obj.data["Roundcube"] = True
obj.data["change"] = False
for prm in RoundCubeParameters():
obj.data[prm] = getattr(self, prm)
if bpy.context.mode == "EDIT_MESH":
active_object = context.active_object
name_active_object = active_object.name
bpy.ops.object.mode_set(mode='OBJECT')
verts, faces = round_cube(self.radius, self.arc_div, self.lin_div,
self.size, self.div_type, self.odd_axis_align)
mesh = bpy.data.meshes.new('Roundcube')
mesh.from_pydata(verts, [], faces)
obj = object_utils.object_data_add(context, mesh, operator=self)
obj.select_set(True)
active_object.select_set(True)
bpy.context.view_layer.objects.active = active_object
bpy.ops.object.join()
context.active_object.name = name_active_object
bpy.ops.object.mode_set(mode='EDIT')
if use_enter_edit_mode:
bpy.ops.object.mode_set(mode = 'EDIT')
# restore pre operator state
bpy.context.preferences.edit.use_enter_edit_mode = use_enter_edit_mode
return {'FINISHED'}
def check(self, context):
self.arcdiv, self.lindiv, self.vert_count = round_cube(
self.radius, self.arc_div, self.lin_div,
self.size, self.div_type, self.odd_axis_align,
True
)
return True
def invoke(self, context, event):
self.check(context)
return self.execute(context)
def draw(self, context):
self.check(context)
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
layout.separator()
layout.prop(self, 'radius')
layout.column().prop(self, 'size', expand=True)
layout.separator()
layout.prop(self, 'div_type', text='Division Method')
layout.prop(self, 'arc_div', text='Arc')
row = layout.row()
row.enabled = (
self.div_type != 'CORNERS' and
(
self.size[0] > self.radius*2 or
self.size[1] > self.radius*2 or
self.size[2] > self.radius*2
)
)
row.prop(self, 'lin_div', text='Linear')
row = layout.row()
row.alert = self.vert_count > self.sanity_check_verts
row.prop(self, 'no_limit')
row = layout.row()
row.active = self.arcdiv % 2
row.prop(self, 'odd_axis_align', text='Triangle Corners')
if self.change == False:
layout.separator()
draw_transform_props(self, layout)
def RoundCubeParameters():
RoundCubeParameters = [
"radius",
"size",
"arc_div",
"lin_div",
"div_type",
"odd_axis_align",
"no_limit",
]
return RoundCubeParameters
@@ -0,0 +1,405 @@
# SPDX-FileCopyrightText: 2010-2022 Blender Foundation
#
# SPDX-License-Identifier: GPL-2.0-or-later
# Author: DreamPainter
import bpy
from math import sqrt
from mathutils import Vector
from functools import reduce
from bpy.props import (
FloatProperty,
EnumProperty,
BoolProperty,
)
from bpy_extras.object_utils import object_data_add
# function to make the reduce function work as a workaround to sum a list of vectors
def vSum(list):
return reduce(lambda a, b: a + b, list)
# Get a copy of the input faces, but with the normals flipped by reversing the order of the vertex indices of each face.
def flippedFaceNormals(faces):
return [list(reversed(vertexIndices)) for vertexIndices in faces]
# creates the 5 platonic solids as a base for the rest
# plato: should be one of {"4","6","8","12","20"}. decides what solid the
# outcome will be.
# returns a list of vertices and faces
def source(plato):
verts = []
faces = []
# Tetrahedron
if plato == "4":
# Calculate the necessary constants
s = sqrt(2) / 3.0
t = -1 / 3
u = sqrt(6) / 3
# create the vertices and faces
v = [(0, 0, 1), (2 * s, 0, t), (-s, u, t), (-s, -u, t)]
faces = [[0, 1, 2], [0, 2, 3], [0, 3, 1], [1, 3, 2]]
# Hexahedron (cube)
elif plato == "6":
# Calculate the necessary constants
s = 1 / sqrt(3)
# create the vertices and faces
v = [(-s, -s, -s), (s, -s, -s), (s, s, -s), (-s, s, -s), (-s, -s, s), (s, -s, s), (s, s, s), (-s, s, s)]
faces = [[0, 3, 2, 1], [0, 1, 5, 4], [0, 4, 7, 3], [6, 5, 1, 2], [6, 2, 3, 7], [6, 7, 4, 5]]
# Octahedron
elif plato == "8":
# create the vertices and faces
v = [(1, 0, 0), (-1, 0, 0), (0, 1, 0), (0, -1, 0), (0, 0, 1), (0, 0, -1)]
faces = [[4, 0, 2], [4, 2, 1], [4, 1, 3], [4, 3, 0], [5, 2, 0], [5, 1, 2], [5, 3, 1], [5, 0, 3]]
# Dodecahedron
elif plato == "12":
# Calculate the necessary constants
s = 1 / sqrt(3)
t = sqrt((3 - sqrt(5)) / 6)
u = sqrt((3 + sqrt(5)) / 6)
# create the vertices and faces
v = [(s, s, s), (s, s, -s), (s, -s, s), (s, -s, -s), (-s, s, s), (-s, s, -s), (-s, -s, s), (-s, -s, -s),
(t, u, 0), (-t, u, 0), (t, -u, 0), (-t, -u, 0), (u, 0, t), (u, 0, -t), (-u, 0, t), (-u, 0, -t), (0, t, u),
(0, -t, u), (0, t, -u), (0, -t, -u)]
faces = [[0, 8, 9, 4, 16], [0, 12, 13, 1, 8], [0, 16, 17, 2, 12], [8, 1, 18, 5, 9], [12, 2, 10, 3, 13],
[16, 4, 14, 6, 17], [9, 5, 15, 14, 4], [6, 11, 10, 2, 17], [3, 19, 18, 1, 13], [7, 15, 5, 18, 19],
[7, 11, 6, 14, 15], [7, 19, 3, 10, 11]]
# Icosahedron
elif plato == "20":
# Calculate the necessary constants
s = (1 + sqrt(5)) / 2
t = sqrt(1 + s * s)
s = s / t
t = 1 / t
# create the vertices and faces
v = [(s, t, 0), (-s, t, 0), (s, -t, 0), (-s, -t, 0), (t, 0, s), (t, 0, -s), (-t, 0, s), (-t, 0, -s),
(0, s, t), (0, -s, t), (0, s, -t), (0, -s, -t)]
faces = [[0, 8, 4], [0, 5, 10], [2, 4, 9], [2, 11, 5], [1, 6, 8], [1, 10, 7], [3, 9, 6], [3, 7, 11],
[0, 10, 8], [1, 8, 10], [2, 9, 11], [3, 11, 9], [4, 2, 0], [5, 0, 2], [6, 1, 3], [7, 3, 1],
[8, 6, 4], [9, 4, 6], [10, 5, 7], [11, 7, 5]]
# convert the tuples to Vectors
verts = [Vector(i) for i in v]
return verts, faces
# processes the raw data from source
def createSolid(plato, vtrunc, etrunc, dual, snub):
# the duals from each platonic solid
dualSource = {"4": "4",
"6": "8",
"8": "6",
"12": "20",
"20": "12"}
# constants saving space and readability
vtrunc *= 0.5
etrunc *= 0.5
supposedSize = 0
noSnub = (snub == "None") or (etrunc == 0.5) or (etrunc == 0)
lSnub = (snub == "Left") and (0 < etrunc < 0.5)
rSnub = (snub == "Right") and (0 < etrunc < 0.5)
# no truncation
if vtrunc == 0:
if dual: # dual is as simple as another, but mirrored platonic solid
vInput, fInput = source(dualSource[plato])
supposedSize = vSum(vInput[i] for i in fInput[0]).length / len(fInput[0])
vInput = [-i * supposedSize for i in vInput] # mirror it
# Inverting vInput turns the mesh inside-out, so normals need to be flipped.
return vInput, flippedFaceNormals(fInput)
return source(plato)
elif 0 < vtrunc <= 0.5: # simple truncation of the source
vInput, fInput = source(plato)
else:
# truncation is now equal to simple truncation of the dual of the source
vInput, fInput = source(dualSource[plato])
supposedSize = vSum(vInput[i] for i in fInput[0]).length / len(fInput[0])
vtrunc = 1 - vtrunc # account for the source being a dual
if vtrunc == 0: # no truncation needed
if dual:
vInput, fInput = source(plato)
vInput = [-i * supposedSize for i in vInput]
# Inverting vInput turns the mesh inside-out, so normals need to be flipped.
return vInput, flippedFaceNormals(fInput)
# generate connection database
vDict = [{} for i in vInput]
# for every face, store what vertex comes after and before the current vertex
for x in range(len(fInput)):
i = fInput[x]
for j in range(len(i)):
vDict[i[j - 1]][i[j]] = [i[j - 2], x]
if len(vDict[i[j - 1]]) == 1:
vDict[i[j - 1]][-1] = i[j]
# the actual connection database: exists out of:
# [vtrunc pos, etrunc pos, connected vert IDs, connected face IDs]
vData = [[[], [], [], []] for i in vInput]
fvOutput = [] # faces created from truncated vertices
feOutput = [] # faces created from truncated edges
vOutput = [] # newly created vertices
for x in range(len(vInput)):
i = vDict[x] # lookup the current vertex
current = i[-1]
while True: # follow the chain to get a ccw order of connected verts and faces
vData[x][2].append(i[current][0])
vData[x][3].append(i[current][1])
# create truncated vertices
vData[x][0].append((1 - vtrunc) * vInput[x] + vtrunc * vInput[vData[x][2][-1]])
current = i[current][0]
if current == i[-1]:
break # if we're back at the first: stop the loop
fvOutput.append([]) # new face from truncated vert
fOffset = x * (len(i) - 1) # where to start off counting faceVerts
# only create one vert where one is needed (v1 todo: done)
if etrunc == 0.5:
for j in range(len(i) - 1):
vOutput.append((vData[x][0][j] + vData[x][0][j - 1]) * etrunc) # create vert
fvOutput[x].append(fOffset + j) # add to face
fvOutput[x] = fvOutput[x][1:] + [fvOutput[x][0]] # rotate face for ease later on
# create faces from truncated edges.
for j in range(len(i) - 1):
if x > vData[x][2][j]: # only create when other vertex has been added
index = vData[vData[x][2][j]][2].index(x)
feOutput.append([fvOutput[x][j], fvOutput[x][j - 1],
fvOutput[vData[x][2][j]][index],
fvOutput[vData[x][2][j]][index - 1]])
# edge truncation between none and full
elif etrunc > 0:
for j in range(len(i) - 1):
# create snubs from selecting verts from rectified meshes
if rSnub:
vOutput.append(etrunc * vData[x][0][j] + (1 - etrunc) * vData[x][0][j - 1])
fvOutput[x].append(fOffset + j)
elif lSnub:
vOutput.append((1 - etrunc) * vData[x][0][j] + etrunc * vData[x][0][j - 1])
fvOutput[x].append(fOffset + j)
else: # noSnub, select both verts from rectified mesh
vOutput.append(etrunc * vData[x][0][j] + (1 - etrunc) * vData[x][0][j - 1])
vOutput.append((1 - etrunc) * vData[x][0][j] + etrunc * vData[x][0][j - 1])
fvOutput[x].append(2 * fOffset + 2 * j)
fvOutput[x].append(2 * fOffset + 2 * j + 1)
# rotate face for ease later on
if noSnub:
fvOutput[x] = fvOutput[x][2:] + fvOutput[x][:2]
else:
fvOutput[x] = fvOutput[x][1:] + [fvOutput[x][0]]
# create single face for each edge
if noSnub:
for j in range(len(i) - 1):
if x > vData[x][2][j]:
index = vData[vData[x][2][j]][2].index(x)
feOutput.append([fvOutput[x][j * 2], fvOutput[x][2 * j - 1],
fvOutput[vData[x][2][j]][2 * index],
fvOutput[vData[x][2][j]][2 * index - 1]])
# create 2 tri's for each edge for the snubs
elif rSnub:
for j in range(len(i) - 1):
if x > vData[x][2][j]:
index = vData[vData[x][2][j]][2].index(x)
feOutput.append([fvOutput[x][j], fvOutput[x][j - 1],
fvOutput[vData[x][2][j]][index]])
feOutput.append([fvOutput[x][j], fvOutput[vData[x][2][j]][index],
fvOutput[vData[x][2][j]][index - 1]])
elif lSnub:
for j in range(len(i) - 1):
if x > vData[x][2][j]:
index = vData[vData[x][2][j]][2].index(x)
feOutput.append([fvOutput[x][j], fvOutput[x][j - 1],
fvOutput[vData[x][2][j]][index - 1]])
feOutput.append([fvOutput[x][j - 1], fvOutput[vData[x][2][j]][index],
fvOutput[vData[x][2][j]][index - 1]])
# special rules for birectified mesh (v1 todo: done)
elif vtrunc == 0.5:
for j in range(len(i) - 1):
if x < vData[x][2][j]: # use current vert, since other one has not passed yet
vOutput.append(vData[x][0][j])
fvOutput[x].append(len(vOutput) - 1)
else:
# search for other edge to avoid duplicity
connectee = vData[x][2][j]
fvOutput[x].append(fvOutput[connectee][vData[connectee][2].index(x)])
else: # vert truncation only
vOutput.extend(vData[x][0]) # use generated verts from way above
for j in range(len(i) - 1): # create face from them
fvOutput[x].append(fOffset + j)
# calculate supposed vertex length to ensure continuity
if supposedSize and not dual: # this to make the vtrunc > 1 work
supposedSize *= len(fvOutput[0]) / vSum(vOutput[i] for i in fvOutput[0]).length
vOutput = [-i * supposedSize for i in vOutput]
# Inverting vOutput turns the mesh inside-out, so normals need to be flipped.
flipNormals = True
else:
flipNormals = False
# create new faces by replacing old vert IDs by newly generated verts
ffOutput = [[] for i in fInput]
for x in range(len(fInput)):
# only one generated vert per vertex, so choose accordingly
if etrunc == 0.5 or (etrunc == 0 and vtrunc == 0.5) or lSnub or rSnub:
ffOutput[x] = [fvOutput[i][vData[i][3].index(x) - 1] for i in fInput[x]]
# two generated verts per vertex
elif etrunc > 0:
for i in fInput[x]:
ffOutput[x].append(fvOutput[i][2 * vData[i][3].index(x) - 1])
ffOutput[x].append(fvOutput[i][2 * vData[i][3].index(x) - 2])
else: # cutting off corners also makes 2 verts
for i in fInput[x]:
ffOutput[x].append(fvOutput[i][vData[i][3].index(x)])
ffOutput[x].append(fvOutput[i][vData[i][3].index(x) - 1])
if not dual:
fOutput = fvOutput + feOutput + ffOutput
if flipNormals:
fOutput = flippedFaceNormals(fOutput)
return vOutput, fOutput
else:
# do the same procedure as above, only now on the generated mesh
# generate connection database
vDict = [{} for i in vOutput]
dvOutput = [0 for i in fvOutput + feOutput + ffOutput]
dfOutput = []
for x in range(len(dvOutput)): # for every face
i = (fvOutput + feOutput + ffOutput)[x] # choose face to work with
# find vertex from face
normal = (vOutput[i[0]] - vOutput[i[1]]).cross(vOutput[i[2]] - vOutput[i[1]]).normalized()
dvOutput[x] = normal / (normal.dot(vOutput[i[0]]))
for j in range(len(i)): # create vert chain
vDict[i[j - 1]][i[j]] = [i[j - 2], x]
if len(vDict[i[j - 1]]) == 1:
vDict[i[j - 1]][-1] = i[j]
# calculate supposed size for continuity
supposedSize = vSum([vInput[i] for i in fInput[0]]).length / len(fInput[0])
supposedSize /= dvOutput[-1].length
dvOutput = [i * supposedSize for i in dvOutput]
# use chains to create faces
for x in range(len(vOutput)):
i = vDict[x]
current = i[-1]
face = []
while True:
face.append(i[current][1])
current = i[current][0]
if current == i[-1]:
break
dfOutput.append(face)
return dvOutput, dfOutput
class Solids(bpy.types.Operator):
"""Add one of the (regular) solids (mesh)"""
bl_idname = "mesh.primitive_solid_add"
bl_label = "Add Regular Solid"
bl_description = "Add one of the Platonic, Archimedean or Catalan solids"
bl_options = {'REGISTER', 'UNDO', 'PRESET'}
source: EnumProperty(
items=(("4", "Tetrahedron", ""),
("6", "Hexahedron", ""),
("8", "Octahedron", ""),
("12", "Dodecahedron", ""),
("20", "Icosahedron", "")),
name="Source",
description="Starting point of your solid"
)
size: FloatProperty(
name="Size",
description="Radius of the sphere through the vertices",
min=0.01,
soft_min=0.01,
max=100,
soft_max=100,
default=1.0
)
vTrunc: FloatProperty(
name="Vertex Truncation",
description="Amount of vertex truncation",
min=0.0,
soft_min=0.0,
max=2.0,
soft_max=2.0,
default=0.0,
precision=3,
step=0.5
)
eTrunc: FloatProperty(
name="Edge Truncation",
description="Amount of edge truncation",
min=0.0,
soft_min=0.0,
max=1.0,
soft_max=1.0,
default=0.0,
precision=3,
step=0.2
)
snub: EnumProperty(
items=(("None", "No Snub", ""),
("Left", "Left Snub", ""),
("Right", "Right Snub", "")),
name="Snub",
description="Create the snub version"
)
dual: BoolProperty(
name="Dual",
description="Create the dual of the current solid",
default=False
)
keepSize: BoolProperty(
name="Keep Size",
description="Keep the whole solid at a constant size",
default=False
)
def execute(self, context):
# generate mesh
verts, faces = createSolid(self.source,
self.vTrunc,
self.eTrunc,
self.dual,
self.snub
)
# resize to normal size, or if keepSize, make sure all verts are of length 'size'
if self.keepSize:
rad = self.size / verts[-1 if self.dual else 0].length
else:
rad = self.size
verts = [i * rad for i in verts]
# generate object
# Create new mesh
mesh = bpy.data.meshes.new("Solid")
# Make a mesh from a list of verts/edges/faces.
mesh.from_pydata(verts, [], faces)
# Update mesh geometry after adding stuff.
mesh.update()
object_data_add(context, mesh, operator=None)
# object generation done
return {'FINISHED'}
@@ -0,0 +1,285 @@
# SPDX-FileCopyrightText: 2015-2022 Blender Foundation
#
# SPDX-License-Identifier: GPL-2.0-or-later
# Original by Fourmadmen
import bpy
from mathutils import (
Vector,
Quaternion,
)
from math import pi
from bpy.props import (
IntProperty,
FloatProperty,
StringProperty,
BoolProperty,
)
from bpy_extras import object_utils
from .interface import draw_transform_props
# Create a new mesh (object) from verts/edges/faces.
# verts/edges/faces ... List of vertices/edges/faces for the
# new mesh (as used in from_pydata)
# name ... Name of the new mesh (& object)
def create_mesh_object(context, verts, edges, faces, name):
# Create new mesh
mesh = bpy.data.meshes.new(name)
# Make a mesh from a list of verts/edges/faces.
mesh.from_pydata(verts, edges, faces)
# Update mesh geometry after adding stuff.
mesh.update()
from bpy_extras import object_utils
return object_utils.object_data_add(context, mesh, operator=None)
# A very simple "bridge" tool.
def createFaces(vertIdx1, vertIdx2, closed=False, flipped=False):
faces = []
if not vertIdx1 or not vertIdx2:
return None
if len(vertIdx1) < 2 and len(vertIdx2) < 2:
return None
fan = False
if (len(vertIdx1) != len(vertIdx2)):
if (len(vertIdx1) == 1 and len(vertIdx2) > 1):
fan = True
else:
return None
total = len(vertIdx2)
if closed:
# Bridge the start with the end.
if flipped:
face = [
vertIdx1[0],
vertIdx2[0],
vertIdx2[total - 1]]
if not fan:
face.append(vertIdx1[total - 1])
faces.append(face)
else:
face = [vertIdx2[0], vertIdx1[0]]
if not fan:
face.append(vertIdx1[total - 1])
face.append(vertIdx2[total - 1])
faces.append(face)
# Bridge the rest of the faces.
for num in range(total - 1):
if flipped:
if fan:
face = [vertIdx2[num], vertIdx1[0], vertIdx2[num + 1]]
else:
face = [vertIdx2[num], vertIdx1[num],
vertIdx1[num + 1], vertIdx2[num + 1]]
faces.append(face)
else:
if fan:
face = [vertIdx1[0], vertIdx2[num], vertIdx2[num + 1]]
else:
face = [vertIdx1[num], vertIdx2[num],
vertIdx2[num + 1], vertIdx1[num + 1]]
faces.append(face)
return faces
# @todo Clean up vertex&face creation process a bit.
def add_star(points, outer_radius, inner_radius, height):
PI_2 = pi * 2
z_axis = (0, 0, 1)
verts = []
faces = []
segments = points * 2
half_height = height / 2.0
vert_idx_top = len(verts)
verts.append(Vector((0.0, 0.0, half_height)))
vert_idx_bottom = len(verts)
verts.append(Vector((0.0, 0.0, -half_height)))
edgeloop_top = []
edgeloop_bottom = []
for index in range(segments):
quat = Quaternion(z_axis, (index / segments) * PI_2)
if index % 2:
# Uneven
radius = outer_radius
else:
# Even
radius = inner_radius
edgeloop_top.append(len(verts))
vec = quat @ Vector((radius, 0, half_height))
verts.append(vec)
edgeloop_bottom.append(len(verts))
vec = quat @ Vector((radius, 0, -half_height))
verts.append(vec)
faces_top = createFaces([vert_idx_top], edgeloop_top, closed=True)
faces_outside = createFaces(edgeloop_top, edgeloop_bottom, closed=True)
faces_bottom = createFaces([vert_idx_bottom], edgeloop_bottom,
flipped=True, closed=True)
faces.extend(faces_top)
faces.extend(faces_outside)
faces.extend(faces_bottom)
return verts, faces
class AddStar(bpy.types.Operator, object_utils.AddObjectHelper):
bl_idname = "mesh.primitive_star_add"
bl_label = "Simple Star"
bl_description = "Construct a star mesh"
bl_options = {'REGISTER', 'UNDO', 'PRESET'}
Star : BoolProperty(name = "Star",
default = True,
description = "Star")
change : BoolProperty(name = "Change",
default = False,
description = "change Star")
points: IntProperty(
name="Points",
description="Number of points for the star",
min=2,
max=256,
default=5
)
outer_radius: FloatProperty(
name="Outer Radius",
description="Outer radius of the star",
min=0.01,
max=9999.0,
default=1.0
)
innter_radius: FloatProperty(
name="Inner Radius",
description="Inner radius of the star",
min=0.01,
max=9999.0,
default=0.5
)
height: FloatProperty(name="Height",
description="Height of the star",
min=0.01,
max=9999.0,
default=0.5
)
def draw(self, context):
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
layout.separator()
layout.prop(self, 'points')
layout.prop(self, 'height')
col = layout.column(align=True)
col.prop(self, 'outer_radius', text='Radius Outer')
col.prop(self, 'innter_radius', text='Inner')
if self.change == False:
layout.separator()
draw_transform_props(self, layout)
def execute(self, context):
# turn off 'Enter Edit Mode'
use_enter_edit_mode = bpy.context.preferences.edit.use_enter_edit_mode
bpy.context.preferences.edit.use_enter_edit_mode = False
if bpy.context.mode == "OBJECT":
if context.selected_objects != [] and context.active_object and \
(context.active_object.data is not None) and ('Star' in context.active_object.data.keys()) and \
(self.change == True):
obj = context.active_object
oldmesh = obj.data
oldmeshname = obj.data.name
verts, faces = add_star(
self.points,
self.outer_radius,
self.innter_radius,
self.height
)
mesh = bpy.data.meshes.new('Star')
mesh.from_pydata(verts, [], faces)
obj.data = mesh
for material in oldmesh.materials:
obj.data.materials.append(material)
bpy.data.meshes.remove(oldmesh)
obj.data.name = oldmeshname
else:
verts, faces = add_star(
self.points,
self.outer_radius,
self.innter_radius,
self.height
)
mesh = bpy.data.meshes.new('Star')
mesh.from_pydata(verts, [], faces)
obj = object_utils.object_data_add(context, mesh, operator=self)
obj.data["Star"] = True
obj.data["change"] = False
for prm in StarParameters():
obj.data[prm] = getattr(self, prm)
if bpy.context.mode == "EDIT_MESH":
active_object = context.active_object
name_active_object = active_object.name
bpy.ops.object.mode_set(mode='OBJECT')
verts, faces = add_star(
self.points,
self.outer_radius,
self.innter_radius,
self.height
)
mesh = bpy.data.meshes.new('Star')
mesh.from_pydata(verts, [], faces)
obj = object_utils.object_data_add(context, mesh, operator=self)
obj.select_set(True)
active_object.select_set(True)
bpy.context.view_layer.objects.active = active_object
bpy.ops.object.join()
context.active_object.name = name_active_object
bpy.ops.object.mode_set(mode='EDIT')
if use_enter_edit_mode:
bpy.ops.object.mode_set(mode = 'EDIT')
# restore pre operator state
bpy.context.preferences.edit.use_enter_edit_mode = use_enter_edit_mode
return {'FINISHED'}
def StarParameters():
StarParameters = [
"points",
"outer_radius",
"innter_radius",
"height",
]
return StarParameters
@@ -0,0 +1,331 @@
# SPDX-FileCopyrightText: 2011-2022 Blender Foundation
#
# SPDX-License-Identifier: GPL-2.0-or-later
# Author: DreamPainter
import bpy
from bpy.props import (
FloatProperty,
BoolProperty,
IntProperty,
EnumProperty,
)
from math import pi, cos, sin
from mathutils import Vector
from bpy_extras import object_utils
from .interface import draw_transform_props
# A very simple "bridge" tool
def createFaces(vertIdx1, vertIdx2, closed=False, flipped=False):
faces = []
if not vertIdx1 or not vertIdx2:
return None
if len(vertIdx1) < 2 and len(vertIdx2) < 2:
return None
fan = False
if (len(vertIdx1) != len(vertIdx2)):
if (len(vertIdx1) == 1 and len(vertIdx2) > 1):
fan = True
else:
return None
total = len(vertIdx2)
if closed:
# Bridge the start with the end.
if flipped:
face = [
vertIdx1[0],
vertIdx2[0],
vertIdx2[total - 1]]
if not fan:
face.append(vertIdx1[total - 1])
faces.append(face)
else:
face = [vertIdx2[0], vertIdx1[0]]
if not fan:
face.append(vertIdx1[total - 1])
face.append(vertIdx2[total - 1])
faces.append(face)
# Bridge the rest of the faces.
for num in range(total - 1):
if flipped:
if fan:
face = [vertIdx2[num], vertIdx1[0], vertIdx2[num + 1]]
else:
face = [vertIdx2[num], vertIdx1[num],
vertIdx1[num + 1], vertIdx2[num + 1]]
faces.append(face)
else:
if fan:
face = [vertIdx1[0], vertIdx2[num], vertIdx2[num + 1]]
else:
face = [vertIdx1[num], vertIdx2[num],
vertIdx2[num + 1], vertIdx1[num + 1]]
faces.append(face)
return faces
def power(a, b):
if a < 0:
return -((-a) ** b)
return a ** b
def supertoroid(R, r, u, v, n1, n2):
"""
R = big radius
r = small radius
u = lateral segmentation
v = radial segmentation
n1 = value determines the shape of the torus
n2 = value determines the shape of the cross-section
"""
# create the necessary constants
a = 2 * pi / u
b = 2 * pi / v
verts = []
faces = []
# create each cross-section by calculating each vector on the
# the wannabe circle
# x = (cos(theta) ** n1)*(R + r * (cos(phi) ** n2))
# y = (sin(theta) ** n1)*(R + r * (cos(phi) ** n2))
# z = (r * sin(phi) ** n2)
# with theta and phi ranging from 0 to 2pi
for i in range(u):
s = power(sin(i * a), n1)
c = power(cos(i * a), n1)
for j in range(v):
c2 = R + r * power(cos(j * b), n2)
s2 = r * power(sin(j * b), n2)
verts.append(Vector((c * c2, s * c2, s2)))
# bridge the last circle with the previous circle
if i > 0: # but not for the first circle, 'cus there's no previous before the first
f = createFaces(range((i - 1) * v, i * v), range(i * v, (i + 1) * v), closed=True)
faces.extend(f)
# bridge the last circle with the first
f = createFaces(range((u - 1) * v, u * v), range(v), closed=True)
faces.extend(f)
return verts, faces
class add_supertoroid(bpy.types.Operator, object_utils.AddObjectHelper):
bl_idname = "mesh.primitive_supertoroid_add"
bl_label = "Add SuperToroid"
bl_description = "Construct a supertoroid mesh"
bl_options = {'REGISTER', 'UNDO', 'PRESET'}
SuperToroid : BoolProperty(name = "SuperToroid",
default = True,
description = "SuperToroid")
change : BoolProperty(name = "Change",
default = False,
description = "change SuperToroid")
method: EnumProperty(
name='Method',
description='Method for determining the size and thickness of the torus',
items=(
('MAJOR-MINOR', 'Major / Minor', 'Uses the major radius for the overall size and the minor for the thickness'),
('INT-EXT', 'Interior / Exterior', 'Uses the absolute size of the inner and outer circles to determine the size and thickness'),
),
default='MAJOR-MINOR',
)
R: FloatProperty(
name="Big radius",
description="The radius inside the tube",
default=1.0,
min=0.01, max=100.0
)
r: FloatProperty(
name="Small radius",
description="The radius of the tube",
default=0.3,
min=0.01, max=100.0
)
outer_r: FloatProperty(
name="Exterior Radius",
description="Total Exterior Radius of the torus",
min=0.01,
max=100.0,
default=1.3
)
inner_r: FloatProperty(
name="Inside Radius",
description="Total Interior Radius of the torus",
min=0.01,
max=100.0,
default=0.7
)
u: IntProperty(
name="U-segments",
description="Radial segmentation",
default=16,
min=3, max=265
)
v: IntProperty(
name="V-segments",
description="Lateral segmentation",
default=8,
min=3, max=265
)
n1: FloatProperty(
name="Ring manipulator",
description="Manipulates the shape of the Ring",
default=1.0,
min=0.01, max=100.0
)
n2: FloatProperty(
name="Cross manipulator",
description="Manipulates the shape of the cross-section",
default=1.0,
min=0.01, max=100.0
)
edit: BoolProperty(
name="",
description="",
default=False,
options={'HIDDEN'}
)
def draw(self, context):
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
layout.separator()
layout.prop(self, 'method', text='Dimensions Mode')
col = layout.column(align=True)
if self.method == 'MAJOR-MINOR':
col.prop(self, 'R', text='Radius Major')
col.prop(self, 'r', text='Minor')
else:
col.prop(self, 'outer_r', text='Radius Exterior')
col.prop(self, 'inner_r', text='Interior')
col = layout.column(align=True)
col.prop(self, 'u', text='Segments Major')
col.prop(self, 'v', text='Minor')
layout.prop(self, 'n1', text='Ring')
layout.prop(self, 'n2', text='Cross')
if self.change == False:
draw_transform_props(self, layout)
def execute(self, context):
# turn off 'Enter Edit Mode'
use_enter_edit_mode = bpy.context.preferences.edit.use_enter_edit_mode
bpy.context.preferences.edit.use_enter_edit_mode = False
props = self.properties
# check how the radii properties must be used
if props.method == 'INT-EXT':
rad1 = (props.outer_r + props.inner_r) / 2
rad2 = (props.outer_r - props.inner_r) / 2
# for consistency in the mesh, ie no crossing faces, make the largest of the two
# the outer radius
if rad2 > rad1:
[rad1, rad2] = [rad2, rad1]
else:
rad1 = props.R
rad2 = props.r
# again for consistency, make the radius in the tube,
# at least as big as the radius of the tube
if rad2 > rad1:
rad1 = rad2
if bpy.context.mode == "OBJECT":
if context.selected_objects != [] and context.active_object and \
(context.active_object.data is not None) and ('SuperToroid' in context.active_object.data.keys()) and \
(self.change == True):
obj = context.active_object
oldmesh = obj.data
oldmeshname = obj.data.name
verts, faces = supertoroid(rad1,
rad2,
props.u,
props.v,
props.n1,
props.n2
)
mesh = bpy.data.meshes.new('SuperToroid')
mesh.from_pydata(verts, [], faces)
obj.data = mesh
for material in oldmesh.materials:
obj.data.materials.append(material)
bpy.data.meshes.remove(oldmesh)
obj.data.name = oldmeshname
else:
verts, faces = supertoroid(rad1,
rad2,
props.u,
props.v,
props.n1,
props.n2
)
mesh = bpy.data.meshes.new('SuperToroid')
mesh.from_pydata(verts, [], faces)
obj = object_utils.object_data_add(context, mesh, operator=self)
obj.data["SuperToroid"] = True
obj.data["change"] = False
for prm in SuperToroidParameters():
obj.data[prm] = getattr(self, prm)
if bpy.context.mode == "EDIT_MESH":
active_object = context.active_object
name_active_object = active_object.name
bpy.ops.object.mode_set(mode='OBJECT')
verts, faces = supertoroid(rad1,
rad2,
props.u,
props.v,
props.n1,
props.n2
)
mesh = bpy.data.meshes.new('SuperToroid')
mesh.from_pydata(verts, [], faces)
obj = object_utils.object_data_add(context, mesh, operator=self)
obj.select_set(True)
active_object.select_set(True)
bpy.context.view_layer.objects.active = active_object
bpy.ops.object.join()
context.active_object.name = name_active_object
bpy.ops.object.mode_set(mode='EDIT')
if use_enter_edit_mode:
bpy.ops.object.mode_set(mode = 'EDIT')
# restore pre operator state
bpy.context.preferences.edit.use_enter_edit_mode = use_enter_edit_mode
return {'FINISHED'}
def SuperToroidParameters():
SuperToroidParameters = [
"R",
"r",
"u",
"v",
"n1",
"n2",
"method",
"edit",
"inner_r",
"outer_r",
]
return SuperToroidParameters
@@ -0,0 +1,865 @@
# SPDX-FileCopyrightText: 2012-2022 Blender Foundation
#
# SPDX-License-Identifier: GPL-2.0-or-later
# Author, Anthony D'Agostino
import bpy
from bpy.props import (
IntProperty,
EnumProperty,
)
import mathutils
import io
import operator
import functools
from bpy_extras import object_utils
from .interface import draw_transform_props
class AddTeapot(bpy.types.Operator, object_utils.AddObjectHelper):
bl_idname = "mesh.primitive_teapot_add"
bl_label = "Add Teapot"
bl_description = "Construct a teapot or teaspoon mesh"
bl_options = {"REGISTER", "UNDO"}
resolution: IntProperty(
name="Resolution",
description="Resolution of the Teapot",
default=5,
min=2, max=15,
)
objecttype: EnumProperty(
name="Type",
description="Type of Bezier Object",
items=(('1', "Teapot", "Construct a teapot mesh"),
('2', "Tea Spoon", "Construct a teaspoon mesh")),
default='1',
)
def draw(self, context):
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
layout.prop(self, 'objecttype')
layout.prop(self, 'resolution')
layout.separator()
draw_transform_props(self, layout)
def execute(self, context):
# turn off 'Enter Edit Mode'
use_enter_edit_mode = bpy.context.preferences.edit.use_enter_edit_mode
bpy.context.preferences.edit.use_enter_edit_mode = False
cmode = bpy.context.mode
verts, faces = make_teapot(self.objecttype,
self.resolution)
# Actually create the mesh object from this geometry data.
obj = create_mesh_object(self, context, verts, [], faces, "Teapot")
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.remove_doubles()
if cmode != "EDIT_MESH":
bpy.ops.object.mode_set(mode=cmode)
if use_enter_edit_mode:
bpy.ops.object.mode_set(mode = 'EDIT')
# restore pre operator state
bpy.context.preferences.edit.use_enter_edit_mode = use_enter_edit_mode
return {'FINISHED'}
def create_mesh_face_hack(faces):
# FIXME, faces with duplicate vertices shouldn't be created in the first place.
faces_copy = []
for f in faces:
f_copy = []
for i in f:
if i not in f_copy:
f_copy.append(i)
faces_copy.append(f_copy)
faces[:] = faces_copy
def create_mesh_object(self, context, verts, edges, faces, name):
create_mesh_face_hack(faces)
# Create new mesh
mesh = bpy.data.meshes.new(name)
# Make a mesh from a list of verts/edges/faces.
mesh.from_pydata(verts, edges, faces)
# Update mesh geometry after adding stuff.
mesh.update()
return object_utils.object_data_add(context, mesh, operator=self)
# ==========================
# === Bezier patch Block ===
# ==========================
def read_indexed_patch_file(filename):
file = io.StringIO(filename)
rawpatches = []
patches = []
numpatches = int(file.readline())
for i in range(numpatches):
line = file.readline()
(a, b, c, d,
e, f, g, h,
i, j, k, l,
m, n, o, p,
) = map(int, line.split(","))
patches.append([[a, b, c, d], [e, f, g, h], [i, j, k, l], [m, n, o, p]])
rawpatches.append([[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]])
verts = []
numverts = int(file.readline())
for i in range(numverts):
line = file.readline()
v1, v2, v3 = map(float, line.split(","))
verts.append((v1, v2, v3))
for i in range(len(patches)):
for j in range(4): # len(patches[i])):
for k in range(4): # len(patches[i][j])):
index = patches[i][j][k] - 1
rawpatches[i][j][k] = verts[index]
return rawpatches
def patches_to_raw(patches, resolution):
raw = []
for patch in patches:
verts = make_verts(patch, resolution)
faces = make_faces(resolution)
rawquads = indexed_to_rawquads(verts, faces)
raw.append(rawquads)
raw = functools.reduce(operator.add, raw) # flatten the list
return raw
def make_bezier(ctrlpnts, resolution):
def b1(t):
return t * t * t
def b2(t):
return 3.0 * t * t * (1.0 - t)
def b3(t):
return 3.0 * t * (1.0 - t) * (1.0 - t)
def b4(t):
return (1.0 - t) * (1.0 - t) * (1.0 - t)
p1, p2, p3, p4 = map(mathutils.Vector, ctrlpnts)
def makevert(t):
x, y, z = b1(t) * p1 + b2(t) * p2 + b3(t) * p3 + b4(t) * p4
return (x, y, z)
curveverts = [makevert(i / resolution) for i in range(resolution + 1)]
return curveverts
def make_verts(a, resolution):
s = []
for i in a:
c = make_bezier(i, resolution)
s.append(c)
b = transpose(s)
s = []
for i in b:
c = make_bezier(i, resolution)
s.append(c)
verts = s
verts = functools.reduce(operator.add, verts) # flatten the list
return verts
def make_faces(resolution):
n = resolution + 1
faces = []
for i in range(resolution):
for j in range(resolution):
v1 = (i + 1) * n + j
v2 = (i + 1) * n + j + 1
v3 = i * n + j + 1
v4 = i * n + j
faces.append([v1, v2, v3, v4])
return faces
def indexed_to_rawquads(verts, faces):
rows = len(faces)
cols = len(faces[0]) # or 4
rawquads = [[None] * cols for i in range(rows)]
for i in range(rows):
for j in range(cols):
index = faces[i][j]
rawquads[i][j] = verts[index]
return rawquads
def raw_to_indexed(rawfaces):
# Generate verts and faces lists, without dups
verts = []
coords = {}
index = 0
for i in range(len(rawfaces)):
for j in range(len(rawfaces[i])):
vertex = rawfaces[i][j]
if vertex not in coords:
coords[vertex] = index
index += 1
verts.append(vertex)
rawfaces[i][j] = coords[vertex]
return verts, rawfaces
def transpose(rowsbycols):
rows = len(rowsbycols)
cols = len(rowsbycols[0])
colsbyrows = [[None] * rows for i in range(cols)]
for i in range(cols):
for j in range(rows):
colsbyrows[i][j] = rowsbycols[j][i]
return colsbyrows
def make_teapot(enumname, resolution):
filenames = [None, teapot, teaspoon]
try:
indexes = int(enumname)
filename = filenames[indexes]
except:
print("Add Teapot Error: EnumProperty could not be set")
filename = filenames[1]
patches = read_indexed_patch_file(filename)
raw = patches_to_raw(patches, resolution)
verts, faces = raw_to_indexed(raw)
return (verts, faces)
# =================================
# === Indexed Bezier Data Block ===
# =================================
teapot = """32
1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16
4,17,18,19,8,20,21,22,12,23,24,25,16,26,27,28
19,29,30,31,22,32,33,34,25,35,36,37,28,38,39,40
31,41,42,1,34,43,44,5,37,45,46,9,40,47,48,13
13,14,15,16,49,50,51,52,53,54,55,56,57,58,59,60
16,26,27,28,52,61,62,63,56,64,65,66,60,67,68,69
28,38,39,40,63,70,71,72,66,73,74,75,69,76,77,78
40,47,48,13,72,79,80,49,75,81,82,53,78,83,84,57
57,58,59,60,85,86,87,88,89,90,91,92,93,94,95,96
60,67,68,69,88,97,98,99,92,100,101,102,96,103,104,105
69,76,77,78,99,106,107,108,102,109,110,111,105,112,113,114
78,83,84,57,108,115,116,85,111,117,118,89,114,119,120,93
121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136
124,137,138,121,128,139,140,125,132,141,142,129,136,143,144,133
133,134,135,136,145,146,147,148,149,150,151,152,69,153,154,155
136,143,144,133,148,156,157,145,152,158,159,149,155,160,161,69
162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177
165,178,179,162,169,180,181,166,173,182,183,170,177,184,185,174
174,175,176,177,186,187,188,189,190,191,192,193,194,195,196,197
177,184,185,174,189,198,199,186,193,200,201,190,197,202,203,194
204,204,204,204,207,208,209,210,211,211,211,211,212,213,214,215
204,204,204,204,210,217,218,219,211,211,211,211,215,220,221,222
204,204,204,204,219,224,225,226,211,211,211,211,222,227,228,229
204,204,204,204,226,230,231,207,211,211,211,211,229,232,233,212
212,213,214,215,234,235,236,237,238,239,240,241,242,243,244,245
215,220,221,222,237,246,247,248,241,249,250,251,245,252,253,254
222,227,228,229,248,255,256,257,251,258,259,260,254,261,262,263
229,232,233,212,257,264,265,234,260,266,267,238,263,268,269,242
270,270,270,270,279,280,281,282,275,276,277,278,271,272,273,274
270,270,270,270,282,289,290,291,278,286,287,288,274,283,284,285
270,270,270,270,291,298,299,300,288,295,296,297,285,292,293,294
270,270,270,270,300,305,306,279,297,303,304,275,294,301,302,271
306
1.4,0.0,2.4
1.4,-0.784,2.4
0.784,-1.4,2.4
0.0,-1.4,2.4
1.3375,0.0,2.53125
1.3375,-0.749,2.53125
0.749,-1.3375,2.53125
0.0,-1.3375,2.53125
1.4375,0.0,2.53125
1.4375,-0.805,2.53125
0.805,-1.4375,2.53125
0.0,-1.4375,2.53125
1.5,0.0,2.4
1.5,-0.84,2.4
0.84,-1.5,2.4
0.0,-1.5,2.4
-0.784,-1.4,2.4
-1.4,-0.784,2.4
-1.4,0.0,2.4
-0.749,-1.3375,2.53125
-1.3375,-0.749,2.53125
-1.3375,0.0,2.53125
-0.805,-1.4375,2.53125
-1.4375,-0.805,2.53125
-1.4375,0.0,2.53125
-0.84,-1.5,2.4
-1.5,-0.84,2.4
-1.5,0.0,2.4
-1.4,0.784,2.4
-0.784,1.4,2.4
0.0,1.4,2.4
-1.3375,0.749,2.53125
-0.749,1.3375,2.53125
0.0,1.3375,2.53125
-1.4375,0.805,2.53125
-0.805,1.4375,2.53125
0.0,1.4375,2.53125
-1.5,0.84,2.4
-0.84,1.5,2.4
0.0,1.5,2.4
0.784,1.4,2.4
1.4,0.784,2.4
0.749,1.3375,2.53125
1.3375,0.749,2.53125
0.805,1.4375,2.53125
1.4375,0.805,2.53125
0.84,1.5,2.4
1.5,0.84,2.4
1.75,0.0,1.875
1.75,-0.98,1.875
0.98,-1.75,1.875
0.0,-1.75,1.875
2.0,0.0,1.35
2.0,-1.12,1.35
1.12,-2.0,1.35
0.0,-2.0,1.35
2.0,0.0,0.9
2.0,-1.12,0.9
1.12,-2.0,0.9
0.0,-2.0,0.9
-0.98,-1.75,1.875
-1.75,-0.98,1.875
-1.75,0.0,1.875
-1.12,-2.0,1.35
-2.0,-1.12,1.35
-2.0,0.0,1.35
-1.12,-2.0,0.9
-2.0,-1.12,0.9
-2.0,0.0,0.9
-1.75,0.98,1.875
-0.98,1.75,1.875
0.0,1.75,1.875
-2.0,1.12,1.35
-1.12,2.0,1.35
0.0,2.0,1.35
-2.0,1.12,0.9
-1.12,2.0,0.9
0.0,2.0,0.9
0.98,1.75,1.875
1.75,0.98,1.875
1.12,2.0,1.35
2.0,1.12,1.35
1.12,2.0,0.9
2.0,1.12,0.9
2.0,0.0,0.45
2.0,-1.12,0.45
1.12,-2.0,0.45
0.0,-2.0,0.45
1.5,0.0,0.225
1.5,-0.84,0.225
0.84,-1.5,0.225
0.0,-1.5,0.225
1.5,0.0,0.15
1.5,-0.84,0.15
0.84,-1.5,0.15
0.0,-1.5,0.15
-1.12,-2.0,0.45
-2.0,-1.12,0.45
-2.0,0.0,0.45
-0.84,-1.5,0.225
-1.5,-0.84,0.225
-1.5,0.0,0.225
-0.84,-1.5,0.15
-1.5,-0.84,0.15
-1.5,0.0,0.15
-2.0,1.12,0.45
-1.12,2.0,0.45
0.0,2.0,0.45
-1.5,0.84,0.225
-0.84,1.5,0.225
0.0,1.5,0.225
-1.5,0.84,0.15
-0.84,1.5,0.15
0.0,1.5,0.15
1.12,2.0,0.45
2.0,1.12,0.45
0.84,1.5,0.225
1.5,0.84,0.225
0.84,1.5,0.15
1.5,0.84,0.15
-1.6,0.0,2.025
-1.6,-0.3,2.025
-1.5,-0.3,2.25
-1.5,0.0,2.25
-2.3,0.0,2.025
-2.3,-0.3,2.025
-2.5,-0.3,2.25
-2.5,0.0,2.25
-2.7,0.0,2.025
-2.7,-0.3,2.025
-3.0,-0.3,2.25
-3.0,0.0,2.25
-2.7,0.0,1.8
-2.7,-0.3,1.8
-3.0,-0.3,1.8
-3.0,0.0,1.8
-1.5,0.3,2.25
-1.6,0.3,2.025
-2.5,0.3,2.25
-2.3,0.3,2.025
-3.0,0.3,2.25
-2.7,0.3,2.025
-3.0,0.3,1.8
-2.7,0.3,1.8
-2.7,0.0,1.575
-2.7,-0.3,1.575
-3.0,-0.3,1.35
-3.0,0.0,1.35
-2.5,0.0,1.125
-2.5,-0.3,1.125
-2.65,-0.3,0.9375
-2.65,0.0,0.9375
-2.0,-0.3,0.9
-1.9,-0.3,0.6
-1.9,0.0,0.6
-3.0,0.3,1.35
-2.7,0.3,1.575
-2.65,0.3,0.9375
-2.5,0.3,1.125
-1.9,0.3,0.6
-2.0,0.3,0.9
1.7,0.0,1.425
1.7,-0.66,1.425
1.7,-0.66,0.6
1.7,0.0,0.6
2.6,0.0,1.425
2.6,-0.66,1.425
3.1,-0.66,0.825
3.1,0.0,0.825
2.3,0.0,2.1
2.3,-0.25,2.1
2.4,-0.25,2.025
2.4,0.0,2.025
2.7,0.0,2.4
2.7,-0.25,2.4
3.3,-0.25,2.4
3.3,0.0,2.4
1.7,0.66,0.6
1.7,0.66,1.425
3.1,0.66,0.825
2.6,0.66,1.425
2.4,0.25,2.025
2.3,0.25,2.1
3.3,0.25,2.4
2.7,0.25,2.4
2.8,0.0,2.475
2.8,-0.25,2.475
3.525,-0.25,2.49375
3.525,0.0,2.49375
2.9,0.0,2.475
2.9,-0.15,2.475
3.45,-0.15,2.5125
3.45,0.0,2.5125
2.8,0.0,2.4
2.8,-0.15,2.4
3.2,-0.15,2.4
3.2,0.0,2.4
3.525,0.25,2.49375
2.8,0.25,2.475
3.45,0.15,2.5125
2.9,0.15,2.475
3.2,0.15,2.4
2.8,0.15,2.4
0.0,0.0,3.15
0.0,-0.002,3.15
0.002,0.0,3.15
0.8,0.0,3.15
0.8,-0.45,3.15
0.45,-0.8,3.15
0.0,-0.8,3.15
0.0,0.0,2.85
0.2,0.0,2.7
0.2,-0.112,2.7
0.112,-0.2,2.7
0.0,-0.2,2.7
-0.002,0.0,3.15
-0.45,-0.8,3.15
-0.8,-0.45,3.15
-0.8,0.0,3.15
-0.112,-0.2,2.7
-0.2,-0.112,2.7
-0.2,0.0,2.7
0.0,0.002,3.15
-0.8,0.45,3.15
-0.45,0.8,3.15
0.0,0.8,3.15
-0.2,0.112,2.7
-0.112,0.2,2.7
0.0,0.2,2.7
0.45,0.8,3.15
0.8,0.45,3.15
0.112,0.2,2.7
0.2,0.112,2.7
0.4,0.0,2.55
0.4,-0.224,2.55
0.224,-0.4,2.55
0.0,-0.4,2.55
1.3,0.0,2.55
1.3,-0.728,2.55
0.728,-1.3,2.55
0.0,-1.3,2.55
1.3,0.0,2.4
1.3,-0.728,2.4
0.728,-1.3,2.4
0.0,-1.3,2.4
-0.224,-0.4,2.55
-0.4,-0.224,2.55
-0.4,0.0,2.55
-0.728,-1.3,2.55
-1.3,-0.728,2.55
-1.3,0.0,2.55
-0.728,-1.3,2.4
-1.3,-0.728,2.4
-1.3,0.0,2.4
-0.4,0.224,2.55
-0.224,0.4,2.55
0.0,0.4,2.55
-1.3,0.728,2.55
-0.728,1.3,2.55
0.0,1.3,2.55
-1.3,0.728,2.4
-0.728,1.3,2.4
0.0,1.3,2.4
0.224,0.4,2.55
0.4,0.224,2.55
0.728,1.3,2.55
1.3,0.728,2.55
0.728,1.3,2.4
1.3,0.728,2.4
0.0,0.0,0.0
1.5,0.0,0.15
1.5,0.84,0.15
0.84,1.5,0.15
0.0,1.5,0.15
1.5,0.0,0.075
1.5,0.84,0.075
0.84,1.5,0.075
0.0,1.5,0.075
1.425,0.0,0.0
1.425,0.798,0.0
0.798,1.425,0.0
0.0,1.425,0.0
-0.84,1.5,0.15
-1.5,0.84,0.15
-1.5,0.0,0.15
-0.84,1.5,0.075
-1.5,0.84,0.075
-1.5,0.0,0.075
-0.798,1.425,0.0
-1.425,0.798,0.0
-1.425,0.0,0.0
-1.5,-0.84,0.15
-0.84,-1.5,0.15
0.0,-1.5,0.15
-1.5,-0.84,0.075
-0.84,-1.5,0.075
0.0,-1.5,0.075
-1.425,-0.798,0.0
-0.798,-1.425,0.0
0.0,-1.425,0.0
0.84,-1.5,0.15
1.5,-0.84,0.15
0.84,-1.5,0.075
1.5,-0.84,0.075
0.798,-1.425,0.0
1.425,-0.798,0.0
"""
teaspoon = """16
1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16
17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32
33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48
49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64
65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80
81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96
97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112
113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128
129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144
145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160
161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176
177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192
193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208
209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224
225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240
241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256
256
-0.000107143,0.205357,0.0
0.0,0.196429,-0.0178571
0.0,0.196429,-0.0178571
0.000107143,0.205357,0.0
-0.0535714,0.205357,0.0
-0.0222714,0.178571,-0.0534286
0.0222714,0.178571,-0.0534286
0.0535714,0.205357,0.0
-0.107143,0.0952429,-0.0178571
-0.0446429,0.0952429,-0.0892857
0.0446429,0.0952429,-0.0892857
0.107143,0.0952429,-0.0178571
-0.107143,0.0,-0.0178571
-0.0446429,0.0,-0.0892857
0.0446429,0.0,-0.0892857
0.107143,0.0,-0.0178571
0.000107143,0.205357,0.0
0.000135714,0.207589,0.00446429
0.000157143,0.216518,0.00446429
0.000125,0.214286,0.0
0.0535714,0.205357,0.0
0.0613964,0.212054,0.0133571
0.0714286,0.220982,0.015625
0.0625,0.214286,0.0
0.107143,0.0952429,-0.0178571
0.122768,0.0952429,0.0
0.142857,0.0952429,0.00446429
0.125,0.0952429,-0.0178571
0.107143,0.0,-0.0178571
0.122768,0.0,0.0
0.142857,0.0,0.00446429
0.125,0.0,-0.0178571
0.000125,0.214286,0.0
0.0,0.205357,-0.0178571
0.0,0.205357,-0.0178571
-0.000125,0.214286,0.0
0.0625,0.214286,0.0
0.0267857,0.1875,-0.0625
-0.0267857,0.1875,-0.0625
-0.0625,0.214286,0.0
0.125,0.0952429,-0.0178571
0.0535714,0.0952429,-0.107143
-0.0535714,0.0952429,-0.107143
-0.125,0.0952429,-0.0178571
0.125,0.0,-0.0178571
0.0535714,0.0,-0.107143
-0.0535714,0.0,-0.107143
-0.125,0.0,-0.0178571
-0.000125,0.214286,0.0
-0.000157143,0.216518,0.00446429
-0.000135714,0.207589,0.00446429
-0.000107143,0.205357,0.0
-0.0625,0.214286,0.0
-0.0714286,0.220982,0.015625
-0.0613964,0.212054,0.0133571
-0.0535714,0.205357,0.0
-0.125,0.0952429,-0.0178571
-0.142857,0.0952429,0.00446429
-0.122768,0.0952429,0.0
-0.107143,0.0952429,-0.0178571
-0.125,0.0,-0.0178571
-0.142857,0.0,0.00446429
-0.122768,0.0,0.0
-0.107143,0.0,-0.0178571
-0.107143,0.0,-0.0178571
-0.0446429,0.0,-0.0892857
0.0446429,0.0,-0.0892857
0.107143,0.0,-0.0178571
-0.107143,-0.142857,-0.0178571
-0.0446429,-0.142857,-0.0892857
0.0446429,-0.142857,-0.0892857
0.107143,-0.142857,-0.0178571
-0.0133929,-0.160714,0.0386893
-0.00557857,-0.160714,0.0386893
0.00557857,-0.160714,0.0386893
0.0133929,-0.160714,0.0386893
-0.0133929,-0.25,0.0535714
-0.00557857,-0.25,0.0535714
0.00557857,-0.25,0.0535714
0.0133929,-0.25,0.0535714
0.107143,0.0,-0.0178571
0.122768,0.0,0.0
0.142857,0.0,0.00446429
0.125,0.0,-0.0178571
0.107143,-0.142857,-0.0178571
0.122768,-0.142857,0.0
0.142857,-0.142857,0.00446429
0.125,-0.142857,-0.0178571
0.0133929,-0.160714,0.0386893
0.0153464,-0.160714,0.0386893
0.0178571,-0.160714,0.0314357
0.015625,-0.160714,0.0297607
0.0133929,-0.25,0.0535714
0.0153464,-0.25,0.0535714
0.0178571,-0.25,0.0463179
0.015625,-0.25,0.0446429
0.125,0.0,-0.0178571
0.0535714,0.0,-0.107143
-0.0535714,0.0,-0.107143
-0.125,0.0,-0.0178571
0.125,-0.142857,-0.0178571
0.0535714,-0.142857,-0.107143
-0.0535714,-0.142857,-0.107143
-0.125,-0.142857,-0.0178571
0.015625,-0.160714,0.0297607
0.00669643,-0.160714,0.0230643
-0.00781071,-0.160714,0.0208321
-0.015625,-0.160714,0.0297607
0.015625,-0.25,0.0446429
0.00669643,-0.25,0.0379464
-0.00781071,-0.25,0.0357143
-0.015625,-0.25,0.0446429
-0.125,0.0,-0.0178571
-0.142857,0.0,0.00446429
-0.122768,0.0,0.0
-0.107143,0.0,-0.0178571
-0.125,-0.142857,-0.0178571
-0.142857,-0.142857,0.00446429
-0.122768,-0.142857,0.0
-0.107143,-0.142857,-0.0178571
-0.015625,-0.160714,0.0297607
-0.0175786,-0.160714,0.0319929
-0.0153464,-0.160714,0.0386893
-0.0133929,-0.160714,0.0386893
-0.015625,-0.25,0.0446429
-0.0175786,-0.25,0.046875
-0.0153464,-0.25,0.0535714
-0.0133929,-0.25,0.0535714
-0.0133929,-0.25,0.0535714
-0.00557857,-0.25,0.0535714
0.00557857,-0.25,0.0535714
0.0133929,-0.25,0.0535714
-0.0133929,-0.46425,0.0892857
-0.00557857,-0.46425,0.0892857
0.00557857,-0.46425,0.0892857
0.0133929,-0.46425,0.0892857
-0.0446429,-0.678571,0.0535714
-0.00892857,-0.678571,0.0625
0.00892857,-0.678571,0.0625
0.0446429,-0.678571,0.0535714
-0.0446429,-0.857143,0.0357143
-0.00892857,-0.857143,0.0446429
0.00892857,-0.857143,0.0446429
0.0446429,-0.857143,0.0357143
0.0133929,-0.25,0.0535714
0.0153464,-0.25,0.0535714
0.0178571,-0.25,0.0463179
0.015625,-0.25,0.0446429
0.0133929,-0.46425,0.0892857
0.0153464,-0.464286,0.0892857
0.0178571,-0.46425,0.0820321
0.015625,-0.46425,0.0803571
0.0446429,-0.678571,0.0535714
0.0535714,-0.678571,0.0513393
0.0535714,-0.678571,0.0334821
0.0446429,-0.678571,0.0357143
0.0446429,-0.857143,0.0357143
0.0535714,-0.857143,0.0334821
0.0535714,-0.857143,0.015625
0.0446429,-0.857143,0.0178571
0.015625,-0.25,0.0446429
0.00669643,-0.25,0.0379464
-0.00781071,-0.25,0.0357143
-0.015625,-0.25,0.0446429
0.015625,-0.46425,0.0803571
0.00669643,-0.464286,0.0736607
-0.00781071,-0.46425,0.0714286
-0.015625,-0.46425,0.0803571
0.0446429,-0.678571,0.0357143
0.00892857,-0.678571,0.0446429
-0.00892857,-0.678571,0.0446429
-0.0446429,-0.678571,0.0357143
0.0446429,-0.857143,0.0178571
0.00892857,-0.857143,0.0267857
-0.00892857,-0.857143,0.0267857
-0.0446429,-0.857143,0.0178571
-0.015625,-0.25,0.0446429
-0.0175786,-0.25,0.046875
-0.0153464,-0.25,0.0535714
-0.0133929,-0.25,0.0535714
-0.015625,-0.46425,0.0803571
-0.0175786,-0.464286,0.0825893
-0.0153464,-0.464286,0.0892857
-0.0133929,-0.46425,0.0892857
-0.0446429,-0.678571,0.0357143
-0.0535714,-0.678571,0.0334821
-0.0535714,-0.678571,0.0513393
-0.0446429,-0.678571,0.0535714
-0.0446429,-0.857143,0.0178571
-0.0535714,-0.857143,0.015625
-0.0535714,-0.857143,0.0334821
-0.0446429,-0.857143,0.0357143
-0.0446429,-0.857143,0.0357143
-0.00892857,-0.857143,0.0446429
0.00892857,-0.857143,0.0446429
0.0446429,-0.857143,0.0357143
-0.0446429,-0.928571,0.0285714
-0.00892857,-0.928571,0.0375
0.00892857,-0.928571,0.0375
0.0446429,-0.928571,0.0285714
-0.0539286,-0.999643,0.0178571
0.000357143,-0.999643,0.0178571
0.0,-0.999643,0.0178571
0.0535714,-0.999643,0.0178571
-0.000357143,-1,0.0178571
0.000357143,-1,0.0178571
0.0,-1,0.0178571
0.0,-1,0.0178571
0.0446429,-0.857143,0.0357143
0.0535714,-0.857143,0.0334821
0.0535714,-0.857143,0.015625
0.0446429,-0.857143,0.0178571
0.0446429,-0.928571,0.0285714
0.0535714,-0.928571,0.0263393
0.0535714,-0.928571,0.00848214
0.0446429,-0.928571,0.0107143
0.0535714,-0.999643,0.0178571
0.0669643,-0.999643,0.0178571
0.0673214,-0.999643,0.0
0.0539286,-0.999643,0.0
0.0,-1,0.0178571
0.0,-1,0.0178571
0.000357143,-1,0.0
0.000357143,-1,0.0
0.0446429,-0.857143,0.0178571
0.00892857,-0.857143,0.0267857
-0.00892857,-0.857143,0.0267857
-0.0446429,-0.857143,0.0178571
0.0446429,-0.928571,0.0107143
0.00892857,-0.928571,0.0196429
-0.00892857,-0.928571,0.0196429
-0.0446429,-0.928571,0.0107143
0.0539286,-0.999643,0.0
0.000357143,-0.999643,0.0
-0.000357143,-0.999643,0.0
-0.0539286,-0.999643,0.0
0.000357143,-1,0.0
0.000357143,-1,0.0
-0.000357143,-1,0.0
-0.000357143,-1,0.0
-0.0446429,-0.857143,0.0178571
-0.0535714,-0.857143,0.015625
-0.0535714,-0.857143,0.0334821
-0.0446429,-0.857143,0.0357143
-0.0446429,-0.928571,0.0107143
-0.0535714,-0.928571,0.00848214
-0.0535714,-0.928571,0.0263393
-0.0446429,-0.928571,0.0285714
-0.0539286,-0.999643,0.0
-0.0673214,-0.999643,0.0
-0.0675,-0.999643,0.0178571
-0.0539286,-0.999643,0.0178571
-0.000357143,-1,0.0
-0.000357143,-1,0.0
-0.000535714,-1,0.0178571
-0.000357143,-1,0.0178571
"""
@@ -0,0 +1,201 @@
# SPDX-FileCopyrightText: 2012-2022 Blender Foundation
#
# SPDX-License-Identifier: GPL-2.0-or-later
# Author: Anthony D'Agostino
import bpy
from mathutils import Vector
from math import sin, cos, pi
from bpy.props import (
BoolProperty,
IntProperty,
StringProperty,
)
from bpy_extras import object_utils
from .interface import draw_transform_props
def create_mesh_object(context, verts, edges, faces, name):
# Create new mesh
mesh = bpy.data.meshes.new(name)
# Make a mesh from a list of verts/edges/faces.
mesh.from_pydata(verts, edges, faces)
# Update mesh geometry after adding stuff.
mesh.update()
from bpy_extras import object_utils
return object_utils.object_data_add(context, mesh, operator=None)
# ========================
# === Torus Knot Block ===
# ========================
def k1(t):
x = cos(t) - 2 * cos(2 * t)
y = sin(t) + 2 * sin(2 * t)
z = sin(3 * t)
return Vector([x, y, z])
def k2(t):
x = 10 * (cos(t) + cos(3 * t)) + cos(2 * t) + cos(4 * t)
y = 6 * sin(t) + 10 * sin(3 * t)
z = 4 * sin(3 * t) * sin(5 * t / 2) + 4 * sin(4 * t) - 2 * sin(6 * t)
return Vector([x, y, z]) * 0.2
def k3(t):
x = 2.5 * cos(t + pi) / 3 + 2 * cos(3 * t)
y = 2.5 * sin(t) / 3 + 2 * sin(3 * t)
z = 1.5 * sin(4 * t) + sin(2 * t) / 3
return Vector([x, y, z])
def make_verts(ures, vres, r2, knotfunc):
verts = []
for i in range(ures):
t1 = (i + 0) * 2 * pi / ures
t2 = (i + 1) * 2 * pi / ures
a = knotfunc(t1) # curr point
b = knotfunc(t2) # next point
a, b = map(Vector, (a, b))
e = a - b
f = a + b
g = e.cross(f)
h = e.cross(g)
g.normalize()
h.normalize()
for j in range(vres):
k = j * 2 * pi / vres
l = (cos(k), 0.0, sin(k))
l = Vector(l)
m = l * r2
x, y, z = m
n = h * x
o = g * z
p = n + o
q = a + p
verts.append(q)
return verts
def make_faces(ures, vres):
faces = []
for u in range(0, ures):
for v in range(0, vres):
p1 = v + u * vres
p2 = v + ((u + 1) % ures) * vres
p4 = (v + 1) % vres + u * vres
p3 = (v + 1) % vres + ((u + 1) % ures) * vres
faces.append([p4, p3, p2, p1])
return faces
def make_knot(knotidx, ures):
knots = [k1, k2, k3]
knotfunc = knots[knotidx - 1]
vres = ures // 10
r2 = 0.5
verts = make_verts(ures, vres, r2, knotfunc)
faces = make_faces(ures, vres)
return (verts, faces)
class AddTorusKnot(bpy.types.Operator, object_utils.AddObjectHelper):
bl_idname = "mesh.primitive_torusknot_add"
bl_label = "Add Torus Knot"
bl_description = "Construct a torus knot mesh"
bl_options = {"REGISTER", "UNDO"}
TorusKnot : BoolProperty(name = "TorusKnot",
default = True,
description = "TorusKnot")
change : BoolProperty(name = "Change",
default = False,
description = "change TorusKnot")
resolution: IntProperty(
name="Resolution",
description="Resolution of the Torus Knot",
default=80,
min=30, max=256
)
objecttype: IntProperty(
name="Knot Type",
description="Type of Knot",
default=1,
min=1, max=3
)
def draw(self, context):
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
layout.prop(self, 'resolution', expand=True)
layout.prop(self, 'objecttype', expand=True)
if self.change == False:
draw_transform_props(self, layout)
def execute(self, context):
# turn off 'Enter Edit Mode'
use_enter_edit_mode = bpy.context.preferences.edit.use_enter_edit_mode
bpy.context.preferences.edit.use_enter_edit_mode = False
if bpy.context.mode == "OBJECT":
if context.selected_objects != [] and context.active_object and \
(context.active_object.data is not None) and ('TorusKnot' in context.active_object.data.keys()) and \
(self.change == True):
obj = context.active_object
oldmesh = obj.data
oldmeshname = obj.data.name
verts, faces = make_knot(self.objecttype, self.resolution)
mesh = bpy.data.meshes.new('TorusKnot')
mesh.from_pydata(verts, [], faces)
obj.data = mesh
for material in oldmesh.materials:
obj.data.materials.append(material)
bpy.data.meshes.remove(oldmesh)
obj.data.name = oldmeshname
else:
verts, faces = make_knot(self.objecttype, self.resolution)
mesh = bpy.data.meshes.new('TorusKnot')
mesh.from_pydata(verts, [], faces)
obj = object_utils.object_data_add(context, mesh, operator=self)
obj.data["TorusKnot"] = True
obj.data["change"] = False
for prm in TorusKnotParameters():
obj.data[prm] = getattr(self, prm)
if bpy.context.mode == "EDIT_MESH":
active_object = context.active_object
name_active_object = active_object.name
bpy.ops.object.mode_set(mode='OBJECT')
verts, faces = make_knot(self.objecttype, self.resolution)
mesh = bpy.data.meshes.new('TorusKnot')
mesh.from_pydata(verts, [], faces)
obj = object_utils.object_data_add(context, mesh, operator=self)
obj.select_set(True)
active_object.select_set(True)
bpy.context.view_layer.objects.active = active_object
bpy.ops.object.join()
context.active_object.name = name_active_object
bpy.ops.object.mode_set(mode='EDIT')
if use_enter_edit_mode:
bpy.ops.object.mode_set(mode = 'EDIT')
# restore pre operator state
bpy.context.preferences.edit.use_enter_edit_mode = use_enter_edit_mode
return {'FINISHED'}
def TorusKnotParameters():
TorusKnotParameters = [
"resolution",
"objecttype",
]
return TorusKnotParameters
@@ -0,0 +1,313 @@
# SPDX-FileCopyrightText: 2017-2023 Blender Foundation
#
# SPDX-License-Identifier: GPL-2.0-or-later
"""
This script provides a triangle mesh primitive
and a toolbar menu to further specify settings
"""
import math
import bpy
from mathutils import Vector
from bpy.types import Operator
from bpy.props import (
BoolProperty,
EnumProperty,
FloatProperty,
)
from bpy_extras import object_utils
from .interface import draw_transform_props
def checkEditMode():
# Check if we are in edit mode
# Returns: 1 if True
# 0 if False
if (bpy.context.active_object.mode == 'EDIT'):
return 1
return 0
def exitEditMode():
# Check if we are in edit mode (cuz we don't want this when creating a new Mesh)
# If we are then toggle back to object mode
# Check if there are active objects
if bpy.context.active_object is not None:
# Only the active object should be in edit mode
if (bpy.context.active_object.mode == 'EDIT'):
bpy.ops.object.editmode_toggle()
class MakeTriangle(Operator, object_utils.AddObjectHelper):
bl_idname = "mesh.make_triangle"
bl_label = "Add Triangle"
bl_description = "Construct different types of Triangle Meshes"
bl_options = {"REGISTER", "UNDO"}
nothing = 0
Ya = 0.0
Xb = 0.0
Xc = 0.0
Vertices = []
Faces = []
triangleTypeList = [
('ISOSCELES', "Isosceles", "Two equal sides", 0),
('EQUILATERAL', "Equilateral", "Three equal sides and angles (60°)", 1),
('ISOSCELESRIGHTANGLE', "Isosceles right angled", "90° angle and two equal sides", 2),
('SCALENERIGHTANGLE', "Scalene right angled", "90° angle, no equal sides", 3)
]
triangleFaceList = [
('DEFAULT', "Triangle", "1 Triangle face", 0),
('TRIANGLES', "3 Triangles", "4 Vertices & 3 Triangle faces", 1),
('QUADS', "3 Quads", "7 Vertices & 3 Quad faces", 2),
('SAFEQUADS', "6 Quads", "12 Vertices & 6 Quad faces", 3)
]
# add definitions for some manipulation buttons
flipX: BoolProperty(
name="Flip X sign",
description="Draw on the other side of the X axis (Mirror on Y axis)",
default=False
)
flipY: BoolProperty(
name="Flip Y sign",
description="Draw on the other side of the Y axis (Mirror on X axis)",
default=False
)
scale: FloatProperty(
name="Scale",
description="Triangle scale",
default=1.0,
min=1.0
)
triangleType: EnumProperty(
items=triangleTypeList,
name="Type",
description="Triangle Type"
)
triangleFace: EnumProperty(
items=triangleFaceList,
name="Face Types",
description="Triangle Face Types"
)
at_3Dcursor: BoolProperty(
name="Use 3D Cursor",
description="Draw the triangle where the 3D cursor is",
default=True
)
def draw(self, context):
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
col = layout.column()
col.prop(self, "triangleType", text="Type")
col.prop(self, "triangleFace", text="Fill Type")
col.prop(self, "scale")
col.separator()
row = col.row(heading='At')
row.prop(self, "at_3Dcursor", text="3D Cursor")
col.separator()
row = col.row(heading='Flip')
row.prop(self, "flipX", text='X')
col.prop(self, "flipY", text='Y')
col.separator()
draw_transform_props(self, col)
def drawBasicTriangleShape(self):
# set everything to 0
Xb = Xc = 0.0
Ya = 0.0
scale = self.scale
Xsign = -1 if self.flipX else 1
Ysign = -1 if self.flipY else 1
# Isosceles (2 equal sides)
if (self.triangleType == 'ISOSCELES'):
# below a simple triangle containing 2 triangles with 1:2 side ratio
Ya = (1 * Ysign * scale)
A = Vector([0.0, Ya, 0.0])
Xb = (0.5 * Xsign * scale)
B = Vector([Xb, 0.0, 0.0])
Xc = (-0.5 * Xsign * scale)
C = Vector([Xc, 0.0, 0.0])
self.Ya = Ya
self.Xb = Xb
self.Xc = Xc
self.Vertices = [A, B, C, ]
return True
# Equilateral (all sides equal)
if (self.triangleType == 'EQUILATERAL'):
Ya = (math.sqrt(0.75) * Ysign * scale)
A = Vector([0.0, Ya, 0.0])
Xb = (0.5 * Xsign * scale)
B = Vector([Xb, 0.0, 0.0])
Xc = (-0.5 * Xsign * scale)
C = Vector([Xc, 0.0, 0.0])
self.Ya = Ya
self.Xb = Xb
self.Xc = Xc
self.Vertices = [A, B, C, ]
return True
# Isosceles right angled (1, 1, sqrt(2))
if (self.triangleType == 'ISOSCELESRIGHTANGLE'):
Ya = (1 * Ysign * scale)
A = Vector([0.0, Ya, 0.0])
Xb = 0.0
B = Vector([Xb, 0.0, 0.0])
Xc = (1 * Xsign * scale)
C = Vector([Xc, 0.0, 0.0])
self.Ya = Ya
self.Xb = Xb
self.Xc = Xc
self.Vertices = [A, B, C, ]
return True
# Scalene right angled (3, 4, 5)
if (self.triangleType == 'SCALENERIGHTANGLE'):
Ya = (1 * Ysign * scale)
A = Vector([0.0, Ya, 0.0])
Xb = 0
B = Vector([Xb, 0.0, 0.0])
Xc = (0.75 * Xsign * scale)
C = Vector([Xc, 0.0, 0.0])
self.Ya = Ya
self.Xb = Xb
self.Xc = Xc
self.Vertices = [A, B, C, ]
return True
return False
def addFaces(self, fType=None):
Ya = self.Ya
Xb = self.Xb
Xc = self.Xc
if (self.triangleFace == 'DEFAULT'):
self.Faces = [[0, 1, 2]]
return True
if (self.triangleFace == 'TRIANGLES'):
A = Vector([0.0, Ya, 0.0])
B = Vector([Xb, 0.0, 0.0])
C = Vector([Xc, 0.0, 0.0])
D = Vector([((A.x + B.x + C.x) / 3), ((A.y + B.y + C.y) / 3), ((A.z + B.z + C.z) / 3)])
self.Vertices = [A, B, C, D, ]
self.Faces = [[0, 1, 3], [1, 2, 3], [2, 0, 3]]
return True
if (self.triangleFace == 'QUADS'):
A = Vector([0.0, Ya, 0.0])
B = Vector([Xb, 0.0, 0.0])
C = Vector([Xc, 0.0, 0.0])
D = Vector([((A.x + B.x + C.x) / 3), ((A.y + B.y + C.y) / 3), ((A.z + B.z + C.z) / 3)])
AB = A.lerp(B, 0.5)
AC = A.lerp(C, 0.5)
BC = B.lerp(C, 0.5)
self.Vertices = [A, AB, B, BC, C, AC, D, ]
self.Faces = [[0, 1, 6, 5], [1, 2, 3, 6], [3, 4, 5, 6]]
return True
if (self.triangleFace == 'SAFEQUADS'):
A = Vector([0.0, Ya, 0.0])
B = Vector([Xb, 0.0, 0.0])
C = Vector([Xc, 0.0, 0.0])
D = Vector([((A.x + B.x + C.x) / 3), ((A.y + B.y + C.y) / 3), ((A.z + B.z + C.z) / 3)])
E = A.lerp(D, 0.5)
AB = A.lerp(B, 0.5)
AC = A.lerp(C, 0.5)
BC = B.lerp(C, 0.5)
AAB = AB.lerp(A, 0.5)
AAC = AC.lerp(A, 0.5)
BBA = AB.lerp(B, 0.5)
BBC = BC.lerp(B, 0.5)
BCC = BC.lerp(C, 0.5)
CCA = AC.lerp(C, 0.5)
self.Vertices = [A, AAB, BBA, B, BBC, BC, BCC, C, CCA, AAC, D, E, ]
self.Faces = [[0, 1, 11, 9], [1, 2, 10, 11], [2, 3, 4, 10],
[4, 5, 6, 10], [6, 7, 8, 10], [8, 9, 11, 10]]
return True
return False
def action_common(self, context):
# definitions:
# a triangle consists of 3 points: A, B, C
# a 'safer' subdividable triangle consists of 4 points: A, B, C, D
# a subdivide friendly triangle consists of 7 points: A, B, C, D, AB, AC, BC
# a truly subdivide friendly triangle consists of (3 x 4 = )12 points:
# A, B, C, D, E, BC, AAB, AAC, BBA, BBC, BCC, CCA
BasicShapeCreated = False
ShapeFacesCreated = False
go = 0
#
# call the functions for creating the triangles and test if successful
#
BasicShapeCreated = self.drawBasicTriangleShape()
if (BasicShapeCreated):
ShapeFacesCreated = self.addFaces()
if ShapeFacesCreated:
go = 1
if (go == 1):
NewMesh = bpy.data.meshes.new("Triangle")
NewMesh.from_pydata(self.Vertices, [], self.Faces)
NewMesh.update()
NewObj = bpy.data.objects.new("Triangle", NewMesh)
context.collection.objects.link(NewObj)
# before doing the deselect make sure edit mode isn't active
exitEditMode()
bpy.ops.object.select_all(action="DESELECT")
NewObj.select_set(True)
context.view_layer.objects.active = NewObj
if self.at_3Dcursor is True:
# we'll need to be sure there is actually an object selected
if NewObj.select_get() is True:
# we also have to check if we're considered to be in 3D View (view3d)
if bpy.ops.view3d.snap_selected_to_cursor.poll() is True:
bpy.ops.view3d.snap_selected_to_cursor()
else:
# as we weren't considered to be in 3D View
# the object couldn't be moved to the 3D cursor
# so to avoid confusion we change the at_3Dcursor boolean to false
self.at_3Dcursor = False
else:
self.report({'WARNING'},
"Triangle could not be completed. (See Console for more Info)")
print("\n[Add Mesh Extra Objects]\n\nModule: add_mesh_triangle")
print("Triangle type: %s\n" % self.triangleType,
"Face type: %s\n" % self.triangleFace,
"Ya: %s, Xb: %s, Xc: %s\n" % (self.Ya, self.Xb, self.Xc),
"Vertices: %s\n" % self.Vertices,
"Faces: %s\n" % self.Faces)
def execute(self, context):
self.action_common(context)
return {"FINISHED"}
def invoke(self, context, event):
self.action_common(context)
return {"FINISHED"}
@@ -0,0 +1,327 @@
# SPDX-FileCopyrightText: 2010-2022 Blender Foundation
#
# SPDX-License-Identifier: GPL-2.0-or-later
# Author: Paulo_Gomes
import bpy
from mathutils import Quaternion, Vector
from math import cos, sin, pi
from bpy.props import (
FloatProperty,
IntProperty,
BoolProperty,
EnumProperty,
)
from bpy_extras import object_utils
from .interface import draw_transform_props
# Create a new mesh (object) from verts/edges/faces
# verts/edges/faces ... List of vertices/edges/faces for the
# new mesh (as used in from_pydata)
# name ... Name of the new mesh (& object)
def create_mesh_object(context, verts, edges, faces, name):
# Create new mesh
mesh = bpy.data.meshes.new(name)
# Make a mesh from a list of verts/edges/faces
mesh.from_pydata(verts, edges, faces)
# Update mesh geometry after adding stuff
mesh.update()
from bpy_extras import object_utils
return object_utils.object_data_add(context, mesh, operator=None)
# A very simple "bridge" tool
def createFaces(vertIdx1, vertIdx2, closed=False, flipped=False):
faces = []
if not vertIdx1 or not vertIdx2:
return None
if len(vertIdx1) < 2 and len(vertIdx2) < 2:
return None
fan = False
if (len(vertIdx1) != len(vertIdx2)):
if (len(vertIdx1) == 1 and len(vertIdx2) > 1):
fan = True
else:
return None
total = len(vertIdx2)
if closed:
# Bridge the start with the end
if flipped:
face = [
vertIdx1[0],
vertIdx2[0],
vertIdx2[total - 1]]
if not fan:
face.append(vertIdx1[total - 1])
faces.append(face)
else:
face = [vertIdx2[0], vertIdx1[0]]
if not fan:
face.append(vertIdx1[total - 1])
face.append(vertIdx2[total - 1])
faces.append(face)
# Bridge the rest of the faces
for num in range(total - 1):
if flipped:
if fan:
face = [vertIdx2[num], vertIdx1[0], vertIdx2[num + 1]]
else:
face = [vertIdx2[num], vertIdx1[num],
vertIdx1[num + 1], vertIdx2[num + 1]]
faces.append(face)
else:
if fan:
face = [vertIdx1[0], vertIdx2[num], vertIdx2[num + 1]]
else:
face = [vertIdx1[num], vertIdx2[num],
vertIdx2[num + 1], vertIdx1[num + 1]]
faces.append(face)
return faces
def add_twisted_torus(major_rad, minor_rad, major_seg, minor_seg, twists):
PI_2 = pi * 2.0
z_axis = (0.0, 0.0, 1.0)
verts = []
faces = []
edgeloop_prev = []
for major_index in range(major_seg):
quat = Quaternion(z_axis, (major_index / major_seg) * PI_2)
rot_twists = PI_2 * major_index / major_seg * twists
edgeloop = []
# Create section ring
for minor_index in range(minor_seg):
angle = (PI_2 * minor_index / minor_seg) + rot_twists
vec = Vector((
major_rad + (cos(angle) * minor_rad),
0.0,
sin(angle) * minor_rad))
vec = quat @ vec
edgeloop.append(len(verts))
verts.append(vec)
# Remember very first edgeloop
if major_index == 0:
edgeloop_first = edgeloop
# Bridge last with current ring
if edgeloop_prev:
f = createFaces(edgeloop_prev, edgeloop, closed=True)
faces.extend(f)
edgeloop_prev = edgeloop
# Bridge first and last ring
f = createFaces(edgeloop_prev, edgeloop_first, closed=True)
faces.extend(f)
return verts, faces
class AddTwistedTorus(bpy.types.Operator, object_utils.AddObjectHelper):
bl_idname = "mesh.primitive_twisted_torus_add"
bl_label = "Add Twisted Torus"
bl_description = "Construct a twisted torus mesh"
bl_options = {'REGISTER', 'UNDO', 'PRESET'}
TwistedTorus : BoolProperty(name = "TwistedTorus",
default = True,
description = "TwistedTorus")
change : BoolProperty(name = "Change",
default = False,
description = "change TwistedTorus")
method: EnumProperty(
name='Method',
description='Method for determining the size and thickness of the torus',
items=(
('MAJOR-MINOR', 'Major / Minor', 'Uses the major radius for the overall size and the minor for the thickness'),
('INT-EXT', 'Interior / Exterior', 'Uses the absolute size of the inner and outer circles to determine the size and thickness'),
),
default='MAJOR-MINOR',
)
major_radius: FloatProperty(
name="Major Radius",
description="Radius from the origin to the"
" center of the cross section",
min=0.01,
max=100.0,
default=1.0
)
minor_radius: FloatProperty(
name="Minor Radius",
description="Radius of the torus' cross section",
min=0.01,
max=100.0,
default=0.25
)
major_segments: IntProperty(
name="Major Segments",
description="Number of segments for the main ring of the torus",
min=3,
max=256,
default=48
)
minor_segments: IntProperty(
name="Minor Segments",
description="Number of segments for the minor ring of the torus",
min=3,
max=256,
default=12
)
twists: IntProperty(
name="Twists",
description="Number of twists of the torus",
min=0,
max=256,
default=1
)
abso_major_rad: FloatProperty(
name="Exterior Radius",
description="Total Exterior Radius of the torus",
min=0.01,
max=100.0,
default=1.0
)
abso_minor_rad: FloatProperty(
name="Inside Radius",
description="Total Interior Radius of the torus",
min=0.01,
max=100.0,
default=0.5
)
def draw(self, context):
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
layout.separator()
layout.prop(self, 'method', text='Dimensions Mode')
col = layout.column(align=True)
if self.method == 'MAJOR-MINOR':
col.prop(self, 'major_radius', text='Radius Major')
col.prop(self, 'minor_radius', text='Minor')
else:
col.prop(self, 'abso_major_rad', text='Radius Exterior')
col.prop(self, 'abso_minor_rad', text='Interior')
col = layout.column(align=True)
col.prop(self, 'major_segments', text='Segments Major')
col.prop(self, 'minor_segments', text='Minor')
layout.prop(self, 'twists', expand=True)
if self.change == False:
draw_transform_props(self, layout)
def execute(self, context):
# turn off 'Enter Edit Mode'
use_enter_edit_mode = bpy.context.preferences.edit.use_enter_edit_mode
bpy.context.preferences.edit.use_enter_edit_mode = False
if self.method == 'INT-EXT':
extra_helper = (self.abso_major_rad - self.abso_minor_rad) * 0.5
self.major_radius = self.abso_minor_rad + extra_helper
self.minor_radius = extra_helper
if bpy.context.mode == "OBJECT":
if context.selected_objects != [] and context.active_object and \
(context.active_object.data is not None) and ('TwistedTorus' in context.active_object.data.keys()) and \
(self.change == True):
obj = context.active_object
oldmesh = obj.data
oldmeshname = obj.data.name
verts, faces = add_twisted_torus(
self.major_radius,
self.minor_radius,
self.major_segments,
self.minor_segments,
self.twists
)
mesh = bpy.data.meshes.new('TwistedTorus')
mesh.from_pydata(verts, [], faces)
obj.data = mesh
for material in oldmesh.materials:
obj.data.materials.append(material)
bpy.data.meshes.remove(oldmesh)
obj.data.name = oldmeshname
else:
verts, faces = add_twisted_torus(
self.major_radius,
self.minor_radius,
self.major_segments,
self.minor_segments,
self.twists
)
mesh = bpy.data.meshes.new('TwistedTorus')
mesh.from_pydata(verts, [], faces)
obj = object_utils.object_data_add(context, mesh, operator=self)
obj.data["TwistedTorus"] = True
obj.data["change"] = False
for prm in TwistedTorusParameters():
obj.data[prm] = getattr(self, prm)
if bpy.context.mode == "EDIT_MESH":
active_object = context.active_object
name_active_object = active_object.name
bpy.ops.object.mode_set(mode='OBJECT')
verts, faces = add_twisted_torus(
self.major_radius,
self.minor_radius,
self.major_segments,
self.minor_segments,
self.twists
)
mesh = bpy.data.meshes.new('TwistedTorus')
mesh.from_pydata(verts, [], faces)
obj = object_utils.object_data_add(context, mesh, operator=self)
obj.select_set(True)
active_object.select_set(True)
bpy.context.view_layer.objects.active = active_object
bpy.ops.object.join()
context.active_object.name = name_active_object
bpy.ops.object.mode_set(mode='EDIT')
if use_enter_edit_mode:
bpy.ops.object.mode_set(mode = 'EDIT')
# restore pre operator state
bpy.context.preferences.edit.use_enter_edit_mode = use_enter_edit_mode
return {'FINISHED'}
def TwistedTorusParameters():
TwistedTorusParameters = [
"major_radius",
"minor_radius",
"major_segments",
"minor_segments",
"twists",
"method",
"abso_major_rad",
"abso_minor_rad",
]
return TwistedTorusParameters
@@ -0,0 +1,144 @@
# SPDX-FileCopyrightText: 2015-2023 Blender Foundation
#
# SPDX-License-Identifier: GPL-2.0-or-later
# Originals by meta-androcto, Pablo Vazquez, Liero, Richard Wilks
import bpy
from bpy.types import Operator
from bpy_extras import object_utils
def object_origin(width, height, depth):
"""
This function takes inputs and returns vertex and face arrays.
no actual mesh data creation is done here.
"""
verts = [(+0.0, +0.0, +0.0)]
faces = []
# apply size
for i, v in enumerate(verts):
verts[i] = v[0] * width, v[1] * depth, v[2] * height
return verts, faces
class AddVert(Operator):
bl_idname = "mesh.primitive_vert_add"
bl_label = "Single Vert"
bl_description = "Add a Single Vertice to Edit Mode"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
mesh = bpy.data.meshes.new("Vert")
mesh.vertices.add(1)
object_utils.object_data_add(context, mesh, operator=None)
bpy.ops.object.mode_set(mode='EDIT')
return {'FINISHED'}
class AddEmptyVert(Operator):
bl_idname = "mesh.primitive_emptyvert_add"
bl_label = "Empty Object Origin"
bl_description = "Add an Object Origin to Edit Mode"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
mesh = bpy.data.meshes.new("Vert")
mesh.vertices.add(1)
object_utils.object_data_add(context, mesh, operator=None)
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.delete(type='VERT')
return {'FINISHED'}
def Add_Symmetrical_Empty():
bpy.ops.mesh.primitive_plane_add(enter_editmode=True)
sempty = bpy.context.object
sempty.name = "SymmEmpty"
# check if we have a mirror modifier, otherwise add
if not any(mod.type == 'MIRROR' for mod in sempty.modifiers):
bpy.ops.object.modifier_add(type='MIRROR')
# Delete all!
bpy.ops.mesh.select_all(action='TOGGLE')
bpy.ops.mesh.select_all(action='TOGGLE')
bpy.ops.mesh.delete(type='VERT')
def Add_Symmetrical_Vert():
bpy.ops.mesh.primitive_plane_add(enter_editmode=True)
sempty = bpy.context.object
sempty.name = "SymmVert"
# check if we have a mirror modifier, otherwise add
if not any(mod.type == 'MIRROR' for mod in sempty.modifiers):
bpy.ops.object.modifier_add(type='MIRROR')
# Delete all!
bpy.ops.mesh.select_all(action='TOGGLE')
bpy.ops.mesh.select_all(action='TOGGLE')
bpy.ops.mesh.merge(type='CENTER')
class AddSymmetricalEmpty(Operator):
bl_idname = "mesh.primitive_symmetrical_empty_add"
bl_label = "Add Symmetrical Object Origin"
bl_description = "Object Origin with a Mirror Modifier for symmetrical modeling"
bl_options = {'UNDO'}
def draw(self, context):
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
mirror = next(mod for mod in bpy.context.object.modifiers
if mod.type == 'MIRROR')
layout.prop(mirror, "use_clip", text="Use Clipping")
layout.label(text="Mirror Axis")
col = layout.column(align=True)
col.prop(mirror, "use_axis")
col.prop(mirror, "use_axis")
col.prop(mirror, "use_axis")
def execute(self, context):
Add_Symmetrical_Empty()
return {'FINISHED'}
class AddSymmetricalVert(Operator):
bl_idname = "mesh.primitive_symmetrical_vert_add"
bl_label = "Add Symmetrical Origin & Vert"
bl_description = "Object Origin with a Mirror Modifier for symmetrical modeling"
bl_options = {'UNDO'}
def draw(self, context):
layout = self.layout
mirror = next(mod for mod in bpy.context.object.modifiers
if mod.type == 'MIRROR')
layout.prop(mirror, "use_clip", text="Use Clipping")
layout.label(text="Mirror Axis")
col = layout.column(align=True)
col.prop(mirror, "use_axis")
col.prop(mirror, "use_axis")
col.prop(mirror, "use_axis")
def execute(self, context):
Add_Symmetrical_Vert()
return {'FINISHED'}
@@ -0,0 +1,12 @@
schema_version = "1.0.0"
id = "extra_mesh_objects"
name = "Extra Mesh Objects"
version = "0.4.0"
tagline = "Add extra mesh object types"
maintainer = "Community"
type = "add-on"
tags = ["Add Mesh"]
blender_version_min = "4.2.0"
license = ["SPDX:GPL-3.0-or-later"]
website = "https://projects.blender.org/extensions/add_mesh_extra_objects"
copyright = ["2024 Multiple Authors"]
@@ -0,0 +1,9 @@
import bpy
def draw_transform_props(self, layout):
if hasattr(self, 'align'):
layout.prop(self, 'align', expand=False)
if hasattr(self, 'location'):
layout.prop(self, 'location', expand=True)
if hasattr(self, 'rotation'):
layout.prop(self, 'rotation', expand=True)
@@ -0,0 +1,58 @@
import bpy
class AddMeshExtraObjectsPreferences(bpy.types.AddonPreferences):
bl_idname = __package__
show_round_cube: bpy.props.BoolProperty(
name = "Round Cube",
default = True,
)
show_single_vert: bpy.props.BoolProperty(
name = "Single Vert Menu",
default = True,
)
show_torus_objects: bpy.props.BoolProperty(
name = "Torus Objects Menu",
default = True,
)
show_math_functions: bpy.props.BoolProperty(
name = "Math Functions Menu",
default = True,
)
show_gears: bpy.props.BoolProperty(
name = "Gears Menu",
default = True,
)
show_pipe_joints: bpy.props.BoolProperty(
name = "Pipe Joints Menu",
default = True,
)
show_gemstones: bpy.props.BoolProperty(
name = "Gemstones Menu",
default = True,
)
show_extras: bpy.props.BoolProperty(
name = "Extras Menu",
default = True,
)
show_parent_to_empty: bpy.props.BoolProperty(
name = "Parent to Empty",
default = True,
)
def draw(self, context):
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
col = layout.column(heading="Filter Add Menu Items")
col.prop(self, "show_round_cube")
col.prop(self, "show_single_vert")
col.prop(self, "show_torus_objects")
col.prop(self, "show_math_functions")
col.prop(self, "show_gears")
col.prop(self, "show_pipe_joints")
col.prop(self, "show_gemstones")
col.prop(self, "show_extras")
col.prop(self, "show_parent_to_empty")
@@ -0,0 +1,8 @@
import bpy
op = bpy.context.active_operator
op.radius = 0.5
op.arc_div = 8
op.lin_div = 0
op.size = (0.0, 0.0, 3.0)
op.div_type = 'CORNERS'
@@ -0,0 +1,8 @@
import bpy
op = bpy.context.active_operator
op.radius = 0.4
op.arc_div = 8
op.lin_div = 0
op.size = (1.5, 3.0, 1.0)
op.div_type = 'ALL'
@@ -0,0 +1,9 @@
import bpy
op = bpy.context.active_operator
op.radius = 0.0
op.arc_div = 1
op.lin_div = 0
op.size = (2.0, 2.0, 2.0)
op.div_type = 'CORNERS'
op.odd_axis_align = False
@@ -0,0 +1,7 @@
import bpy
op = bpy.context.active_operator
op.radius = 0
op.size = (2, 2, 2)
op.lin_div = 5
op.div_type = 'ALL'

Some files were not shown because too many files have changed in this diff Show More