├── .gitignore ├── README.md ├── imgs ├── vicAddons │ ├── blenderBridge.gif │ ├── blenderStair.gif │ ├── changeRope.gif │ ├── createConnect.gif │ ├── createLantern.gif │ ├── createProxy.gif │ ├── lanternTutorial.gif │ ├── pss_interface.jpg │ └── pss_perform.gif └── vicTools │ ├── CreateLookAt.gif │ ├── CreateMirrorCube.gif │ ├── MeshFlatten.gif │ ├── img1.png │ ├── img2.png │ ├── img3.png │ ├── img4.png │ ├── img5.png │ ├── particlesToRigidbody.gif │ └── selectByName.gif ├── vicAddons ├── __init__.py ├── auto_load.py ├── proceduralBridge.py ├── proceduralLantern.py ├── proceduralSplineStair.py ├── proceduralStair.py ├── springBone.py └── vic_tools.py └── vicTools ├── __init__.py ├── operators ├── CreateCameraTarget.py ├── HandDrag.py ├── LineAlign.py ├── MeshFlatten.py ├── MirrorCubeAdd.py ├── ParticleToRigidbody.py └── SelectByName.py ├── vic_actions.py ├── vic_make_it_voxel.py ├── vic_spring_bone.py └── vic_tools.py /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # VicAddons 2 | 3 | ## 2.90 下載 https://drive.google.com/file/d/1JC_PHu37mJ81T40-0AsuCquVrYj4womN/view?usp=sharing 4 | 5 | ## 安裝方法 6 | 7 | Edit => Preferences => Add-ons => Install => 點選下載的zip => 打開addon => 點擊 N 打開界面 8 | 9 | ![Alt text](/imgs/vicAddons/pss_interface.jpg) 10 | 11 | ### Spline Stair Generator 12 | 13 | ![Alt text](/imgs/vicAddons/pss_perform.gif) 14 | 15 | 教學 https://docs.google.com/document/d/1g6ggwmvVlbe3UNW7qg-WUbgkTnyOrpukyj0RRYH78Ps/edit?usp=sharing 16 | 17 | ### Stair Generator 18 | 19 | 程序化階梯 20 | 21 | ![Alt text](/imgs/vicAddons/blenderStair.gif) 22 | 23 | 教學 https://docs.google.com/document/d/1PuDeD7YafzfP63JVKogzpvt-D57eoYOKeMlQ1LcdEAc/edit?usp=sharing 24 | 25 | ### Bridge Generator 26 | 27 | 程序化拱橋 28 | 29 | ![Alt text](/imgs/vicAddons/blenderBridge.gif) 30 | 31 | 教學 https://docs.google.com/document/d/1AVF1ZvqPcBSMp24uOjV5Z_kAnSumQ_7lGG4k5X-m1UA/edit?usp=sharing 32 | 33 | ### Lantern Generator 34 | 35 | 程序化燈籠 36 | 37 | ![Alt text](/imgs/vicAddons/createLantern.gif) 38 | 39 | 教學gif 40 | 41 | ![Alt text](/imgs/vicAddons/lanternTutorial.gif) 42 | 43 | 教學 https://docs.google.com/document/d/1RlXnlFQZT2g0P5HSZCrkWiOFY4e0oVgeh2qvvbo-N5Q/edit?usp=sharing 44 | 45 | # Vic Tools 46 | 47 | ## 2.79 的連接改到 https://1drv.ms/u/s!Apea4elXUnuxhJIhENBdzd0qdQY1PQ (不再更新) 48 | 49 | ## 安裝方法 50 | 51 | 下載後解壓,如圖複製vicTool資料夾 52 | 53 | ![Alt text](/imgs/vicTools/img3.png) 54 | 55 | 到安裝的blender addon資料夾下(你的blender目錄\2.80\scripts\addons),在blender中啓用 56 | 57 | ![Alt text](/imgs/vicTools/img4.png) 58 | 59 | 在視圖中按N鍵打開右側選單就可以看到 60 | 61 | ![Alt text](/imgs/vicTools/img5.png) 62 | 63 | # VicTool功能介紹 64 | 65 | ![Alt text](/imgs/vicTools/img1.png) 66 | 67 | ![Alt text](/imgs/vicTools/img2.png) 68 | 69 | ## Action 是一些流程的快速鍵 70 | 71 | ### Create Mirror Cube 72 | 73 | 快速創建有mirror modifier並切一半的立方體,如果覺得每次拉模型前都要做一樣的事很煩,就可以試試這個功能。 74 | 75 | ![Alt text](/imgs/vicTools/CreateMirrorCube.gif) 76 | 77 | ### Create Camera Target 78 | 79 | 快速創建look at modifier並新增目標物件。方便快速的設定需要面向對像的物件。 80 | 81 | ![Alt text](/imgs/vicTools/CreateLookAt.gif) 82 | 83 | ### Particle To Rigidbodys 84 | 85 | 可以建立剛體物理的粒子效果 86 | 87 | ![Alt text](/imgs/vicTools/particlesToRigidbody.gif) 88 | 89 | 以下是介紹影片 90 | 91 | [![Particle To Rigidbodys](http://img.youtube.com/vi/G61hp533SEk/0.jpg)](https://www.youtube.com/watch?v=G61hp533SEk "Particle To Rigidbodys") 92 | 93 | ### Select By Name 94 | 95 | 快速選取有同樣名稱的物件 96 | 97 | ### Drag Effect 98 | 99 | 把mesh有伸縮的能力,以下是介紹影片 100 | 101 | [![Drag Effect](http://img.youtube.com/vi/5haIDFWUm-Y/0.jpg)](https://www.youtube.com/watch?v=5haIDFWUm-Y "Drag Effect") 102 | 103 | #### Make It Drag 104 | 105 | 把mesh有伸縮的能力 106 | 107 | #### Healing All 108 | 109 | 把DRAG的效果清空 110 | 111 | ### BGE(2.79) 112 | 113 | blender game engine 用的功能 114 | 115 | #### Make It Emitter(2.79) 116 | 117 | bge用的pure particle system 的粒子發射器 118 | 119 | [![pure particle system](http://img.youtube.com/vi/UIB5_1OyqcY/0.jpg)](https://www.youtube.com/watch?v=UIB5_1OyqcY "pure particle system") 120 | 121 | [![pure particle system](http://img.youtube.com/vi/bcfBA7S42d4/0.jpg)](https://www.youtube.com/watch?v=bcfBA7S42d4 "pure particle system") 122 | 123 | [![pure particle system](http://img.youtube.com/vi/4-qa2oTSPC8/0.jpg)](https://www.youtube.com/watch?v=4-qa2oTSPC8 "pure particle system") 124 | 125 | #### Make It Sprite(2.79) 126 | 127 | bge用的pure particle system 的粒子 128 | 129 | #### Make It Drag(2.79) 130 | 131 | 把mesh有伸縮的能力,bge版 132 | 133 | [![pure particle system](http://img.youtube.com/vi/XEqQVRYO_i4/0.jpg)](https://www.youtube.com/watch?v=XEqQVRYO_i4 "pure particle system") 134 | 135 | #### Quick Character(2.79) 136 | 137 | 快速把物件填加wasd操作節點 138 | 139 | ## SpringBoneTool 是彈性骨骼 140 | 141 | 直接看影片吧 142 | 143 | [![SpringBoneTool](http://img.youtube.com/vi/Up4L2wYsorI/0.jpg)](https://www.youtube.com/watch?v=Up4L2wYsorI "SpringBoneTool") 144 | 145 | ## VoxelTool 是體素效果(2.79) 146 | 147 | 把模型包含頂點顏色轉成體素效果,以下是介紹影片 148 | 149 | [![VoxelTool](http://img.youtube.com/vi/VU7X1fNcg7M/0.jpg)](https://www.youtube.com/watch?v=VU7X1fNcg7M "VoxelTool") 150 | 151 | ## ObjectAroundObject 是有趣的動態效果(2.79) 152 | 153 | 以下是介紹影片 154 | 155 | [![ObjectAroundObject](http://img.youtube.com/vi/F9LaiSzq4Sg/0.jpg)](https://www.youtube.com/watch?v=F9LaiSzq4Sg "ObjectAroundObject") 156 | 157 | 158 | -------------------------------------------------------------------------------- /imgs/vicAddons/blenderBridge.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VicYu1983/blenderAddon/cde169a98b251ae28d3e84be94ac5dee9c2c2f75/imgs/vicAddons/blenderBridge.gif -------------------------------------------------------------------------------- /imgs/vicAddons/blenderStair.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VicYu1983/blenderAddon/cde169a98b251ae28d3e84be94ac5dee9c2c2f75/imgs/vicAddons/blenderStair.gif -------------------------------------------------------------------------------- /imgs/vicAddons/changeRope.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VicYu1983/blenderAddon/cde169a98b251ae28d3e84be94ac5dee9c2c2f75/imgs/vicAddons/changeRope.gif -------------------------------------------------------------------------------- /imgs/vicAddons/createConnect.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VicYu1983/blenderAddon/cde169a98b251ae28d3e84be94ac5dee9c2c2f75/imgs/vicAddons/createConnect.gif -------------------------------------------------------------------------------- /imgs/vicAddons/createLantern.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VicYu1983/blenderAddon/cde169a98b251ae28d3e84be94ac5dee9c2c2f75/imgs/vicAddons/createLantern.gif -------------------------------------------------------------------------------- /imgs/vicAddons/createProxy.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VicYu1983/blenderAddon/cde169a98b251ae28d3e84be94ac5dee9c2c2f75/imgs/vicAddons/createProxy.gif -------------------------------------------------------------------------------- /imgs/vicAddons/lanternTutorial.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VicYu1983/blenderAddon/cde169a98b251ae28d3e84be94ac5dee9c2c2f75/imgs/vicAddons/lanternTutorial.gif -------------------------------------------------------------------------------- /imgs/vicAddons/pss_interface.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VicYu1983/blenderAddon/cde169a98b251ae28d3e84be94ac5dee9c2c2f75/imgs/vicAddons/pss_interface.jpg -------------------------------------------------------------------------------- /imgs/vicAddons/pss_perform.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VicYu1983/blenderAddon/cde169a98b251ae28d3e84be94ac5dee9c2c2f75/imgs/vicAddons/pss_perform.gif -------------------------------------------------------------------------------- /imgs/vicTools/CreateLookAt.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VicYu1983/blenderAddon/cde169a98b251ae28d3e84be94ac5dee9c2c2f75/imgs/vicTools/CreateLookAt.gif -------------------------------------------------------------------------------- /imgs/vicTools/CreateMirrorCube.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VicYu1983/blenderAddon/cde169a98b251ae28d3e84be94ac5dee9c2c2f75/imgs/vicTools/CreateMirrorCube.gif -------------------------------------------------------------------------------- /imgs/vicTools/MeshFlatten.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VicYu1983/blenderAddon/cde169a98b251ae28d3e84be94ac5dee9c2c2f75/imgs/vicTools/MeshFlatten.gif -------------------------------------------------------------------------------- /imgs/vicTools/img1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VicYu1983/blenderAddon/cde169a98b251ae28d3e84be94ac5dee9c2c2f75/imgs/vicTools/img1.png -------------------------------------------------------------------------------- /imgs/vicTools/img2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VicYu1983/blenderAddon/cde169a98b251ae28d3e84be94ac5dee9c2c2f75/imgs/vicTools/img2.png -------------------------------------------------------------------------------- /imgs/vicTools/img3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VicYu1983/blenderAddon/cde169a98b251ae28d3e84be94ac5dee9c2c2f75/imgs/vicTools/img3.png -------------------------------------------------------------------------------- /imgs/vicTools/img4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VicYu1983/blenderAddon/cde169a98b251ae28d3e84be94ac5dee9c2c2f75/imgs/vicTools/img4.png -------------------------------------------------------------------------------- /imgs/vicTools/img5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VicYu1983/blenderAddon/cde169a98b251ae28d3e84be94ac5dee9c2c2f75/imgs/vicTools/img5.png -------------------------------------------------------------------------------- /imgs/vicTools/particlesToRigidbody.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VicYu1983/blenderAddon/cde169a98b251ae28d3e84be94ac5dee9c2c2f75/imgs/vicTools/particlesToRigidbody.gif -------------------------------------------------------------------------------- /imgs/vicTools/selectByName.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VicYu1983/blenderAddon/cde169a98b251ae28d3e84be94ac5dee9c2c2f75/imgs/vicTools/selectByName.gif -------------------------------------------------------------------------------- /vicAddons/__init__.py: -------------------------------------------------------------------------------- 1 | # This program is free software; you can redistribute it and/or modify 2 | # it under the terms of the GNU General Public License as published by 3 | # the Free Software Foundation; either version 3 of the License, or 4 | # (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, but 7 | # WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTIBILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 9 | # General Public License for more details. 10 | # 11 | # You should have received a copy of the GNU General Public License 12 | # along with this program. If not, see . 13 | 14 | bl_info = { 15 | "name" : "vicAddons", 16 | "author" : "VicYu", 17 | "description" : "", 18 | "blender" : (2, 80, 0), 19 | "version" : (0, 0, 1), 20 | "location" : "", 21 | "warning" : "", 22 | "category" : "Generic" 23 | } 24 | 25 | from . import auto_load 26 | 27 | auto_load.init() 28 | 29 | def register(): 30 | auto_load.register() 31 | 32 | def unregister(): 33 | auto_load.unregister() 34 | -------------------------------------------------------------------------------- /vicAddons/auto_load.py: -------------------------------------------------------------------------------- 1 | import os 2 | import bpy 3 | import sys 4 | import typing 5 | import inspect 6 | import pkgutil 7 | import importlib 8 | from pathlib import Path 9 | 10 | __all__ = ( 11 | "init", 12 | "register", 13 | "unregister", 14 | ) 15 | 16 | modules = None 17 | ordered_classes = None 18 | 19 | def init(): 20 | global modules 21 | global ordered_classes 22 | 23 | modules = get_all_submodules(Path(__file__).parent) 24 | ordered_classes = get_ordered_classes_to_register(modules) 25 | 26 | def register(): 27 | for cls in ordered_classes: 28 | bpy.utils.register_class(cls) 29 | 30 | for module in modules: 31 | if module.__name__ == __name__: 32 | continue 33 | if hasattr(module, "register"): 34 | module.register() 35 | 36 | def unregister(): 37 | for cls in reversed(ordered_classes): 38 | bpy.utils.unregister_class(cls) 39 | 40 | for module in modules: 41 | if module.__name__ == __name__: 42 | continue 43 | if hasattr(module, "unregister"): 44 | module.unregister() 45 | 46 | 47 | # Import modules 48 | ################################################# 49 | 50 | def get_all_submodules(directory): 51 | return list(iter_submodules(directory, directory.name)) 52 | 53 | def iter_submodules(path, package_name): 54 | for name in sorted(iter_submodule_names(path)): 55 | yield importlib.import_module("." + name, package_name) 56 | 57 | def iter_submodule_names(path, root=""): 58 | for _, module_name, is_package in pkgutil.iter_modules([str(path)]): 59 | if is_package: 60 | sub_path = path / module_name 61 | sub_root = root + module_name + "." 62 | yield from iter_submodule_names(sub_path, sub_root) 63 | else: 64 | yield root + module_name 65 | 66 | 67 | # Find classes to register 68 | ################################################# 69 | 70 | def get_ordered_classes_to_register(modules): 71 | return toposort(get_register_deps_dict(modules)) 72 | 73 | def get_register_deps_dict(modules): 74 | deps_dict = {} 75 | classes_to_register = set(iter_classes_to_register(modules)) 76 | for cls in classes_to_register: 77 | deps_dict[cls] = set(iter_own_register_deps(cls, classes_to_register)) 78 | return deps_dict 79 | 80 | def iter_own_register_deps(cls, own_classes): 81 | yield from (dep for dep in iter_register_deps(cls) if dep in own_classes) 82 | 83 | def iter_register_deps(cls): 84 | for value in typing.get_type_hints(cls, {}, {}).values(): 85 | dependency = get_dependency_from_annotation(value) 86 | if dependency is not None: 87 | yield dependency 88 | 89 | def get_dependency_from_annotation(value): 90 | if isinstance(value, tuple) and len(value) == 2: 91 | if value[0] in (bpy.props.PointerProperty, bpy.props.CollectionProperty): 92 | return value[1]["type"] 93 | return None 94 | 95 | def iter_classes_to_register(modules): 96 | base_types = get_register_base_types() 97 | for cls in get_classes_in_modules(modules): 98 | if any(base in base_types for base in cls.__bases__): 99 | if not getattr(cls, "is_registered", False): 100 | yield cls 101 | 102 | def get_classes_in_modules(modules): 103 | classes = set() 104 | for module in modules: 105 | for cls in iter_classes_in_module(module): 106 | classes.add(cls) 107 | return classes 108 | 109 | def iter_classes_in_module(module): 110 | for value in module.__dict__.values(): 111 | if inspect.isclass(value): 112 | yield value 113 | 114 | def get_register_base_types(): 115 | return set(getattr(bpy.types, name) for name in [ 116 | "Panel", "Operator", "PropertyGroup", 117 | "AddonPreferences", "Header", "Menu", 118 | "Node", "NodeSocket", "NodeTree", 119 | "UIList", "RenderEngine" 120 | ]) 121 | 122 | 123 | # Find order to register to solve dependencies 124 | ################################################# 125 | 126 | def toposort(deps_dict): 127 | sorted_list = [] 128 | sorted_values = set() 129 | while len(deps_dict) > 0: 130 | unsorted = [] 131 | for value, deps in deps_dict.items(): 132 | if len(deps) == 0: 133 | sorted_list.append(value) 134 | sorted_values.add(value) 135 | else: 136 | unsorted.append(value) 137 | deps_dict = {value : deps_dict[value] - sorted_values for value in unsorted} 138 | return sorted_list -------------------------------------------------------------------------------- /vicAddons/proceduralBridge.py: -------------------------------------------------------------------------------- 1 | import bpy, time 2 | from math import * 3 | from mathutils import * 4 | from bpy.props import ( 5 | BoolProperty, 6 | EnumProperty, 7 | FloatProperty, 8 | FloatVectorProperty, 9 | IntProperty, 10 | IntVectorProperty, 11 | StringProperty, 12 | PointerProperty 13 | ) 14 | from .vic_tools import * 15 | 16 | styleSetList = [ 17 | ('0','Normal', 'Normal'), 18 | ('1','Sin', 'Sin') 19 | ] 20 | 21 | class vic_procedural_bridge(bpy.types.Operator): 22 | bl_idname = 'vic.vic_procedural_bridge' 23 | bl_label = 'Bridge Generator' 24 | bl_description = '' 25 | bl_options = {'REGISTER', 'UNDO'} 26 | 27 | baseMesh:StringProperty( 28 | name='Pick Base', 29 | description='', 30 | default='Base' 31 | ) 32 | 33 | stepMesh:StringProperty( 34 | name='Pick Step', 35 | description='', 36 | default='Step' 37 | ) 38 | 39 | wallMesh:StringProperty( 40 | name='Pick Wall', 41 | description='', 42 | default='Wall' 43 | ) 44 | 45 | pileMesh:StringProperty( 46 | name='Pick Pile', 47 | description='', 48 | default='Pile' 49 | ) 50 | 51 | bridgeLength:FloatProperty( 52 | name='Length', 53 | min=1, 54 | default=100 55 | ) 56 | 57 | bridgeHeight:FloatProperty( 58 | name='Height', 59 | min=0, 60 | default=4 61 | ) 62 | 63 | bridgeWidth:FloatProperty( 64 | name='Width', 65 | min=1, 66 | default=10 67 | ) 68 | 69 | bridgePow:FloatProperty( 70 | name='Curve', 71 | min=.1, 72 | default=2 73 | ) 74 | 75 | stableBridge:BoolProperty( 76 | name='Stable', 77 | default=False 78 | ) 79 | 80 | bridgeStableHeight:FloatProperty( 81 | name='Stable Height', 82 | default=-5 83 | ) 84 | 85 | stepHeight:FloatProperty( 86 | name='Step Height', 87 | min=0, 88 | default=.1 89 | ) 90 | 91 | wallHeight:FloatProperty( 92 | name='Wall Height', 93 | default=0 94 | ) 95 | 96 | wallInset:FloatProperty( 97 | name='Wall Inset', 98 | default=1, 99 | ) 100 | 101 | pileCount:IntProperty( 102 | name='Pile Count', 103 | min=2, 104 | max=30, 105 | default=10 106 | ) 107 | 108 | styleSet: EnumProperty( 109 | name='Style', 110 | description='', 111 | items=styleSetList, 112 | default='0' 113 | ) 114 | 115 | pilePerWall: BoolProperty( 116 | name='Per Wall', 117 | default=True 118 | ) 119 | 120 | def draw(self, context): 121 | layout = self.layout 122 | scene = context.scene 123 | 124 | box = layout.box() 125 | box.label(text='Base Shape') 126 | box.prop_search(self, "baseMesh", bpy.data, "objects") 127 | box.prop_search(self, "stepMesh", bpy.data, "objects") 128 | box.prop(self, 'bridgeWidth') 129 | box.prop(self, 'bridgeLength') 130 | box.prop(self, 'bridgeHeight') 131 | box.prop(self, 'bridgePow') 132 | box.prop(self, 'stepHeight') 133 | box.prop(self, 'styleSet') 134 | 135 | # row = box.row() 136 | # row.prop(self, 'stableBridge') 137 | # row.prop(self, 'bridgeStableHeight') 138 | 139 | box = layout.box() 140 | box.label(text='Wall & Pile') 141 | box.prop_search(self, "wallMesh", bpy.data, "objects") 142 | box.prop_search(self, "pileMesh", bpy.data, "objects") 143 | box.prop(self, 'wallHeight') 144 | box.prop(self, 'wallInset') 145 | box.prop(self, 'pileCount') 146 | box.prop(self, 'pilePerWall') 147 | 148 | def transformMesh( self, obj, minpt, maxpt, firstpt, offsetZ = 0 ): 149 | localMin = obj.matrix_world.inverted() @ minpt 150 | localMax = obj.matrix_world.inverted() @ maxpt 151 | localFirst = obj.matrix_world.inverted() @ firstpt 152 | height = localMax.z - localMin.z 153 | width = localMax.x - localMin.x 154 | for v in obj.data.vertices: 155 | # effectFactor = 1 156 | # if self.stableBridge: 157 | # if v.co.z < self.bridgeStableHeight: 158 | # effectFactor = 0 159 | # else: 160 | # effectFactor = (v.co.z - self.bridgeStableHeight) / abs(self.bridgeStableHeight) 161 | # effectFactor = min(1, effectFactor) 162 | 163 | if v.co.x >= localMin.x and v.co.x < localMax.x: 164 | percentX = (v.co.x - localMin.x)/width 165 | v.co.z += (percentX * height + (localMin.z - localFirst.z) + offsetZ) 166 | # v.co.z += (percentX * height + (localMin.z - localFirst.z) + offsetZ) * effectFactor 167 | 168 | def getCurveLocation(self, count, allLength): 169 | halfCount = count / 2 170 | useLength = allLength / count 171 | pts = [] 172 | for i in range( count + 1 ): 173 | x = useLength * i 174 | if self.styleSet == styleSetList[0][0]: 175 | z = 1 - pow(abs(i-halfCount) / halfCount, self.bridgePow ) 176 | elif self.styleSet == styleSetList[1][0]: 177 | z = sin(x/allLength * pi * 2 - pi / 2) 178 | # sin值域-1~1,轉爲0~1 179 | z = (z+1)/2 180 | else: 181 | z = 0 182 | pts.append(Vector((x, 0, z * self.bridgeHeight))) 183 | return pts 184 | 185 | def createWall(self, allLength): 186 | prefab_wall = None 187 | if self.wallMesh in bpy.context.view_layer.objects and bpy.context.view_layer.objects[self.wallMesh].type == 'MESH': 188 | prefab_wall = bpy.context.view_layer.objects[self.wallMesh] 189 | 190 | wall = None 191 | if prefab_wall != None: 192 | stepLength = prefab_wall.dimensions.x 193 | count = round(self.bridgeLength / stepLength) 194 | usingLength = (self.bridgeLength / count) 195 | scaleX = usingLength / stepLength 196 | joinList = [] 197 | joinList2 = [] 198 | for i in range(count): 199 | wallObj = copyToScene(prefab_wall) 200 | matT = Matrix.Translation(Vector(((i+.5) * usingLength, self.bridgeWidth/2 - self.wallInset, 0))) 201 | matS = Matrix.Scale(scaleX, 4, Vector((1,0,0))) 202 | wallObj.matrix_world = matT @ matS 203 | joinList.append(wallObj) 204 | 205 | wallObj = copyToScene(prefab_wall) 206 | matT = Matrix.Translation(Vector(((i+.5) * usingLength, -self.bridgeWidth/2 + self.wallInset, 0))) 207 | wallObj.matrix_world = matT @ matS 208 | joinList.append(wallObj) 209 | 210 | joinObj(joinList, joinList[0]) 211 | wall = joinList[0] 212 | wall.matrix_world.row[0][3] = allLength/2 213 | return wall 214 | 215 | def createPile(self, allLength): 216 | prefab_pile = None 217 | if self.pileMesh in bpy.context.view_layer.objects and bpy.context.view_layer.objects[self.pileMesh].type == 'MESH': 218 | prefab_pile = bpy.context.view_layer.objects[self.pileMesh] 219 | 220 | prefab_wall = None 221 | if self.wallMesh in bpy.context.view_layer.objects and bpy.context.view_layer.objects[self.wallMesh].type == 'MESH': 222 | prefab_wall = bpy.context.view_layer.objects[self.wallMesh] 223 | 224 | pile = None 225 | if prefab_pile != None: 226 | count = self.pileCount-1 227 | if self.pilePerWall and prefab_wall != None: 228 | stepLength = prefab_wall.dimensions.x 229 | count = round(self.bridgeLength / stepLength) 230 | 231 | pts = self.getCurveLocation(count, self.bridgeLength) 232 | joinList = [] 233 | for p in pts: 234 | pileObj = copyToScene(prefab_pile) 235 | pileObj.matrix_world = Matrix.Translation(Vector((p.x, self.bridgeWidth/2 - self.wallInset, p.z + self.wallHeight))) 236 | joinList.append(pileObj) 237 | 238 | pileObj = copyToScene(prefab_pile) 239 | pileObj.matrix_world = Matrix.Translation(Vector((p.x, -self.bridgeWidth/2 + self.wallInset, p.z + self.wallHeight))) 240 | joinList.append(pileObj) 241 | 242 | joinObj(joinList, joinList[0]) 243 | pile = joinList[0] 244 | pile.matrix_world.row[0][3] = allLength/2 245 | return pile 246 | 247 | def createStep(self, prefab_step, allLength, base, wall): 248 | stepLength = prefab_step.dimensions.x 249 | count = round(allLength / stepLength) 250 | pts = self.getCurveLocation(count, allLength) 251 | widthScaleForStep = self.bridgeWidth / prefab_step.dimensions.y 252 | 253 | joinList = [] 254 | for i, p in enumerate(pts): 255 | if i == len(pts)-1: 256 | break 257 | first = p 258 | second = pts[i+1] 259 | diff = second - first 260 | dir = diff.normalized() 261 | radian = atan2(dir.z, dir.x) 262 | scaleX = diff.length / stepLength 263 | 264 | stepObj = copyToScene(prefab_step) 265 | # 强制修改matrix_world 266 | matT = Matrix.Translation(first + diff / 2 + Vector((0,0,self.stepHeight))) 267 | matR = Matrix.Rotation(-radian, 4, Vector((0,1,0))) 268 | matS = Matrix.Scale(scaleX, 4, Vector((1,0,0))) 269 | matS2 = Matrix.Scale(widthScaleForStep, 4, Vector((0,1,0))) 270 | stepObj.matrix_world = matT @ matR @ matS @ matS2 271 | joinList.append( stepObj ) 272 | 273 | self.transformMesh(base, first, second, pts[0] ) 274 | if wall != None: 275 | self.transformMesh(wall, first, second, pts[0], self.wallHeight ) 276 | 277 | joinObj(joinList, joinList[0]) 278 | step = joinList[0] 279 | return step 280 | 281 | def createBase(self, prefab_base, allLength): 282 | widthScale = self.bridgeWidth / prefab_base.dimensions.y 283 | lengthScale = self.bridgeLength / prefab_base.dimensions.x 284 | base = copyToScene(prefab_base) 285 | scaleObjVertex(base, (lengthScale, widthScale, 1)) 286 | base.matrix_world = Matrix.Translation(Vector((allLength/2,0,0))) # 强制修改matrix_world 287 | return base 288 | 289 | def execute(self, context): 290 | if self.baseMesh not in bpy.context.view_layer.objects or bpy.context.view_layer.objects[self.baseMesh].type != 'MESH': 291 | return {'FINISHED'} 292 | if self.stepMesh not in bpy.context.view_layer.objects or bpy.context.view_layer.objects[self.stepMesh].type != 'MESH': 293 | return {'FINISHED'} 294 | 295 | prefab_base = bpy.context.view_layer.objects[self.baseMesh] 296 | prefab_step = bpy.context.view_layer.objects[self.stepMesh] 297 | allLength = self.bridgeLength + .5 298 | 299 | base = self.createBase(prefab_base, allLength) 300 | wall = self.createWall(allLength) 301 | pile = self.createPile(allLength) 302 | step = self.createStep(prefab_step, allLength, base, wall) 303 | 304 | joinList = filter(lambda obj: obj != None, [base, wall, pile, step]) 305 | joinObj(joinList, base) 306 | base.name = 'ProcedrualBridge' 307 | 308 | return {'FINISHED'} 309 | 310 | class vic_procedural_bridge_panel(bpy.types.Panel): 311 | bl_category = "Vic Addons" 312 | bl_space_type = "VIEW_3D" 313 | bl_region_type = "UI" 314 | bl_label = "Procedrual Bridge" 315 | 316 | def draw(self, context): 317 | layout = self.layout 318 | 319 | col = layout.column(align=True) 320 | col.operator(vic_procedural_bridge.bl_idname) -------------------------------------------------------------------------------- /vicAddons/proceduralLantern.py: -------------------------------------------------------------------------------- 1 | import bpy, math, time, random 2 | from mathutils import * 3 | from .vic_tools import * 4 | 5 | class vic_procedural_lantern_manager(bpy.types.Operator): 6 | bl_idname = "vic.vic_procedural_lantern_manager" 7 | bl_label = "Create Manager" 8 | def execute(self, context): 9 | 10 | for obj in bpy.data.objects: 11 | if obj.name == "LanternManager": 12 | return {'FINISHED'} 13 | 14 | bpy.ops.object.empty_add(type='CUBE', location=([0,0,0])) 15 | mgrObj = context.object 16 | mgrObj.name = "LanternManager" 17 | mgrObj["Rope"] = 1 18 | mgrObj["NGon"] = 3 19 | mgrObj["Segment"] = 1.0 20 | mgrObj["Gravity"] = 20.0 21 | mgrObj["Radius"] = 1.0 22 | mgrObj["Lantern"] = 1 23 | mgrObj["LanternPrefab"] = "" 24 | mgrObj["LanternSegment"] = 5.0 25 | mgrObj["LanternRandomOffset"] = .1 26 | return {'FINISHED'} 27 | 28 | class vic_procedural_lantern_proxy(bpy.types.Operator): 29 | bl_idname = "vic.vic_procedural_lantern_proxy" 30 | bl_label = "Create Proxy" 31 | 32 | _timer = None 33 | count = 0 34 | 35 | def execute(self, context): 36 | bpy.ops.vic.vic_procedural_lantern_manager() 37 | 38 | proxys = [o for o in bpy.data.objects if "ProxyData" in o.name] 39 | currentId = -1 40 | for proxy in proxys: 41 | if proxy["Id"] > currentId: 42 | currentId = proxy["Id"] 43 | 44 | bpy.ops.object.empty_add(type='SPHERE') 45 | obj = bpy.context.object 46 | obj.name = "ProxyData" 47 | obj.parent = bpy.data.objects["LanternManager"] 48 | 49 | addProps(obj, "Id", currentId+1) 50 | 51 | return {'FINISHED'} 52 | 53 | class vic_procedural_lantern_connect(bpy.types.Operator): 54 | bl_idname = "vic.vic_procedural_lantern_connect" 55 | bl_label = "Create Connect" 56 | 57 | def execute(self, context): 58 | bpy.ops.vic.vic_procedural_lantern_manager() 59 | objs = [o for o in getSelectedWithOrder() if "ProxyData" in o.name] 60 | 61 | if len(objs) >= 2: 62 | connectIds = [str(o["Id"]) for o in objs] 63 | 64 | bpy.ops.object.empty_add(type='PLAIN_AXES', location=([0,0,0])) 65 | connectObj = bpy.context.object 66 | connectObj.parent = bpy.data.objects["LanternManager"] 67 | connectObj.name = "ConnectData" 68 | connectObj["Connect"] = "_".join(connectIds) 69 | connectObj["NGon Add"] = 0 70 | connectObj["Radius Scale"] = 1.0 71 | connectObj["Gravity Scale"] = 1.0 72 | connectObj["Segment Scale"] = 1.0 73 | connectObj["Lantern"] = 1 74 | connectObj["LanternPrefab Override"] = "" 75 | connectObj["LanternSegment Scale"] = 1.0 76 | connectObj["LanternRandomOffset Scale"] = 1.0 77 | 78 | currentConstraintId = 0 79 | for p in objs: 80 | bpy.ops.object.constraint_add(type='COPY_LOCATION') 81 | bpy.context.object.constraints[currentConstraintId].target = p 82 | bpy.context.object.constraints[currentConstraintId].influence = 0.5 83 | currentConstraintId += 1 84 | 85 | bpy.ops.vic.vic_procedural_lantern() 86 | return {'FINISHED'} 87 | 88 | class vic_procedural_lantern(bpy.types.Operator): 89 | bl_idname = "vic.vic_procedural_lantern" 90 | bl_label = "Update" 91 | 92 | def execute(self, context): 93 | self.updateMesh() 94 | return {'FINISHED'} 95 | 96 | def getCurve(self, index, segment, gravity): 97 | return (1-pow((abs(index - segment/2)/(segment/2)), 2)) * gravity 98 | 99 | def getSegment(self, start, end, seg, gravity, randomOffset = 0): 100 | dir = end - start 101 | segpoint = [] 102 | for i in range(seg): 103 | offsetI = i + random.random() * randomOffset 104 | gravityEffect = Vector((0,0,-self.getCurve(offsetI, seg, gravity))) 105 | pos = offsetI * dir / seg + start + gravityEffect 106 | segpoint.append(pos) 107 | segpoint.append(end) 108 | return segpoint 109 | 110 | def updateMesh(self): 111 | 112 | if "LanternManager" not in bpy.data.objects.keys(): 113 | return 114 | 115 | current_focus = None 116 | if bpy.context.object and "Ropes" not in bpy.context.object.name: 117 | current_focus = bpy.context.object 118 | 119 | mats = None 120 | if "Ropes" in bpy.data.objects.keys(): 121 | mats = bpy.data.objects["Ropes"].data.materials 122 | for o in [bpy.data.objects["Ropes"]] + list(bpy.data.objects["Ropes"].children): 123 | focusObject(o) 124 | bpy.ops.object.delete() 125 | 126 | if current_focus: focusObject(current_focus) 127 | 128 | lanternManager = bpy.data.objects["LanternManager"] 129 | rope = lanternManager["Rope"] 130 | segment = lanternManager["Segment"] 131 | gravity = lanternManager["Gravity"] 132 | radius = lanternManager["Radius"] 133 | ngon = lanternManager["NGon"] 134 | lantern = lanternManager["Lantern"] 135 | prefab = lanternManager["LanternPrefab"] 136 | lanternSegment = lanternManager["LanternSegment"] 137 | lanternRandomOffset = lanternManager["LanternRandomOffset"] 138 | 139 | creator = prepareAndCreateMesh("Ropes") 140 | obj = creator["obj"] 141 | update = creator["update"] 142 | addRectVertex = creator["addRectVertex"] 143 | 144 | lanterns = [] 145 | connects = [o for o in bpy.data.objects if "ConnectData" in o.name] 146 | proxys = [o for o in bpy.data.objects if "ProxyData" in o.name] 147 | for connect in connects: 148 | idstr = connect["Connect"] 149 | ngonAdd = connect["NGon Add"] 150 | radiusScale = connect["Radius Scale"] 151 | gravityScale = connect["Gravity Scale"] 152 | segmentScale = connect["Segment Scale"] 153 | lenternSelf = connect["Lantern"] 154 | lanternPrefabOverride = connect["LanternPrefab Override"] 155 | lanternSegmentScale = connect["LanternSegment Scale"] 156 | lanternRandomOffsetScale = connect["LanternRandomOffset Scale"] 157 | 158 | cngon = ngon + ngonAdd 159 | cngon = max(cngon, 3) 160 | 161 | cradius = radius * radiusScale 162 | cradius = max(cradius, 0.01) 163 | 164 | cgravity = gravity * gravityScale 165 | 166 | csegment = segment * segmentScale 167 | csegment = max(csegment, .01) 168 | 169 | clantern = lantern and lenternSelf 170 | 171 | cprefab = lanternPrefabOverride if lanternPrefabOverride != "" else prefab 172 | 173 | clanternSegment = lanternSegment * lanternSegmentScale 174 | 175 | clanternRandomOffset = lanternRandomOffset * lanternRandomOffsetScale 176 | 177 | # 繩子的橫截面的點的坐標 178 | shape = [] 179 | for i in range(cngon): 180 | radian = (2 * math.pi) * i / cngon 181 | shape.append((0, math.cos(radian)*cradius,math.sin(radian)*cradius)) 182 | shape.append(shape[0]) 183 | 184 | # 依照所選的順序收集點 185 | proxyWithOrder = [] 186 | ids = idstr.split("_") 187 | for id in ids: 188 | for p in proxys: 189 | if p["Id"] == int(id): 190 | proxyWithOrder.append(p) 191 | continue 192 | 193 | # 按照順序來產生繩子 194 | for i in range(len(proxyWithOrder)-1): 195 | p = proxyWithOrder[i] 196 | nextP = proxyWithOrder[i+1] 197 | 198 | # 全域坐標只能從世界矩陣來取 199 | pWorldLocation = Vector((p.matrix_world[0][3], p.matrix_world[1][3], p.matrix_world[2][3])) 200 | nextPWorldLocation = Vector((nextP.matrix_world[0][3], nextP.matrix_world[1][3], nextP.matrix_world[2][3])) 201 | 202 | # 取得方向 203 | dir = nextPWorldLocation - pWorldLocation 204 | 205 | # 取得總長 206 | dist = dir.length 207 | if rope: 208 | 209 | # 每一小段的長度,不能超過總長 210 | segLength = min(csegment, dist ) 211 | 212 | # 總共有幾段 213 | seg = round(dist / segLength) 214 | 215 | # 算出兩個端點之間的路徑 216 | segpoint = self.getSegment(pWorldLocation, nextPWorldLocation, seg, cgravity) 217 | 218 | # 算出路徑當中每一個點的方向 219 | rotmats = [] 220 | for i in range(len(segpoint)-1): 221 | p = segpoint[i] 222 | np = segpoint[i+1] 223 | forward = np - p 224 | forward.normalize() 225 | right = forward.cross(Vector((0,0,1))) 226 | right.normalize() 227 | up = right.cross(forward) 228 | up.normalize() 229 | rotmat = Matrix(( 230 | (forward.x, right.x, up.x, p.x), 231 | (forward.y, right.y, up.y, p.y), 232 | (forward.z, right.z, up.z, p.z), 233 | (0,0,0,1) 234 | )) 235 | rotmats.append(rotmat) 236 | 237 | # 產生所有的點 238 | for i, rotmat in enumerate(rotmats): 239 | 240 | # for debug 241 | # bpy.ops.object.empty_add(type='ARROWS') 242 | # bpy.context.object.matrix_world = rotmat 243 | # bpy.context.object.name = "Ropes" 244 | 245 | # 用橫截面加上矩陣來產生所有的點(當前的點) 246 | shapeOffset = [] 247 | for pos in shape: 248 | pos = Vector(pos) 249 | pos = rotmat @ pos 250 | shapeOffset.append((pos.x, pos.y, pos.z)) 251 | 252 | # 用橫截面加上矩陣來產生所有的點(下一點) 253 | nextShapeOffset = [] 254 | if i == len(rotmats)-1: 255 | 256 | # 如果當前的點是最後一個點,他的下一點就是端點,取nextPWorldLocation 257 | for pos in shapeOffset: 258 | pos = Vector(pos) 259 | pos += nextPWorldLocation - Vector((rotmat[0][3], rotmat[1][3], rotmat[2][3])) 260 | nextShapeOffset.append((pos.x, pos.y, pos.z)) 261 | else: 262 | 263 | # 如果當前的點不是最後一點,就取下一點。i+1 264 | for pos in shape: 265 | pos = Vector(pos) 266 | pos = rotmats[i+1] @ pos 267 | nextShapeOffset.append((pos.x, pos.y, pos.z)) 268 | 269 | # 收集所有的點及uv 270 | for i in range(len(shapeOffset)-1): 271 | segheight = (1 / (len(shapeOffset)-1)) 272 | uvy = segheight * i 273 | uvheight = uvy + segheight 274 | addRectVertex( 275 | [shapeOffset[i+1], shapeOffset[i], nextShapeOffset[i], nextShapeOffset[i+1]], [(0,uvheight), (0,uvy), (1,uvy), (1,uvheight)] 276 | ) 277 | 278 | if clantern and cprefab != "" and (cprefab in bpy.data.objects.keys()): 279 | childCount = len(bpy.data.objects[cprefab].children) 280 | 281 | # 每一小段的長度,不能超過總長 282 | segLength = min(clanternSegment, dist ) 283 | 284 | # 總共有幾段 285 | seg = round(dist / segLength) 286 | lenternPoint = self.getSegment(pWorldLocation, nextPWorldLocation, seg, cgravity, clanternRandomOffset)[1:-1] 287 | for i, p in enumerate(lenternPoint): 288 | # 實時預覽時不要產生真的燈籠,節省效能 289 | if bpy.context.window_manager.vic_procedural_lantern_live: 290 | copyFrom = bpy.data.objects["LanternManager"] 291 | else: 292 | 293 | if childCount == 0: 294 | copyFrom = bpy.data.objects[cprefab] 295 | else: 296 | copyId = random.randint(0, childCount-1) 297 | copyFrom = bpy.data.objects[cprefab].children[copyId] 298 | newcopy = copyToScene(copyFrom, True) 299 | # newcopy.display_type = "BOUNDS" 300 | 301 | # 確保複製出來的物件不會打亂小孩的順序 302 | newcopy.parent = None 303 | 304 | newcopy.location.x = p.x 305 | newcopy.location.y = p.y 306 | newcopy.location.z = p.z 307 | lanterns.append(newcopy) 308 | 309 | update() 310 | if rope: 311 | if mats is not None: 312 | for mat in mats: 313 | obj.data.materials.append(mat) 314 | 315 | for lantern in lanterns: 316 | lantern.parent = bpy.data.objects["Ropes"] 317 | lanterns = None 318 | 319 | # 非實時預覽時,執行完就合并多餘的點。實時預覽時,等結束預覽再合并 320 | if not bpy.context.window_manager.vic_procedural_lantern_live: 321 | finishEdit() 322 | 323 | 324 | 325 | def finishEdit(): 326 | if "Ropes" in bpy.data.objects.keys(): 327 | obj = bpy.data.objects["Ropes"] 328 | obj.select_set(True) 329 | activeObject(obj) 330 | bpy.ops.object.editmode_toggle() 331 | bpy.ops.mesh.select_all(action='SELECT') 332 | bpy.ops.mesh.remove_doubles() 333 | bpy.ops.mesh.faces_shade_smooth() 334 | bpy.ops.object.editmode_toggle() 335 | bpy.ops.object.select_all(action='DESELECT') 336 | 337 | if "LanternProxy" in bpy.data.objects.keys(): 338 | bpy.ops.object.delete({"selected_objects": [bpy.data.objects["LanternProxy"]] + list(bpy.data.objects["LanternProxy"].children)}) 339 | 340 | def updateMesh(scene): 341 | bpy.ops.vic.vic_procedural_lantern() 342 | 343 | def invokeLiveEdit(self, context): 344 | if context.window_manager.vic_procedural_lantern_live: 345 | bpy.ops.screen.animation_play() 346 | if updateMesh in bpy.app.handlers.frame_change_post: 347 | bpy.app.handlers.frame_change_post.remove(updateMesh) 348 | bpy.app.handlers.frame_change_post.append(updateMesh) 349 | else: 350 | bpy.ops.screen.animation_cancel() 351 | bpy.app.handlers.frame_change_post.remove(updateMesh) 352 | updateMesh(None) 353 | 354 | bpy.types.WindowManager.vic_procedural_lantern_live = bpy.props.BoolProperty( 355 | default = False, 356 | update = invokeLiveEdit) 357 | 358 | 359 | class vic_procedural_lantern_live_panel(bpy.types.Panel): 360 | bl_category = "Vic Addons" 361 | bl_space_type = "VIEW_3D" 362 | bl_region_type = "UI" 363 | bl_label = "Procedrual Lantern" 364 | 365 | def draw(self, context): 366 | layout = self.layout 367 | 368 | col = layout.column(align=True) 369 | col.label(text='Lantern Generator') 370 | col.operator(vic_procedural_lantern_proxy.bl_idname) 371 | col.operator(vic_procedural_lantern_connect.bl_idname) 372 | col.operator(vic_procedural_lantern.bl_idname) 373 | 374 | col.prop(context.window_manager, 'vic_procedural_lantern_live', text="Live Edit", toggle=True, icon="EDITMODE_HLT") 375 | -------------------------------------------------------------------------------- /vicAddons/proceduralSplineStair.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from .vic_tools import * 3 | from mathutils import * 4 | from math import * 5 | 6 | caches = { 7 | "curve":None, 8 | "obj":None, 9 | "obj_materials":None, 10 | "update":None, 11 | "clear":None, 12 | "addRectVertex":None, 13 | "pilePoints":None, 14 | "pssProxyPool":[], 15 | "register":False 16 | } 17 | 18 | def onSceneUpdate(): 19 | if not checkAndToggle(bpy.context): 20 | return 21 | curve = bpy.context.object 22 | if "Width" not in curve: return 23 | 24 | bpy.context.window_manager.vic_procedural_stair_update_width = curve["Width"] 25 | bpy.context.window_manager.vic_procedural_stair_update_wall_inner_distance = curve["Wall_Inner_Distance"] 26 | bpy.context.window_manager.vic_procedural_stair_update_pile_per_step = curve["Pile_Per_Step"] 27 | bpy.context.window_manager.vic_procedural_stair_update_pile_z = curve["Pile_Z"] 28 | bpy.context.window_manager.vic_procedural_stair_update_step = curve["Step"] 29 | bpy.context.window_manager.vic_procedural_stair_update_step_threshold = curve["Step_Threshold"] 30 | bpy.context.window_manager.vic_procedural_stair_update_ground = curve["Ground"] 31 | bpy.context.window_manager.vic_procedural_stair_update_onGround = curve["OnGround"] 32 | bpy.context.window_manager.vic_procedural_stair_update_pile = curve["Pile"] 33 | bpy.context.window_manager.vic_procedural_stair_update_wall = curve["Wall"] 34 | 35 | def createStairManager(): 36 | for obj in bpy.data.objects: 37 | if obj.name == "StairManager": 38 | return 39 | bpy.ops.object.empty_add(type='CUBE', location=([0,0,0])) 40 | mgrObj = bpy.context.object 41 | mgrObj.name = "StairManager" 42 | 43 | def createStairProxy(isLive = False): 44 | 45 | curve = caches["curve"] 46 | update = caches["update"] 47 | clear = caches["clear"] 48 | addRectVertex = caches["addRectVertex"] 49 | 50 | curve["Width"] = bpy.context.window_manager.vic_procedural_stair_update_width 51 | curve["Wall_Inner_Distance"] = bpy.context.window_manager.vic_procedural_stair_update_wall_inner_distance 52 | curve["Pile_Per_Step"] = bpy.context.window_manager.vic_procedural_stair_update_pile_per_step 53 | curve["Pile_Z"] = bpy.context.window_manager.vic_procedural_stair_update_pile_z 54 | curve["Step"] = bpy.context.window_manager.vic_procedural_stair_update_step 55 | curve["Step_Threshold"] = bpy.context.window_manager.vic_procedural_stair_update_step_threshold 56 | curve["Ground"] = bpy.context.window_manager.vic_procedural_stair_update_ground 57 | curve["OnGround"] = bpy.context.window_manager.vic_procedural_stair_update_onGround 58 | 59 | width = curve["Width"] 60 | wall_inner_distance = curve["Wall_Inner_Distance"] 61 | pile_per_step = curve["Pile_Per_Step"] 62 | pile_z = curve["Pile_Z"] 63 | step = curve["Step"] 64 | step_threshold = curve["Step_Threshold"] 65 | ground = curve["Ground"] 66 | onGround = curve["OnGround"] 67 | uv_scale = .1 68 | 69 | if width <= 0 or step <= 0: return 70 | 71 | length, matrices = getCurvePosAndLength(curve, step) 72 | if length == 0: return 73 | 74 | stepLength = length / step 75 | 76 | # reset mesh data 77 | clear() 78 | pilePoints = [] 79 | 80 | # 前一對點 81 | last_pts = None 82 | 83 | # 前一個階梯的高度 84 | last_height = 0 85 | 86 | # 前一次處理有沒有形成階梯 87 | last_isStep = False 88 | 89 | # 記錄前一次處理的左邊墻面的前進距離 90 | uv_last_left_x = 0 91 | 92 | # 記錄前一次處理的右邊墻面的前進距離 93 | uv_last_right_x = 0 94 | 95 | # 記錄前一次處理的梯面的前進距離 96 | uv_last_step_x = 0 97 | 98 | # 記錄前一次處理的底部的前進距離 99 | uv_last_bottom_x = 0 100 | 101 | # 樓梯面的點位置 102 | pts = (Vector((0,width/2,0)), Vector((0,-width/2,0))) 103 | 104 | # 柱子的位置 105 | pile_pts = (Vector((0,width/2 - wall_inner_distance,0)), Vector((0,-width/2 + wall_inner_distance,0))) 106 | 107 | # 開始計算 108 | for i, mat in enumerate(matrices): 109 | curr_pts = [] 110 | pile_mats = [] 111 | for j, pt in enumerate(pts): 112 | 113 | # 只留下水平的旋轉(yaw),需要單位化,不然轉成矩陣的時候,會有拉扯 114 | hori_quat = mat.to_quaternion() 115 | hori_quat.x = hori_quat.y = 0 116 | hori_quat.normalize() 117 | 118 | # 把處理好的旋轉矩陣乘上位置矩陣,形成新的4x4矩陣 119 | hori_mat = hori_quat.to_matrix().to_4x4() 120 | hori_mat = Matrix.Translation(mat.to_translation()) @ hori_mat 121 | pos = hori_mat @ pt 122 | curr_pts.append(pos) 123 | 124 | # 檢查是否需要生成柱子 125 | is_per = (i % pile_per_step == 0) 126 | is_first = (i == 1) 127 | is_last = (i == len(matrices)-1) 128 | 129 | # 需要有前一次處理的資料才能開始計算柱子的真正位置 130 | if last_pts and (is_per or is_first or is_last): 131 | pile_pos = hori_mat @ pile_pts[j] 132 | last_pt = last_pts[j] 133 | offset_pt = (pile_pos + last_pt) / 2 134 | offset_pt.z = last_pt.z + pile_z 135 | pile_mat = Matrix.Translation(offset_pt) @ hori_quat.to_matrix().to_4x4() 136 | pile_mats.append(pile_mat) 137 | 138 | # 記錄下所有柱子的位置,之後生成用 139 | pilePoints += pile_mats 140 | 141 | # 第二次處理時才有足夠的資訊來計算 142 | if i > 0: 143 | step_pt1 = curr_pts[1].copy() 144 | step_pt0 = curr_pts[0].copy() 145 | 146 | current_isStep = False 147 | 148 | # 如果階梯間的高度超過設定的閾值,就產生階梯 149 | current_height = curr_pts[0].z - last_pts[0].z 150 | if abs(current_height) > step_threshold: 151 | current_isStep = True 152 | 153 | # 注意這邊的設定也會影響樓梯面的生成 154 | step_pt1.z = last_pts[1].z 155 | step_pt0.z = last_pts[0].z 156 | 157 | # 階梯間的垂直面 158 | addRectVertex((step_pt0,curr_pts[0], curr_pts[1], step_pt1), ((0,uv_last_step_x+stepLength),(0,uv_last_step_x+stepLength+abs(current_height)),(width,uv_last_step_x+stepLength+abs(current_height)),(width,uv_last_step_x+stepLength)), uv_scale) 159 | 160 | # 樓梯面 161 | addRectVertex((last_pts[0],last_pts[1], step_pt1, step_pt0), ((0,uv_last_step_x),(width,uv_last_step_x),(width,uv_last_step_x+stepLength),(0,uv_last_step_x+stepLength)), uv_scale) 162 | 163 | # 墻面接近地面的點 164 | side_pt0 = last_pts[0].copy() 165 | side_pt1 = curr_pts[0].copy() 166 | side_pt2 = last_pts[1].copy() 167 | side_pt3 = curr_pts[1].copy() 168 | 169 | # 如果設定由地面升起的話,直接設定地面高度為點的高度 170 | if onGround: 171 | side_pt0.z = ground 172 | side_pt1.z = ground 173 | side_pt2.z = ground 174 | side_pt3.z = ground 175 | else: 176 | if current_height > 0: 177 | if last_isStep: 178 | side_pt0.z -= last_height 179 | side_pt2.z -= last_height 180 | 181 | if current_isStep: 182 | side_pt1.z -= current_height 183 | side_pt3.z -= current_height 184 | 185 | side_pt0.z += ground 186 | side_pt1.z += ground 187 | side_pt2.z += ground 188 | side_pt3.z += ground 189 | else: 190 | side_pt0.z += ground 191 | side_pt1.z += ground 192 | side_pt2.z += ground 193 | side_pt3.z += ground 194 | 195 | # 記錄左右墻面的uv坐標,每一個坐標都對應生成模型的點 196 | uv_curr_left_x = (side_pt1 - side_pt0) 197 | uv_curr_left_x.z = 0 198 | uv_curr_left_x = uv_curr_left_x.length 199 | 200 | uv_curr_right_x = (side_pt3 - side_pt2) 201 | uv_curr_right_x.z = 0 202 | uv_curr_right_x = uv_curr_right_x.length 203 | 204 | uv_last_pts0 = (uv_last_left_x,last_pts[0].z) 205 | uv_last_pts1 = (uv_last_right_x,last_pts[1].z) 206 | uv_curr_pts0 = (uv_last_left_x+uv_curr_left_x,curr_pts[0].z) 207 | uv_curr_pts1 = (uv_last_right_x+uv_curr_right_x,curr_pts[1].z) 208 | 209 | uv_side_pt0 = (uv_last_left_x,side_pt0.z) 210 | uv_side_pt1 = (uv_last_left_x+uv_curr_left_x,side_pt1.z) 211 | uv_side_pt2 = (uv_last_right_x,side_pt2.z) 212 | uv_side_pt3 = (uv_last_right_x+uv_curr_right_x,side_pt3.z) 213 | 214 | uv_step_pt0 = (uv_last_left_x+uv_curr_left_x, step_pt0.z) 215 | uv_step_pt1 = (uv_last_right_x+uv_curr_right_x, step_pt1.z) 216 | 217 | # 樓梯左側面,對目前的階梯是往上或是往下的處理也不一樣 218 | if current_height < 0: 219 | addRectVertex((last_pts[0],step_pt0, curr_pts[0]), (uv_last_pts0,uv_step_pt0,uv_curr_pts0), uv_scale) 220 | addRectVertex((last_pts[0],curr_pts[0], side_pt1, side_pt0), (uv_last_pts0,uv_curr_pts0,uv_side_pt1,uv_side_pt0), uv_scale) 221 | else: 222 | 223 | # 上一次處理時是不是階梯對這次的處理不一樣 224 | if last_isStep: 225 | step_connect_pt = last_pts[0] + Vector((0,0,-last_height)) 226 | else: 227 | step_connect_pt = last_pts[0] 228 | 229 | uv_step_connect_pt = (uv_last_left_x, step_connect_pt.z) 230 | addRectVertex((last_pts[0],step_pt0, step_connect_pt), (uv_last_pts0,uv_step_pt0,uv_step_connect_pt), uv_scale) 231 | addRectVertex((step_connect_pt, step_pt0, side_pt1, side_pt0), (uv_step_connect_pt,uv_step_pt0,uv_side_pt1,uv_side_pt0), uv_scale) 232 | 233 | # 樓梯右側面,對目前的階梯是往上或是往下的處理也不一樣 234 | if current_height < 0: 235 | addRectVertex((last_pts[1],step_pt1, curr_pts[1]), (uv_last_pts1,uv_step_pt1,uv_curr_pts1), uv_scale) 236 | addRectVertex((last_pts[1],curr_pts[1], side_pt3, side_pt2), (uv_last_pts1,uv_curr_pts1,uv_side_pt3,uv_side_pt2), uv_scale) 237 | else: 238 | 239 | # 上一次處理時是不是階梯對這次的處理不一樣 240 | if last_isStep: 241 | step_connect_pt = last_pts[1] + Vector((0,0,-last_height)) 242 | else: 243 | step_connect_pt = last_pts[1] 244 | 245 | uv_step_connect_pt = (uv_last_right_x, step_connect_pt.z) 246 | addRectVertex((last_pts[1],step_pt1, step_connect_pt), (uv_last_pts1,uv_step_pt1,uv_step_connect_pt), uv_scale) 247 | addRectVertex((step_connect_pt, step_pt1, side_pt3, side_pt2), (uv_step_connect_pt, uv_step_pt1, uv_side_pt3, uv_side_pt2), uv_scale) 248 | 249 | # 底部mesh 250 | addRectVertex((side_pt0, side_pt1, side_pt3, side_pt2), ((0,uv_last_bottom_x), (0, uv_last_bottom_x+stepLength),(width, uv_last_bottom_x+stepLength), (width, uv_last_bottom_x)), uv_scale) 251 | 252 | last_height = current_height 253 | last_isStep = current_isStep 254 | 255 | uv_last_left_x += uv_curr_left_x 256 | uv_last_right_x += uv_curr_right_x 257 | 258 | if current_isStep: 259 | uv_last_step_x += (stepLength + abs(current_height)) 260 | else: 261 | uv_last_step_x += stepLength 262 | uv_last_bottom_x += stepLength 263 | 264 | last_pts = curr_pts 265 | 266 | caches["pilePoints"] = pilePoints 267 | update() 268 | 269 | # 如果是實時模式,就不要直接創建柱子,只創建代理物件,增進效能。 270 | if isLive: 271 | 272 | for o in caches["pssProxyPool"]: o.hide_viewport = True 273 | 274 | curr_focus = bpy.context.object 275 | for i, pp in enumerate(pilePoints): 276 | 277 | # 這裏用物件池模式,進一步節約效能 278 | proxy = getPssProxyFromPool(i) 279 | proxy.matrix_world = pp 280 | proxy.hide_viewport = False 281 | if curr_focus: focusObject(curr_focus) 282 | 283 | def getPssProxyFromPool(i): 284 | pssProxyPool = caches["pssProxyPool"] 285 | if i < len(pssProxyPool) - 1: return pssProxyPool[i] 286 | bpy.ops.object.empty_add(type='ARROWS') 287 | proxy = bpy.context.object 288 | proxy.name = "auto_build_please_ignore" 289 | pssProxyPool.append(proxy) 290 | return proxy 291 | 292 | def checkAndToggle(ctx): 293 | if not ctx.object or ctx.object.type != 'CURVE': 294 | return False 295 | if ctx.mode != 'OBJECT': bpy.ops.object.editmode_toggle() 296 | return True 297 | 298 | class vic_procedural_stair_update(bpy.types.Operator): 299 | bl_idname = "vic.vic_procedural_stair_update" 300 | bl_label = "Create & Update" 301 | bl_description='Please Select One Spline With Object Mode' 302 | bl_options = {'REGISTER', 'UNDO'} 303 | 304 | def execute(self, context): 305 | 306 | if not checkAndToggle(context): 307 | self.report({'INFO'}, "Please select at least one CURVE object.") 308 | return {'FINISHED'} 309 | currentFocus = context.object 310 | startEdit() 311 | createStairProxy() 312 | endEdit() 313 | focusObject(currentFocus) 314 | return {'FINISHED'} 315 | 316 | def startEdit(): 317 | ctx = bpy.context 318 | if not ctx.object or ctx.object.type != 'CURVE': return 319 | 320 | if not caches["register"]: 321 | subscribe_to = bpy.types.LayerObjects, "active" 322 | bpy.msgbus.subscribe_rna( 323 | key = subscribe_to, 324 | owner = bpy.context.scene, 325 | args = (), 326 | notify = onSceneUpdate 327 | ) 328 | caches["register"] = True 329 | onSceneUpdate() 330 | 331 | caches["shading"] = bpy.context.space_data.shading.type 332 | bpy.context.space_data.shading.type = 'WIREFRAME' 333 | 334 | curve = ctx.object 335 | caches["curve"] = curve 336 | 337 | removeMeshs() 338 | 339 | creator = prepareAndCreateMesh(curve.name + "_step") 340 | 341 | obj = creator["obj"] 342 | update = creator["update"] 343 | clear = creator["clear"] 344 | addRectVertex = creator["addRectVertex"] 345 | 346 | caches["obj"] = obj 347 | caches["update"] = update 348 | caches["addRectVertex"] = addRectVertex 349 | caches["clear"] = clear 350 | caches["pilePoints"] = [] 351 | 352 | addProps(curve, "Pile", bpy.context.window_manager.vic_procedural_stair_update_pile, True) 353 | addProps(curve, "Wall", bpy.context.window_manager.vic_procedural_stair_update_wall, True) 354 | addProps(curve, "Width", bpy.context.window_manager.vic_procedural_stair_update_width, True) 355 | addProps(curve, "Wall_Inner_Distance", bpy.context.window_manager.vic_procedural_stair_update_wall_inner_distance, True) 356 | addProps(curve, "Pile_Per_Step", bpy.context.window_manager.vic_procedural_stair_update_pile_per_step, True) 357 | addProps(curve, "Pile_Z", bpy.context.window_manager.vic_procedural_stair_update_pile_z, True) 358 | addProps(curve, "Step", bpy.context.window_manager.vic_procedural_stair_update_step, True) 359 | addProps(curve, "Step_Threshold", bpy.context.window_manager.vic_procedural_stair_update_step_threshold, True) 360 | addProps(curve, "OnGround", bpy.context.window_manager.vic_procedural_stair_update_onGround, True) 361 | addProps(curve, "Ground", bpy.context.window_manager.vic_procedural_stair_update_ground, True) 362 | 363 | def endEdit(): 364 | curve = caches["curve"] 365 | if curve.name not in bpy.data.objects.keys(): return 366 | 367 | createWallAndPiles() 368 | 369 | smooth_list = [] 370 | step_mesh = curve.name + "_step" 371 | if step_mesh in bpy.data.objects.keys(): smooth_list.append( step_mesh ) 372 | 373 | if caches["obj_materials"]: 374 | obj_materials = caches["obj_materials"] 375 | for mat in obj_materials: bpy.data.objects[step_mesh].data.materials.append(mat) 376 | 377 | wall_mesh = curve.name + "_wall" 378 | if wall_mesh in bpy.data.objects.keys(): smooth_list.append( wall_mesh ) 379 | 380 | for smooth in smooth_list: 381 | obj = bpy.data.objects[smooth] 382 | obj.select_set(True) 383 | activeObject(obj) 384 | bpy.ops.object.editmode_toggle() 385 | bpy.ops.mesh.select_all(action='SELECT') 386 | bpy.ops.mesh.remove_doubles() 387 | bpy.ops.mesh.normals_make_consistent(inside=False) 388 | bpy.ops.object.editmode_toggle() 389 | bpy.ops.object.select_all(action='DESELECT') 390 | 391 | # 確認對位用的代理物件被清除乾净 392 | for o in caches["pssProxyPool"]: o.hide_viewport = False 393 | removeObjects([o for o in bpy.data.objects if "auto_build_please_ignore" in o.name]) 394 | caches["pssProxyPool"] = [] 395 | 396 | bpy.context.space_data.shading.type = caches["shading"] 397 | 398 | def createWallAndPiles(): 399 | 400 | curve = caches["curve"] 401 | pile = curve["Pile"] 402 | if pile == "" or pile not in bpy.data.objects: return 403 | 404 | pilePoints = caches["pilePoints"] 405 | parent = caches["obj"] 406 | 407 | copyFrom = bpy.data.objects[pile] 408 | 409 | for pp in pilePoints: 410 | pileobj = copyObject(copyFrom, True) 411 | pileobj.matrix_world = pp 412 | pileobj.name = curve.name + "_pile" 413 | pileobj.parent = parent 414 | addObject(pileobj) 415 | 416 | wall = curve["Wall"] 417 | if wall == "" or wall not in bpy.data.objects: return 418 | 419 | 420 | creator = prepareAndCreateMesh(curve.name + "_wall") 421 | obj = creator["obj"] 422 | update = creator["update"] 423 | addVertexByMesh = creator["addVertexByMesh"] 424 | 425 | leftside_pts = pilePoints[::2] 426 | rightside_pts = pilePoints[1::2] 427 | 428 | copyFrom = bpy.data.objects[wall] 429 | copyFrom_length = copyFrom.dimensions.x 430 | 431 | materials = copyFrom.data.materials 432 | 433 | def createWall(curr_pp, prev_pp): 434 | 435 | def transferVertex(vid, v): 436 | curr_pp_pos = curr_pp.to_translation() 437 | prev_pp_pos = prev_pp.to_translation() 438 | diff = curr_pp_pos - prev_pp_pos 439 | direct = diff.normalized() 440 | 441 | proj_dir = direct.copy() 442 | proj_dir.z = 0 443 | proj_dir.normalize() 444 | 445 | # 確認角度方向,先把方向正規化,再檢查y是正的還是負的,已此來確認兩個向量取角度時,順序是一致的 446 | # 取得當前方向的水平旋轉矩陣 447 | rot_mat = Matrix.Rotation(atan2(proj_dir.y, proj_dir.x), 4, 'Z') 448 | 449 | # 以旋轉矩陣而言,倒置矩陣等於逆矩陣。這裏取得反方向的水平旋轉矩陣 450 | rot_mat.transpose() # rot_mat.inverse() 意思一樣 451 | 452 | # 用叉乘取得兩個向量的垂直向量,代表y軸。這個時候因爲我們不能確定取得的垂直向量都能永遠朝同一個方向,所以有下一步 453 | side_dir = direct.cross(proj_dir) 454 | side_dir.normalize() 455 | 456 | # 把side方向乘上【反方向的水平旋轉矩陣】,就可以把他轉回正規化的方向(0,1,0) or (0,-1,0) 457 | side_dir = rot_mat @ side_dir 458 | 459 | # 有了正規化的方向后,就可以依次來確保取角度的時候,不會取到相反的角度 460 | # angle 等於 acos(direct.dot(proj_dir)),但是這樣算會有不合法參數的問題,改成用内建的算法 461 | pitch = direct.angle(proj_dir) * side_dir.y 462 | 463 | hori_diff = diff.copy() 464 | hori_diff.z = 0 465 | scale_width = hori_diff.length / copyFrom_length 466 | 467 | skew = Vector((v.co.x, v.co.y, v.co.z)) 468 | skew.x *= scale_width 469 | skew.z += tan(pitch) * skew.x 470 | 471 | pos_mat = Matrix.Translation((curr_pp_pos + prev_pp_pos) / 2) 472 | rot_mat = Matrix.Rotation(atan2(direct.y, direct.x), 4, 'Z') 473 | new_mat = pos_mat @ rot_mat 474 | return new_mat @ skew 475 | 476 | addVertexByMesh(copyFrom, 0, transferVertex) 477 | 478 | for i, curr_pp in enumerate(leftside_pts): 479 | if i == 0: continue 480 | prev_pp = leftside_pts[i-1] 481 | createWall(curr_pp, prev_pp) 482 | 483 | for i, curr_pp in enumerate(rightside_pts): 484 | if i == 0: continue 485 | prev_pp = rightside_pts[i-1] 486 | createWall(curr_pp, prev_pp) 487 | 488 | update() 489 | 490 | # 上材質 491 | for mat in materials: obj.data.materials.append(mat) 492 | 493 | def removeMeshs(): 494 | curve = caches["curve"] 495 | 496 | # 刪掉模型前先把他的材質記下來。等到創建模型的時候再給上 497 | if curve.name + "_step" in bpy.data.objects.keys(): 498 | bpy.data.objects[curve.name + "_step"].hide_viewport = False 499 | materials = bpy.data.objects[curve.name + "_step"].data.materials 500 | caches["obj_materials"] = materials 501 | 502 | removeObjects([o for o in bpy.data.objects if curve.name + "_pile" in o.name]) 503 | removeObjects([o for o in bpy.data.objects if curve.name + "_wall" in o.name]) 504 | removeObjects([o for o in bpy.data.objects if curve.name + "_step" in o.name]) 505 | 506 | def updateMesh(scene): 507 | createStairProxy(True) 508 | 509 | def invokeLiveEdit(self, context): 510 | 511 | if context.window_manager.vic_procedural_stair_update_live: 512 | 513 | if not checkAndToggle(context): 514 | context.window_manager.vic_procedural_stair_update_live = False 515 | return 516 | 517 | startEdit() 518 | bpy.ops.screen.animation_play() 519 | if updateMesh in bpy.app.handlers.frame_change_post: 520 | bpy.app.handlers.frame_change_post.remove(updateMesh) 521 | bpy.app.handlers.frame_change_post.append(updateMesh) 522 | else: 523 | 524 | checkAndToggle(context) 525 | 526 | curr_focus = bpy.context.object 527 | 528 | bpy.ops.screen.animation_cancel() 529 | if updateMesh in bpy.app.handlers.frame_change_post: 530 | bpy.app.handlers.frame_change_post.remove(updateMesh) 531 | updateMesh(None) 532 | endEdit() 533 | 534 | if curr_focus: focusObject(curr_focus) 535 | 536 | 537 | bpy.types.WindowManager.vic_procedural_stair_update_live = bpy.props.BoolProperty( 538 | default = False, 539 | update = invokeLiveEdit, 540 | description='Please Select One Spline With Object Mode') 541 | 542 | bpy.types.WindowManager.vic_procedural_stair_update_onGround = bpy.props.BoolProperty( 543 | default = False, 544 | description='Whether the bottom is close to the ground') 545 | 546 | bpy.types.WindowManager.vic_procedural_stair_update_width = bpy.props.FloatProperty( 547 | name='Width', 548 | default=.5, 549 | min=0.01, 550 | description='The width of the stairs') 551 | 552 | bpy.types.WindowManager.vic_procedural_stair_update_wall_inner_distance = bpy.props.FloatProperty( 553 | name='Wall Inner Distance', 554 | default=.1, 555 | description='Inner distance of wall and pillar') 556 | 557 | bpy.types.WindowManager.vic_procedural_stair_update_pile_per_step = bpy.props.IntProperty( 558 | name='Pile Per Step', 559 | default=5, 560 | min=1, 561 | step=1, 562 | description='Every few steps will produce a pillar') 563 | 564 | bpy.types.WindowManager.vic_procedural_stair_update_pile_z = bpy.props.FloatProperty( 565 | name='Pile Z', 566 | default=0, 567 | description='Height of wall and pillar') 568 | 569 | bpy.types.WindowManager.vic_procedural_stair_update_step_threshold = bpy.props.FloatProperty( 570 | name='Step Threshold', 571 | default=0.01, 572 | min=0.0, 573 | description='Steps will be generated when the height between nodes exceeds this height') 574 | 575 | bpy.types.WindowManager.vic_procedural_stair_update_step = bpy.props.IntProperty( 576 | name='Step', 577 | default=20, 578 | min=2, 579 | step=1, 580 | description='Number of steps') 581 | 582 | bpy.types.WindowManager.vic_procedural_stair_update_ground = bpy.props.FloatProperty( 583 | name='Ground', 584 | default=-1, 585 | description='The thickness of the stairs, if it is a pattern that is close to the ground, this parameter is the height of the ground') 586 | 587 | bpy.types.WindowManager.vic_procedural_stair_update_pile = bpy.props.StringProperty( 588 | name='Pile', 589 | default="") 590 | 591 | bpy.types.WindowManager.vic_procedural_stair_update_wall = bpy.props.StringProperty( 592 | name='Wall', 593 | default="") 594 | 595 | class vic_procedural_stair_update_panel(bpy.types.Panel): 596 | bl_category = "Vic Addons" 597 | bl_space_type = "VIEW_3D" 598 | bl_region_type = "UI" 599 | bl_label = "Procedrual Spline Stair" 600 | 601 | def draw(self, context): 602 | layout = self.layout 603 | 604 | col = layout.column(align=True) 605 | col.operator(vic_procedural_stair_update.bl_idname) 606 | col.prop(context.window_manager, 'vic_procedural_stair_update_live', text="Live Edit", toggle=True, icon="EDITMODE_HLT") 607 | col = layout.column(align=True) 608 | col.prop(context.window_manager, 'vic_procedural_stair_update_width') 609 | col.prop(context.window_manager, 'vic_procedural_stair_update_wall_inner_distance') 610 | col.prop(context.window_manager, 'vic_procedural_stair_update_step') 611 | col.prop(context.window_manager, 'vic_procedural_stair_update_step_threshold') 612 | col.prop(context.window_manager, 'vic_procedural_stair_update_pile_per_step') 613 | col.prop(context.window_manager, 'vic_procedural_stair_update_pile_z') 614 | col.prop(context.window_manager, 'vic_procedural_stair_update_ground') 615 | col.prop(context.window_manager, 'vic_procedural_stair_update_onGround', text="On Ground") 616 | col = layout.column(align=True) 617 | col.prop_search(context.window_manager, "vic_procedural_stair_update_wall", bpy.data, "objects") 618 | col.prop_search(context.window_manager, "vic_procedural_stair_update_pile", bpy.data, "objects") 619 | 620 | 621 | 622 | 623 | 624 | 625 | 626 | -------------------------------------------------------------------------------- /vicAddons/proceduralStair.py: -------------------------------------------------------------------------------- 1 | import bpy, time 2 | from math import * 3 | from mathutils import Vector 4 | from bpy.props import ( 5 | BoolProperty, 6 | EnumProperty, 7 | FloatProperty, 8 | FloatVectorProperty, 9 | IntProperty, 10 | IntVectorProperty, 11 | StringProperty, 12 | PointerProperty 13 | ) 14 | from .vic_tools import ( 15 | addObject, 16 | activeObject, 17 | copyObject, 18 | focusObject, 19 | prepareAndCreateMesh 20 | ) 21 | 22 | class vic_procedural_stair(bpy.types.Operator): 23 | bl_idname = 'vic.vic_procedural_stair' 24 | bl_label = 'Stair Generator' 25 | bl_description = '' 26 | bl_options = {'REGISTER', 'UNDO'} 27 | 28 | width:FloatProperty( 29 | name='Width', 30 | min=1.0, 31 | default=3.0 32 | ) 33 | 34 | height:FloatProperty( 35 | name='Height', 36 | min=1.0, 37 | default=5.0 38 | ) 39 | 40 | wallHeight:FloatProperty( 41 | name='Wall Height', 42 | default=0.0 43 | ) 44 | 45 | wallOffsetY:FloatProperty( 46 | name='Wall OffsetY', 47 | default=0.5 48 | ) 49 | 50 | stairUvCenter:FloatProperty( 51 | name='UV Center', 52 | min=0.0, 53 | max=1.0, 54 | default=.5 55 | ) 56 | 57 | stairUvScaleX:FloatProperty( 58 | name='UV Scale X', 59 | default=1.0 60 | ) 61 | 62 | stairUvStep:IntProperty( 63 | name='UV Step', 64 | min=1, 65 | max=10, 66 | default=4 67 | ) 68 | 69 | stairSideUvScale:FloatProperty( 70 | name='Side UV Scale', 71 | default=1.0 72 | ) 73 | 74 | stairMaterial:StringProperty( 75 | name='Stair', 76 | default='StairMaterial' 77 | ) 78 | 79 | stairSideMaterial:StringProperty( 80 | name='Side', 81 | default='StairSideMaterial' 82 | ) 83 | 84 | count:IntProperty( 85 | name='Step Count', 86 | min=1, 87 | max=50, 88 | default=10 89 | ) 90 | 91 | stepDepth:FloatProperty( 92 | name='Step Depth', 93 | min=.1, 94 | default=1. 95 | ) 96 | 97 | showWall:BoolProperty( 98 | name='Show Wall', 99 | default=True 100 | ) 101 | 102 | editMode:BoolProperty( 103 | name='Edit Mode', 104 | default=False, 105 | description='Turn off this will combine all mesh to one' 106 | ) 107 | 108 | wallMesh:StringProperty( 109 | name='Pick Wall', 110 | description='', 111 | default='' 112 | ) 113 | 114 | pileMesh:StringProperty( 115 | name='Pick Pile', 116 | description='', 117 | default='' 118 | ) 119 | 120 | pileCount:IntProperty( 121 | name='Pile Per Step', 122 | min=1, 123 | default=4 124 | ) 125 | 126 | # def scene_mychosenobject_poll(self, object): 127 | # return object.type == 'CURVE' 128 | 129 | # wallMeshj = bpy.props.PointerProperty( 130 | # type=bpy.types.Object, 131 | # poll=scene_mychosenobject_poll 132 | # ) 133 | 134 | # def createPileMesh(self, mesh, pilePos, offsetY): 135 | # uvMap = {} 136 | # currentFaceId = len(self.faces) 137 | # currentVertId = len(self.verts) 138 | # for m in mesh.data.polygons: 139 | # vs = [] 140 | # currentVertexCount = len(self.verts) 141 | # for vid in m.vertices: 142 | # vs.append(vid + currentVertexCount) 143 | # self.faces.append(tuple(vs)) 144 | # self.matIds.append(m.material_index+3) 145 | 146 | # for vert_idx, loop_idx in zip(m.vertices, m.loop_indices): 147 | # self.uvsMap["%i_%i" % (m.index+currentFaceId,vert_idx+currentVertId)] = mesh.data.uv_layers.active.data[loop_idx].uv 148 | 149 | # for vid,v in enumerate(mesh.data.vertices): 150 | # pos = v.co 151 | # newpos = ( 152 | # pos.x + pilePos[0], 153 | # pos.y + offsetY, 154 | # pos.z + pilePos[2] 155 | # ) 156 | # self.verts.append( newpos ) 157 | 158 | # def createOneWall(self, mesh, scaleFactor, tanRadian, startPos, wallPos, offsetY): 159 | # currentFaceId = len(self.faces) 160 | # currentVertId = len(self.verts) 161 | # for m in mesh.data.polygons: 162 | # vs = [] 163 | # currentVertexCount = len(self.verts) 164 | # for vid in m.vertices: 165 | # vs.append(vid + currentVertexCount) 166 | # self.faces.append(tuple(vs)) 167 | # self.matIds.append(m.material_index+2) 168 | 169 | # for vert_idx, loop_idx in zip(m.vertices, m.loop_indices): 170 | # self.uvsMap["%i_%i" % (m.index+currentFaceId,vert_idx+currentVertId)] = mesh.data.uv_layers.active.data[loop_idx].uv 171 | 172 | # for vid,v in enumerate(mesh.data.vertices): 173 | # pos = v.co 174 | # newpos = ( 175 | # pos.x * scaleFactor + wallPos.x + startPos.x, 176 | # pos.y + wallPos.y + startPos.y + offsetY, 177 | # pos.z + tanRadian * (pos.x * scaleFactor) + wallPos.z + startPos.z + self.wallHeight 178 | # ) 179 | # self.verts.append( newpos ) 180 | 181 | def addMaterial(self, name): 182 | if not name in bpy.data.materials: 183 | bpy.data.materials.new(name=name) 184 | 185 | def assignMaterial(self, obj, wallMesh, pileMesh): 186 | self.addMaterial('StairMaterial') 187 | self.addMaterial('StairSideMaterial') 188 | if self.stairMaterial != '': 189 | obj.data.materials.append(bpy.data.materials.get(self.stairMaterial)) 190 | if self.stairSideMaterial != '': 191 | obj.data.materials.append(bpy.data.materials.get(self.stairSideMaterial)) 192 | if wallMesh is not None: 193 | for mat in wallMesh.data.materials: 194 | obj.data.materials.append(mat) 195 | if pileMesh is not None: 196 | for mat in pileMesh.data.materials: 197 | obj.data.materials.append(mat) 198 | 199 | def createPile(self, mesh, piles): 200 | def cacheParam(pilePos, offsetY): 201 | def transformWall(vid, v): 202 | pos = v.co 203 | newpos = ( 204 | pos.x + pilePos[0], 205 | pos.y + offsetY, 206 | pos.z + pilePos[2] 207 | ) 208 | return newpos 209 | return transformWall 210 | for p in piles: 211 | self.addVertexByMesh(mesh, 3, cacheParam(p, self.width/2-self.wallOffsetY)) 212 | self.addVertexByMesh(mesh, 3, cacheParam(p, -self.width/2+self.wallOffsetY)) 213 | # self.createPileMesh(mesh, p, self.width/2-self.wallOffsetY) 214 | # self.createPileMesh(mesh, p, -self.width/2+self.wallOffsetY) 215 | 216 | def createWall(self, mesh): 217 | stepHeight = self.height / self.count 218 | totalLength = self.count * self.stepDepth 219 | startPos = Vector((self.stepDepth / 2, 0, stepHeight)) 220 | endPos = Vector((totalLength - self.stepDepth / 2, 0, self.height)) 221 | connect = endPos - startPos 222 | 223 | count = round(connect.x / mesh.dimensions.x) 224 | targetWidth = connect.x / count 225 | scaleFactor = targetWidth / mesh.dimensions.x 226 | 227 | wallSingle = connect / count 228 | radian = Vector((1,0,0)).angle(connect.normalized()) 229 | tanRadian = tan(radian) 230 | 231 | def cacheParam(scaleFactor, tanRadian, startPos, wallPos, offsetY): 232 | def transformWall(vid, v): 233 | pos = v.co 234 | newpos = ( 235 | pos.x * scaleFactor + wallPos.x + startPos.x, 236 | pos.y + wallPos.y + startPos.y + offsetY, 237 | pos.z + tanRadian * (pos.x * scaleFactor) + wallPos.z + startPos.z + self.wallHeight 238 | ) 239 | return newpos 240 | return transformWall 241 | 242 | #create mesh in the same object 243 | if not self.editMode: 244 | for i in range(count): 245 | wallPos = wallSingle * (i + .5) 246 | 247 | self.addVertexByMesh(mesh, 2, cacheParam(scaleFactor, tanRadian, startPos, wallPos, self.width/2-self.wallOffsetY)) 248 | self.addVertexByMesh(mesh, 2, cacheParam(scaleFactor, tanRadian, startPos, wallPos, -self.width/2+self.wallOffsetY)) 249 | 250 | # self.createOneWall(mesh, scaleFactor, tanRadian, startPos, wallPos, self.width/2-self.wallOffsetY) 251 | # self.createOneWall(mesh, scaleFactor, tanRadian, startPos, wallPos, -self.width/2+self.wallOffsetY) 252 | else: 253 | # create mesh for every wall 254 | wallPrototype = copyObject(mesh) 255 | for v in wallPrototype.data.vertices: 256 | pos = v.co 257 | v.co = ( 258 | pos.x * scaleFactor, 259 | pos.y, 260 | pos.z + tanRadian * (pos.x * scaleFactor) 261 | ) 262 | wallObj = [] 263 | for i in range(count): 264 | wallPos = wallSingle * (i + .5) 265 | cloneWall = copyObject(wallPrototype, True) 266 | cloneWall.location.x = wallPos.x + startPos.x 267 | cloneWall.location.y = wallPos.y + startPos.y + self.width/2-self.wallOffsetY 268 | cloneWall.location.z = wallPos.z + startPos.z + self.wallHeight 269 | addObject(cloneWall) 270 | wallObj.append(cloneWall) 271 | 272 | cloneWall = copyObject(wallPrototype, True) 273 | cloneWall.location.x = wallPos.x + startPos.x 274 | cloneWall.location.y = wallPos.y + startPos.y + -self.width/2+self.wallOffsetY 275 | cloneWall.location.z = wallPos.z + startPos.z + self.wallHeight 276 | addObject(cloneWall) 277 | wallObj.append(cloneWall) 278 | bpy.data.objects.remove(wallPrototype, do_unlink=True) 279 | 280 | def execute(self, context): 281 | 282 | creator = prepareAndCreateMesh("Stair") 283 | obj = creator["obj"] 284 | update = creator["update"] 285 | addVertexAndFaces = creator["addVertexAndFaces"] 286 | addVertexByMesh = creator["addVertexByMesh"] 287 | 288 | self.addVertexByMesh = addVertexByMesh 289 | 290 | # unselect all of object, and then can join my own object 291 | for obj in bpy.context.view_layer.objects: 292 | obj.select_set(False) 293 | 294 | x = self.width / 2 295 | stepHeight = self.height / self.count 296 | stepDepth = self.stepDepth 297 | 298 | pilePerStep = self.pileCount 299 | if pilePerStep > self.count: 300 | pilePerStep = self.count - 1 301 | self.pileCount = pilePerStep 302 | 303 | line = [] 304 | uv = [] 305 | piles = [] 306 | for i in range(self.count): 307 | line.append((i*stepDepth,-x,i*stepHeight)) 308 | line.append((i*stepDepth,-x,i*stepHeight+stepHeight)) 309 | line.append((i*stepDepth+stepDepth,-x,i*stepHeight+stepHeight)) 310 | 311 | uvIndex = i % self.stairUvStep 312 | uv.append((0,uvIndex/self.stairUvStep)) 313 | uv.append((0,uvIndex/self.stairUvStep + self.stairUvCenter/self.stairUvStep)) 314 | uv.append((0,uvIndex/self.stairUvStep + 1/self.stairUvStep)) 315 | 316 | if i % pilePerStep == 0 or (i == self.count - 1): 317 | piles.append((i*stepDepth + stepDepth/2,-x,i*stepHeight+stepHeight)) 318 | 319 | lastVertex = line[len(line)-1] 320 | # 階梯的點及uv 321 | addVertexAndFaces( 322 | line, (0, self.width, 0), 323 | uv, (-self.stairUvScaleX,0), 324 | 1, 0, flip=True) 325 | 326 | # 背面的點及uv 327 | addVertexAndFaces( 328 | [lastVertex, (lastVertex[0],lastVertex[1],0)], (0, self.width, 0), 329 | [(0,lastVertex[2]),(0,0)], (self.width,0), 330 | self.stairSideUvScale, 1, flip=True) 331 | 332 | # 側墻的點及uv 333 | for i in range(self.count): 334 | addVertexAndFaces( 335 | [(i*stepDepth,-x, i*stepHeight+stepHeight), 336 | (i*stepDepth,-x,0), 337 | (i*stepDepth,x,0), 338 | (i*stepDepth,x,i*stepHeight+stepHeight) 339 | ], (stepDepth, 0, 0),[ 340 | (i*stepDepth,i*stepHeight+stepHeight), 341 | (i*stepDepth,0), 342 | (i*stepDepth,0), 343 | (i*stepDepth,i*stepHeight+stepHeight) 344 | ], (stepDepth,0), self.stairSideUvScale, 1, flip=True) 345 | 346 | # check the name of object in the scene! if not, set value to empty 347 | try: 348 | bpy.context.view_layer.objects[self.wallMesh] 349 | except: 350 | self.wallMesh = '' 351 | 352 | try: 353 | bpy.context.view_layer.objects[self.pileMesh] 354 | except: 355 | self.pileMesh = '' 356 | 357 | wallMesh = None 358 | pileMesh = None 359 | if (self.showWall): 360 | if self.wallMesh != '': 361 | wallMesh = bpy.context.view_layer.objects[self.wallMesh] 362 | if wallMesh.type == 'MESH': 363 | self.createWall(wallMesh) 364 | if (self.pileMesh != ''): 365 | pileMesh = bpy.context.view_layer.objects[self.pileMesh] 366 | if pileMesh.type == 'MESH': 367 | self.createPile(pileMesh, piles) 368 | 369 | update() 370 | self.assignMaterial(obj, wallMesh, pileMesh) 371 | 372 | activeObject(obj) 373 | 374 | return {'FINISHED'} 375 | 376 | def draw(self, context): 377 | layout = self.layout 378 | scene = context.scene 379 | 380 | box = layout.box() 381 | box.label(text='Stair Mesh') 382 | row = box.row() 383 | row.prop(self, 'width') 384 | row.prop(self, 'height') 385 | row = box.row() 386 | row.prop(self, 'count') 387 | row.prop(self, 'stepDepth') 388 | 389 | box = layout.box() 390 | box.label(text='Wall Mesh') 391 | row = box.row() 392 | row.prop(self, 'showWall') 393 | #row.prop(self, 'editMode') 394 | box.prop_search(self, "wallMesh", bpy.data, "objects") 395 | box.prop_search(self, "pileMesh", bpy.data, "objects") 396 | #row.prop(self, "wallMesh") 397 | row = box.row() 398 | row.prop(self, 'wallHeight') 399 | row.prop(self, 'wallOffsetY') 400 | box.prop(self, 'pileCount') 401 | 402 | box = layout.box() 403 | box.label(text='Stair Material') 404 | row = box.row() 405 | box.prop(self, 'stairUvStep') 406 | row.prop(self, 'stairUvCenter') 407 | row.prop(self, 'stairUvScaleX') 408 | box.prop(self, 'stairSideUvScale') 409 | box.prop_search(self, "stairMaterial", bpy.data, "materials") 410 | box.prop_search(self, "stairSideMaterial", bpy.data, "materials") 411 | 412 | class vic_procedural_stair_panel(bpy.types.Panel): 413 | bl_category = "Vic Addons" 414 | bl_space_type = "VIEW_3D" 415 | bl_region_type = "UI" 416 | bl_label = "Procedrual Stair" 417 | 418 | def draw(self, context): 419 | layout = self.layout 420 | 421 | col = layout.column(align=True) 422 | col.operator(vic_procedural_stair.bl_idname) -------------------------------------------------------------------------------- /vicAddons/springBone.py: -------------------------------------------------------------------------------- 1 | 2 | import bpy, math 3 | from mathutils import Vector 4 | from mathutils import Matrix 5 | from mathutils import Quaternion 6 | 7 | def clearAllBonesKey( bones, start_frame, end_frame): 8 | for f in range( start_frame, end_frame ): 9 | for b in bones: 10 | try: 11 | b.keyframe_delete(data_path="rotation_quaternion" ,frame=f) 12 | except RuntimeError: 13 | print( "no action!" ) 14 | b.rotation_quaternion = Quaternion( Vector(), 1 ) 15 | 16 | def getLocalQuaternion( m, axis, angle ): 17 | b_rot_mat = getRotationMatrixFromMatrix( m ) 18 | localAxis = b_rot_mat.inverted() * axis 19 | return Quaternion (localAxis, angle) 20 | 21 | def getRotationMatrixFromMatrix( m ): 22 | return m.to_quaternion().to_matrix().to_4x4().copy() 23 | 24 | def getTailMatrix( body, bone, pos ): 25 | qua_mat = body.matrix_world @ bone.matrix 26 | qua_mat = qua_mat.to_quaternion().to_matrix().to_4x4() 27 | pos_mat = Matrix.Translation( body.matrix_world @ pos ) 28 | return pos_mat @ qua_mat 29 | 30 | def collectBonesFromRoot( root_bone ): 31 | bones = [] 32 | temp_bone = root_bone 33 | while( len( temp_bone.children ) != 0 ): 34 | bones.append( temp_bone.children[0] ) 35 | temp_bone = temp_bone.children[0] 36 | return bones 37 | 38 | def getBoneRelativeData( root, pts): 39 | pos_data = [] 40 | dist_data = [] 41 | 42 | for i, p in enumerate( pts, 0 ): 43 | first_point = None 44 | if i == 0: 45 | first_point = root 46 | else: 47 | first_point = pts[i-1] 48 | pos_data.append( first_point.inverted().copy() @ p.to_translation().copy() ) 49 | dist_data.append( (first_point.to_translation().copy() - p.to_translation().copy()).magnitude ) 50 | return pos_data, dist_data 51 | 52 | def saveRootPosition( root, root_locs ): 53 | root_locs.append( root.to_translation().copy() ) 54 | if len( root_locs ) > 100: 55 | root_locs.pop(0) 56 | 57 | def getDiffPosition(root_locs, prev_count): 58 | last = len(root_locs)-1 59 | target = last - prev_count 60 | if target < 0: 61 | return Vector() 62 | if len( root_locs ) < target + 1: 63 | return Vector() 64 | pa = root_locs[target] 65 | pb = root_locs[target-1] 66 | return pa-pb 67 | 68 | def setTranslationForMatrix( mat, pos ): 69 | return Matrix.Translation( pos ) @ mat.to_quaternion().to_matrix().to_4x4() 70 | 71 | def setRotationForMatrix( mat, qua ): 72 | return Matrix.Translation( mat.to_translation() ) @ qua.to_matrix().to_4x4() 73 | 74 | def addForce(pts, pts_spd, root, root_locs, pos_data, gravity): 75 | for i, p in enumerate( pts, 0 ): 76 | first_point = None 77 | if i == 0: 78 | first_point = root 79 | else: 80 | first_point = pts[i-1] 81 | 82 | back_pos = first_point @ pos_data[i].copy() 83 | back_force = back_pos - p.to_translation().copy() 84 | 85 | if bpy.context.window_manager.spring_bone_keep_is_spring: 86 | spd = pts_spd[i] 87 | 88 | diff = getDiffPosition( root_locs, i * 3 ) 89 | 90 | spd += diff * bpy.context.window_manager.spring_bone_extend_factor 91 | spd += back_force * bpy.context.window_manager.spring_bone_spring_factor 92 | spd += gravity * bpy.context.window_manager.spring_bone_gravity_factor 93 | spd *= bpy.context.window_manager.spring_bone_friction_factor 94 | pts[i] = setTranslationForMatrix( p, p.to_translation() + spd ) 95 | else: 96 | pts[i] = setTranslationForMatrix( p, p.to_translation() + back_force * bpy.context.window_manager.spring_bone_spring_factor ) 97 | 98 | def setRotation(root, pts, up_vec): 99 | for i, p in enumerate( pts, 0 ): 100 | first_point = None 101 | if i == 0: 102 | first_point = root 103 | else: 104 | first_point = pts[i-1] 105 | 106 | z_vec = ( first_point.to_translation() - p.to_translation() ).normalized() 107 | rot_quat = z_vec.to_track_quat('-Y', 'Z') 108 | pts[i] = setRotationForMatrix( p, rot_quat ) 109 | 110 | # another method I offen used 111 | ''' 112 | z_vec = ( first_point.to_translation() - p.to_translation() ).normalized() 113 | spin_vec = z_vec.cross( up_vec ).normalized() 114 | spin_angle = z_vec.angle( up_vec ) 115 | pts[i] = setRotationForMatrix( p, Quaternion( spin_vec, spin_angle ).inverted() ) 116 | ''' 117 | 118 | def limitDistance(root, pts, pts_len): 119 | for i, p in enumerate( pts, 0 ): 120 | first_point = None 121 | if i == 0: 122 | first_point = root 123 | else: 124 | first_point = pts[i-1] 125 | len = pts_len[i] 126 | pts[i] = setTranslationForMatrix( p, ( p.to_translation() - first_point.to_translation() ).normalized() * len + first_point.to_translation() ) 127 | 128 | def syncToDebugView( f, start_frame, body, debug_views, pts ): 129 | for i, v in enumerate( debug_views, 0 ): 130 | v.matrix_world = pts[i] 131 | if f >= start_frame: 132 | v.keyframe_insert(data_path="rotation_quaternion" ,frame=f) 133 | v.keyframe_insert(data_path="location" ,frame=f) 134 | 135 | def mapToBone( f, start_frame, body, root, root_bone, bones, pts ): 136 | for i, b in enumerate( bones, 0 ): 137 | b.matrix = body.matrix_world.inverted() @ pts[i] 138 | if f >= start_frame: 139 | b.keyframe_insert(data_path="rotation_quaternion" ,frame=f) 140 | 141 | # this update is very important, blender will update matrix with this function call, if not call will occur strange performance 142 | bpy.context.scene.update_tag() 143 | 144 | # another method, using quaternion 145 | ''' 146 | global_proxy_qua = pts[i].to_quaternion() 147 | global_bone_gua = ( body.matrix_world * b.matrix ).to_quaternion() 148 | global_diff_qua = global_bone_gua.inverted() * global_proxy_qua 149 | 150 | b.rotation_quaternion *= global_diff_qua 151 | 152 | if f >= start_frame: 153 | b.keyframe_insert(data_path="rotation_quaternion" ,frame=f) 154 | ''' 155 | 156 | debugView = False 157 | 158 | class vic_spring_bone(bpy.types.Operator): 159 | bl_idname = "vic.spring_bone" 160 | bl_label = "Bake Spring Bone" 161 | bl_description='Please select bone in the pose mode' 162 | bl_options = {'REGISTER', 'UNDO'} 163 | 164 | def process(self,context): 165 | 166 | objs = bpy.data.objects 167 | body = bpy.context.object 168 | 169 | up_vec = Vector([0, -1, 0]) 170 | gravity = Vector([0,0,-1]) 171 | 172 | start_frame = context.window_manager.spring_bone_frame_start 173 | end_frame = context.window_manager.spring_bone_frame_end 174 | 175 | selected_pose_bones = context.selected_pose_bones 176 | 177 | for root_bone in selected_pose_bones: 178 | root = getTailMatrix( body, root_bone, root_bone.tail ) 179 | bones = collectBonesFromRoot( root_bone ) 180 | 181 | pts = [ getTailMatrix( body, b, b.tail ) for b in bones ] 182 | pts_spd = [Vector() for p in pts] 183 | 184 | # for prev force 185 | root_locs = [] 186 | 187 | # set new mat for relative data 188 | setRotation( root, pts, up_vec ) 189 | 190 | # save relative data for children 191 | pos_data, dist_data = getBoneRelativeData( root, pts ) 192 | 193 | # debug view 194 | # maybe will add new method for create fake bone 195 | 196 | if debugView: 197 | debug_views = [] 198 | for b in bones: 199 | bpy.ops.mesh.primitive_cone_add() 200 | bpy.context.object.rotation_mode = 'QUATERNION' 201 | #bpy.context.object.name = 'abc' 202 | #bpy.ops.transform.resize(value=(.1,.1,.1)) 203 | debug_views.append( bpy.context.object ) 204 | 205 | # 不曉得是不是blender本身的毛病,前几幀的bake會有點問題,幀數剛好為骨頭的數目 206 | # 這裏預留會出問題的幀,提前bake 207 | prebake = start_frame - len(bones) 208 | 209 | for i in range( prebake, end_frame ): 210 | bpy.context.scene.frame_set( i ) 211 | 212 | root = getTailMatrix( body, root_bone, root_bone.tail ) 213 | saveRootPosition( root, root_locs ) 214 | setRotation( root, pts, up_vec ) 215 | addForce( pts, pts_spd, root, root_locs, pos_data, gravity ) 216 | limitDistance( root, pts, dist_data ) 217 | mapToBone( i, prebake, body, root, root_bone, bones, pts ) 218 | 219 | # maybe will add new method for create fake bone 220 | if debugView: 221 | syncToDebugView( i, prebake, body, debug_views, pts ) 222 | 223 | print( 'On Bone: ' + root_bone.name + ', Frame Complete: ' + str( i ) ) 224 | 225 | # 這裏再刪掉提前bake的幀 226 | for f in range(prebake, start_frame): 227 | for b in bones: b.keyframe_delete(data_path="rotation_quaternion" ,frame=f) 228 | 229 | bpy.context.scene.frame_set( start_frame ) 230 | 231 | def execute(self, context): 232 | if context.object == None: 233 | return {'FINISHED'} 234 | else: 235 | if not hasattr( context.object, 'pose' ): 236 | return {'FINISHED'} 237 | if context.active_pose_bone == None: 238 | return {'FINISHED'} 239 | self.process( context ) 240 | return {'FINISHED'} 241 | 242 | class vic_bones_clear_key(bpy.types.Operator): 243 | bl_idname = "vic.bones_clear_key" 244 | bl_label = 'Clear Children Keys' 245 | bl_description = 'Clear all child bone keys of the target bone' 246 | bl_options = {'REGISTER', 'UNDO'} 247 | 248 | def process( self, context ): 249 | selected_pose_bones = context.selected_pose_bones 250 | 251 | for root_bone in selected_pose_bones: 252 | bones = collectBonesFromRoot( root_bone ) 253 | 254 | start_frame = context.window_manager.spring_bone_frame_start 255 | end_frame = context.window_manager.spring_bone_frame_end 256 | 257 | clearAllBonesKey( bones, start_frame, end_frame ) 258 | 259 | def execute(self, context): 260 | if context.object == None: 261 | return {'FINISHED'} 262 | else: 263 | if not hasattr( context.object, 'pose' ): 264 | return {'FINISHED'} 265 | if context.active_pose_bone == None: 266 | return {'FINISHED'} 267 | self.process( context ) 268 | return {'FINISHED'} 269 | 270 | class VIC_PT_SPRING_BONE_TOOL(bpy.types.Panel): 271 | bl_category = "Vic Addons" 272 | bl_space_type = "VIEW_3D" 273 | bl_region_type = "UI" 274 | bl_label = "Spring Bone Tool" 275 | 276 | def draw(self, context): 277 | layout = self.layout 278 | 279 | col = layout.column(align=True) 280 | col.operator(vic_spring_bone.bl_idname) 281 | col.operator(vic_bones_clear_key.bl_idname) 282 | col.prop(context.window_manager, 'spring_bone_frame_start' ) 283 | col.prop(context.window_manager, 'spring_bone_frame_end' ) 284 | col = layout.column(align=True) 285 | col.prop(context.window_manager, 'spring_bone_extend_factor' ) 286 | col.prop(context.window_manager, 'spring_bone_spring_factor' ) 287 | col.prop(context.window_manager, 'spring_bone_gravity_factor' ) 288 | col.prop(context.window_manager, 'spring_bone_friction_factor' ) 289 | col.prop(context.window_manager, 'spring_bone_keep_is_spring' ) 290 | # col = layout.column(align=True) 291 | # col.prop(context.scene, 'spring_bone_roop_gravity', 'Roop Gravity' ) 292 | 293 | #======================================= 294 | 295 | bpy.types.WindowManager.spring_bone_extend_factor = bpy.props.FloatProperty( 296 | name='Extend', 297 | default=0.0, 298 | min=0.0, 299 | max=1.0 300 | ) 301 | 302 | bpy.types.WindowManager.spring_bone_spring_factor = bpy.props.FloatProperty( 303 | name='Keep', 304 | default=.6, 305 | min=0.0, 306 | max=1.0 307 | ) 308 | 309 | bpy.types.WindowManager.spring_bone_gravity_factor = bpy.props.FloatProperty( 310 | name='Gravity', 311 | default=0.0, 312 | min=0.0, 313 | max=1.0 314 | ) 315 | 316 | bpy.types.WindowManager.spring_bone_friction_factor = bpy.props.FloatProperty( 317 | name='Friction', 318 | default=0.5, 319 | min=0.0, 320 | max=1.0 321 | ) 322 | 323 | bpy.types.WindowManager.spring_bone_keep_is_spring = bpy.props.BoolProperty( 324 | name='Spring', 325 | default=True) 326 | 327 | bpy.types.WindowManager.spring_bone_frame_start = bpy.props.IntProperty( 328 | name="Start Frame", description="Start frame of simulation", 329 | default=1, step=1, min=1, max=100000) 330 | 331 | bpy.types.WindowManager.spring_bone_frame_end = bpy.props.IntProperty( 332 | name="End Frame", description="End frame of simulation", 333 | default=50, step=1, min=2, max=100000) 334 | -------------------------------------------------------------------------------- /vicAddons/vic_tools.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from mathutils import * 3 | 4 | def scaleObjVertex(obj, scale): 5 | for v in obj.data.vertices: 6 | v.co.x = v.co.x * scale[0] 7 | v.co.y = v.co.y * scale[1] 8 | v.co.z = v.co.z * scale[2] 9 | 10 | def joinObj( joinList, target ): 11 | focusObject(target) 12 | for obj in joinList: 13 | obj.select_set(True) 14 | bpy.ops.object.join() 15 | bpy.ops.object.origin_set(type='ORIGIN_CENTER_OF_MASS', center='MEDIAN') 16 | 17 | def copyToScene( prefab, sameData = False ): 18 | obj = copyObject(prefab, sameData) 19 | addObject(obj) 20 | return obj 21 | 22 | def addObject( obj ): 23 | bpy.context.collection.objects.link(obj) 24 | 25 | def activeObject( obj ): 26 | bpy.context.view_layer.objects.active = obj 27 | 28 | def updateMatrix(): 29 | bpy.context.view_layer.update() 30 | 31 | def copyObject(obj, sameData = False): 32 | newobj = obj.copy() 33 | if not sameData: 34 | newobj.data = obj.data.copy() 35 | newobj.animation_data_clear() 36 | return newobj 37 | 38 | def focusObject(focusObj): 39 | # unselect all of object, and then can join my own object 40 | for obj in bpy.data.objects: 41 | obj.select_set(False) 42 | focusObj.select_set(True) 43 | activeObject(focusObj) 44 | 45 | def removeObjects(objs): 46 | curr_focus = bpy.context.object 47 | for obj in objs: 48 | focusObject(obj) 49 | bpy.ops.object.delete() 50 | if curr_focus: focusObject(curr_focus) 51 | 52 | def addProps( target, name, value, override = False ): 53 | if not name in target or override: 54 | target[name] = value 55 | 56 | def getSelectedWithOrder(): 57 | count = 0 58 | 59 | if not bpy.context.active_object: return [] 60 | 61 | # start list with current active object: 62 | order = list([bpy.context.active_object.name]) 63 | 64 | #keep going back in time (undo) as long as objects are selected 65 | while len(bpy.context.selected_objects) >= 2: 66 | bpy.ops.ed.undo() 67 | order.insert(0,bpy.context.active_object.name) # add previous active object 68 | count += 1 69 | 70 | # Get back to the future (redo, redo, redo, ...) 71 | for x in range(0, count): 72 | bpy.ops.ed.redo() 73 | 74 | # Collect obj from order names 75 | objs = [] 76 | for name in order: 77 | for o in bpy.data.objects: 78 | if o.name == name: 79 | objs.append(o) 80 | continue 81 | return objs 82 | 83 | def prepareAndCreateMesh(name): 84 | verts = [] 85 | faces = [] 86 | uvsMap = {} 87 | matIds = [] 88 | 89 | mesh = bpy.data.meshes.new(name) 90 | obj = bpy.data.objects.new(name, mesh) 91 | addObject(obj) 92 | 93 | def clear(): 94 | nonlocal verts, faces, uvsMap, matIds, mesh 95 | verts = [] 96 | faces = [] 97 | uvsMap = {} 98 | matIds = [] 99 | mesh.clear_geometry() 100 | 101 | # 給定一條綫上的點,再給定一個偏移向量,用程式產生偏移過後的第二條綫段的點 102 | # 用兩條綫上的點來產生面 103 | # 搭配 prepareAndCreateMesh 一起使用 104 | def addVertexAndFaces( line, offset, uvLine, uvOffset, 105 | uvScale = 1, matId = 0, flip = False, close = False): 106 | line = line.copy() 107 | anotherLine = [] 108 | startId = len(verts) 109 | for i, v in enumerate(line): 110 | offsetVert = ( v[0] + offset[0], 111 | v[1] + offset[1], 112 | v[2] + offset[2]) 113 | anotherLine.append(offsetVert) 114 | 115 | # 收集面id 116 | v1 = startId+i 117 | v2 = v1+len(line) 118 | v3 = v2+1 119 | v4 = v1+1 120 | 121 | isLastFace = (i == len(line)-1) 122 | if isLastFace: 123 | if close: 124 | if flip: 125 | f = (v1,startId,startId+len(line),v2) 126 | else: 127 | f = (v1,v2,startId+len(line),startId) 128 | else: 129 | # last point, not need to create face 130 | continue 131 | else: 132 | if flip: 133 | f = (v1, v4, v3, v2) 134 | else: 135 | f = (v1, v2, v3, v4) 136 | 137 | currentFaceId = len(faces) 138 | uvsMap['%i_%i' % (currentFaceId, f[0])] = ( 139 | (uvLine[i][0])*uvScale, 140 | (uvLine[i][1])*uvScale 141 | ) 142 | uvsMap['%i_%i' % (currentFaceId, f[1])] = ( 143 | (uvLine[i+1][0])*uvScale, 144 | (uvLine[i+1][1])*uvScale 145 | ) 146 | uvsMap['%i_%i' % (currentFaceId, f[2])] = ( 147 | (uvLine[i+1][0]+uvOffset[0])*uvScale, 148 | (uvLine[i+1][1]+uvOffset[1])*uvScale 149 | ) 150 | uvsMap['%i_%i' % (currentFaceId, f[3])] = ( 151 | (uvLine[i][0]+uvOffset[0])*uvScale, 152 | (uvLine[i][1]+uvOffset[1])*uvScale 153 | ) 154 | 155 | faces.append(f) 156 | matIds.append(matId) 157 | 158 | line.extend(anotherLine) 159 | verts.extend(line) 160 | 161 | def addRectVertex( addverts, adduvs, 162 | uvScale = 1, matId = 0): 163 | 164 | startId = len(verts) 165 | face = [] 166 | currentFaceId = len(faces) 167 | for i, v in enumerate(addverts): 168 | verts.append(v) 169 | 170 | vid = i+startId 171 | face.append(vid) 172 | 173 | adduv = adduvs[i] 174 | uvsMap['%i_%i' % (currentFaceId, vid)] = ( 175 | adduv[0]*uvScale, 176 | adduv[1]*uvScale 177 | ) 178 | 179 | faces.append(tuple(face)) 180 | matIds.append(matId) 181 | 182 | # 搭配 prepareAndCreateMesh 一起使用 183 | def addVertexByMesh(mesh, matIdOffset = 0, transformVertex = None): 184 | currentFaceId = len(faces) 185 | currentVertId = len(verts) 186 | for m in mesh.data.polygons: 187 | vs = [] 188 | currentVertexCount = len(verts) 189 | for vid in m.vertices: 190 | vs.append(vid + currentVertexCount) 191 | faces.append(tuple(vs)) 192 | matIds.append(m.material_index+matIdOffset) 193 | 194 | for vert_idx, loop_idx in zip(m.vertices, m.loop_indices): 195 | uvsMap["%i_%i" % (m.index+currentFaceId,vert_idx+currentVertId)] = mesh.data.uv_layers.active.data[loop_idx].uv 196 | 197 | for vid,v in enumerate(mesh.data.vertices): 198 | if transformVertex: newpos = transformVertex(vid, v) 199 | else: newpos = (v.co.x, v.co.y, v.co.z) 200 | verts.append( newpos ) 201 | 202 | def update(): 203 | mesh.from_pydata(verts, [], faces) 204 | 205 | # assign uv 206 | obj.data.uv_layers.new() 207 | for i, face in enumerate(obj.data.polygons): 208 | for vert_idx, loop_idx in zip(face.vertices, face.loop_indices): 209 | uv = uvsMap['%i_%i' % (face.index, vert_idx)] 210 | obj.data.uv_layers.active.data[loop_idx].uv = uv 211 | face.material_index = matIds[i] 212 | 213 | return { 214 | "obj": obj, 215 | "update": update, 216 | "clear": clear, 217 | "addRectVertex": addRectVertex, 218 | "addVertexAndFaces": addVertexAndFaces, 219 | "addVertexByMesh": addVertexByMesh 220 | } 221 | 222 | def mergeOverlayVertex(obj): 223 | obj.select_set(True) 224 | bpy.context.view_layer.objects.active = obj 225 | bpy.ops.object.editmode_toggle() 226 | bpy.ops.mesh.select_all(action='SELECT') 227 | bpy.ops.mesh.remove_doubles() 228 | bpy.ops.object.editmode_toggle() 229 | 230 | # example: 231 | # curve = bpy.data.objects["BezierCurve"] 232 | # total_length, matrices = getCurvePosAndLength(curve, 10) 233 | def getCurvePosAndLength(curve, count): 234 | 235 | if bpy.context.area == None: return (0, []) 236 | if bpy.context.area.type != 'VIEW_3D': return (0, []) 237 | if bpy.context.mode != 'OBJECT': return (0, []) 238 | 239 | current_focus = bpy.context.object 240 | 241 | bpy.ops.object.empty_add(type='ARROWS') 242 | bpy.ops.object.location_clear() 243 | bpy.ops.object.constraint_add(type='FOLLOW_PATH') 244 | constraint = bpy.context.object.constraints["Follow Path"] 245 | constraint.target = curve 246 | constraint.forward_axis = 'FORWARD_X' 247 | constraint.up_axis = 'UP_Z' 248 | constraint.use_fixed_location = True 249 | constraint.use_curve_follow = True 250 | 251 | total_length = 0 252 | current_pos = None 253 | matrices = [] 254 | 255 | for i in range(count+1): 256 | offset = i / count 257 | constraint.offset_factor = offset 258 | 259 | # update matrix while python running 260 | updateMatrix() 261 | 262 | world_mat = bpy.context.object.matrix_world.copy() 263 | world_pos = world_mat.to_translation() 264 | matrices.append( world_mat ) 265 | 266 | if i == 0: pass 267 | else: total_length += (world_pos - current_pos).length 268 | 269 | current_pos = world_pos 270 | 271 | bpy.ops.object.delete() 272 | 273 | if current_focus: focusObject(current_focus) 274 | return total_length, matrices 275 | -------------------------------------------------------------------------------- /vicTools/__init__.py: -------------------------------------------------------------------------------- 1 | bl_info = { 2 | "name": "Vic Tools", 3 | "author": "Vic", 4 | "version": (0, 1), 5 | "blender": (2, 82, 0), 6 | "location": "3D View", 7 | "description": "", 8 | "warning": "", 9 | "wiki_url": "", 10 | "category": "Tools" 11 | } 12 | 13 | from . import vic_actions 14 | from . import vic_spring_bone 15 | from . import vic_make_it_voxel 16 | 17 | pluginObj = ( 18 | vic_actions, 19 | vic_spring_bone 20 | 21 | # hide_viewport目前insert key在2.8好像有點bug 22 | # 2.8的材質沒有using vertex color的屬性,還不知道在哪裏 23 | # vic_make_it_voxel 24 | ) 25 | 26 | def register(): 27 | for p in pluginObj: p.register() 28 | 29 | def unregister(): 30 | for p in pluginObj: p.unregister() 31 | 32 | -------------------------------------------------------------------------------- /vicTools/operators/CreateCameraTarget.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | 3 | def createCameraTarget( currobj, targetName ): 4 | bpy.ops.object.empty_add(type='ARROWS') 5 | currArrow = bpy.context.object 6 | currArrow.name = 'vic_camera_target' 7 | bpy.ops.object.location_clear() 8 | currArrow.select_set( False ) 9 | currobj.select_set( True ) 10 | bpy.context.view_layer.objects.active = currobj 11 | bpy.ops.object.constraint_add(type='TRACK_TO') 12 | currConstraint = currobj.constraints[len(currobj.constraints)-1] 13 | currConstraint.name = targetName 14 | currConstraint.target = currArrow 15 | currConstraint.track_axis = 'TRACK_NEGATIVE_Z' 16 | currConstraint.up_axis = 'UP_Y' 17 | 18 | class vic_create_camera_target(bpy.types.Operator): 19 | bl_idname = 'vic.vic_create_camera_target' 20 | bl_label = 'Create Look At' 21 | bl_description = 'Create Look At' 22 | 23 | target_name = "vic_camera_constraint_name" 24 | 25 | def execute(self, context): 26 | currobj = context.object 27 | if currobj == None: return {'FINISHED'} 28 | cons = currobj.constraints 29 | for con in cons: 30 | if con.name == self.target_name: 31 | self.report( {'ERROR'}, 'already done!' ) 32 | return {'CANCELLED'} 33 | createCameraTarget( currobj, self.target_name ) 34 | return {'FINISHED'} -------------------------------------------------------------------------------- /vicTools/operators/HandDrag.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from mathutils import Vector 3 | from mathutils import noise 4 | from ..vic_tools import addProps 5 | 6 | def collectVertexColor( mesh, color_layer ): 7 | ret = {} 8 | i = 0 9 | for poly in mesh.polygons: 10 | for idx in poly.loop_indices: 11 | loop = mesh.loops[idx] 12 | v = loop.vertex_index 13 | linked = ret.get(v, []) 14 | linked.append(color_layer.data[i].color) 15 | ret[v] = linked 16 | i += 1 17 | return ret 18 | 19 | def avg_col(cols): 20 | avg_col = Color((0.0, 0.0, 0.0)) 21 | for col in cols: 22 | avg_col[0] += col[0]/len(cols) 23 | avg_col[1] += col[1]/len(cols) 24 | avg_col[2] += col[2]/len(cols) 25 | return avg_col 26 | 27 | # def addProps( target, name, value, override = False ): 28 | # if not name in target or override: 29 | # target[name] = value 30 | 31 | def back_to_origin_vertex_position( target ): 32 | for i, v in enumerate( target.data.vertices, 0 ): 33 | target.data.vertices[i].co = target['vic_init_vertex_position'][i] 34 | to_proxy = target.matrix_world @ Vector( target['vic_init_vertex_position'][i] ) 35 | target['vic_proxy_vertex_position'][i][0] = to_proxy.x 36 | target['vic_proxy_vertex_position'][i][1] = to_proxy.y 37 | target['vic_proxy_vertex_position'][i][2] = to_proxy.z 38 | 39 | def save_vertex_position( target ): 40 | # active when 1, or close by 0 41 | addProps( target, 'vic_active', True ) 42 | # no nagetive number, the higher value the more detail 43 | addProps( target, 'vic_detail', 1.0 ) 44 | # 0.0~1.0 will be best! 45 | addProps( target, 'vic_effective', 1.0 ) 46 | # using vertex color 47 | addProps( target, 'vic_using_vertex_color_map', False ) 48 | # using vertex color for effective value 49 | addProps( target, 'vic_effective_by_vertex_color', 'Col' ) 50 | 51 | detail = target['vic_detail'] 52 | map_vertex_color = target['vic_effective_by_vertex_color'] 53 | 54 | addProps( target, 'vic_init_vertex_position', [ v.co.copy() for v in target.data.vertices ], True) 55 | addProps( target, 'vic_proxy_vertex_position', [ target.matrix_world @ v.co.copy() for v in target.data.vertices ], True ) 56 | 57 | if map_vertex_color in target.data.vertex_colors: 58 | collect_color = collectVertexColor( target.data, target.data.vertex_colors[map_vertex_color] ) 59 | map_vertexs = [avg_col(v).hsv[2] for k, v in collect_color.items() ] 60 | addProps( target, 'vic_force_for_each_vertex_by_vertex_color', map_vertexs, True ) 61 | else: 62 | addProps( target, 'vic_force_for_each_vertex_by_vertex_color', [ .2 for v in target.data.vertices ], True ) 63 | addProps( target, 'vic_force_for_each_vertex', [ ((noise.noise(Vector(v)*detail)) + 1) / 2 for v in target['vic_init_vertex_position'] ], True ) 64 | 65 | def move_vertice( target ): 66 | mat = target.matrix_world 67 | vs = target.data.vertices 68 | 69 | # check the object is not in the scene 70 | if not 'vic_init_vertex_position' in target: return None 71 | 72 | active = target['vic_active'] 73 | if active == 0: return None 74 | 75 | init_pos = target['vic_init_vertex_position'] 76 | proxy_pos = target['vic_proxy_vertex_position'] 77 | force_pos = target['vic_force_for_each_vertex_by_vertex_color'] if target['vic_using_vertex_color_map'] else target['vic_force_for_each_vertex'] 78 | effective = target['vic_effective'] 79 | 80 | for i, v in enumerate(vs,0): 81 | toPos = mat @ Vector( init_pos[i] ) 82 | proxy_pos_vec = Vector(proxy_pos[i]) 83 | proxy_pos_vec += (toPos - proxy_pos_vec) * force_pos[i] * effective 84 | set_pos = mat.inverted() @ proxy_pos_vec 85 | v.co = set_pos 86 | 87 | proxy_pos[i][0] = proxy_pos_vec.x 88 | proxy_pos[i][1] = proxy_pos_vec.y 89 | proxy_pos[i][2] = proxy_pos_vec.z 90 | 91 | def filterCanEffect( objs ): 92 | return [ o for o in objs if o.data is not None and hasattr( o.data, 'vertices' ) ] 93 | 94 | def update( scene ): 95 | eff_objects = filterCanEffect( bpy.data.objects ) 96 | for o in eff_objects: 97 | if 'vic_active' in o: 98 | move_vertice( o ) 99 | def addListener(): 100 | #if update in bpy.app.handlers.frame_change_pre: 101 | try: 102 | bpy.app.handlers.frame_change_pre.remove( update ) 103 | except: 104 | print( 'update handler is not in the list' ) 105 | bpy.app.handlers.frame_change_pre.clear() 106 | bpy.app.handlers.frame_change_pre.append( update ) 107 | 108 | class vic_hand_drag(bpy.types.Operator): 109 | bl_idname = 'vic.hand_drag' 110 | bl_label = 'Make It Drag' 111 | bl_description = 'Every time you update vertex color, you should rebuild this effect! see custom properties in object data panel' 112 | 113 | def doEffect( self ): 114 | init_objects = filterCanEffect( bpy.context.selected_objects.copy() ) 115 | for o in init_objects: 116 | save_vertex_position( o ) 117 | addListener() 118 | def execute(self, context): 119 | self.doEffect() 120 | return {'FINISHED'} 121 | ''' 122 | class vic_set_value_to_all_effect_object( bpy.types.Operator): 123 | bl_idname = 'vic.set_value_to_all_effect_object' 124 | bl_label = 'Rewalk All Active Object' 125 | 126 | def doEffect( self ): 127 | init_objects = filterCanEffect( bpy.data.objects ) 128 | for o in init_objects: 129 | if 'vic_active' in o: 130 | save_vertex_position( o ) 131 | addListener() 132 | def execute(self, context): 133 | self.doEffect() 134 | return {'FINISHED'} 135 | ''' 136 | class vic_healing_all_effect_objects( bpy.types.Operator): 137 | bl_idname = 'vic.healing_all_effect_objects' 138 | bl_label = 'Healing All' 139 | bl_description = 'Reset mesh shape to original' 140 | 141 | def doEffect( self ): 142 | bpy.context.scene.frame_current = 1 143 | bpy.ops.object.paths_calculate() 144 | init_objects = filterCanEffect( bpy.data.objects ) 145 | for o in init_objects: 146 | if 'vic_active' in o.data: 147 | back_to_origin_vertex_position( o ) 148 | bpy.ops.object.paths_clear() 149 | addListener() 150 | def execute(self, context): 151 | self.doEffect() 152 | return {'FINISHED'} -------------------------------------------------------------------------------- /vicTools/operators/LineAlign.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | 3 | class vic_line_align(bpy.types.Operator): 4 | bl_idname = 'vic.vic_line_align' 5 | bl_label = 'Line Align' 6 | bl_description = 'Line Align' 7 | 8 | def execute(self, context): 9 | 10 | selectList = bpy.context.selected_objects 11 | count = len( selectList ) 12 | if count < 2: return {'FINISHED'} 13 | distance = 0 14 | startObj = None 15 | endObj = None 16 | for i in range(count): 17 | for j in range(i+1, count): 18 | objA = selectList[i] 19 | objB = selectList[j] 20 | current = (objB.location - objA.location).length 21 | if current > distance: 22 | distance = current 23 | startObj = objA 24 | endObj = objB 25 | 26 | dirVector = endObj.location - startObj.location 27 | dirVectorUnit = dirVector.normalized() 28 | for obj in selectList: 29 | objVector = obj.location - startObj.location 30 | if objVector.length > 0: 31 | obj.location = dirVectorUnit * objVector.dot(dirVectorUnit) + startObj.location 32 | 33 | return {'FINISHED'} 34 | -------------------------------------------------------------------------------- /vicTools/operators/MeshFlatten.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from mathutils import Vector 3 | from mathutils import geometry 4 | 5 | class vic_make_meshs_plane(bpy.types.Operator): 6 | bl_idname = 'vic.make_meshs_plane' 7 | bl_label = 'Mesh Flatten' 8 | bl_description = 'Mesh Flatten' 9 | 10 | def doPlane(self, context): 11 | mode = context.object.mode 12 | 13 | #for blender to update selected verts, faces 14 | bpy.ops.object.mode_set(mode='OBJECT') 15 | selectedFaces = [f for f in bpy.context.object.data.polygons if f.select] 16 | selectedVerts = [v for v in bpy.context.object.data.vertices if v.select] 17 | 18 | if len( selectedFaces ) == 0: 19 | self.report( {'ERROR'}, 'select one meshs at least.' ) 20 | else: 21 | norms = Vector() 22 | centers = Vector() 23 | for f in selectedFaces: 24 | norms += f.normal 25 | centers += f.center 26 | 27 | count = len( selectedFaces ) 28 | 29 | norms_aver = norms / count 30 | center_aver = centers / count 31 | 32 | try: 33 | for v in selectedVerts: 34 | res = geometry.intersect_line_plane( v.co, v.co + norms_aver, center_aver, norms_aver ) 35 | v.co = res 36 | except: 37 | self.report( {'ERROR'}, 'sorry for unknown error, please retry.' ) 38 | 39 | bpy.ops.object.mode_set(mode=mode) 40 | 41 | def execute(self, context): 42 | if context.view_layer.objects.active == None: 43 | self.report( {'ERROR'}, 'please pick one object!' ) 44 | return {'FINISHED'} 45 | else: 46 | if context.object.mode != 'EDIT': 47 | self.report( {'ERROR'}, 'should be in the edit mode!' ) 48 | else: 49 | self.doPlane(context) 50 | return {'FINISHED'} -------------------------------------------------------------------------------- /vicTools/operators/MirrorCubeAdd.py: -------------------------------------------------------------------------------- 1 | import bpy, bmesh 2 | 3 | def createMirrorCube(): 4 | bpy.ops.mesh.primitive_cube_add() 5 | bpy.ops.object.editmode_toggle() 6 | 7 | mesh = bmesh.from_edit_mesh(bpy.context.object.data) 8 | 9 | bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='EDGE') 10 | for e in mesh.edges: 11 | e.select = ( e.index == 2 ) 12 | 13 | bpy.ops.mesh.loop_multi_select(ring=True) 14 | bpy.ops.mesh.subdivide() 15 | 16 | for v in mesh.verts: 17 | v.select = v.co[1] < 0 18 | 19 | bpy.ops.mesh.delete(type='VERT') 20 | bpy.ops.object.editmode_toggle() 21 | 22 | bpy.ops.object.modifier_add(type='MIRROR') 23 | bpy.context.object.modifiers['Mirror'].use_axis[0] = False 24 | bpy.context.object.modifiers['Mirror'].use_axis[1] = True 25 | 26 | bpy.ops.object.modifier_add(type='SUBSURF') 27 | 28 | # class mirror_cube_add(bpy.types.Operator): 29 | # bl_idname = 'vic.mirror_cube_add' 30 | # bl_label = 'Create Mirror Cube' 31 | # bl_description = 'Create Mirror Cube' 32 | 33 | # bl_options = {'REGISTER', 'UNDO'} 34 | # def execute(self, context): 35 | # if bpy.context.object != None and bpy.context.object.mode == 'EDIT': 36 | # self.report( {'ERROR'}, 'can not using this function in the EDIT mode!' ) 37 | # return {'CANCELLED'} 38 | # else: 39 | # createMirrorCube() 40 | # return {'FINISHED'} 41 | 42 | class mirror_cube_add(bpy.types.Operator): 43 | bl_idname = 'vic.mirror_cube_add' 44 | bl_label = 'Create Mirror Cube' 45 | bl_description = 'Create Mirror Cube' 46 | 47 | bl_options = {'REGISTER', 'UNDO'} 48 | def execute(self, context): 49 | if bpy.context.object != None and bpy.context.object.mode == 'EDIT': 50 | self.report( {'ERROR'}, 'can not using this function in the EDIT mode!' ) 51 | return {'CANCELLED'} 52 | else: 53 | createMirrorCube() 54 | return {'FINISHED'} -------------------------------------------------------------------------------- /vicTools/operators/ParticleToRigidbody.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | 3 | class ParticlesToRigidbodys(bpy.types.Operator): 4 | bl_idname = 'vic.particle_rigidbody' 5 | bl_label = 'Particles To Rigidbodys' 6 | bl_description = 'Particles To Rigidbodys' 7 | 8 | def setting(self, emitter ): 9 | self.emitter = emitter 10 | eval_ob = bpy.context.evaluated_depsgraph_get().objects.get(self.emitter.name, None) 11 | self.ps = eval_ob.particle_systems[0] 12 | self.ps_set = self.ps.settings 13 | self.ps_set.use_rotation_instance = True 14 | self.start_frame = int( self.ps_set.frame_start ) 15 | self.end_frame = int( self.ps_set.frame_end + ( self.ps_set.lifetime * ( 1 + self.ps_set.lifetime_random))) 16 | self.update_frame = self.end_frame - self.start_frame 17 | self.mesh_clone = self.ps_set.instance_object 18 | self.ms = [] 19 | 20 | def createMesh(self): 21 | self.emitter.select_set( False ) 22 | for p in self.ps.particles: 23 | loc = p.location 24 | rot = p.rotation.to_euler() 25 | o = self.mesh_clone.copy() 26 | bpy.context.view_layer.active_layer_collection.collection.objects.link(o) 27 | o.scale[0] = p.size 28 | o.scale[1] = p.size 29 | o.scale[2] = p.size 30 | o.select_set( True ) 31 | bpy.context.view_layer.objects.active = o 32 | bpy.ops.rigidbody.object_add() 33 | o.location = loc 34 | o.rotation_euler= rot 35 | o.rigid_body.kinematic=True 36 | o.rigid_body.restitution = .2 37 | o.keyframe_insert('rigid_body.kinematic') 38 | o.select_set( False ) 39 | self.ms.append( o ) 40 | 41 | def moveMesh(self): 42 | for i, p in enumerate( self.ps.particles ): 43 | o = self.ms[i] 44 | if p.alive_state == 'DEAD': 45 | if o.rigid_body.kinematic: 46 | o.rigid_body.kinematic=False 47 | o.keyframe_insert('rigid_body.kinematic') 48 | elif p.alive_state == 'ALIVE': 49 | o.location = p.location 50 | o.rotation_euler = p.rotation.to_euler() 51 | o.keyframe_insert('location') 52 | o.keyframe_insert('scale') 53 | o.keyframe_insert('rotation_euler') 54 | else: 55 | o.location = p.location 56 | o.rotation_euler = p.rotation.to_euler() 57 | 58 | def clearSetting(self): 59 | self.ms = [] 60 | 61 | def update( self, f ): 62 | if f == self.start_frame: 63 | self.clearSetting() 64 | self.createMesh() 65 | elif f == self.end_frame - 1: 66 | self.moveMesh() 67 | self.clearSetting() 68 | else: 69 | self.moveMesh() 70 | 71 | def executeAll(self): 72 | for i in range( self.update_frame ): 73 | f = self.start_frame + i 74 | bpy.context.scene.frame_set(f) 75 | self.update(f) 76 | print( 'frame solved:', f ) 77 | 78 | def execute(self, context): 79 | if context.view_layer.objects.active == None: 80 | self.report( {'ERROR'}, 'please pick one object!' ) 81 | return {'FINISHED'} 82 | elif len( context.view_layer.objects.active.particle_systems ) == 0: 83 | self.report( {'ERROR'}, 'need particle system!' ) 84 | return {'FINISHED'} 85 | elif context.view_layer.objects.active.particle_systems[0].settings.instance_object == None: 86 | self.report( {'ERROR'}, 'particle system duplicate object need to be setting!' ) 87 | return {'FINISHED'} 88 | else: 89 | self.setting(context.view_layer.objects.active) 90 | self.executeAll() 91 | return {'FINISHED'} -------------------------------------------------------------------------------- /vicTools/operators/SelectByName.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | 3 | class vic_select_by_name(bpy.types.Operator): 4 | bl_idname = 'vic.select_by_name' 5 | bl_label = 'Select By Name' 6 | bl_description = 'Select By Name' 7 | 8 | def execute(self, context): 9 | select_name = context.scene.action_properties.string_select_name 10 | for b in bpy.data.objects: 11 | find_str = b.name.find( select_name ) 12 | b.select_set( False ) 13 | if find_str != -1: 14 | b.hide_viewport = False 15 | b.select_set( True ) 16 | return {'FINISHED'} -------------------------------------------------------------------------------- /vicTools/vic_actions.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | 3 | from .operators import ( 4 | CreateCameraTarget, 5 | MirrorCubeAdd, 6 | SelectByName, 7 | HandDrag, 8 | ParticleToRigidbody, 9 | MeshFlatten, 10 | LineAlign 11 | ) 12 | 13 | class ActionProperties(bpy.types.PropertyGroup): 14 | string_select_name:bpy.props.StringProperty( name="", description="Name of select objects", default="") 15 | 16 | class VIC_PT_ACTION_PANEL(bpy.types.Panel): 17 | bl_category = "Vic Tools" 18 | bl_space_type = "VIEW_3D" 19 | bl_region_type = "UI" 20 | bl_label = "Actions" 21 | 22 | def draw(self, context): 23 | layout = self.layout 24 | 25 | col = layout.column(align=True) 26 | col.operator(MirrorCubeAdd.mirror_cube_add.bl_idname) 27 | col.operator(CreateCameraTarget.vic_create_camera_target.bl_idname) 28 | col.operator(MeshFlatten.vic_make_meshs_plane.bl_idname) 29 | col.operator(ParticleToRigidbody.ParticlesToRigidbodys.bl_idname) 30 | 31 | row = col.row(align=True) 32 | row.prop(context.scene.action_properties, 'string_select_name' ) 33 | row.operator(SelectByName.vic_select_by_name.bl_idname) 34 | 35 | col.label(text='Drag Effect') 36 | col.operator(HandDrag.vic_hand_drag.bl_idname) 37 | col.operator(HandDrag.vic_healing_all_effect_objects.bl_idname) 38 | 39 | col.label(text='Align') 40 | col.operator(LineAlign.vic_line_align.bl_idname) 41 | 42 | classes = ( 43 | # ui 44 | ActionProperties, 45 | VIC_PT_ACTION_PANEL, 46 | 47 | # operation 48 | CreateCameraTarget.vic_create_camera_target, 49 | MirrorCubeAdd.mirror_cube_add, 50 | SelectByName.vic_select_by_name, 51 | HandDrag.vic_hand_drag, 52 | HandDrag.vic_healing_all_effect_objects, 53 | ParticleToRigidbody.ParticlesToRigidbodys, 54 | MeshFlatten.vic_make_meshs_plane, 55 | LineAlign.vic_line_align 56 | ) 57 | def register(): 58 | for cls in classes: bpy.utils.register_class(cls) 59 | bpy.types.Scene.action_properties = bpy.props.PointerProperty(type=ActionProperties) 60 | 61 | def unregister(): 62 | for cls in classes: bpy.utils.unregister_class(cls) 63 | del bpy.types.Scene.action_properties -------------------------------------------------------------------------------- /vicTools/vic_make_it_voxel.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from mathutils import geometry 3 | from mathutils import Vector 4 | from mathutils import Color 5 | from numpy import arange 6 | import time 7 | import platform 8 | 9 | voxel_name = 'Voxel_map' 10 | ray_dir = [Vector([1,0,0]),Vector([0,1,0]),Vector([0,0,1])] 11 | 12 | def collectVertexColor( mesh, color_layer ): 13 | ret = {} 14 | i = 0 15 | for poly in mesh.polygons: 16 | for idx in poly.loop_indices: 17 | loop = mesh.loops[idx] 18 | v = loop.vertex_index 19 | linked = ret.get(v, []) 20 | linked.append(color_layer.data[i].color) 21 | ret[v] = linked 22 | i += 1 23 | return ret 24 | 25 | def avg_col(cols): 26 | avg_col = Color((0.0, 0.0, 0.0)) 27 | for col in cols: 28 | avg_col[0] += col[0]/len(cols) 29 | avg_col[1] += col[1]/len(cols) 30 | avg_col[2] += col[2]/len(cols) 31 | return avg_col 32 | 33 | class vic_make_it_voxel(bpy.types.Operator): 34 | bl_idname = 'vic.make_it_voxel' 35 | bl_label = 'Make It Voxel' 36 | 37 | proxy = None 38 | obj = None 39 | attach = None 40 | size = None 41 | obj_vertex_color = None 42 | create_voxel = [] 43 | color_map = None 44 | simple_way = True 45 | cubes_map = {} 46 | 47 | def getCubeByColor(self, color, temp_map): 48 | color_str = str(color) 49 | self.createVoxel( self.size, color ) 50 | new_obj = bpy.context.object 51 | #if self.bool_voxel_animation: 52 | # new_obj.scale.x = new_obj.scale.y = new_obj.scale.z = 1 53 | if color_str in self.cubes_map: 54 | self.cubes_map[color_str].append( new_obj ) 55 | else: 56 | self.cubes_map[color_str] = [new_obj] 57 | return new_obj 58 | 59 | def createVoxel( self, resize, color ): 60 | resize = resize / 2 61 | bpy.ops.mesh.primitive_cube_add() 62 | 63 | if self.hasColorMap(): 64 | bpy.context.object.data.vertex_colors.new() 65 | bpy.context.object.data.vertex_colors['Col'].name = voxel_name 66 | for c in bpy.context.object.data.vertex_colors[voxel_name].data: 67 | c.color[0] = color[0] 68 | c.color[1] = color[1] 69 | c.color[2] = color[2] 70 | 71 | bpy.ops.object.editmode_toggle() 72 | bpy.ops.transform.resize(value=(resize, resize, resize), constraint_axis=(False, False, False), constraint_orientation='GLOBAL', mirror=False, proportional='DISABLED', proportional_edit_falloff='SMOOTH', proportional_size=1) 73 | bpy.ops.object.editmode_toggle() 74 | 75 | def placeCubeAndSetColor( self, cube, loc, resize, color ): 76 | resize = resize / 2 77 | cube.select_set( True ) 78 | cube.location = loc 79 | bpy.context.view_layer.objects.active = cube 80 | 81 | if not voxel_name in bpy.context.object.data.vertex_colors: 82 | bpy.context.object.data.vertex_colors.new() 83 | bpy.context.object.data.vertex_colors['Col'].name = voxel_name 84 | for c in bpy.context.object.data.vertex_colors[voxel_name].data: 85 | c.color[0] = color[0] 86 | c.color[1] = color[1] 87 | c.color[2] = color[2] 88 | 89 | def checkHitAndCreateVoxel( self, temp_cubes ): 90 | v1 = self.proxy.location 91 | v2 = self.obj.location 92 | 93 | pos = None 94 | hit = False 95 | id = None 96 | if self.simple_way: 97 | hit_simple, pos, normal, id_simple = self.obj.ray_cast( self.obj.matrix_world.inverted() * v1, v2-v1 ) 98 | pos = self.obj.matrix_world * pos 99 | hit = hit_simple 100 | id = id_simple 101 | else: 102 | hit_data = [] 103 | use_dir = ray_dir.copy() 104 | use_dir.append( v2-v1 ) 105 | for dir in use_dir: 106 | hit, pos, normal, id = self.obj.ray_cast( self.obj.matrix_world.inverted() @ v1, dir ) 107 | if hit: 108 | hit_data.append( [pos, id] ) 109 | if len( hit_data ) > 0: 110 | hit = True 111 | min_dist = 1000000 112 | for h_p in hit_data: 113 | dist_proxy = (h_p[0] - self.obj.matrix_world.inverted() @ v1).magnitude 114 | if dist_proxy < min_dist: 115 | min_dist = dist_proxy 116 | pos = self.obj.matrix_world @ h_p[0] 117 | id = h_p[1] 118 | if hit: 119 | dist = ( pos - self.proxy.location ).magnitude 120 | if dist < (self.size if self.sensor_as_size else self.sensor_distance): 121 | color = Color() 122 | if self.hasColorMap(): 123 | color_id = self.obj.data.polygons[id].vertices[0] 124 | color = self.obj_vertex_color[color_id] 125 | c = self.getCubeByColor( color, temp_cubes ) 126 | if not 'Voxel_material' in c.data.materials: 127 | c.data.materials.append( bpy.data.materials['Voxel_material'] ) 128 | self.placeCubeAndSetColor( c, self.proxy.location, self.size, color ) 129 | 130 | 131 | def hasColorMap( self ): 132 | return self.color_map in self.obj.data.vertex_colors 133 | 134 | def getVoxelCount( self, temp ): 135 | count = 0 136 | for ary in temp: 137 | for v in temp[ary]: 138 | count += 1 139 | return count 140 | 141 | def doVoxel(self, scene): 142 | 143 | bounding_box = self.obj.bound_box 144 | border_size = self.voxel_border_distance 145 | 146 | min_x = 0 147 | min_y = 0 148 | min_z = 0 149 | max_x = 0 150 | max_y = 0 151 | max_z = 0 152 | 153 | for v in self.obj.bound_box: 154 | globalV = self.obj.matrix_world @ Vector( v ) 155 | 156 | now_x = globalV[0] 157 | now_y = globalV[1] 158 | now_z = globalV[2] 159 | 160 | if now_x < min_x: 161 | min_x = now_x 162 | if now_x > max_x: 163 | max_x = now_x 164 | if now_y < min_y: 165 | min_y = now_y 166 | if now_y > max_y: 167 | max_y = now_y 168 | if now_z < min_z: 169 | min_z = now_z 170 | if now_z > max_z: 171 | max_z = now_z 172 | 173 | obj_faces_normal = [] 174 | obj_faces_vertex = [] 175 | 176 | for m in self.obj.data.polygons: 177 | face_normal = m.normal 178 | face_vertex = self.obj.matrix_world @ self.obj.data.vertices[m.vertices[0]].co 179 | obj_faces_normal.append( face_normal ) 180 | obj_faces_vertex.append( face_vertex ) 181 | 182 | self.create_voxel = [] 183 | 184 | usingForPop = {} 185 | for color_str in self.cubes_map: 186 | usingForPop[color_str] = self.cubes_map[color_str].copy() 187 | 188 | for i in arange( min_x, max_x, self.size ): 189 | print( 'Create Voxles: ' + str(int(( i - min_x ) / ( max_x - min_x ) * 100)) + '%' ) 190 | for j in arange( min_y, max_y, self.size ): 191 | for k in arange( min_z, max_z, self.size ): 192 | self.proxy.location = Vector([i, j, k]) 193 | self.checkHitAndCreateVoxel(usingForPop) 194 | 195 | def addHideShowKey( self,obj, f): 196 | # key as visible on the current frame 197 | # insert keys in the hide_viewport have some bug in blender 2.8 198 | obj.hide_viewport = False 199 | obj.hide_render = False 200 | obj.keyframe_insert('hide_viewport',frame=f+1) 201 | obj.keyframe_insert('hide_render',frame=f+1) 202 | # hide it 203 | obj.hide_viewport = True 204 | obj.hide_render = True 205 | # key as hidden on the previous frame 206 | if f != self.int_frame_start: 207 | obj.keyframe_insert('hide_viewport',frame=f) 208 | obj.keyframe_insert('hide_render',frame=f) 209 | # key as hidden on the next frame 210 | if f != self.int_frame_end - 1: 211 | obj.keyframe_insert('hide_viewport',frame=f+2) 212 | obj.keyframe_insert('hide_render',frame=f+2) 213 | 214 | def createOneFrame( self, f ): 215 | if self.attach: 216 | self.createVoxel( self.size, Color() ) 217 | real = bpy.context.object 218 | real.name = self.obj.name + '_Voxels_Frame_' + str( f ) 219 | real.location = self.obj.location 220 | bpy.ops.object.editmode_toggle() 221 | bpy.ops.mesh.delete(type='VERT') 222 | bpy.ops.object.editmode_toggle() 223 | 224 | self.doVoxel(bpy.context.scene) 225 | 226 | if self.attach: 227 | for o in bpy.data.objects: 228 | o.select_set( False ) 229 | for color_str in self.cubes_map: 230 | for c in self.cubes_map[color_str]: 231 | c.select_set( True ) 232 | real.select_set( True ) 233 | bpy.context.view_layer.objects.active = real 234 | bpy.ops.object.join() 235 | if self.bool_voxel_animation: 236 | self.addHideShowKey( real, f ) 237 | 238 | self.cubes_map = {} 239 | 240 | def execute(self, context): 241 | 242 | if context.object == None: 243 | return {'FINISHED'} 244 | else: 245 | if not hasattr( context.object.data, 'vertices' ): 246 | return {'FINISHED'} 247 | 248 | if platform.system() == 'Windows': 249 | bpy.ops.wm.console_toggle() 250 | 251 | self.cubes_map = {} 252 | 253 | self.size = bpy.context.scene.VoxelProperties.float_voxel_size 254 | self.color_map = bpy.context.scene.VoxelProperties.string_voxel_color_map 255 | self.simple_way = False 256 | self.sensor_distance = bpy.context.scene.VoxelProperties.float_voxel_sensor_distance 257 | self.sensor_as_size = bpy.context.scene.VoxelProperties.bool_sensor_as_size 258 | self.voxel_border_distance = 1 259 | self.obj = bpy.context.object 260 | self.int_frame_start = bpy.context.scene.VoxelProperties.int_frame_start 261 | self.int_frame_end = bpy.context.scene.VoxelProperties.int_frame_end 262 | self.bool_voxel_animation = bpy.context.scene.VoxelProperties.bool_voxel_animation 263 | self.attach = bpy.context.scene.VoxelProperties.bool_voxel_attach 264 | if self.bool_voxel_animation: 265 | self.attach = True 266 | 267 | if self.int_frame_end <= self.int_frame_start: 268 | self.int_frame_end = self.int_frame_start + 1 269 | 270 | if self.hasColorMap(): 271 | collect_color = collectVertexColor( self.obj.data, self.obj.data.vertex_colors[self.color_map] ) 272 | self.obj_vertex_color = [avg_col(v) for k, v in collect_color.items() ] 273 | 274 | if not 'Voxel_material' in bpy.data.materials: 275 | bpy.data.materials.new(name="Voxel_material") 276 | #bpy.data.materials['Voxel_material'].use_vertex_color_paint = True 277 | 278 | self.createVoxel( self.size, Color() ) 279 | self.proxy = bpy.context.object 280 | 281 | if self.bool_voxel_animation: 282 | for i in range( self.int_frame_start, self.int_frame_end ): 283 | bpy.context.scene.frame_current = i 284 | 285 | bpy.context.view_layer.objects.active = self.obj 286 | self.obj.select_set( True ) 287 | 288 | bpy.ops.object.paths_calculate() 289 | self.createOneFrame( bpy.context.scene.frame_current ) 290 | 291 | bpy.context.view_layer.objects.active = self.obj 292 | self.obj.select_set( True ) 293 | bpy.ops.object.paths_clear() 294 | print( 'Frame Complete: ' + str( i ) ) 295 | else: 296 | self.createOneFrame( bpy.context.scene.frame_current ) 297 | 298 | for o in bpy.data.objects: 299 | o.select_set( False ) 300 | self.proxy.select_set( True ) 301 | bpy.ops.object.delete() 302 | 303 | if platform.system() == 'Windows': 304 | bpy.ops.wm.console_toggle() 305 | return {'FINISHED'} 306 | 307 | class VoxelProperties(bpy.types.PropertyGroup): 308 | int_frame_start = bpy.props.IntProperty( 309 | name="Start Frame", description="Start frame of animation", 310 | default=1, step=1, min=1, max=100000) 311 | 312 | int_frame_end = bpy.props.IntProperty( 313 | name="End Frame", description="End frame of animation", 314 | default=2, step=1, min=2, max=100000) 315 | 316 | bool_voxel_animation = bpy.props.BoolProperty( 317 | name="Animation", description="", 318 | default=False) 319 | 320 | ''' 321 | int_voxel_pool = bpy.props.IntProperty( 322 | name="Count", description="Count of Voxel", 323 | default=10, step=1, min=10, max=100000) 324 | 325 | bool_voxel_simple_way = bpy.props.BoolProperty( 326 | name="Simple Way", description="Simple way for sensor", 327 | default=True) 328 | ''' 329 | bool_voxel_attach = bpy.props.BoolProperty( 330 | name="Single Voxel", description="Attach all voxels together", 331 | default=True) 332 | 333 | bool_sensor_as_size = bpy.props.BoolProperty( 334 | name="Sensor As Size", description="As same as size", 335 | default=True) 336 | 337 | float_voxel_size = bpy.props.FloatProperty( 338 | name="Size", description="Size for voxel.", 339 | default=1.0, step=1.0, min=0.1, max=100.0) 340 | 341 | float_voxel_sensor_distance = bpy.props.FloatProperty( 342 | name="Sensor", description="Sensor distance for ray.", 343 | default=1.0, step=1.0, min=0.01, max=100.0) 344 | ''' 345 | float_voxel_border_distance = bpy.props.FloatProperty( 346 | name="Border Size Scale", description="Border size scale value, less value more speed", 347 | default=2.0, step=1.0, min=1.0, max=4.0) 348 | ''' 349 | string_voxel_color_map = bpy.props.StringProperty( 350 | name="Color Map", description="Color map for voxel", default="Col") 351 | 352 | class VIC_VOXEL_PANEL(bpy.types.Panel): 353 | bl_category = "Vic Tools" 354 | bl_space_type = "VIEW_3D" 355 | bl_region_type = "UI" 356 | bl_label = "Voxel Tool" 357 | 358 | def draw(self, context): 359 | layout = self.layout 360 | 361 | col = layout.column(align=True) 362 | col.operator("vic.make_it_voxel") 363 | 364 | #col.prop(context.scene, 'int_voxel_pool' ) 365 | col.prop(context.scene.VoxelProperties, 'float_voxel_size' ) 366 | col.prop(context.scene.VoxelProperties, 'float_voxel_sensor_distance' ) 367 | #col.prop(context.scene, 'float_voxel_border_distance' ) 368 | col.prop(context.scene.VoxelProperties, 'string_voxel_color_map' ) 369 | col.prop(context.scene.VoxelProperties, 'bool_sensor_as_size' ) 370 | col.prop(context.scene.VoxelProperties, 'bool_voxel_attach' ) 371 | col.prop(context.scene.VoxelProperties, 'bool_voxel_animation' ) 372 | col.prop(context.scene.VoxelProperties, 'int_frame_start' ) 373 | col.prop(context.scene.VoxelProperties, 'int_frame_end' ) 374 | 375 | #col.prop(context.scene, 'bool_voxel_simple_way' ) 376 | 377 | classes = ( 378 | # ui 379 | VoxelProperties, 380 | VIC_VOXEL_PANEL, 381 | 382 | # operation 383 | vic_make_it_voxel, 384 | ) 385 | def register(): 386 | for cls in classes: bpy.utils.register_class(cls) 387 | bpy.types.Scene.VoxelProperties = bpy.props.PointerProperty(type=VoxelProperties) 388 | 389 | def unregister(): 390 | for cls in classes: bpy.utils.unregister_class(cls) 391 | del bpy.types.Scene.VoxelProperties -------------------------------------------------------------------------------- /vicTools/vic_spring_bone.py: -------------------------------------------------------------------------------- 1 | 2 | import bpy, math 3 | from mathutils import Vector 4 | from mathutils import Matrix 5 | from mathutils import Quaternion 6 | 7 | def clearAllBonesKey( bones, start_frame, end_frame): 8 | for f in range( start_frame, end_frame ): 9 | for b in bones: 10 | try: 11 | b.keyframe_delete(data_path="rotation_quaternion" ,frame=f) 12 | except RuntimeError: 13 | print( "no action!" ) 14 | b.rotation_quaternion = Quaternion( Vector(), 1 ) 15 | 16 | def getLocalQuaternion( m, axis, angle ): 17 | b_rot_mat = getRotationMatrixFromMatrix( m ) 18 | localAxis = b_rot_mat.inverted() * axis 19 | return Quaternion (localAxis, angle) 20 | 21 | def getRotationMatrixFromMatrix( m ): 22 | return m.to_quaternion().to_matrix().to_4x4().copy() 23 | 24 | def getTailMatrix( body, bone, pos ): 25 | qua_mat = body.matrix_world @ bone.matrix 26 | qua_mat = qua_mat.to_quaternion().to_matrix().to_4x4() 27 | pos_mat = Matrix.Translation( body.matrix_world @ pos ) 28 | return pos_mat @ qua_mat 29 | 30 | def collectBonesFromRoot( root_bone ): 31 | bones = [] 32 | temp_bone = root_bone 33 | while( len( temp_bone.children ) != 0 ): 34 | bones.append( temp_bone.children[0] ) 35 | temp_bone = temp_bone.children[0] 36 | return bones 37 | 38 | def getBoneRelativeData( root, pts): 39 | pos_data = [] 40 | dist_data = [] 41 | 42 | for i, p in enumerate( pts, 0 ): 43 | first_point = None 44 | if i == 0: 45 | first_point = root 46 | else: 47 | first_point = pts[i-1] 48 | pos_data.append( first_point.inverted().copy() @ p.to_translation().copy() ) 49 | dist_data.append( (first_point.to_translation().copy() - p.to_translation().copy()).magnitude ) 50 | return pos_data, dist_data 51 | 52 | def saveRootPosition( root, root_locs ): 53 | root_locs.append( root.to_translation().copy() ) 54 | if len( root_locs ) > 100: 55 | root_locs.pop(0) 56 | 57 | def getDiffPosition(root_locs, prev_count): 58 | last = len(root_locs)-1 59 | target = last - prev_count 60 | if target < 0: 61 | return Vector() 62 | if len( root_locs ) < target + 1: 63 | return Vector() 64 | pa = root_locs[target] 65 | pb = root_locs[target-1] 66 | return pa-pb 67 | 68 | def setTranslationForMatrix( mat, pos ): 69 | return Matrix.Translation( pos ) @ mat.to_quaternion().to_matrix().to_4x4() 70 | 71 | def setRotationForMatrix( mat, qua ): 72 | return Matrix.Translation( mat.to_translation() ) @ qua.to_matrix().to_4x4() 73 | 74 | def addForce(pts, pts_spd, root, root_locs, pos_data, gravity): 75 | for i, p in enumerate( pts, 0 ): 76 | first_point = None 77 | if i == 0: 78 | first_point = root 79 | else: 80 | first_point = pts[i-1] 81 | 82 | back_pos = first_point @ pos_data[i].copy() 83 | back_force = back_pos - p.to_translation().copy() 84 | 85 | if bpy.context.scene.SpringBoneProperties.spring_bone_keep_is_spring: 86 | spd = pts_spd[i] 87 | 88 | diff = getDiffPosition( root_locs, i * 3 ) 89 | 90 | spd += diff * bpy.context.scene.SpringBoneProperties.spring_bone_extend_factor 91 | spd += back_force * bpy.context.scene.SpringBoneProperties.spring_bone_spring_factor 92 | spd += gravity * bpy.context.scene.SpringBoneProperties.spring_bone_gravity_factor 93 | spd *= bpy.context.scene.SpringBoneProperties.spring_bone_friction_factor 94 | pts[i] = setTranslationForMatrix( p, p.to_translation() + spd ) 95 | else: 96 | pts[i] = setTranslationForMatrix( p, p.to_translation() + back_force * bpy.context.scene.SpringBoneProperties.spring_bone_spring_factor ) 97 | 98 | def setRotation(root, pts, up_vec): 99 | for i, p in enumerate( pts, 0 ): 100 | first_point = None 101 | if i == 0: 102 | first_point = root 103 | else: 104 | first_point = pts[i-1] 105 | 106 | z_vec = ( first_point.to_translation() - p.to_translation() ).normalized() 107 | rot_quat = z_vec.to_track_quat('-Y', 'Z') 108 | pts[i] = setRotationForMatrix( p, rot_quat ) 109 | 110 | # another method I offen used 111 | ''' 112 | z_vec = ( first_point.to_translation() - p.to_translation() ).normalized() 113 | spin_vec = z_vec.cross( up_vec ).normalized() 114 | spin_angle = z_vec.angle( up_vec ) 115 | pts[i] = setRotationForMatrix( p, Quaternion( spin_vec, spin_angle ).inverted() ) 116 | ''' 117 | 118 | def limitDistance(root, pts, pts_len): 119 | for i, p in enumerate( pts, 0 ): 120 | first_point = None 121 | if i == 0: 122 | first_point = root 123 | else: 124 | first_point = pts[i-1] 125 | len = pts_len[i] 126 | pts[i] = setTranslationForMatrix( p, ( p.to_translation() - first_point.to_translation() ).normalized() * len + first_point.to_translation() ) 127 | 128 | def syncToDebugView( f, start_frame, body, debug_views, pts ): 129 | for i, v in enumerate( debug_views, 0 ): 130 | v.matrix_world = pts[i] 131 | if f >= start_frame: 132 | v.keyframe_insert(data_path="rotation_quaternion" ,frame=f) 133 | v.keyframe_insert(data_path="location" ,frame=f) 134 | 135 | def mapToBone( f, start_frame, body, root, root_bone, bones, pts ): 136 | for i, b in enumerate( bones, 0 ): 137 | b.matrix = body.matrix_world.inverted() @ pts[i] 138 | if f >= start_frame: 139 | b.keyframe_insert(data_path="rotation_quaternion" ,frame=f) 140 | 141 | # this update is very important, blender will update matrix with this function call, if not call will occur strange performance 142 | bpy.context.scene.update_tag() 143 | 144 | # another method, using quaternion 145 | ''' 146 | global_proxy_qua = pts[i].to_quaternion() 147 | global_bone_gua = ( body.matrix_world * b.matrix ).to_quaternion() 148 | global_diff_qua = global_bone_gua.inverted() * global_proxy_qua 149 | 150 | b.rotation_quaternion *= global_diff_qua 151 | 152 | if f >= start_frame: 153 | b.keyframe_insert(data_path="rotation_quaternion" ,frame=f) 154 | ''' 155 | 156 | debugView = False 157 | 158 | class vic_spring_bone(bpy.types.Operator): 159 | bl_idname = 'vic.spring_bone' 160 | bl_label = 'Bake Spring Bone' 161 | bl_description = 'Please select bone in the pose mode' 162 | 163 | def process(self,context): 164 | 165 | objs = bpy.data.objects 166 | body = bpy.context.object 167 | 168 | up_vec = Vector([0, -1, 0]) 169 | gravity = Vector([0,0,-1]) 170 | 171 | start_frame = context.scene.SpringBoneProperties.spring_bone_frame_start 172 | end_frame = context.scene.SpringBoneProperties.spring_bone_frame_end 173 | 174 | selected_pose_bones = context.selected_pose_bones 175 | 176 | for root_bone in selected_pose_bones: 177 | root = getTailMatrix( body, root_bone, root_bone.tail ) 178 | bones = collectBonesFromRoot( root_bone ) 179 | 180 | pts = [ getTailMatrix( body, b, b.tail ) for b in bones ] 181 | pts_spd = [Vector() for p in pts] 182 | 183 | # for prev force 184 | root_locs = [] 185 | 186 | # set new mat for relative data 187 | setRotation( root, pts, up_vec ) 188 | 189 | # save relative data for children 190 | pos_data, dist_data = getBoneRelativeData( root, pts ) 191 | 192 | # debug view 193 | # maybe will add new method for create fake bone 194 | 195 | if debugView: 196 | debug_views = [] 197 | for b in bones: 198 | bpy.ops.mesh.primitive_cone_add() 199 | bpy.context.object.rotation_mode = 'QUATERNION' 200 | #bpy.context.object.name = 'abc' 201 | #bpy.ops.transform.resize(value=(.1,.1,.1)) 202 | debug_views.append( bpy.context.object ) 203 | 204 | for i in range( start_frame, end_frame ): 205 | bpy.context.scene.frame_set( i ) 206 | 207 | root = getTailMatrix( body, root_bone, root_bone.tail ) 208 | saveRootPosition( root, root_locs ) 209 | setRotation( root, pts, up_vec ) 210 | addForce( pts, pts_spd, root, root_locs, pos_data, gravity ) 211 | limitDistance( root, pts, dist_data ) 212 | mapToBone( i, start_frame, body, root, root_bone, bones, pts ) 213 | 214 | # maybe will add new method for create fake bone 215 | if debugView: 216 | syncToDebugView( i, start_frame, body, debug_views, pts ) 217 | 218 | print( 'On Bone: ' + root_bone.name + ', Frame Complete: ' + str( i ) ) 219 | bpy.context.scene.frame_set( 1 ) 220 | 221 | def execute(self, context): 222 | if context.object == None: 223 | return {'FINISHED'} 224 | else: 225 | if not hasattr( context.object, 'pose' ): 226 | return {'FINISHED'} 227 | if context.active_pose_bone == None: 228 | return {'FINISHED'} 229 | self.process( context ) 230 | return {'FINISHED'} 231 | 232 | class vic_bones_clear_key(bpy.types.Operator): 233 | bl_idname = 'vic.bones_clear_key' 234 | bl_label = 'Clear Children Keys' 235 | bl_description = 'Clear all child bone keys of the target bone' 236 | 237 | def process( self, context ): 238 | selected_pose_bones = context.selected_pose_bones 239 | 240 | for root_bone in selected_pose_bones: 241 | bones = collectBonesFromRoot( root_bone ) 242 | 243 | start_frame = context.scene.SpringBoneProperties.spring_bone_frame_start 244 | end_frame = context.scene.SpringBoneProperties.spring_bone_frame_end 245 | 246 | clearAllBonesKey( bones, start_frame, end_frame ) 247 | 248 | def execute(self, context): 249 | if context.object == None: 250 | return {'FINISHED'} 251 | else: 252 | if not hasattr( context.object, 'pose' ): 253 | return {'FINISHED'} 254 | if context.active_pose_bone == None: 255 | return {'FINISHED'} 256 | self.process( context ) 257 | return {'FINISHED'} 258 | 259 | class VIC_PT_SPRING_BONE_TOOL(bpy.types.Panel): 260 | bl_category = "Vic Tools" 261 | bl_space_type = "VIEW_3D" 262 | bl_region_type = "UI" 263 | bl_label = "Spring Bone Tool" 264 | 265 | def draw(self, context): 266 | layout = self.layout 267 | 268 | col = layout.column(align=True) 269 | col.operator("vic.spring_bone") 270 | col.operator(vic_bones_clear_key.bl_idname) 271 | col.prop(context.scene.SpringBoneProperties, 'spring_bone_frame_start' ) 272 | col.prop(context.scene.SpringBoneProperties, 'spring_bone_frame_end' ) 273 | col = layout.column(align=True) 274 | col.prop(context.scene.SpringBoneProperties, 'spring_bone_extend_factor' ) 275 | col.prop(context.scene.SpringBoneProperties, 'spring_bone_spring_factor' ) 276 | col.prop(context.scene.SpringBoneProperties, 'spring_bone_gravity_factor' ) 277 | col.prop(context.scene.SpringBoneProperties, 'spring_bone_friction_factor' ) 278 | col.prop(context.scene.SpringBoneProperties, 'spring_bone_keep_is_spring' ) 279 | # col = layout.column(align=True) 280 | # col.prop(context.scene, 'spring_bone_roop_gravity', 'Roop Gravity' ) 281 | 282 | #======================================= 283 | 284 | class SpringBoneProperties(bpy.types.PropertyGroup): 285 | spring_bone_extend_factor:bpy.props.FloatProperty( 286 | name='Extend', 287 | default=0.0, 288 | min=0.0, 289 | max=1.0 290 | ) 291 | 292 | spring_bone_spring_factor:bpy.props.FloatProperty( 293 | name='Keep', 294 | default=.6, 295 | min=0.0, 296 | max=1.0 297 | ) 298 | 299 | spring_bone_gravity_factor:bpy.props.FloatProperty( 300 | name='Gravity', 301 | default=0.0, 302 | min=0.0, 303 | max=1.0 304 | ) 305 | 306 | spring_bone_friction_factor:bpy.props.FloatProperty( 307 | name='Friction', 308 | default=0.5, 309 | min=0.0, 310 | max=1.0 311 | ) 312 | 313 | spring_bone_keep_is_spring:bpy.props.BoolProperty( 314 | name='Spring', 315 | default=True) 316 | 317 | spring_bone_frame_start:bpy.props.IntProperty( 318 | name="Start Frame", description="Start frame of simulation", 319 | default=1, step=1, min=1, max=100000) 320 | 321 | spring_bone_frame_end:bpy.props.IntProperty( 322 | name="End Frame", description="End frame of simulation", 323 | default=50, step=1, min=2, max=100000) 324 | 325 | classes = ( 326 | # ui 327 | SpringBoneProperties, 328 | VIC_PT_SPRING_BONE_TOOL, 329 | 330 | # operation 331 | vic_bones_clear_key, 332 | vic_spring_bone, 333 | ) 334 | def register(): 335 | for cls in classes: bpy.utils.register_class(cls) 336 | bpy.types.Scene.SpringBoneProperties = bpy.props.PointerProperty(type=SpringBoneProperties) 337 | 338 | def unregister(): 339 | for cls in classes: bpy.utils.unregister_class(cls) 340 | del bpy.types.Scene.SpringBoneProperties -------------------------------------------------------------------------------- /vicTools/vic_tools.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from mathutils import * 3 | 4 | def scaleObjVertex(obj, scale): 5 | for v in obj.data.vertices: 6 | v.co.x = v.co.x * scale[0] 7 | v.co.y = v.co.y * scale[1] 8 | v.co.z = v.co.z * scale[2] 9 | 10 | def joinObj( joinList, target ): 11 | focusObject(target) 12 | for obj in joinList: 13 | obj.select_set(True) 14 | bpy.ops.object.join() 15 | bpy.ops.object.origin_set(type='ORIGIN_CENTER_OF_MASS', center='MEDIAN') 16 | 17 | def copyToScene( prefab, sameData = False ): 18 | obj = copyObject(prefab, sameData) 19 | addObject(obj) 20 | return obj 21 | 22 | def addObject( obj ): 23 | #bpy.context.view_layer.active_layer_collection.collection.objects.link(obj) 24 | bpy.context.collection.objects.link(obj) 25 | 26 | def activeObject( obj ): 27 | bpy.context.view_layer.objects.active = obj 28 | 29 | def updateMatrix(): 30 | bpy.context.view_layer.update() 31 | 32 | def copyObject(obj, sameData = False): 33 | newobj = obj.copy() 34 | if not sameData: 35 | newobj.data = obj.data.copy() 36 | newobj.animation_data_clear() 37 | return newobj 38 | 39 | def focusObject(focusObj): 40 | # unselect all of object, and then can join my own object 41 | for obj in bpy.data.objects: 42 | obj.select_set(False) 43 | focusObj.select_set(True) 44 | activeObject(focusObj) 45 | 46 | def addProps( target, name, value, override = False ): 47 | if not name in target or override: 48 | target[name] = value 49 | 50 | def getSelectedWithOrder(): 51 | count = 0 52 | 53 | if not bpy.context.active_object: return [] 54 | 55 | # start list with current active object: 56 | order = list([bpy.context.active_object.name]) 57 | 58 | #keep going back in time (undo) as long as objects are selected 59 | while len(bpy.context.selected_objects) >= 2: 60 | bpy.ops.ed.undo() 61 | order.insert(0,bpy.context.active_object.name) # add previous active object 62 | count += 1 63 | 64 | # Get back to the future (redo, redo, redo, ...) 65 | for x in range(0, count): 66 | bpy.ops.ed.redo() 67 | 68 | # Collect obj from order names 69 | objs = [] 70 | for name in order: 71 | for o in bpy.data.objects: 72 | if o.name == name: 73 | objs.append(o) 74 | continue 75 | return objs 76 | 77 | def prepareAndCreateMesh(name): 78 | verts = [] 79 | faces = [] 80 | uvsMap = {} 81 | matIds = [] 82 | 83 | mesh = bpy.data.meshes.new(name) 84 | obj = bpy.data.objects.new(name, mesh) 85 | addObject(obj) 86 | 87 | def clear(): 88 | nonlocal verts, faces, uvsMap, matIds, mesh 89 | verts = [] 90 | faces = [] 91 | uvsMap = {} 92 | matIds = [] 93 | mesh.clear_geometry() 94 | 95 | # 給定一條綫上的點,再給定一個偏移向量,用程式產生偏移過後的第二條綫段的點 96 | # 用兩條綫上的點來產生面 97 | # 搭配 prepareAndCreateMesh 一起使用 98 | def addVertexAndFaces( line, offset, uvLine, uvOffset, 99 | uvScale = 1, matId = 0, flip = False, close = False): 100 | line = line.copy() 101 | anotherLine = [] 102 | startId = len(verts) 103 | for i, v in enumerate(line): 104 | offsetVert = ( v[0] + offset[0], 105 | v[1] + offset[1], 106 | v[2] + offset[2]) 107 | anotherLine.append(offsetVert) 108 | 109 | # 收集面id 110 | v1 = startId+i 111 | v2 = v1+len(line) 112 | v3 = v2+1 113 | v4 = v1+1 114 | 115 | isLastFace = (i == len(line)-1) 116 | if isLastFace: 117 | if close: 118 | if flip: 119 | f = (v1,startId,startId+len(line),v2) 120 | else: 121 | f = (v1,v2,startId+len(line),startId) 122 | else: 123 | # last point, not need to create face 124 | continue 125 | else: 126 | if flip: 127 | f = (v1, v4, v3, v2) 128 | else: 129 | f = (v1, v2, v3, v4) 130 | 131 | currentFaceId = len(faces) 132 | uvsMap['%i_%i' % (currentFaceId, f[0])] = ( 133 | (uvLine[i][0])*uvScale, 134 | (uvLine[i][1])*uvScale 135 | ) 136 | uvsMap['%i_%i' % (currentFaceId, f[1])] = ( 137 | (uvLine[i+1][0])*uvScale, 138 | (uvLine[i+1][1])*uvScale 139 | ) 140 | uvsMap['%i_%i' % (currentFaceId, f[2])] = ( 141 | (uvLine[i+1][0]+uvOffset[0])*uvScale, 142 | (uvLine[i+1][1]+uvOffset[1])*uvScale 143 | ) 144 | uvsMap['%i_%i' % (currentFaceId, f[3])] = ( 145 | (uvLine[i][0]+uvOffset[0])*uvScale, 146 | (uvLine[i][1]+uvOffset[1])*uvScale 147 | ) 148 | 149 | faces.append(f) 150 | matIds.append(matId) 151 | 152 | line.extend(anotherLine) 153 | verts.extend(line) 154 | 155 | def addRectVertex( addverts, adduvs, 156 | uvScale = 1, matId = 0): 157 | 158 | startId = len(verts) 159 | face = [] 160 | currentFaceId = len(faces) 161 | for i, v in enumerate(addverts): 162 | verts.append(v) 163 | 164 | vid = i+startId 165 | face.append(vid) 166 | 167 | adduv = adduvs[i] 168 | uvsMap['%i_%i' % (currentFaceId, vid)] = ( 169 | adduv[0]*uvScale, 170 | adduv[1]*uvScale 171 | ) 172 | 173 | faces.append(tuple(face)) 174 | matIds.append(matId) 175 | 176 | # 搭配 prepareAndCreateMesh 一起使用 177 | def addVertexByMesh(mesh, matIdOffset = 0, transformVertex = None): 178 | currentFaceId = len(faces) 179 | currentVertId = len(verts) 180 | for m in mesh.data.polygons: 181 | vs = [] 182 | currentVertexCount = len(verts) 183 | for vid in m.vertices: 184 | vs.append(vid + currentVertexCount) 185 | faces.append(tuple(vs)) 186 | matIds.append(m.material_index+matIdOffset) 187 | 188 | for vert_idx, loop_idx in zip(m.vertices, m.loop_indices): 189 | uvsMap["%i_%i" % (m.index+currentFaceId,vert_idx+currentVertId)] = mesh.data.uv_layers.active.data[loop_idx].uv 190 | 191 | for vid,v in enumerate(mesh.data.vertices): 192 | if transformVertex: newpos = transformVertex(vid, v) 193 | else: newpos = (v.co.x, v.co.y, v.co.z) 194 | verts.append( newpos ) 195 | 196 | def update(): 197 | mesh.from_pydata(verts, [], faces) 198 | 199 | # assign uv 200 | obj.data.uv_layers.new() 201 | for i, face in enumerate(obj.data.polygons): 202 | for vert_idx, loop_idx in zip(face.vertices, face.loop_indices): 203 | uv = uvsMap['%i_%i' % (face.index, vert_idx)] 204 | obj.data.uv_layers.active.data[loop_idx].uv = uv 205 | face.material_index = matIds[i] 206 | 207 | return (obj, update, clear, addRectVertex, addVertexAndFaces, addVertexByMesh) 208 | 209 | def mergeOverlayVertex(obj): 210 | obj.select_set(True) 211 | bpy.context.view_layer.objects.active = obj 212 | bpy.ops.object.editmode_toggle() 213 | bpy.ops.mesh.select_all(action='SELECT') 214 | bpy.ops.mesh.remove_doubles() 215 | bpy.ops.object.editmode_toggle() 216 | 217 | # example: 218 | # curve = bpy.data.objects["BezierCurve"] 219 | # total_length, matrices = getCurvePosAndLength(curve, 10) 220 | def getCurvePosAndLength(curve, count): 221 | if bpy.context.area.type != 'VIEW_3D': return (0, []) 222 | if bpy.context.mode != 'OBJECT': return (0, []) 223 | 224 | current_focus = bpy.context.object 225 | 226 | bpy.ops.object.empty_add(type='ARROWS') 227 | bpy.ops.object.location_clear() 228 | bpy.ops.object.constraint_add(type='FOLLOW_PATH') 229 | constraint = bpy.context.object.constraints["Follow Path"] 230 | constraint.target = curve 231 | constraint.forward_axis = 'FORWARD_X' 232 | constraint.up_axis = 'UP_Z' 233 | constraint.use_fixed_location = True 234 | constraint.use_curve_follow = True 235 | 236 | total_length = 0 237 | current_pos = None 238 | matrices = [] 239 | 240 | for i in range(count+1): 241 | offset = i / count 242 | constraint.offset_factor = offset 243 | 244 | # update matrix while python running 245 | updateMatrix() 246 | 247 | world_mat = bpy.context.object.matrix_world.copy() 248 | world_pos = world_mat.to_translation() 249 | matrices.append( world_mat ) 250 | 251 | if i == 0: pass 252 | else: total_length += (world_pos - current_pos).length 253 | 254 | current_pos = world_pos 255 | 256 | bpy.ops.object.delete() 257 | 258 | if current_focus: focusObject(current_focus) 259 | return total_length, matrices 260 | --------------------------------------------------------------------------------