Sidebar (Nパネル)", "description": "リアルタイム編集対応。多角形トーラスを生成し、確定・切り離しが可能", "category": "Object", } import bpy import webbrowser # ========================================================================= # 【基本設定】 # ========================================================================= TAB_NAME = "多角形ツール" PREFIX_NAME = "poly_torus" # --- リンク設定 (ドキュメント用) --- ADDON_LINKS = [ { "label": "地球儀 緯度経度 xyz (参考)", "url": "https://note.com/zionadmillion/n/na0b25ce45488", "icon": "URL" }, { "label": "最新 posfie (参考)", "url": "https://posfie.com/@timekagura/t/zionad2022?sort=0", "icon": "URL" } ] INIT_COLOR = (0.0, 1.0, 0.5) # 無限ループ防止用のグローバル変数 IS_UPDATING = False # ======================================================================"> Sidebar (Nパネル)", "description": "リアルタイム編集対応。多角形トーラスを生成し、確定・切り離しが可能", "category": "Object", } import bpy import webbrowser # ========================================================================= # 【基本設定】 # ========================================================================= TAB_NAME = "多角形ツール" PREFIX_NAME = "poly_torus" # --- リンク設定 (ドキュメント用) --- ADDON_LINKS = [ { "label": "地球儀 緯度経度 xyz (参考)", "url": "https://note.com/zionadmillion/n/na0b25ce45488", "icon": "URL" }, { "label": "最新 posfie (参考)", "url": "https://posfie.com/@timekagura/t/zionad2022?sort=0", "icon": "URL" } ] INIT_COLOR = (0.0, 1.0, 0.5) # 無限ループ防止用のグローバル変数 IS_UPDATING = False # ======================================================================"> Sidebar (Nパネル)", "description": "リアルタイム編集対応。多角形トーラスを生成し、確定・切り離しが可能", "category": "Object", } import bpy import webbrowser # ========================================================================= # 【基本設定】 # ========================================================================= TAB_NAME = "多角形ツール" PREFIX_NAME = "poly_torus" # --- リンク設定 (ドキュメント用) --- ADDON_LINKS = [ { "label": "地球儀 緯度経度 xyz (参考)", "url": "https://note.com/zionadmillion/n/na0b25ce45488", "icon": "URL" }, { "label": "最新 posfie (参考)", "url": "https://posfie.com/@timekagura/t/zionad2022?sort=0", "icon": "URL" } ] INIT_COLOR = (0.0, 1.0, 0.5) # 無限ループ防止用のグローバル変数 IS_UPDATING = False # ======================================================================">
bl_info = {
"name": "Polygon Torus Generator Pro (Realtime & Detach)",
"author": "Your Name",
"version": (2, 0),
"blender": (5, 0, 0), # Blender 5.0対応
"location": "View3D > Sidebar (Nパネル)",
"description": "リアルタイム編集対応。多角形トーラスを生成し、確定・切り離しが可能",
"category": "Object",
}
import bpy
import webbrowser
# =========================================================================
# 【基本設定】
# =========================================================================
TAB_NAME = "多角形ツール"
PREFIX_NAME = "poly_torus"
# --- リンク設定 (ドキュメント用) ---
ADDON_LINKS = [
{
"label": "地球儀 緯度経度 xyz (参考)",
"url": "<https://note.com/zionadmillion/n/na0b25ce45488>",
"icon": "URL"
},
{
"label": "最新 posfie (参考)",
"url": "<https://posfie.com/@timekagura/t/zionad2022?sort=0>",
"icon": "URL"
}
]
INIT_COLOR = (0.0, 1.0, 0.5)
# 無限ループ防止用のグローバル変数
IS_UPDATING = False
# =========================================================================
# 【リアルタイム更新関数】
# スライダーや数値を変更した瞬間に、対象のオブジェクトを自動更新します
# =========================================================================
def update_poly_torus(self, context):
global IS_UPDATING
if IS_UPDATING:
return
props = context.scene.poly_torus_props
if not props.active_obj_name:
return
obj = bpy.data.objects.get(props.active_obj_name)
# 対象が存在しない、または切り離し済みの場合は更新しない
if not obj or not obj.get("pt_live"):
return
IS_UPDATING = True
try:
# 1. 位置・回転の更新
obj.location = (props.loc_x, props.loc_y, props.loc_z)
obj.rotation_euler = (props.rot_x, props.rot_y, props.rot_z)
# 2. マテリアル・色の更新 (マテリアルはオブジェクトごとに独立しています)
mat = obj.active_material
if mat and mat.use_nodes:
bsdf = mat.node_tree.nodes.get("Principled BSDF")
if bsdf:
bsdf.inputs["Base Color"].default_value = (*props.color, 1.0)
bsdf.inputs["Alpha"].default_value = props.alpha
mat.blend_method = 'BLEND' if props.alpha < 1.0 else 'OPAQUE'
# 3. 形状(メッシュ)の更新
# 3Dビューポートのコンテキストを取得して安全にメッシュを再生成
area = next((a for a in context.screen.areas if a.type == 'VIEW_3D'), None)
if area:
region = next((r for r in area.regions if r.type == 'WINDOW'), None)
old_active = context.view_layer.objects.active
temp_obj = None
with context.temp_override(area=area, region=region):
bpy.ops.mesh.primitive_torus_add(
major_radius=props.radius,
minor_radius=props.thick,
major_segments=props.segments,
minor_segments=32,
location=(0,0,0), rotation=(0,0,0)
)
temp_obj = context.active_object
# メッシュデータをすり替える(オブジェクト本体は維持)
if temp_obj:
new_mesh = temp_obj.data
old_mesh = obj.data
obj.data = new_mesh
if mat:
obj.data.materials.append(mat) # マテリアルを再割り当て
bpy.data.objects.remove(temp_obj)
if old_mesh:
bpy.data.meshes.remove(old_mesh)
context.view_layer.objects.active = old_active
obj.select_set(True)
finally:
IS_UPDATING = False
# =========================================================================
# 【プロパティ定義】 (全項目に update コールバックを設定)
# =========================================================================
class PolyTorusProperties(bpy.types.PropertyGroup):
active_obj_name: bpy.props.StringProperty(name="編集中のオブジェクト", default="")
segments: bpy.props.IntProperty(name="角数 (3=三角, 4=四角)", default=3, min=3, max=256, update=update_poly_torus)
radius: bpy.props.FloatProperty(name="半径 (R)", default=1.0, min=0.01, update=update_poly_torus)
thick: bpy.props.FloatProperty(name="太さ (断面)", default=0.05, min=0.001, update=update_poly_torus)
color: bpy.props.FloatVectorProperty(name="色", subtype='COLOR', default=INIT_COLOR, min=0.0, max=1.0, update=update_poly_torus)
alpha: bpy.props.FloatProperty(name="透明度", default=1.0, min=0.0, max=1.0, update=update_poly_torus)
loc_x: bpy.props.FloatProperty(name="位置 X", default=0.0, update=update_poly_torus)
loc_y: bpy.props.FloatProperty(name="位置 Y", default=0.0, update=update_poly_torus)
loc_z: bpy.props.FloatProperty(name="位置 Z", default=0.0, update=update_poly_torus)
rot_x: bpy.props.FloatProperty(name="X軸", default=0.0, subtype='ANGLE', update=update_poly_torus)
rot_y: bpy.props.FloatProperty(name="Y軸", default=0.0, subtype='ANGLE', update=update_poly_torus)
rot_z: bpy.props.FloatProperty(name="Z軸", default=0.0, subtype='ANGLE', update=update_poly_torus)
# =========================================================================
# 【オペレーター:生成処理】
# =========================================================================
class POLY_OT_create_torus(bpy.types.Operator):
bl_idname = f"mesh.{PREFIX_NAME.lower()}_create"
bl_label = "多角形を生成"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
global IS_UPDATING
props = getattr(context.scene, f"{PREFIX_NAME.lower()}_props")
# 以前編集していたオブジェクトがあれば、自動で切り離す
if props.active_obj_name:
old_obj = bpy.data.objects.get(props.active_obj_name)
if old_obj and "pt_live" in old_obj:
del old_obj["pt_live"]
# アドオン削除後も完全に独立して残る標準マテリアルを新規作成
mat = bpy.data.materials.new(name=f"{PREFIX_NAME}_Mat")
mat.use_nodes = True
# オブジェクト生成
area = next((a for a in context.screen.areas if a.type == 'VIEW_3D'), None)
region = next((r for r in area.regions if r.type == 'WINDOW'), None) if area else None
IS_UPDATING = True
try:
if area and region:
with context.temp_override(area=area, region=region):
bpy.ops.mesh.primitive_torus_add(
major_radius=props.radius, minor_radius=props.thick,
major_segments=props.segments, minor_segments=32,
location=(props.loc_x, props.loc_y, props.loc_z),
rotation=(props.rot_x, props.rot_y, props.rot_z)
)
else:
# 3Dビュー外で実行された場合のフォールバック
bpy.ops.mesh.primitive_torus_add(
major_radius=props.radius, minor_radius=props.thick, major_segments=props.segments
)
obj = context.active_object
obj.data.materials.append(mat)
# トラッキング用のマーカーと名前を記録
obj["pt_live"] = True
props.active_obj_name = obj.name
finally:
IS_UPDATING = False
# 色などを初期適用
update_poly_torus(self, context)
return {'FINISHED'}
# =========================================================================
# 【オペレーター:アドオンから切り離し (確定)】
# =========================================================================
class POLY_OT_detach_torus(bpy.types.Operator):
bl_idname = f"object.{PREFIX_NAME.lower()}_detach"
bl_label = "アドオンから切り離し (確定)"
bl_description = "現在の図形を確定し、アドオンの連動から切り離します"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
global IS_UPDATING
props = getattr(context.scene, f"{PREFIX_NAME.lower()}_props")
obj = bpy.data.objects.get(props.active_obj_name)
if obj and "pt_live" in obj:
del obj["pt_live"] # マーカーを削除
IS_UPDATING = True
props.active_obj_name = ""
IS_UPDATING = False
self.report({'INFO'}, "オブジェクトを確定し、切り離しました。")
return {'FINISHED'}
# =========================================================================
# 【オペレーター:URL&アドオン削除機能】
# =========================================================================
class POLY_OT_open_url(bpy.types.Operator):
bl_idname = f"wm.{PREFIX_NAME.lower()}_open_url"
bl_label = "URL"
url: bpy.props.StringProperty()
def execute(self, context):
webbrowser.open(self.url)
return {'FINISHED'}
class POLY_OT_remove_addon(bpy.types.Operator):
bl_idname = f"wm.{PREFIX_NAME.lower()}_remove_addon"
bl_label = "アドオン削除"
bl_description = "アドオンをUIから消去します (作成したオブジェクト・マテリアルはそのまま残ります)"
def execute(self, context):
unregister()
return {'FINISHED'}
# =========================================================================
# 【UIパネル群】
# =========================================================================
class POLY_PT_main_panel(bpy.types.Panel):
bl_idname = f"{PREFIX_NAME.upper()}_PT_main_panel"
bl_label = "多角形トーラス (リアルタイム生成)"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = TAB_NAME
def draw(self, context):
layout = self.layout
props = getattr(context.scene, f"{PREFIX_NAME.lower()}_props")
# ドキュメント(説明)
box_doc = layout.box()
box_doc.label(text="【使い方】", icon='HELP')
box_doc.label(text="数値を変更するとリアルタイムに変形します")
box_doc.label(text="「角数」3で三角形、4で四角形")
layout.separator()
# 状態表示と切り離しボタン
if props.active_obj_name and bpy.data.objects.get(props.active_obj_name):
box_live = layout.box()
box_live.label(text=f"🔄 リアルタイム編集中: {props.active_obj_name}", icon='EDITMODE_HLT')
box_live.scale_y = 1.3
box_live.operator(f"object.{PREFIX_NAME.lower()}_detach", text="アドオンから切り離し (確定)", icon='UNLINKED')
layout.separator()
else:
# 生成ボタン
layout.scale_y = 1.3
layout.operator(f"mesh.{PREFIX_NAME.lower()}_create", text="新しく多角形を生成", icon='ADD')
layout.scale_y = 1.0
layout.separator()
# 形状設定
box_s = layout.box()
box_s.label(text="形状と色 (リアルタイム連動)", icon='MESH_TORUS')
col_s = box_s.column(align=True)
col_s.prop(props, "segments")
col_s.separator()
col_s.prop(props, "radius")
col_s.prop(props, "thick")
col_s.separator()
row_c = col_s.row(align=True)
row_c.prop(props, "color", text="")
row_c.prop(props, "alpha", text="透明")
class POLY_PT_transform_panel(bpy.types.Panel):
bl_idname = f"{PREFIX_NAME.upper()}_PT_transform_panel"
bl_label = "配置 (位置と回転)"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = TAB_NAME
bl_options = {'DEFAULT_CLOSED'}
def draw(self, context):
props = getattr(context.scene, f"{PREFIX_NAME.lower()}_props")
c1, c2 = self.layout.column(align=True), self.layout.column(align=True)
c1.label(text="位置 (リアルタイム連動):")
c1.prop(props, "loc_x")
c1.prop(props, "loc_y")
c1.prop(props, "loc_z")
self.layout.separator()
c2.label(text="回転 (リアルタイム連動):")
c2.prop(props, "rot_x")
c2.prop(props, "rot_y")
c2.prop(props, "rot_z")
class POLY_PT_footer_panel(bpy.types.Panel):
bl_idname = f"{PREFIX_NAME.upper()}_PT_footer_panel"
bl_label = "ドキュメント / 管理"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = TAB_NAME
bl_order = 100
bl_options = {'DEFAULT_CLOSED'}
def draw(self, context):
layout = self.layout
box_links = layout.box()
box_links.label(text="関連リンク:", icon='BOOKMARKS')
for link in ADDON_LINKS:
box_links.operator(f"wm.{PREFIX_NAME.lower()}_open_url", text=link["label"], icon='URL').url = link["url"]
layout.separator(factor=2.0)
# このボタンを押すとアドオン自体がUIから消えます
layout.operator(f"wm.{PREFIX_NAME.lower()}_remove_addon", text="アドオンを無効化して閉じる", icon='CANCEL')
# =========================================================================
# 【登録処理】
# =========================================================================
classes = [
PolyTorusProperties,
POLY_OT_create_torus,
POLY_OT_detach_torus,
POLY_OT_open_url,
POLY_OT_remove_addon,
POLY_PT_main_panel,
POLY_PT_transform_panel,
POLY_PT_footer_panel
]
def register():
for c in classes:
bpy.utils.register_class(c)
setattr(bpy.types.Scene, f"{PREFIX_NAME.lower()}_props", bpy.props.PointerProperty(type=PolyTorusProperties))
def unregister():
if hasattr(bpy.types.Scene, f"{PREFIX_NAME.lower()}_props"):
delattr(bpy.types.Scene, f"{PREFIX_NAME.lower()}_props")
for c in reversed(classes):
try: bpy.utils.unregister_class(c)
except: pass
if __name__ == "__main__":
try: unregister()
except: pass
register()