├── SS.jpg ├── Anime Hair Supporter ├── _append_data.blend ├── maincurve_hide.py ├── maincurve_select.py ├── maincurve_volume_down.py ├── maincurve_set_order.py ├── maincurve_set_resolution.py ├── tapercurve_hide.py ├── maincurve_activate.py ├── maincurve_extra_deform.py ├── tapercurve_select.py ├── tapercurve_activate.py ├── convert_curve_to_edgemesh.py ├── tapercurve_remove_alones.py ├── tapercurve_id_singlize.py ├── tapercurve_relocation.py ├── _common.py ├── __init__.py ├── tapercurve_mirror.py ├── tapercurve_change_type.py ├── convert_curve_to_armature.py ├── maincurve_volume_up.py ├── convert_edgemesh_to_curve.py ├── convert_curve_to_mesh.py ├── maincurve_gradation_tilt.py └── _panel.py ├── README.md └── LICENSE /SS.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/relarar/Blender-AnimeHairSupporter/HEAD/SS.jpg -------------------------------------------------------------------------------- /Anime Hair Supporter/_append_data.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/relarar/Blender-AnimeHairSupporter/HEAD/Anime Hair Supporter/_append_data.blend -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [バグ修正とBlender2.79対応版はこちら](https://github.com/dskjal/Blender-AnimeHairSupporter) 2 | [Blender2.8対応版はこちら](https://github.com/Taremin/Blender-AnimeHairSupporter) 3 | 4 | ポリゴン数について: 5 | これでリアルタイム3D(ゲーム用など)に持ち込む髪を製作するとポリゴン数がとんでもないことになります。 6 | このアドオンはBlender標準のカーブ機能を使っており、カーブには解像度(分割数)が設定されています、それを見た目が許容できるまで下げてみてください。 7 | BlenderのDecimate(ポリゴン数削減)モディファイアがなかなか優秀なので、カーブをメッシュ化したあとに使ってみるのも良いかもしれません。 8 | あとは房ごとにBoolean(ブーリアン、メッシュの統合のこと)を実行しても減るでしょう。 9 | どの角度からも見えないポリゴンを削除する機能を実装したかったのですが、許容できない計算時間になったので断念しました、自分は手動でやってます。 10 | -------------------------------------------------------------------------------- /Anime Hair Supporter/maincurve_hide.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | 3 | class ahs_maincurve_hide(bpy.types.Operator): 4 | bl_idname = 'object.ahs_maincurve_hide' 5 | bl_label = "隠す" 6 | bl_description = "メインカーブをすべて隠す/表示" 7 | bl_options = {'REGISTER', 'UNDO'} 8 | 9 | is_hide = bpy.props.BoolProperty(name="隠す") 10 | 11 | @classmethod 12 | def poll(cls, context): 13 | try: 14 | for ob in context.blend_data.objects: 15 | if ob.type != 'CURVE': continue 16 | if ob.data.taper_object and ob.data.bevel_object: break 17 | else: return False 18 | except: return False 19 | return True 20 | 21 | def execute(self, context): 22 | for ob in context.blend_data.objects: 23 | if ob.type != 'CURVE': continue 24 | if ob.data.taper_object and ob.data.bevel_object: 25 | ob.hide = self.is_hide 26 | return {'FINISHED'} 27 | -------------------------------------------------------------------------------- /Anime Hair Supporter/maincurve_select.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | 3 | class ahs_maincurve_select(bpy.types.Operator): 4 | bl_idname = 'object.ahs_maincurve_select' 5 | bl_label = "選択" 6 | bl_description = "メインカーブをすべて選択する" 7 | bl_options = {'REGISTER', 'UNDO'} 8 | 9 | @classmethod 10 | def poll(cls, context): 11 | try: 12 | for ob in context.visible_objects: 13 | if ob.type != 'CURVE': continue 14 | if ob.data.taper_object and ob.data.bevel_object: break 15 | else: return False 16 | except: return False 17 | return True 18 | 19 | def execute(self, context): 20 | target_objects = [] 21 | for ob in context.visible_objects: 22 | if ob.type != 'CURVE': continue 23 | if ob.data.taper_object and ob.data.bevel_object: 24 | target_objects.append(ob) 25 | if not len(target_objects): return {'FINISHED'} 26 | 27 | if context.active_object not in target_objects: 28 | target_objects.sort(key=lambda ob: ob.name) 29 | context.scene.objects.active = target_objects[0] 30 | for ob in target_objects: ob.select = True 31 | return {'FINISHED'} 32 | -------------------------------------------------------------------------------- /Anime Hair Supporter/maincurve_volume_down.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | 3 | class ahs_maincurve_volume_down(bpy.types.Operator): 4 | bl_idname = 'object.ahs_maincurve_volume_down' 5 | bl_label = "肉付けを削除" 6 | bl_description = "選択カーブの設定したテーパー/ベベルを削除" 7 | bl_options = {'REGISTER', 'UNDO'} 8 | 9 | @classmethod 10 | def poll(cls, context): 11 | try: 12 | for ob in context.selected_objects: 13 | if ob.type != 'CURVE': continue 14 | if ob.data.taper_object or ob.data.bevel_object: break 15 | else: return False 16 | except: return False 17 | return True 18 | 19 | def execute(self, context): 20 | for ob in context.selected_objects: 21 | if ob.type != 'CURVE': continue 22 | if ob.data.taper_object: 23 | o, c = ob.data.taper_object, ob.data.taper_object.data 24 | if o: context.blend_data.objects.remove(o, do_unlink=True) 25 | if c: context.blend_data.curves.remove(c, do_unlink=True) 26 | if ob.data.bevel_object: 27 | o, c = ob.data.bevel_object, ob.data.bevel_object.data 28 | if o: context.blend_data.objects.remove(o, do_unlink=True) 29 | if c: context.blend_data.curves.remove(c, do_unlink=True) 30 | 31 | for area in context.screen.areas: area.tag_redraw() 32 | return {'FINISHED'} 33 | -------------------------------------------------------------------------------- /Anime Hair Supporter/maincurve_set_order.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | 3 | class ahs_maincurve_set_order(bpy.types.Operator): 4 | bl_idname = 'object.ahs_maincurve_set_order' 5 | bl_label = "次数を変更" 6 | bl_description = "選択カーブの次数(ゆるやかさ)を一括設定" 7 | bl_options = {'REGISTER', 'UNDO'} 8 | 9 | value = bpy.props.IntProperty(name="値", default=3, min=-6, max=6, soft_min=-6, soft_max=6) 10 | 11 | items = [ 12 | ('ABSOLUTE', "絶対", "", 'PREFERENCES', 1), 13 | ('RELATIVE', "相対", "", 'ZOOMIN', 2), 14 | ] 15 | mode = bpy.props.EnumProperty(items=items, name="モード", default='ABSOLUTE') 16 | 17 | @classmethod 18 | def poll(cls, context): 19 | try: 20 | for ob in context.selected_objects: 21 | if ob.type != 'CURVE': continue 22 | break 23 | else: return False 24 | except: return False 25 | return True 26 | 27 | def invoke(self, context, event): 28 | try: 29 | self.value = context.active_object.data.splines.active.order_u 30 | except: pass 31 | return self.execute(context) 32 | 33 | def execute(self, context): 34 | for ob in context.selected_objects: 35 | if ob.type != 'CURVE': continue 36 | for spline in ob.data.splines: 37 | if self.mode == 'ABSOLUTE': spline.order_u = self.value 38 | if self.mode == 'RELATIVE': spline.order_u += self.value 39 | return {'FINISHED'} 40 | -------------------------------------------------------------------------------- /Anime Hair Supporter/maincurve_set_resolution.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | 3 | class ahs_maincurve_set_resolution(bpy.types.Operator): 4 | bl_idname = 'object.ahs_maincurve_set_resolution' 5 | bl_label = "解像度を変更" 6 | bl_description = "選択カーブの解像度(分割数)を一括設定" 7 | bl_options = {'REGISTER', 'UNDO'} 8 | 9 | value = bpy.props.IntProperty(name="値", default=12, min=-64, max=64, soft_min=-64, soft_max=64) 10 | 11 | items = [ 12 | ('ABSOLUTE', "絶対", "", 'PREFERENCES', 1), 13 | ('RELATIVE', "相対", "", 'ZOOMIN', 2), 14 | ] 15 | mode = bpy.props.EnumProperty(items=items, name="モード", default='ABSOLUTE') 16 | 17 | @classmethod 18 | def poll(cls, context): 19 | try: 20 | for ob in context.selected_objects: 21 | if ob.type != 'CURVE': continue 22 | break 23 | else: return False 24 | except: return False 25 | return True 26 | 27 | def invoke(self, context, event): 28 | try: 29 | self.value = context.active_object.data.splines.active.resolution_u 30 | except: pass 31 | return self.execute(context) 32 | 33 | def execute(self, context): 34 | for ob in context.selected_objects: 35 | if ob.type != 'CURVE': continue 36 | for spline in ob.data.splines: 37 | if self.mode == 'ABSOLUTE': spline.resolution_u = self.value 38 | if self.mode == 'RELATIVE': spline.resolution_u += self.value 39 | return {'FINISHED'} 40 | -------------------------------------------------------------------------------- /Anime Hair Supporter/tapercurve_hide.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | 3 | class ahs_tapercurve_hide(bpy.types.Operator): 4 | bl_idname = 'object.ahs_tapercurve_hide' 5 | bl_label = "隠す" 6 | bl_description = "テーパー/ベベルをすべて隠す/表示" 7 | bl_options = {'REGISTER', 'UNDO'} 8 | 9 | items = [ 10 | ('TAPER', "テーパー", "", 'CURVE_NCURVE', 1), 11 | ('BEVEL', "ベベル", "", 'SURFACE_NCIRCLE', 2), 12 | ('BOTH', "両方", "", 'ARROW_LEFTRIGHT', 3), 13 | ] 14 | mode = bpy.props.EnumProperty(items=items, name="モード", default='BOTH') 15 | 16 | is_hide = bpy.props.BoolProperty(name="隠す") 17 | 18 | @classmethod 19 | def poll(cls, context): 20 | try: 21 | taper_and_bevel_objects = [c.taper_object for c in context.blend_data.curves if c.taper_object] + [c.bevel_object for c in context.blend_data.curves if c.bevel_object] 22 | if not len(taper_and_bevel_objects): return False 23 | except: return False 24 | return True 25 | 26 | def execute(self, context): 27 | if self.mode == 'TAPER': taper_or_bevel_objects = [c.taper_object for c in context.blend_data.curves if c.taper_object] 28 | elif self.mode == 'BEVEL': taper_or_bevel_objects = [c.bevel_object for c in context.blend_data.curves if c.bevel_object] 29 | else: taper_or_bevel_objects = [c.taper_object for c in context.blend_data.curves if c.taper_object] + [c.bevel_object for c in context.blend_data.curves if c.bevel_object] 30 | 31 | for ob in taper_or_bevel_objects: 32 | ob.hide = self.is_hide 33 | return {'FINISHED'} 34 | -------------------------------------------------------------------------------- /Anime Hair Supporter/maincurve_activate.py: -------------------------------------------------------------------------------- 1 | import bpy, mathutils 2 | 3 | class ahs_maincurve_activate(bpy.types.Operator): 4 | bl_idname = 'object.ahs_maincurve_activate' 5 | bl_label = "メインカーブをアクティブ化" 6 | bl_description = "参照元であるメインカーブへアクティブを移す" 7 | bl_options = {'REGISTER', 'UNDO'} 8 | 9 | @classmethod 10 | def poll(cls, context): 11 | try: 12 | taper_and_bevel_objects = [c.taper_object for c in context.blend_data.curves if c.taper_object] + [c.bevel_object for c in context.blend_data.curves if c.bevel_object] 13 | if context.active_object not in taper_and_bevel_objects: return False 14 | except: return False 15 | return True 16 | 17 | def execute(self, context): 18 | ob = context.active_object 19 | 20 | parent_objects = [] 21 | for o in context.blend_data.objects: 22 | if o.type != 'CURVE': continue 23 | if o.data.taper_object == ob: parent_objects.append(o) 24 | if o.data.bevel_object == ob: parent_objects.append(o) 25 | 26 | for o in context.blend_data.objects: o.select = False 27 | 28 | def get_center(ob): 29 | total_co = mathutils.Vector() 30 | for seq in ob.bound_box: 31 | total_co += ob.matrix_world * mathutils.Vector((seq[0], seq[1], seq[2])) 32 | return total_co / 8 33 | 34 | nearest_length = 9**9 35 | for target_ob in parent_objects: 36 | target_ob.select, target_ob.hide = True, False 37 | 38 | length = (ob.location - get_center(target_ob)).length 39 | if length < nearest_length: 40 | nearest_length = length 41 | context.scene.objects.active = target_ob 42 | 43 | return {'FINISHED'} 44 | -------------------------------------------------------------------------------- /Anime Hair Supporter/maincurve_extra_deform.py: -------------------------------------------------------------------------------- 1 | import bpy, mathutils 2 | 3 | class ahs_maincurve_extra_deform(bpy.types.Operator): 4 | bl_idname = 'object.ahs_maincurve_extra_deform' 5 | bl_label = "余剰変形" 6 | bl_description = "選択中のカーブを過剰に変形、もしくは緩やかにする" 7 | bl_options = {'REGISTER', 'UNDO'} 8 | 9 | order_u = bpy.props.IntProperty(name="次数", default=3, min=3, max=6, soft_min=3, soft_max=6) 10 | extra_deform_multi = bpy.props.IntProperty(name="余剰変形率", default=50, min=-100, max=200, soft_min=-100, soft_max=200, subtype='PERCENTAGE') 11 | 12 | @classmethod 13 | def poll(cls, context): 14 | try: 15 | for ob in context.selected_objects: 16 | if ob.type != 'CURVE': continue 17 | break 18 | else: return False 19 | except: return False 20 | return True 21 | 22 | def draw(self, context): 23 | self.layout.prop(self, 'order_u', slider=True) 24 | self.layout.prop(self, 'extra_deform_multi', slider=True) 25 | 26 | def execute(self, context): 27 | for ob in context.selected_objects: 28 | if ob.type != 'CURVE': continue 29 | 30 | curve = ob.data 31 | if not curve.splines.active: continue 32 | 33 | for spline in curve.splines: 34 | spline.order_u = self.order_u 35 | if len(spline.points) < 3: continue 36 | for index, point in enumerate(spline.points): 37 | if index == 0 or len(spline.points) - 1 == index: continue 38 | 39 | co = mathutils.Vector(point.co[:3]) 40 | prev_line = co - mathutils.Vector(spline.points[index - 1].co[:3]) 41 | next_line = co - mathutils.Vector(spline.points[index + 1].co[:3]) 42 | 43 | plus_co = prev_line.lerp(next_line, 0.5) * (self.extra_deform_multi * 0.01) 44 | point.co = list(co + plus_co) + [point.co.w] 45 | 46 | return {'FINISHED'} 47 | -------------------------------------------------------------------------------- /Anime Hair Supporter/tapercurve_select.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | 3 | class ahs_tapercurve_select(bpy.types.Operator): 4 | bl_idname = 'object.ahs_tapercurve_select' 5 | bl_label = "選択" 6 | bl_description = "テーパー/ベベルをすべて選択" 7 | bl_options = {'REGISTER', 'UNDO'} 8 | 9 | items = [ 10 | ('TAPER', "テーパー", "", 'CURVE_NCURVE', 1), 11 | ('BEVEL', "ベベル", "", 'SURFACE_NCIRCLE', 2), 12 | ('BOTH', "両方", "", 'ARROW_LEFTRIGHT', 3), 13 | ] 14 | mode = bpy.props.EnumProperty(items=items, name="モード", default='BOTH') 15 | 16 | @classmethod 17 | def poll(cls, context): 18 | try: 19 | taper_and_bevel_objects = [c.taper_object for c in context.blend_data.curves if c.taper_object] + [c.bevel_object for c in context.blend_data.curves if c.bevel_object] 20 | for ob in context.visible_objects: 21 | if ob in taper_and_bevel_objects: break 22 | else: return False 23 | except: return False 24 | return True 25 | 26 | def execute(self, context): 27 | if self.mode == 'TAPER': taper_or_bevel_objects = [c.taper_object for c in context.blend_data.curves if c.taper_object] 28 | elif self.mode == 'BEVEL': taper_or_bevel_objects = [c.bevel_object for c in context.blend_data.curves if c.bevel_object] 29 | else: taper_or_bevel_objects = [c.taper_object for c in context.blend_data.curves if c.taper_object] + [c.bevel_object for c in context.blend_data.curves if c.bevel_object] 30 | 31 | target_objects = [] 32 | for ob in context.visible_objects: 33 | if ob in taper_or_bevel_objects: target_objects.append(ob) 34 | if not len(target_objects): return {'FINISHED'} 35 | 36 | if context.active_object not in target_objects: 37 | target_objects.sort(key=lambda ob: ob.name) 38 | context.scene.objects.active = target_objects[0] 39 | for ob in target_objects: ob.select = True 40 | return {'FINISHED'} 41 | -------------------------------------------------------------------------------- /Anime Hair Supporter/tapercurve_activate.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | 3 | class ahs_tapercurve_activate(bpy.types.Operator): 4 | bl_idname = 'object.ahs_tapercurve_activate' 5 | bl_label = "テーパー/ベベルをアクティブ化" 6 | bl_description = "テーパー/ベベルにアクティブを移す" 7 | bl_options = {'REGISTER', 'UNDO'} 8 | 9 | items = [ 10 | ('TAPER', "テーパー", "", 'CURVE_NCURVE', 1), 11 | ('BEVEL', "ベベル", "", 'SURFACE_NCIRCLE', 2), 12 | ] 13 | mode = bpy.props.EnumProperty(items=items, name="モード") 14 | 15 | @classmethod 16 | def poll(cls, context): 17 | try: 18 | curve = context.active_object.data 19 | if curve.taper_object and curve.bevel_object: return True 20 | 21 | taper_and_bevel_objects = [c.taper_object for c in context.blend_data.curves if c.taper_object] + [c.bevel_object for c in context.blend_data.curves if c.bevel_object] 22 | if context.active_object in taper_and_bevel_objects: return True 23 | except: return False 24 | return True 25 | 26 | def execute(self, context): 27 | ob, curve = context.active_object, context.active_object.data 28 | 29 | if curve.taper_object and curve.bevel_object: 30 | if self.mode == 'TAPER': target_ob = curve.taper_object 31 | elif self.mode == 'BEVEL': target_ob = curve.bevel_object 32 | 33 | if not target_ob: return {'CANCELLED'} 34 | 35 | for o in context.blend_data.objects: o.select = False 36 | target_ob.select, target_ob.hide = True, False 37 | context.scene.objects.active = target_ob 38 | else: 39 | target_ob = None 40 | for o in context.blend_data.objects: 41 | if o.type != 'CURVE': continue 42 | if o.data.taper_object == ob and self.mode == 'BEVEL': 43 | target_ob = o.data.bevel_object 44 | break 45 | if o.data.bevel_object == ob and self.mode == 'TAPER': 46 | target_ob = o.data.taper_object 47 | break 48 | if not target_ob: return {'CANCELLED'} 49 | 50 | for o in context.blend_data.objects: o.select = False 51 | target_ob.select = True 52 | context.scene.objects.active = target_ob 53 | 54 | return {'FINISHED'} 55 | -------------------------------------------------------------------------------- /Anime Hair Supporter/convert_curve_to_edgemesh.py: -------------------------------------------------------------------------------- 1 | import bpy, mathutils, re 2 | 3 | class ahs_convert_curve_to_edgemesh(bpy.types.Operator): 4 | bl_idname = 'object.ahs_convert_curve_to_edgemesh' 5 | bl_label = "カーブ > 辺メッシュ" 6 | bl_description = "選択中のNURBSカーブを辺メッシュに戻す" 7 | bl_options = {'REGISTER', 'UNDO'} 8 | 9 | @classmethod 10 | def poll(cls, context): 11 | try: 12 | for ob in context.selected_objects: 13 | if ob.type != 'CURVE': continue 14 | for spline in ob.data.splines: 15 | if spline.type == 'NURBS': return True 16 | except: return False 17 | return True 18 | 19 | def execute(self, context): 20 | name = context.active_object.name 21 | re_result = re.search(r'^(.+):HairCurve', name) 22 | if re_result: name = re_result.group(1) 23 | 24 | new_verts, new_edges = [], [] 25 | for ob in context.selected_objects[:]: 26 | ob.select = False 27 | if ob.type != 'CURVE': continue 28 | curve = ob.data 29 | 30 | splines = [s for s in curve.splines if s.type == 'NURBS' and 2 <= len(s.points)] 31 | if not len(splines): continue 32 | 33 | # テーパー/ベベルを完全削除 34 | if curve.taper_object: 35 | temp_ob = curve.taper_object 36 | context.blend_data.curves.remove(temp_ob.data, do_unlink=True) 37 | context.blend_data.objects.remove(temp_ob, do_unlink=True) 38 | if curve.bevel_object: 39 | temp_ob = curve.bevel_object 40 | context.blend_data.curves.remove(temp_ob.data, do_unlink=True) 41 | context.blend_data.objects.remove(temp_ob, do_unlink=True) 42 | 43 | # 頂点/辺情報を格納 44 | for spline in splines: 45 | for index, point in enumerate(spline.points): 46 | if 1 <= index: new_edges.append((len(new_verts)-1, len(new_verts))) 47 | new_verts.append( ob.matrix_world * mathutils.Vector(point.co[:3]) ) 48 | 49 | context.blend_data.curves.remove(ob.data, do_unlink=True) 50 | context.blend_data.objects.remove(ob, do_unlink=True) 51 | 52 | me = context.blend_data.meshes.new(name) 53 | me.from_pydata(new_verts, new_edges, []) 54 | 55 | ob = context.blend_data.objects.new(name, me) 56 | context.scene.objects.link(ob) 57 | ob.select = True 58 | context.scene.objects.active = ob 59 | 60 | return {'FINISHED'} 61 | -------------------------------------------------------------------------------- /Anime Hair Supporter/tapercurve_remove_alones.py: -------------------------------------------------------------------------------- 1 | import bpy, re 2 | 3 | class ahs_tapercurve_remove_alones(bpy.types.Operator): 4 | bl_idname = 'object.ahs_tapercurve_remove_alones' 5 | bl_label = "ぼっち駆除" 6 | bl_description = "どのカーブにも属していないテーパー/ベベルと思われるカーブを削除" 7 | bl_options = {'REGISTER', 'UNDO'} 8 | 9 | items = [ 10 | ('TAPER', "テーパー", "", 'CURVE_NCURVE', 1), 11 | ('BEVEL', "ベベル", "", 'SURFACE_NCIRCLE', 2), 12 | ('BOTH', "両方", "", 'ARROW_LEFTRIGHT', 3), 13 | ] 14 | mode = bpy.props.EnumProperty(items=items, name="モード", default='BOTH') 15 | 16 | @classmethod 17 | def poll(cls, context): 18 | try: 19 | taper_and_bevel_objects = [] 20 | for ob in context.blend_data.objects: 21 | if ob.type != 'CURVE': continue 22 | if ob.data.taper_object: taper_and_bevel_objects.append(ob.data.taper_object) 23 | if ob.data.bevel_object: taper_and_bevel_objects.append(ob.data.bevel_object) 24 | for ob in context.blend_data.objects: 25 | name = re.sub(r'\.\d{3,}$', "", ob.name) 26 | if re.search(r':(Taper|Bevel)$', name) and ob not in taper_and_bevel_objects: break 27 | else: return False 28 | except: return False 29 | return True 30 | 31 | def execute(self, context): 32 | if self.mode != 'BEVEL': 33 | taper_objects = [] 34 | for ob in context.blend_data.objects: 35 | if ob.type != 'CURVE': continue 36 | if ob.data.taper_object: taper_objects.append(ob.data.taper_object) 37 | for ob in context.blend_data.objects: 38 | name = re.sub(r'\.\d{3,}$', "", ob.name) 39 | if re.search(r':Taper$', name) and ob not in taper_objects: 40 | context.blend_data.curves.remove(ob.data, do_unlink=True) 41 | context.blend_data.objects.remove(ob, do_unlink=True) 42 | 43 | if self.mode != 'TAPER': 44 | bevel_objects = [] 45 | for ob in context.blend_data.objects: 46 | if ob.type != 'CURVE': continue 47 | if ob.data.bevel_object: bevel_objects.append(ob.data.bevel_object) 48 | for ob in context.blend_data.objects: 49 | name = re.sub(r'\.\d{3,}$', "", ob.name) 50 | if re.search(r':Bevel$', name) and ob not in bevel_objects: 51 | context.blend_data.curves.remove(ob.data, do_unlink=True) 52 | context.blend_data.objects.remove(ob, do_unlink=True) 53 | 54 | for area in context.screen.areas: area.tag_redraw() 55 | return {'FINISHED'} 56 | -------------------------------------------------------------------------------- /Anime Hair Supporter/tapercurve_id_singlize.py: -------------------------------------------------------------------------------- 1 | import bpy, mathutils, math 2 | from . import _common 3 | 4 | class ahs_tapercurve_id_singlize(bpy.types.Operator): 5 | bl_idname = 'object.ahs_tapercurve_id_singlize' 6 | bl_label = "テーパー/ベベルのシングルユーザー化" 7 | bl_description = "テーパー/ベベルの参照が複数ある場合は複製して各個割り当てる" 8 | bl_options = {'REGISTER', 'UNDO'} 9 | 10 | @classmethod 11 | def poll(cls, context): 12 | try: 13 | taper_and_bevel_counts = {} 14 | for ob in context.blend_data.objects: taper_and_bevel_counts[ob] = 0 15 | for ob in context.blend_data.objects: 16 | if ob.type != 'CURVE': continue 17 | 18 | if ob.data.taper_object: taper_and_bevel_counts[ob.data.taper_object] += 1 19 | if ob.data.bevel_object: taper_and_bevel_counts[ob.data.bevel_object] += 1 20 | 21 | for ob in context.selected_objects: 22 | if 2 <= taper_and_bevel_counts[ob]: return True 23 | if ob.data.taper_object: 24 | if 2 <= taper_and_bevel_counts[ob.data.taper_object]: return True 25 | if ob.data.bevel_object: 26 | if 2 <= taper_and_bevel_counts[ob.data.bevel_object]: return True 27 | else: return False 28 | except: return False 29 | return True 30 | 31 | def execute(self, context): 32 | targets = {} 33 | for ob in context.selected_objects: 34 | if ob.type != 'CURVE': continue 35 | targets[ob] = [] 36 | if ob.data.taper_object: targets[ob.data.taper_object] = [] 37 | if ob.data.bevel_object: targets[ob.data.bevel_object] = [] 38 | 39 | is_tapers = {} 40 | for ob in context.blend_data.objects: 41 | if ob.type != 'CURVE': continue 42 | 43 | if ob.data.taper_object: 44 | for key in targets.keys(): 45 | if ob.data.taper_object == key: 46 | targets[key].append(ob) 47 | is_tapers[key] = True 48 | 49 | if ob.data.bevel_object: 50 | for key in targets.keys(): 51 | if ob.data.bevel_object == key: 52 | targets[key].append(ob) 53 | is_tapers[key] = False 54 | 55 | for ob, parents in targets.items(): 56 | if len(parents) < 2: continue 57 | 58 | for index, parent in enumerate(parents): 59 | if index == 0: 60 | new_ob, new_curve = ob, ob.data 61 | else: 62 | new_ob, new_curve = ob.copy(), ob.data.copy() 63 | new_ob.data = new_curve 64 | context.scene.objects.link(new_ob) 65 | 66 | if is_tapers[ob]: parent.data.taper_object = new_ob 67 | else : parent.data.bevel_object = new_ob 68 | 69 | _common.relocation_taper_and_bevel(parent, new_ob, is_tapers[ob]) 70 | 71 | return {'FINISHED'} 72 | -------------------------------------------------------------------------------- /Anime Hair Supporter/tapercurve_relocation.py: -------------------------------------------------------------------------------- 1 | import bpy, mathutils, math 2 | from . import _common 3 | 4 | class ahs_tapercurve_relocation(bpy.types.Operator): 5 | bl_idname = 'object.ahs_tapercurve_relocation' 6 | bl_label = "再配置" 7 | bl_description = "見えているテーパー/ベベルの位置/回転を再設定" 8 | bl_options = {'REGISTER', 'UNDO'} 9 | 10 | items = [ 11 | ('TAPER', "テーパー", "", 'CURVE_NCURVE', 1), 12 | ('BEVEL', "ベベル", "", 'SURFACE_NCIRCLE', 2), 13 | ('BOTH', "両方", "", 'ARROW_LEFTRIGHT', 3), 14 | ] 15 | mode = bpy.props.EnumProperty(items=items, name="モード", default='BOTH') 16 | 17 | is_location = bpy.props.BoolProperty(name="位置", default=True) 18 | is_rotation = bpy.props.BoolProperty(name="回転", default=True) 19 | 20 | @classmethod 21 | def poll(cls, context): 22 | try: 23 | taper_and_bevel_objects = [c.taper_object for c in context.blend_data.curves if c.taper_object] + [c.bevel_object for c in context.blend_data.curves if c.bevel_object] 24 | for ob in context.visible_objects: 25 | if ob in taper_and_bevel_objects: break 26 | else: return False 27 | except: return False 28 | return True 29 | 30 | def draw(self, context): 31 | self.layout.prop(self, 'mode') 32 | 33 | row = self.layout.row(align=True) 34 | row.prop(self, 'is_location', icon='MAN_TRANS', toggle=True) 35 | row.prop(self, 'is_rotation', icon='MAN_ROT', toggle=True) 36 | 37 | def execute(self, context): 38 | if self.mode == 'TAPER': taper_or_bevel_objects = [c.taper_object for c in context.blend_data.curves if c.taper_object] 39 | elif self.mode == 'BEVEL': taper_or_bevel_objects = [c.bevel_object for c in context.blend_data.curves if c.bevel_object] 40 | else: taper_or_bevel_objects = [c.taper_object for c in context.blend_data.curves if c.taper_object] + [c.bevel_object for c in context.blend_data.curves if c.bevel_object] 41 | 42 | target_zips = [] 43 | for ob in context.visible_objects: 44 | if ob.type != 'CURVE': continue 45 | if ob not in taper_or_bevel_objects: continue 46 | 47 | parent_ob = None 48 | for o in context.blend_data.objects: 49 | if o.type != 'CURVE': continue 50 | 51 | if self.mode == 'TAPER' and o.data.taper_object == ob: parent_ob = o 52 | elif self.mode == 'BEVEL' and o.data.bevel_object == ob: parent_ob = o 53 | elif self.mode == 'BOTH' and (o.data.taper_object == ob or o.data.bevel_object == ob): parent_ob = o 54 | if not parent_ob: continue 55 | 56 | target_zips.append((ob, parent_ob)) 57 | 58 | for ob, parent_ob in target_zips: 59 | if not len(parent_ob.data.splines): continue 60 | _common.relocation_taper_and_bevel(parent_ob, ob, parent_ob.data.taper_object == ob) 61 | 62 | return {'FINISHED'} 63 | -------------------------------------------------------------------------------- /Anime Hair Supporter/_common.py: -------------------------------------------------------------------------------- 1 | import bpy, os, math, mathutils 2 | 3 | def get_append_data_blend_path(): 4 | return os.path.join(os.path.dirname(__file__), "_append_data.blend") 5 | 6 | def get_taper_enum_items(): 7 | items = [ 8 | ('Tapered', "先細り", "", 'CURVE_DATA'), 9 | ('TaperedSuper', "より先細り", "", 'MOD_CURVE'), 10 | ('Sphere', "円", "", 'SPHERECURVE'), 11 | ('Reversed', "先太り", "", 'PMARKER'), 12 | ('ReversedSuper', "より先太り", "", 'CURVE_BEZCURVE'), 13 | ('TaperedOpen', "[根本開き] 先細り", "", 'CURVE_DATA'), 14 | ('TaperedSuperOpen', "[根本開き] より先細り", "", 'MOD_CURVE'), 15 | ('SphereOpen', "[根本開き] 円", "", 'SPHERECURVE'), 16 | ('ReversedOpen', "[根本開き] 先太り", "", 'PMARKER'), 17 | ('ReversedSuperOpen', "[根本開き] より先太り", "", 'CURVE_BEZCURVE'), 18 | ] 19 | for i, item in enumerate(items): items[i] = tuple(list(item) + [i + 1]) 20 | return items 21 | 22 | def get_bevel_enum_items(): 23 | items = [ 24 | ('Sphere', "円", "", 'MESH_CIRCLE'), 25 | ('2', "2本", "", 'OUTLINER_OB_META'), 26 | ('3', "3本", "", 'COLLAPSEMENU'), 27 | ('Triangle', "三角", "", 'EDITMODE_VEC_HLT'), 28 | ('TriangleLoose', "ゆるやか三角", "", 'PLAY_REVERSE'), 29 | ('Square', "四角", "", 'MESH_PLANE'), 30 | ('SquareLoose', "ゆるやか四角", "", 'LATTICE_DATA'), 31 | ('Diamond', "ひし形", "", 'SPACE3'), 32 | ('DiamondLoose', "ゆるやかひし形", "", 'KEYTYPE_EXTREME_VEC'), 33 | ('Sharp', "シャープ", "", 'LINCURVE'), 34 | ('Leaf', "葉っぱ", "", 'MAN_ROT'), 35 | ('V', "切り込み", "", 'FILE_TICK'), 36 | ('Tilde', "波", "", 'IPO_EASE_IN_OUT'), 37 | ('Step', "段差", "", 'IPO_CONSTANT'), 38 | ('Corrugated', "ギザギザ", "", 'RNDCURVE'), 39 | ('Cloud', "雲", "", 'IPO_ELASTIC'), 40 | ] 41 | for i, item in enumerate(items): items[i] = tuple(list(item) + [i + 1]) 42 | return items 43 | 44 | def relocation_taper_and_bevel(main_ob, sub_ob, is_taper): 45 | # 位置変更 46 | if len(main_ob.data.splines): 47 | if len(main_ob.data.splines[0].points): 48 | end_co = main_ob.matrix_world * mathutils.Vector(main_ob.data.splines[0].points[-1].co[:3]) 49 | sub_ob.location = end_co.copy() 50 | 51 | # 回転変更 52 | if len(main_ob.data.splines): 53 | spline = main_ob.data.splines[0] 54 | if 2 <= len(spline.points): 55 | # 最後の辺をトラック 56 | sub_ob.rotation_mode = 'QUATERNION' 57 | last_direction = main_ob.matrix_world * mathutils.Vector(spline.points[-2].co[:3]) - main_ob.matrix_world * mathutils.Vector(spline.points[-1].co[:3]) 58 | up_direction = mathutils.Vector((0, 0, 1)) 59 | sub_ob.rotation_quaternion = up_direction.rotation_difference(last_direction) 60 | # Z回転 61 | diff_co = main_ob.matrix_world * mathutils.Vector(spline.points[-1].co[:3]) - main_ob.matrix_world * mathutils.Vector(spline.points[0].co[:3]) 62 | rotation_z = math.atan2(diff_co.y, diff_co.x) - spline.points[-1].tilt 63 | if is_taper: sub_ob.rotation_quaternion *= mathutils.Quaternion((0, 0, 1), rotation_z) 64 | else : sub_ob.rotation_quaternion *= mathutils.Quaternion((0, 0, 1), rotation_z - math.radians(90)) 65 | -------------------------------------------------------------------------------- /Anime Hair Supporter/__init__.py: -------------------------------------------------------------------------------- 1 | # アドオンを読み込む時に最初にこのファイルが読み込まれます 2 | 3 | # アドオン情報 4 | bl_info = { 5 | 'name' : "Anime Hair Supporter", 6 | 'author' : "saidenka", 7 | 'version' : (1, 0), 8 | 'blender' : (2, 7, 8), 9 | 'location' : "3Dビュー > オブジェクトモード > ツールシェルフ > ツール > アニメ髪支援パネル", 10 | 'description' : "", 11 | 'warning' : "", 12 | 'wiki_url' : "https://github.com/saidenka/Blender-AnimeHairSupporter", 13 | 'tracker_url' : "https://github.com/saidenka/Blender-AnimeHairSupporter", 14 | 'category' : "Tools" 15 | } 16 | 17 | # サブスクリプト群をインポート 18 | if 'bpy' in locals(): 19 | import imp 20 | imp.reload(_panel) 21 | 22 | imp.reload(convert_edgemesh_to_curve) 23 | imp.reload(convert_curve_to_edgemesh) 24 | 25 | imp.reload(maincurve_activate) 26 | imp.reload(maincurve_volume_up) 27 | imp.reload(maincurve_volume_down) 28 | imp.reload(maincurve_extra_deform) 29 | imp.reload(maincurve_gradation_tilt) 30 | imp.reload(maincurve_select) 31 | imp.reload(maincurve_hide) 32 | imp.reload(maincurve_set_resolution) 33 | imp.reload(maincurve_set_order) 34 | 35 | imp.reload(tapercurve_activate) 36 | imp.reload(tapercurve_id_singlize) 37 | imp.reload(tapercurve_change_type) 38 | imp.reload(tapercurve_mirror) 39 | imp.reload(tapercurve_relocation) 40 | imp.reload(tapercurve_remove_alones) 41 | imp.reload(tapercurve_select) 42 | imp.reload(tapercurve_hide) 43 | 44 | imp.reload(convert_curve_to_armature) 45 | imp.reload(convert_curve_to_mesh) 46 | else: 47 | from . import _panel 48 | 49 | from . import convert_edgemesh_to_curve 50 | from . import convert_curve_to_edgemesh 51 | 52 | from . import maincurve_activate 53 | from . import maincurve_volume_up 54 | from . import maincurve_volume_down 55 | from . import maincurve_extra_deform 56 | from . import maincurve_gradation_tilt 57 | from . import maincurve_select 58 | from . import maincurve_hide 59 | from . import maincurve_set_resolution 60 | from . import maincurve_set_order 61 | 62 | from . import tapercurve_activate 63 | from . import tapercurve_id_singlize 64 | from . import tapercurve_change_type 65 | from . import tapercurve_mirror 66 | from . import tapercurve_relocation 67 | from . import tapercurve_remove_alones 68 | from . import tapercurve_select 69 | from . import tapercurve_hide 70 | 71 | from . import convert_curve_to_armature 72 | from . import convert_curve_to_mesh 73 | 74 | # この位置に記述 (重要) 75 | import bpy 76 | 77 | # プラグインをインストールしたときの処理 78 | def register(): 79 | 80 | class AHS_Props(bpy.types.PropertyGroup): 81 | maincurve_expand = bpy.props.BoolProperty(name="メインパネルを展開", default=True) 82 | tapercurve_expand = bpy.props.BoolProperty(name="テーパーパネルを展開", default=True) 83 | bevelcurve_expand = bpy.props.BoolProperty(name="ベベルパネルを展開", default=True) 84 | 85 | bpy.utils.register_module(__name__) 86 | bpy.types.Scene.ahs_props = bpy.props.PointerProperty(type=AHS_Props) 87 | 88 | # プラグインをアンインストールしたときの処理 89 | def unregister(): 90 | if bpy.context.scene.get('ahs_props'): del bpy.context.scene['ahs_props'] 91 | bpy.utils.unregister_module(__name__) 92 | 93 | # 最初に実行される 94 | if __name__ == '__main__': 95 | register() 96 | -------------------------------------------------------------------------------- /Anime Hair Supporter/tapercurve_mirror.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | 3 | class ahs_tapercurve_mirror(bpy.types.Operator): 4 | bl_idname = 'object.ahs_tapercurve_mirror' 5 | bl_label = "ミラー" 6 | bl_description = "テーパー/ベベルの形状を左右/上下反転" 7 | bl_options = {'REGISTER', 'UNDO'} 8 | 9 | items = [ 10 | ('TAPER', "テーパー", "", 'CURVE_NCURVE', 1), 11 | ('BEVEL', "ベベル", "", 'SURFACE_NCIRCLE', 2), 12 | ('BOTH', "両方", "", 'ARROW_LEFTRIGHT', 3), 13 | ] 14 | mode = bpy.props.EnumProperty(items=items, name="モード", default='BOTH') 15 | 16 | is_mirror_x = bpy.props.BoolProperty(name="左右反転") 17 | is_mirror_y = bpy.props.BoolProperty(name="上下反転") 18 | 19 | @classmethod 20 | def poll(cls, context): 21 | try: 22 | taper_and_bevel_objects = [c.taper_object for c in context.blend_data.curves if c.taper_object] + [c.bevel_object for c in context.blend_data.curves if c.bevel_object] 23 | for ob in context.selected_objects: 24 | if ob.type != 'CURVE': continue 25 | if ob.data.taper_object or ob.data.bevel_object: break 26 | if ob in taper_and_bevel_objects: break 27 | else: return False 28 | except: return False 29 | return True 30 | 31 | def draw(self, context): 32 | self.layout.prop(self, 'mode') 33 | 34 | row = self.layout.row(align=True) 35 | row.prop(self, 'is_mirror_x', icon='ARROW_LEFTRIGHT', toggle=True) 36 | row.prop(self, 'is_mirror_y', icon='FILE_PARENT', toggle=True) 37 | 38 | def execute(self, context): 39 | taper_objects = [c.taper_object for c in context.blend_data.curves if c.taper_object] 40 | bevel_objects = [c.bevel_object for c in context.blend_data.curves if c.bevel_object] 41 | for selected_object in context.selected_objects: 42 | if selected_object.type != 'CURVE': continue 43 | 44 | target_objects, is_tapers = [], [] 45 | if self.mode != 'BEVEL': 46 | if selected_object.data.taper_object: 47 | target_objects.append(selected_object.data.taper_object) 48 | is_tapers.append(True) 49 | elif selected_object in taper_objects: 50 | target_objects.append(selected_object) 51 | is_tapers.append(True) 52 | if self.mode != 'TAPER': 53 | if selected_object.data.bevel_object: 54 | target_objects.append(selected_object.data.bevel_object) 55 | is_tapers.append(False) 56 | elif selected_object in bevel_objects: 57 | target_objects.append(selected_object) 58 | is_tapers.append(False) 59 | 60 | for ob, is_taper in zip(target_objects, is_tapers): 61 | curve = ob.data 62 | 63 | if is_taper: is_mirror_x, is_mirror_y = self.is_mirror_y, self.is_mirror_x 64 | else: is_mirror_x, is_mirror_y = self.is_mirror_x, self.is_mirror_y 65 | 66 | for spline in curve.splines: 67 | if is_mirror_x: 68 | co_list = [list(p.co) for p in spline.points] 69 | co_list.reverse() 70 | for point, new_co in zip(spline.points, co_list): 71 | new_co[0] = -new_co[0] 72 | point.co = new_co 73 | 74 | if is_mirror_y: 75 | co_list = [list(p.co) for p in spline.points] 76 | if not is_taper:co_list.reverse() 77 | for point, new_co in zip(spline.points, co_list): 78 | new_co[1] = -new_co[1] 79 | point.co = new_co 80 | return {'FINISHED'} 81 | -------------------------------------------------------------------------------- /Anime Hair Supporter/tapercurve_change_type.py: -------------------------------------------------------------------------------- 1 | import bpy, os 2 | from . import _common 3 | 4 | class ahs_tapercurve_change_type(bpy.types.Operator): 5 | bl_idname = 'object.ahs_tapercurve_change_type' 6 | bl_label = "種類を変更" 7 | bl_description = "選択カーブのテーパー/ベベルの形状を一覧から再設定" 8 | bl_options = {'REGISTER', 'UNDO'} 9 | 10 | is_taper = bpy.props.BoolProperty(name="テーパーを変更") 11 | taper_type = bpy.props.EnumProperty(items=_common.get_taper_enum_items(), name="テーパー", default='Tapered') 12 | 13 | is_bevel = bpy.props.BoolProperty(name="ベベルを変更") 14 | bevel_type = bpy.props.EnumProperty(items=_common.get_bevel_enum_items(), name="ベベル", default='Sharp') 15 | is_bevel_mirror = bpy.props.BoolProperty(name="ベベルを左右反転", default=False) 16 | 17 | @classmethod 18 | def poll(cls, context): 19 | try: 20 | taper_and_bevel_objects = [c.taper_object for c in context.blend_data.curves if c.taper_object] + [c.bevel_object for c in context.blend_data.curves if c.bevel_object] 21 | for ob in context.selected_objects: 22 | if ob.type != 'CURVE': continue 23 | if ob.data.taper_object or ob.data.bevel_object: break 24 | if ob in taper_and_bevel_objects: break 25 | else: return False 26 | except: return False 27 | return True 28 | 29 | def draw(self, context): 30 | row = self.layout.row(align=True) 31 | if self.is_taper: icon = 'FILE_TICK' 32 | else: icon = 'BLANK1' 33 | row.prop(self, 'is_taper', text="", icon=icon, toggle=True) 34 | sub_row = row.row(align=True) 35 | sub_row.prop(self, 'taper_type') 36 | sub_row.enabled = self.is_taper 37 | 38 | row = self.layout.row(align=True) 39 | if self.is_bevel: icon = 'FILE_TICK' 40 | else: icon = 'BLANK1' 41 | row.prop(self, 'is_bevel', text="", icon=icon, toggle=True) 42 | sub_row = row.row(align=True) 43 | sub_row.prop(self, 'bevel_type') 44 | sub_row.prop(self, 'is_bevel_mirror', text="", icon='MOD_MIRROR') 45 | sub_row.enabled = self.is_bevel 46 | 47 | def execute(self, context): 48 | blend_path = _common.get_append_data_blend_path() 49 | 50 | if self.is_taper: 51 | target_objects = [] 52 | taper_objects = [c.taper_object for c in context.blend_data.curves if c.taper_object] 53 | for ob in context.selected_objects: 54 | if ob.type != 'CURVE': continue 55 | 56 | if ob.data.taper_object: target_objects.append(ob.data.taper_object) 57 | elif ob in taper_objects: target_objects.append(ob) 58 | 59 | for ob in target_objects: 60 | pre_curve = ob.data 61 | pre_name = pre_curve.name 62 | 63 | with context.blend_data.libraries.load(blend_path) as (data_from, data_to): 64 | data_to.curves = ["Taper." + self.taper_type] 65 | new_curve = data_to.curves[0] 66 | 67 | ob.data = new_curve 68 | context.blend_data.curves.remove(pre_curve, do_unlink=True) 69 | new_curve.name = pre_name 70 | 71 | if self.is_bevel: 72 | target_objects = [] 73 | bevel_objects = [c.bevel_object for c in context.blend_data.curves if c.bevel_object] 74 | for ob in context.selected_objects: 75 | if ob.type != 'CURVE': continue 76 | 77 | if ob.data.bevel_object: target_objects.append(ob.data.bevel_object) 78 | elif ob in bevel_objects: target_objects.append(ob) 79 | 80 | for ob in target_objects: 81 | pre_curve = ob.data 82 | pre_name = pre_curve.name 83 | 84 | with context.blend_data.libraries.load(blend_path) as (data_from, data_to): 85 | data_to.curves = ["Bevel." + self.bevel_type] 86 | new_curve = data_to.curves[0] 87 | 88 | ob.data = new_curve 89 | context.blend_data.curves.remove(pre_curve, do_unlink=True) 90 | new_curve.name = pre_name 91 | 92 | # 左右反転処理 93 | if self.is_bevel_mirror: 94 | co_list = [list(p.co) for p in new_curve.splines[0].points] 95 | co_list.reverse() 96 | for point, new_co in zip(new_curve.splines[0].points, co_list): 97 | new_co[0] = -new_co[0] 98 | point.co = new_co 99 | 100 | return {'FINISHED'} 101 | -------------------------------------------------------------------------------- /Anime Hair Supporter/convert_curve_to_armature.py: -------------------------------------------------------------------------------- 1 | import bpy, mathutils 2 | 3 | class ahs_convert_curve_to_armature(bpy.types.Operator): 4 | bl_idname = 'object.ahs_convert_curve_to_armature' 5 | bl_label = "カーブ > アーマチュア" 6 | bl_description = "選択中のカーブにそった新規アーマチュアを作成" 7 | bl_options = {'REGISTER', 'UNDO'} 8 | 9 | bone_subdivide_count = bpy.props.IntProperty(name="ボーン分割数", default=1, min=0, max=8, soft_min=0, soft_max=8) 10 | 11 | @classmethod 12 | def poll(cls, context): 13 | return True 14 | 15 | def execute(self, context): 16 | bone_points = [] 17 | for ob in context.selected_objects: 18 | ob.select = False 19 | if ob.type != 'CURVE': continue 20 | if not len(ob.data.splines): continue 21 | if len(ob.data.splines[0].points) < 2: continue 22 | 23 | pre_curve = ob.data 24 | temp_curve = pre_curve.copy() 25 | ob.data = temp_curve 26 | 27 | temp_curve.extrude = 0 28 | temp_curve.bevel_depth = 0 29 | temp_curve.bevel_factor_start = 0 30 | temp_curve.bevel_factor_end = 1 31 | temp_curve.taper_object = None 32 | temp_curve.bevel_object = None 33 | for spline in temp_curve.splines: spline.resolution_u = 64 34 | 35 | curve_point_cos = [mathutils.Vector(p.co[:3]) for p in temp_curve.splines[0].points] 36 | curve_point_lengths = [0] 37 | for index, co in enumerate(curve_point_cos): 38 | if index == 0: continue 39 | prev_co = curve_point_cos[index - 1] 40 | curve_point_lengths.append((co - prev_co).length) 41 | total_curve_point_length = sum(curve_point_lengths) 42 | curve_point_ratios = [l / total_curve_point_length for l in curve_point_lengths] 43 | 44 | temp_me = ob.to_mesh(context.scene, False, 'PREVIEW') 45 | 46 | ob.data = pre_curve 47 | context.blend_data.curves.remove(temp_curve, do_unlink=True) 48 | 49 | vert_cos = [ob.matrix_world * v.co for v in temp_me.vertices] 50 | vert_lengths = [0] 51 | for index, co in enumerate(vert_cos): 52 | if index == 0: continue 53 | prev_co = vert_cos[index - 1] 54 | vert_lengths.append((co - prev_co).length) 55 | total_vert_length = sum(vert_lengths) 56 | 57 | local_bone_points = [vert_cos[0].copy()] 58 | current_length = 0 59 | for ratio_index, ratio in enumerate(curve_point_ratios): 60 | if ratio_index == 0: continue 61 | raw_bone_length = total_vert_length * ratio 62 | bone_length = raw_bone_length / (self.bone_subdivide_count + 1) 63 | 64 | for bone_index in range(self.bone_subdivide_count + 1): 65 | target_length = current_length + (bone_length * (bone_index + 1)) 66 | 67 | current_vert_length = 0 68 | for co_index, co in enumerate(vert_cos): 69 | if co_index == 0: continue 70 | pre_co = vert_cos[co_index - 1] 71 | current_vert_length += (co - pre_co).length 72 | target_co = co.copy() 73 | if target_length <= current_vert_length: break 74 | 75 | local_bone_points.append(target_co) 76 | 77 | current_length += raw_bone_length 78 | 79 | context.blend_data.meshes.remove(temp_me, do_unlink=True) 80 | 81 | bone_points.append(local_bone_points) 82 | 83 | new_arm = context.blend_data.armatures.new("Armature") 84 | new_ob = context.blend_data.objects.new("Armature", new_arm) 85 | new_ob.data = new_arm 86 | context.scene.objects.link(new_ob) 87 | new_ob.select = True 88 | context.scene.objects.active = new_ob 89 | new_ob.show_x_ray = True 90 | new_arm.draw_type = 'STICK' 91 | 92 | bpy.ops.object.mode_set(mode='EDIT') 93 | 94 | for index, local_bone_points in enumerate(bone_points): 95 | prev_bone = None 96 | for point_index, point in enumerate(local_bone_points): 97 | if point_index == 0: continue 98 | new_bone = new_arm.edit_bones.new("Hair " + str(index+1) + "-" + str(point_index)) 99 | new_bone.head = local_bone_points[point_index - 1].copy() 100 | new_bone.tail = point.copy() 101 | new_bone.parent = prev_bone 102 | new_bone.use_connect = bool(prev_bone) 103 | prev_bone = new_bone 104 | 105 | bpy.ops.object.mode_set(mode='OBJECT') 106 | return {'FINISHED'} 107 | -------------------------------------------------------------------------------- /Anime Hair Supporter/maincurve_volume_up.py: -------------------------------------------------------------------------------- 1 | import bpy, mathutils, math, os 2 | from . import _common 3 | 4 | class ahs_maincurve_volume_up(bpy.types.Operator): 5 | bl_idname = 'object.ahs_maincurve_volume_up' 6 | bl_label = "肉付け" 7 | bl_description = "選択中のカーブにテーパー/ベベルを設定して実体化する" 8 | bl_options = {'REGISTER', 'UNDO'} 9 | 10 | taper_type = bpy.props.EnumProperty(items=_common.get_taper_enum_items(), name="テーパー", default='Tapered') 11 | 12 | bevel_type = bpy.props.EnumProperty(items=_common.get_bevel_enum_items(), name="ベベル", default='Sharp') 13 | is_bevel_mirror = bpy.props.BoolProperty(name="ベベルを左右反転", default=False) 14 | 15 | scale = bpy.props.FloatProperty(name="半径", default=0.2, min=0, max=10, soft_min=0, soft_max=10, step=0.1, precision=3) 16 | scale_y_multi = bpy.props.IntProperty(name="平たさ", default=50, min=0, max=100, soft_min=0, soft_max=100, subtype='PERCENTAGE') 17 | 18 | @classmethod 19 | def poll(cls, context): 20 | try: 21 | for ob in context.selected_objects: 22 | if ob.type == 'CURVE': break 23 | else: return False 24 | except: return False 25 | return True 26 | 27 | def draw(self, context): 28 | self.layout.prop(self, 'taper_type') 29 | 30 | row = self.layout.row(align=True) 31 | row.prop(self, 'bevel_type') 32 | row.prop(self, 'is_bevel_mirror', text="", icon='MOD_MIRROR') 33 | 34 | self.layout.prop(self, 'scale') 35 | self.layout.prop(self, 'scale_y_multi', slider=True) 36 | 37 | def execute(self, context): 38 | # すでにテーパーかベベルとして指定されているオブジェクトをリスト 39 | taper_and_bevel_objects = [c.taper_object for c in context.blend_data.curves] + [c.bevel_object for c in context.blend_data.curves] 40 | 41 | blend_path = _common.get_append_data_blend_path() 42 | 43 | for ob in context.selected_objects: 44 | if ob in taper_and_bevel_objects: continue 45 | if ob.type != 'CURVE': continue 46 | 47 | curve = ob.data 48 | 49 | # すでにテーパーかベベルがあって参照が1つの場合は削除 50 | if curve.taper_object: 51 | if len([c.taper_object for c in context.blend_data.curves if c.taper_object == curve.taper_object]) == 1: 52 | o, c = curve.taper_object, curve.taper_object.data 53 | if o: context.blend_data.objects.remove(o, do_unlink=True) 54 | if c: context.blend_data.curves.remove(c, do_unlink=True) 55 | if curve.bevel_object: 56 | if len([c.bevel_object for c in context.blend_data.curves if c.bevel_object == curve.bevel_object]) == 1: 57 | o, c = curve.bevel_object, curve.bevel_object.data 58 | if o: context.blend_data.objects.remove(o, do_unlink=True) 59 | if c: context.blend_data.curves.remove(c, do_unlink=True) 60 | 61 | # テーパーオブジェクトをアペンドして割り当て 62 | with context.blend_data.libraries.load(blend_path) as (data_from, data_to): 63 | data_to.objects = ["Taper." + self.taper_type] 64 | taper_curve_ob = data_to.objects[0] 65 | taper_curve = taper_curve_ob.data 66 | name = ob.name + ":Taper" 67 | taper_curve_ob.name, taper_curve.name = name, name 68 | taper_curve_ob.select = False 69 | context.scene.objects.link(taper_curve_ob) 70 | curve.taper_object = taper_curve_ob 71 | 72 | # ベベルオブジェクトをアペンドして割り当て 73 | with context.blend_data.libraries.load(blend_path) as (data_from, data_to): 74 | data_to.objects = ["Bevel." + self.bevel_type] 75 | bevel_curve_ob = data_to.objects[0] 76 | bevel_curve = bevel_curve_ob.data 77 | name = ob.name + ":Bevel" 78 | bevel_curve_ob.name, bevel_curve.name = name, name 79 | bevel_curve_ob.select = False 80 | context.scene.objects.link(bevel_curve_ob) 81 | curve.bevel_object = bevel_curve_ob 82 | # 左右反転処理 83 | if self.is_bevel_mirror: 84 | co_list = [list(p.co) for p in bevel_curve.splines[0].points] 85 | co_list.reverse() 86 | for point, new_co in zip(bevel_curve.splines[0].points, co_list): 87 | new_co[0] = -new_co[0] 88 | point.co = new_co 89 | 90 | _common.relocation_taper_and_bevel(ob, taper_curve_ob, True) 91 | _common.relocation_taper_and_bevel(ob, bevel_curve_ob, False) 92 | 93 | # 拡縮変更 94 | taper_curve_ob.scale = (self.scale, self.scale, self.scale) 95 | bevel_curve_ob.scale = (self.scale, self.scale * self.scale_y_multi * 0.01, self.scale) 96 | 97 | return {'FINISHED'} 98 | -------------------------------------------------------------------------------- /Anime Hair Supporter/convert_edgemesh_to_curve.py: -------------------------------------------------------------------------------- 1 | import bpy, bmesh, mathutils 2 | 3 | class ahs_convert_edgemesh_to_curve(bpy.types.Operator): 4 | bl_idname = 'object.ahs_convert_edgemesh_to_curve' 5 | bl_label = "辺メッシュ > カーブ" 6 | bl_description = "選択中の辺のみで構成されたメッシュをNURBSカーブに変換" 7 | bl_options = {'REGISTER', 'UNDO'} 8 | 9 | order_u = bpy.props.IntProperty(name="次数", default=3, min=3, max=6, soft_min=3, soft_max=6) 10 | extra_deform_multi = bpy.props.IntProperty(name="余剰変形率", default=0, min=-100, max=200, soft_min=-100, soft_max=200, subtype='PERCENTAGE') 11 | is_remove_mesh = bpy.props.BoolProperty(name="元にした辺メッシュを削除", default=True) 12 | 13 | @classmethod 14 | def poll(cls, context): 15 | return True 16 | 17 | def draw(self, context): 18 | self.layout.prop(self, 'order_u', slider=True) 19 | self.layout.prop(self, 'extra_deform_multi', slider=True) 20 | self.layout.prop(self, 'is_remove_mesh', icon='X', toggle=True) 21 | 22 | def execute(self, context): 23 | new_objects = [] 24 | for ob in context.selected_objects[:]: 25 | ob.select = False 26 | if ob.type != 'MESH': continue 27 | if len(ob.data.vertices) < 2 or len(ob.data.edges) < 1 or len(ob.data.polygons): continue 28 | 29 | bm = bmesh.new() 30 | bm.from_mesh(ob.data) 31 | 32 | separated_verts = [] 33 | already_edges = [] 34 | for i in range(len(bm.verts) * 2): 35 | 36 | # まだ拾ってない開始頂点/辺を検索 37 | current_vert, current_edge = None, None 38 | for vert in bm.verts: 39 | # 繋がってる辺が2つは開始地点として不適切 40 | if len(vert.link_edges) == 2: continue 41 | # まだ拾ってない辺を検索 42 | for edge in vert.link_edges: 43 | if edge in already_edges: continue 44 | # 変数に確保して離脱 45 | current_vert, current_edge = vert, edge 46 | break 47 | if current_edge: break 48 | # 離脱 49 | else: break 50 | if not current_edge: break 51 | 52 | # 辿っていく 53 | local_verts = [current_vert] 54 | for j in range(len(bm.verts) * 2): 55 | # すでに拾ったもとと登録 56 | already_edges.append(current_edge) 57 | # 次の頂点を現在頂点に 58 | current_vert = current_edge.other_vert(current_vert) 59 | # ローカル結果にアペンド 60 | local_verts.append(current_vert) 61 | # 離脱 62 | if len(current_vert.link_edges) != 2: break 63 | # 2つある辺の次にあたるのを現在辺に 64 | for edge in current_vert.link_edges: 65 | if edge != current_edge: 66 | current_edge = edge 67 | break 68 | # 離脱 69 | else: break 70 | # 結果をアペンド 71 | separated_verts.append(local_verts) 72 | 73 | for local_verts in separated_verts: 74 | local_points = [ob.matrix_world * v.co for v in local_verts] 75 | 76 | # グローバルZ軸が上の頂点を開始地点とする 77 | begin_co = local_points[0] 78 | end_co = local_points[-1] 79 | if begin_co.z < end_co.z: 80 | local_verts.reverse(), local_points.reverse() 81 | 82 | # カーブとオブジェクトを新規作成してリンク 83 | name = ob.name + ":HairCurve" 84 | curve = context.blend_data.curves.new(name, 'CURVE') 85 | curve_ob = context.blend_data.objects.new(name, curve) 86 | curve_ob.matrix_world = mathutils.Matrix.Translation(local_points[0]) 87 | context.scene.objects.link(curve_ob) 88 | new_objects.append(curve_ob) 89 | 90 | # カーブの設定 91 | curve.dimensions = '3D' 92 | 93 | # スプライン&ポイントを作成 94 | spline = curve.splines.new('NURBS') 95 | spline.points.add(len(local_points) - 1) 96 | for index, (point, co) in enumerate(zip(spline.points, local_points)): 97 | if index != 0 and len(local_points) - 1 != index: 98 | prev_line = co - local_points[index - 1] 99 | next_line = co - local_points[index + 1] 100 | co += prev_line.lerp(next_line, 0.5) * (self.extra_deform_multi * 0.01) 101 | point.co = list(curve_ob.matrix_world.inverted() * co) + [1.0] 102 | 103 | # スプラインの設定 104 | spline.order_u = self.order_u 105 | spline.use_endpoint_u = True 106 | 107 | bm.free() 108 | 109 | if self.is_remove_mesh: 110 | context.blend_data.meshes.remove(ob.data, do_unlink=True) 111 | context.blend_data.objects.remove(ob, do_unlink=True) 112 | 113 | # 新規オブジェクトをアクティブ&選択 114 | if len(new_objects): context.scene.objects.active = new_objects[0] 115 | for new_object in new_objects: new_object.select = True 116 | 117 | return {'FINISHED'} 118 | -------------------------------------------------------------------------------- /Anime Hair Supporter/convert_curve_to_mesh.py: -------------------------------------------------------------------------------- 1 | import bpy, bmesh, mathutils 2 | 3 | class ahs_convert_curve_to_mesh(bpy.types.Operator): 4 | bl_idname = 'object.ahs_convert_curve_to_mesh' 5 | bl_label = "カーブ > メッシュ" 6 | bl_description = "選択中の肉付けしたカーブをメッシュに変換" 7 | bl_options = {'REGISTER', 'UNDO'} 8 | 9 | is_join = bpy.props.BoolProperty(name="オブジェクトを統合", default=True) 10 | is_remove_doubles = bpy.props.BoolProperty(name="重複頂点を削除", default=True) 11 | is_uv_pack_islands = bpy.props.BoolProperty(name="UVの島を梱包", default=True) 12 | 13 | @classmethod 14 | def poll(cls, context): 15 | return True 16 | 17 | def draw(self, context): 18 | self.layout.prop(self, 'is_join', icon='FORCE_LENNARDJONES', toggle=True) 19 | 20 | self.layout.prop(self, 'is_remove_doubles', icon='STICKY_UVS_LOC', toggle=True) 21 | 22 | row = self.layout.row(align=True) 23 | row.prop(self, 'is_uv_pack_islands', icon='UV_ISLANDSEL', toggle=True) 24 | row.enabled = self.is_join 25 | 26 | def execute(self, context): 27 | # メッシュ選択モードを一時的に頂点モードに 28 | pre_mesh_select_mode = context.tool_settings.mesh_select_mode 29 | context.tool_settings.mesh_select_mode = (True, False, False) 30 | 31 | # 変換するカーブを抽出 32 | target_objects = [] 33 | for ob in context.selected_objects: 34 | if ob.type != 'CURVE': 35 | ob.select = False 36 | continue 37 | if not ob.data.taper_object or not ob.data.bevel_object: 38 | ob.select = False 39 | continue 40 | target_objects.append(ob) 41 | if not len(target_objects): return {'CANCELLED'} 42 | 43 | # テーパー/ベベルオブジェクトを保管 44 | taper_and_bevel_objects = [] 45 | for ob in target_objects: 46 | taper_and_bevel_objects.append(ob.data.taper_object) 47 | taper_and_bevel_objects.append(ob.data.bevel_object) 48 | 49 | # 公式のカーブ > メッシュ 50 | bpy.ops.object.convert(target='MESH', keep_original=False) 51 | 52 | # 選択メッシュの中心位置を計算 53 | x_list, y_list, z_list = [], [], [] 54 | for ob in target_objects: 55 | for co_tuple in ob.bound_box: 56 | co = ob.matrix_world * mathutils.Vector(co_tuple) 57 | x_list.append(co.x), y_list.append(co.y), z_list.append(co.z) 58 | x_center, y_center, z_center = (min(x_list) + max(x_list)) / 2, (min(y_list) + max(y_list)) / 2, (min(z_list) + max(z_list)) / 2 59 | objects_center = mathutils.Vector((x_center, y_center, z_center)) 60 | 61 | # 変換されたメッシュを順に処理 62 | for ob in target_objects: 63 | # 事前処理 64 | context.scene.objects.active = ob 65 | bpy.ops.object.mode_set(mode='EDIT') 66 | bpy.ops.mesh.select_all(action='DESELECT') 67 | 68 | bm = bmesh.from_edit_mesh(ob.data) 69 | 70 | # 真ん中あたりの面 > ループ > 辺と辿って選択 71 | bm.faces.ensure_lookup_table() 72 | center_face = bm.faces[int(len(bm.faces) * 0.5)] 73 | center_face.loops[1].edge.select_set(True) 74 | 75 | # 辺ループ選択 76 | bpy.ops.mesh.loop_multi_select(ring=False) 77 | 78 | # 対象の断面の頂点/辺を保管 79 | selected_verts = [v for v in bm.verts if v.select] 80 | selected_edges = [e for e in bm.edges if e.select] 81 | bpy.ops.mesh.select_all(action='DESELECT') 82 | 83 | # シームを入れるべき辺ループに繋がってる頂点を検索 84 | verts_distance = [((ob.matrix_world * v.co) - objects_center).length for v in selected_verts] 85 | verts_density = [] 86 | for vert in selected_verts: 87 | lengths = [e.calc_length() for e in vert.link_edges] 88 | verts_density.append(sum(lengths) / len(lengths)) 89 | best_vert, best_score = selected_verts[0], 0 90 | min_verts_distance, max_verts_distance = min(verts_distance), max(verts_distance) 91 | min_verts_density, max_verts_density = min(verts_density), max(verts_density) 92 | for vert, distance, density in zip(selected_verts, verts_distance, verts_density): 93 | distance_score = (distance - min_verts_distance) / (max_verts_distance - min_verts_distance) 94 | density_score = (density - min_verts_density) / (max_verts_density - min_verts_density) 95 | score = ((1 - distance_score) * 0.5) + ((1 - density_score) * 0.5) 96 | if best_score < score: 97 | best_vert, best_score = vert, score 98 | 99 | # 1頂点選択状態から辺ループ選択してシームを入れる 100 | for edge in best_vert.link_edges: 101 | if edge not in selected_edges: 102 | edge.select_set(True) 103 | bpy.ops.mesh.loop_multi_select(ring=False) 104 | bpy.ops.mesh.mark_seam(clear=False) 105 | 106 | # 重複頂点を削除 107 | if self.is_remove_doubles: 108 | bpy.ops.mesh.select_all(action='DESELECT') 109 | bpy.ops.mesh.select_non_manifold(extend=True, use_wire=True, use_boundary=True, use_multi_face=True, use_non_contiguous=True, use_verts=True) 110 | bpy.ops.mesh.remove_doubles(threshold=0.0001, use_unselected=False) 111 | 112 | # UV展開 113 | bpy.ops.mesh.select_all(action='SELECT') 114 | bpy.ops.uv.unwrap(method='ANGLE_BASED', fill_holes=True, correct_aspect=True, use_subsurf_data=False, margin=0.001) 115 | 116 | # 事後処理 117 | bmesh.update_edit_mesh(ob.data) 118 | bpy.ops.object.mode_set(mode='OBJECT') 119 | 120 | # 名前順で一番前のオブジェクトをアクティブ化 121 | context.scene.objects.active = sorted(target_objects, key=lambda o: o.name)[0] 122 | 123 | # 公式のオブジェクト統合 124 | if self.is_join: bpy.ops.object.join() 125 | 126 | # UVの島を梱包 127 | if self.is_uv_pack_islands and self.is_join: 128 | bpy.ops.object.mode_set(mode='EDIT') 129 | bpy.ops.mesh.select_all(action='SELECT') 130 | bpy.ops.uv.average_islands_scale() 131 | bpy.ops.uv.pack_islands(rotate=False, margin=0.02) 132 | bpy.ops.object.mode_set(mode='OBJECT') 133 | 134 | # 必要なくなったテーパー/ベベルを完全削除 135 | for ob in taper_and_bevel_objects: 136 | context.blend_data.curves.remove(ob.data, do_unlink=True) 137 | context.blend_data.objects.remove(ob, do_unlink=True) 138 | 139 | context.tool_settings.mesh_select_mode = pre_mesh_select_mode 140 | return {'FINISHED'} 141 | -------------------------------------------------------------------------------- /Anime Hair Supporter/maincurve_gradation_tilt.py: -------------------------------------------------------------------------------- 1 | import bpy, math, mathutils 2 | 3 | class ahs_maincurve_gradation_tilt(bpy.types.Operator): 4 | bl_idname = 'object.ahs_maincurve_gradation_tilt' 5 | bl_label = "グラデーションひねり" 6 | bl_description = "選択カーブをゆるやかにひねる/傾ける" 7 | bl_options = {'REGISTER', 'UNDO'} 8 | 9 | is_tilt = bpy.props.BoolProperty(name="傾き", default=True) 10 | is_radius = bpy.props.BoolProperty(name="半径", default=False) 11 | is_weight_softbody = bpy.props.BoolProperty(name="ウェイト", default=False) 12 | 13 | begin_ratio = bpy.props.IntProperty(name="始点", default=33, min=0, max=100, soft_min=0, soft_max=100, subtype='PERCENTAGE') 14 | begin_tilt = bpy.props.FloatProperty(name="傾き", default=0, min=math.radians(-360), max=math.radians(360), soft_min=math.radians(-360), soft_max=math.radians(360), step=3, precision=0, subtype='ANGLE', unit='ROTATION') 15 | begin_radius = bpy.props.FloatProperty(name="半径", default=1, min=0, max=10, soft_min=0, soft_max=10, step=3, precision=2) 16 | begin_weight_softbody = bpy.props.IntProperty(name="ウェイト", default=100, min=0, max=100, soft_min=0, soft_max=100, subtype='PERCENTAGE') 17 | 18 | end_ratio = bpy.props.IntProperty(name="終点", default=100, min=0, max=100, soft_min=0, soft_max=100, subtype='PERCENTAGE') 19 | end_tilt = bpy.props.FloatProperty(name="傾き", default=0, min=math.radians(-360), max=math.radians(360), soft_min=math.radians(-360), soft_max=math.radians(360), step=3, precision=0, subtype='ANGLE', unit='ROTATION') 20 | end_radius = bpy.props.FloatProperty(name="半径", default=0, min=0, max=10, soft_min=0, soft_max=10, step=3, precision=2) 21 | end_weight_softbody = bpy.props.IntProperty(name="ウェイト", default=0, min=0, max=100, soft_min=0, soft_max=100, subtype='PERCENTAGE') 22 | 23 | items = [ 24 | ('ABSOLUTE', "絶対", "", 'PREFERENCES', 1), 25 | ('RELATIVE', "相対", "", 'ZOOMIN', 2), 26 | ] 27 | mode = bpy.props.EnumProperty(items=items, name="モード", default='ABSOLUTE') 28 | 29 | @classmethod 30 | def poll(cls, context): 31 | try: 32 | for ob in context.selected_objects: 33 | if ob.type != 'CURVE': continue 34 | break 35 | else: return False 36 | except: return False 37 | return True 38 | 39 | def draw(self, context): 40 | row = self.layout.row(align=True) 41 | row.prop(self, 'is_tilt', icon='FORCE_MAGNETIC', toggle=True) 42 | row.prop(self, 'is_radius', icon='FORCE_FORCE', toggle=True) 43 | row.prop(self, 'is_weight_softbody', icon='MOD_SOFT', toggle=True) 44 | 45 | column = self.layout.column(align=True) 46 | column.prop(self, 'begin_ratio', slider=True) 47 | row = column.row(align=True) 48 | row.prop(self, 'begin_tilt', slider=True) 49 | row.enabled = self.is_tilt 50 | row = column.row(align=True) 51 | row.prop(self, 'begin_radius') 52 | row.enabled = self.is_radius 53 | row = column.row(align=True) 54 | row.prop(self, 'begin_weight_softbody', slider=True) 55 | row.enabled = self.is_weight_softbody 56 | 57 | column = self.layout.column(align=True) 58 | column.prop(self, 'end_ratio', slider=True) 59 | row = column.row(align=True) 60 | row.prop(self, 'end_tilt', slider=True) 61 | row.enabled = self.is_tilt 62 | row = column.row(align=True) 63 | row.prop(self, 'end_radius') 64 | row.enabled = self.is_radius 65 | row = column.row(align=True) 66 | row.prop(self, 'end_weight_softbody', slider=True) 67 | row.enabled = self.is_weight_softbody 68 | 69 | self.layout.prop(self, 'mode') 70 | 71 | def execute(self, context): 72 | begin_ratio, end_ratio = self.begin_ratio * 0.01, self.end_ratio * 0.01 73 | 74 | for ob in context.selected_objects: 75 | if ob.type != 'CURVE': continue 76 | curve = ob.data 77 | if not len(curve.splines): continue 78 | 79 | for spline in curve.splines: 80 | if len(spline.points) < 2: continue 81 | 82 | total_length = 0.0 83 | for index, point in enumerate(spline.points): 84 | if index == 0: continue 85 | diff_co = mathutils.Vector(point.co[:3]) - mathutils.Vector(spline.points[index - 1].co[:3]) 86 | total_length += diff_co.length 87 | 88 | if total_length == 0.0: continue 89 | 90 | current_length = 0.0 91 | for index, point in enumerate(spline.points): 92 | if 1 <= index: 93 | diff_co = mathutils.Vector(point.co[:3]) - mathutils.Vector(spline.points[index - 1].co[:3]) 94 | current_length += diff_co.length 95 | 96 | current_length_ratio = current_length / total_length 97 | 98 | if current_length_ratio <= begin_ratio: 99 | current_tilt = self.begin_tilt 100 | current_radius = self.begin_radius 101 | current_weight_softbody = self.begin_weight_softbody 102 | elif end_ratio <= current_length_ratio: 103 | current_tilt = self.end_tilt 104 | current_radius = self.end_radius 105 | current_weight_softbody = self.end_weight_softbody 106 | else: 107 | ratio = (current_length_ratio - begin_ratio) / (end_ratio - begin_ratio) 108 | current_tilt = (self.begin_tilt * (1 - ratio)) + (self.end_tilt * (ratio)) 109 | current_radius = (self.begin_radius * (1 - ratio)) + (self.end_radius * (ratio)) 110 | current_weight_softbody = ((self.begin_weight_softbody * (1 - ratio)) + (self.end_weight_softbody * (ratio))) * 0.01 111 | 112 | if self.is_tilt: 113 | if self.mode == 'ABSOLUTE': point.tilt = current_tilt 114 | elif self.mode == 'RELATIVE': point.tilt += current_tilt 115 | if self.is_radius: 116 | if self.mode == 'ABSOLUTE': point.radius = current_radius 117 | elif self.mode == 'RELATIVE': point.radius += current_radius 118 | if self.is_weight_softbody: 119 | if self.mode == 'ABSOLUTE': point.weight_softbody = current_weight_softbody 120 | elif self.mode == 'RELATIVE': point.weight_softbody += current_weight_softbody 121 | 122 | return {'FINISHED'} 123 | -------------------------------------------------------------------------------- /Anime Hair Supporter/_panel.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | 3 | class VIEW3D_PT_tools_anime_hair_supporter(bpy.types.Panel): 4 | bl_space_type = 'VIEW_3D' 5 | bl_region_type = 'TOOLS' 6 | bl_category = 'Tools' 7 | bl_context = 'objectmode' 8 | bl_label = "アニメ髪支援" 9 | bl_options = {'DEFAULT_CLOSED'} 10 | 11 | def draw(self, context): 12 | props = context.scene.ahs_props 13 | 14 | # コンバーターズ 15 | column = self.layout.column(align=True) 16 | row = column.row(align=True) 17 | row.operator('object.ahs_convert_edgemesh_to_curve', icon='IPO_EASE_IN_OUT') 18 | row.enabled = bool( len([o for o in context.selected_objects if o.type == 'MESH']) ) 19 | row = column.row(align=True) 20 | row.operator('object.ahs_convert_curve_to_edgemesh', icon='IPO_CONSTANT') 21 | row.enabled = bool( len([o for o in context.selected_objects if o.type == 'CURVE']) ) 22 | 23 | 24 | 25 | # メインカーブ 26 | box = self.layout.box() 27 | row = box.row(align=True) 28 | row.prop(props, 'maincurve_expand', text="メインカーブ", icon='MAN_ROT', emboss=False) 29 | row.operator('object.ahs_maincurve_activate', text="", icon='ZOOM_SELECTED') 30 | row.label("", icon='BLANK1') 31 | 32 | if props.maincurve_expand: 33 | 34 | # 肉付け関係 35 | row = box.row(align=True) 36 | row.operator('object.ahs_maincurve_volume_up', icon='MESH_CAPSULE') 37 | row.operator('object.ahs_maincurve_volume_down', text="", icon='X') 38 | 39 | column = box.column(align=True) 40 | # 余剰変形 41 | column.operator('object.ahs_maincurve_extra_deform', icon='PARTICLE_PATH') 42 | # グラデーションひねり 43 | column.operator('object.ahs_maincurve_gradation_tilt', icon='FORCE_MAGNETIC') 44 | 45 | # サブツール 46 | column = box.column(align=True) 47 | row = column.row(align=True) 48 | row.operator('object.ahs_maincurve_select', icon='RESTRICT_SELECT_OFF') 49 | row.operator('object.ahs_maincurve_hide', text="表示", icon='VISIBLE_IPO_ON').is_hide = False 50 | row.operator('object.ahs_maincurve_hide', text="隠す", icon='VISIBLE_IPO_OFF').is_hide = True 51 | 52 | # 解像度 53 | row = column.row(align=True) 54 | try: is_successed = context.active_object.data.taper_object and context.active_object.data.bevel_object and context.active_object.data.splines.active 55 | except: is_successed = False 56 | if is_successed: row.prop(context.active_object.data.splines.active, 'resolution_u', text="解像度") 57 | else: row.label(text="解像度:") 58 | row.operator('object.ahs_maincurve_set_resolution', text="", icon='PREFERENCES') 59 | # 次数 60 | row = column.row(align=True) 61 | try: is_successed = context.active_object.data.taper_object and context.active_object.data.bevel_object and context.active_object.data.splines.active 62 | except: is_successed = False 63 | if is_successed: row.prop(context.active_object.data.splines.active, 'order_u', text="次数") 64 | else: row.label(text="次数:") 65 | row.operator('object.ahs_maincurve_set_order', text="", icon='PREFERENCES') 66 | 67 | 68 | 69 | # テーパーカーブ 70 | box = self.layout.box() 71 | row = box.row(align=True) 72 | row.prop(props, 'tapercurve_expand', text="テーパーカーブ", icon='CURVE_NCURVE', emboss=False) 73 | row.operator('object.ahs_tapercurve_activate', text="", icon='ZOOM_SELECTED').mode = 'TAPER' 74 | row.operator('object.ahs_tapercurve_id_singlize', text="", icon='COPY_ID') 75 | 76 | if props.tapercurve_expand: 77 | 78 | # 種類を変更とか 79 | row = box.split(percentage=0.6, align=False) 80 | op = row.operator('object.ahs_tapercurve_change_type', icon='HAND') 81 | op.is_taper, op.is_bevel = True, False 82 | op = row.operator('object.ahs_tapercurve_mirror', icon='MOD_MIRROR') 83 | op.mode, op.is_mirror_x, op.is_mirror_y = 'TAPER', False, True 84 | 85 | # 位置を再設定とか 86 | row = box.row(align=False) 87 | row.operator('object.ahs_tapercurve_relocation', icon='PARTICLE_TIP').mode = 'BOTH' 88 | row.operator('object.ahs_tapercurve_remove_alones', icon='X').mode = 'BOTH' 89 | 90 | # サブツール 91 | column = box.column(align=True) 92 | row = column.row(align=True) 93 | row.operator('object.ahs_tapercurve_select', icon='RESTRICT_SELECT_OFF').mode = 'TAPER' 94 | op = row.operator('object.ahs_tapercurve_hide', text="表示", icon='VISIBLE_IPO_ON') 95 | op.mode, op.is_hide = 'TAPER', False 96 | op = row.operator('object.ahs_tapercurve_hide', text="隠す", icon='VISIBLE_IPO_OFF') 97 | op.mode, op.is_hide = 'TAPER', True 98 | 99 | # 解像度 100 | row = column.row(align=True) 101 | try: 102 | row.prop(context.active_object.data.taper_object.data.splines.active, 'resolution_u', text="解像度") 103 | is_successed = True 104 | except: is_successed = False 105 | if not is_successed: 106 | taper_objects = [c.taper_object for c in context.blend_data.curves if c.taper_object] 107 | try: 108 | if context.active_object in taper_objects: 109 | row.prop(context.active_object.data.splines.active, 'resolution_u', text="解像度") 110 | is_successed = True 111 | except: is_successed = False 112 | if not is_successed: row.label(text="解像度:") 113 | row.operator('object.ahs_maincurve_set_resolution', text="", icon='PREFERENCES') 114 | 115 | 116 | 117 | # ベベルカーブ 118 | box = self.layout.box() 119 | row = box.row(align=True) 120 | row.prop(props, 'bevelcurve_expand', text="ベベルカーブ", icon='SURFACE_NCIRCLE', emboss=False) 121 | row.operator('object.ahs_tapercurve_activate', text="", icon='ZOOM_SELECTED').mode = 'BEVEL' 122 | row.operator('object.ahs_tapercurve_id_singlize', text="", icon='COPY_ID') 123 | 124 | if props.bevelcurve_expand: 125 | 126 | # 種類を変更とか 127 | row = box.split(percentage=0.6, align=False) 128 | op = row.operator('object.ahs_tapercurve_change_type', icon='HAND') 129 | op.is_taper, op.is_bevel = False, True 130 | op = row.operator('object.ahs_tapercurve_mirror', icon='MOD_MIRROR') 131 | op.mode, op.is_mirror_x, op.is_mirror_y = 'BEVEL', True, False 132 | 133 | # 位置を再設定とか 134 | row = box.row(align=False) 135 | row.operator('object.ahs_tapercurve_relocation', icon='PARTICLE_TIP').mode = 'BOTH' 136 | row.operator('object.ahs_tapercurve_remove_alones', icon='X').mode = 'BOTH' 137 | 138 | # サブツール 139 | column = box.column(align=True) 140 | row = column.row(align=True) 141 | row.operator('object.ahs_tapercurve_select', icon='RESTRICT_SELECT_OFF').mode = 'BEVEL' 142 | op = row.operator('object.ahs_tapercurve_hide', text="表示", icon='VISIBLE_IPO_ON') 143 | op.mode, op.is_hide = 'BEVEL', False 144 | op = row.operator('object.ahs_tapercurve_hide', text="隠す", icon='VISIBLE_IPO_OFF') 145 | op.mode, op.is_hide = 'BEVEL', True 146 | 147 | # 解像度 148 | row = column.row(align=True) 149 | try: 150 | row.prop(context.active_object.data.bevel_object.data.splines.active, 'resolution_u', text="解像度") 151 | is_successed = True 152 | except: is_successed = False 153 | if not is_successed: 154 | bevel_objects = [c.bevel_object for c in context.blend_data.curves if c.bevel_object] 155 | try: 156 | if context.active_object in bevel_objects: 157 | row.prop(context.active_object.data.splines.active, 'resolution_u', text="解像度") 158 | is_successed = True 159 | except: is_successed = False 160 | if not is_successed: row.label(text="解像度:") 161 | row.operator('object.ahs_maincurve_set_resolution', text="", icon='PREFERENCES') 162 | 163 | 164 | 165 | # コンバーターズ 166 | row = self.layout.row(align=True) 167 | row.operator('object.ahs_convert_curve_to_armature', icon='ARMATURE_DATA') 168 | row.enabled = bool(len([o for o in context.selected_objects if o.type == 'CURVE'])) 169 | 170 | row = self.layout.row(align=True) 171 | row.operator('object.ahs_convert_curve_to_mesh', icon='MESH_UVSPHERE') 172 | for ob in context.selected_objects: 173 | if ob.type != 'CURVE': continue 174 | if ob.data.taper_object and ob.data.bevel_object: 175 | row.enabled = True 176 | break 177 | else: row.enabled = False 178 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | --------------------------------------------------------------------------------