20261003 円錐頂点矢印 と 球体
bl_info = {
"name": "Tangent Cone Generator",
"author": "AI",
"version": (1, 10, 0),
"blender": (4, 0, 0),
"location": "View3D > Sidebar > Tangent Cone",
"description": "コレクション管理改善・円錐と矢印の別色設定・球体ON/OFF対応 (フリーズ対策版)",
"category": "Add Mesh",
}
import bpy
import bmesh
import math
import webbrowser
import random
from mathutils import Vector, Matrix
from bpy.app.handlers import persistent
# ==========================================
# 初期値
# ==========================================
ADDON_LINKS = {
"20261003 円錐頂点矢印 と 球体_002": "<https://app.notion.com/p/20261003-3ed691cbf90680a187f7d9c5ea268c2b>",
"338 Sphere Cam": "<https://app.notion.com/p/338-Sphere-Cam-3c0347b3454a80abbb61ff2cd585792c>",
"posfie": "<https://posfie.com/@timekagura/t/zionad2022?sort=0>",
"zionadchat": "<https://x.com/zionadchat>",
"背後霊カメラ 最新版リンク": "<https://app.notion.com/p/Cam-blender-39f347b3454a806490a6f0107c2347dd>",
"blender コンテナ 20260713": "<https://note.com/zionadmillion/n/n9a47d88fe7b4>",
"骨格立方体": "<https://app.notion.com/p/351-2017-3cb347b3454a8086b15fec2f69408bd5>",
}
INIT_APEX_LOC = (0.0, 0.0, 10.0)
INIT_RADIUS = 1.0
INIT_SEGMENTS = 128
INIT_DRAW_MODE = 'CONE'
INIT_ARROW_CNT = 4
INIT_SHOW_SPHERE = True
INIT_SPHERE_COLOR = (0.2, 0.6, 1.0)
INIT_SPHERE_ALPHA = 0.35
INIT_CONE_COLOR = (1.0, 0.5, 0.1)
INIT_CONE_ALPHA = 0.25
INIT_ARROWS_COLOR = (0.15, 0.9, 0.35)
INIT_ARROWS_ALPHA = 0.45
INIT_SHOW_SHORT_ARROW = True
INIT_SHORT_ARROW_COLOR = (1.0, 0.0, 0.0)
INIT_SHORT_ARROW_ALPHA = 1.0
_IS_UPDATING = False
MAIN_COLL_NAME = "Tangent Cone Setup"
FIXED_COLL_NAME = "Tangent Cone Fixed"
# ==========================================
# セットアップ
# ==========================================
@persistent
def setup_workspace_handler(dummy=None):
wm = bpy.context.window_manager
if not wm:
return
for window in wm.windows:
screen = window.screen
if not screen:
continue
for area in screen.areas:
if area.type == 'VIEW_3D':
for space in area.spaces:
if space.type == 'VIEW_3D':
try:
space.show_region_ui = True
if space.shading.type != 'MATERIAL':
space.shading.type = 'MATERIAL'
except:
pass
def delayed_setup():
setup_workspace_handler()
return None
# ==========================================
# ユーティリティ (Object管理 完全修正)
# ==========================================
def get_or_create_collection(name):
coll = bpy.data.collections.get(name)
if coll is None:
coll = bpy.data.collections.new(name)
try:
bpy.context.scene.collection.children.link(coll)
except RuntimeError:
pass
else:
if bpy.context.scene.collection.children.get(name) is None:
try:
bpy.context.scene.collection.children.link(coll)
except RuntimeError:
pass
return coll
def get_or_create_object(name, coll):
# 1. 対象コレクション優先
obj = coll.objects.get(name)
if obj is not None:
if not obj.data or not isinstance(obj.data, bpy.types.Mesh):
obj.data = bpy.data.meshes.new(f"{name}_Mesh")
return obj
# 2. 全体から再利用
obj = bpy.data.objects.get(name)
if obj is not None:
if coll.objects.get(obj.name) is None:
try:
coll.objects.link(obj)
except RuntimeError:
pass
if not obj.data or not isinstance(obj.data, bpy.types.Mesh):
obj.data = bpy.data.meshes.new(f"{name}_Mesh")
return obj
# 3. 新規
mesh = bpy.data.meshes.new(f"{name}_Mesh")
obj = bpy.data.objects.new(name, mesh)
coll.objects.link(obj)
return obj
def remove_object_by_name(name):
"""指定名のオブジェクトを全コレクションから外して完全削除"""
obj = bpy.data.objects.get(name)
if not obj:
return
for c in list(obj.users_collection):
try:
c.objects.unlink(obj)
except:
pass
try:
bpy.data.objects.remove(obj, do_unlink=True)
except:
pass
def safe_replace_mesh(bm, obj):
old_mesh = obj.data
new_mesh_name = old_mesh.name if old_mesh else f"{obj.name}_Mesh"
new_mesh = bpy.data.meshes.new(new_mesh_name)
bm.to_mesh(new_mesh)
bm.free()
obj.data = new_mesh
if old_mesh and old_mesh.users == 0:
try:
bpy.data.meshes.remove(old_mesh)
except:
pass
def assign_material(obj, name, color, alpha, blend='BLEND'):
mat = bpy.data.materials.get(name)
if not mat:
mat = bpy.data.materials.new(name=name)
mat.use_nodes = True
mat.blend_method = blend
if mat.use_nodes and mat.node_tree:
bsdf = mat.node_tree.nodes.get("Principled BSDF")
if bsdf:
try:
bsdf.inputs["Base Color"].default_value = (*color[:3], 1.0)
if "Alpha" in bsdf.inputs:
bsdf.inputs["Alpha"].default_value = alpha
if "Transmission Weight" in bsdf.inputs:
bsdf.inputs["Transmission Weight"].default_value = 0.1
except:
pass
if not obj.data.materials:
obj.data.materials.append(mat)
else:
obj.data.materials[0] = mat
def get_plane_eq(N, Cc):
nx, ny, nz = N.x, N.y, N.z
d = N.dot(Cc)
terms = []
if abs(nx) > 1e-4: terms.append(f"{nx:.2f}x")
if abs(ny) > 1e-4: terms.append(f"{ny:.2f}y")
if abs(nz) > 1e-4: terms.append(f"{nz:.2f}z")
if not terms: terms.append("0")
return " + ".join(terms).replace("+ -", "- ") + f" = {d:.2f}"
def get_sphere_eq(Cc, rc):
def fmt_term(axis, val):
if abs(val) < 1e-4: return f"{axis}^2"
if val < 0: return f"({axis} + {abs(val):.2f})^2"
return f"({axis} - {val:.2f})^2"
return f"{fmt_term('x', Cc.x)} + {fmt_term('y', Cc.y)} + {fmt_term('z', Cc.z)} = {(rc*rc):.2f}"
# ==========================================
# コアロジック
# ==========================================
def update_tangent_cone_logic(self, context=None):
global _IS_UPDATING
if not getattr(self, "is_calculated", False):
return
if _IS_UPDATING:
return
_IS_UPDATING = True
try:
if context is None:
context = bpy.context
P = Vector(self.apex_loc)
C = Vector((0.0, 0.0, 0.0))
R = self.radius
segments = self.segments
V = P - C
d = V.length
if d <= R:
self.res_error = "エラー: 頂点が球の内部または表面にあります"
return
self.res_error = ""
zc = (R * R) / d
rc = R * math.sqrt(max(0.0, d * d - R * R)) / d
N = V.normalized()
Cc = C + N * zc
cone_h = d - zc
len_tangent = math.sqrt(max(0.0, d * d - R * R))
len_shortest = d - R
tmp = Vector((1, 0, 0)) if abs(N.x) < 0.9 else Vector((0, 1, 0))
U = N.cross(tmp).normalized()
W = N.cross(U).normalized()
circle_verts = []
for i in range(segments):
theta = 2 * math.pi * i / segments
circle_verts.append(Cc + rc * (U * math.cos(theta) + W * math.sin(theta)))
coll = get_or_create_collection(MAIN_COLL_NAME)
def add_arrow(bm_mesh, P_start, P_end, scale_R):
vec = P_end - P_start
L = vec.length
if L < 1e-5:
return
dir = vec.normalized()
rot_mat = Vector((0,0,1)).rotation_difference(dir).to_matrix().to_4x4()
r_shaft = scale_R * 0.02
r_head = scale_R * 0.06
L_shaft = L * 0.85
L_head = L * 0.15
ret_shaft = bmesh.ops.create_cone(bm_mesh, cap_ends=True, cap_tris=False, segments=12, radius1=r_shaft, radius2=r_shaft, depth=L_shaft)
bmesh.ops.transform(bm_mesh, matrix=rot_mat, verts=ret_shaft['verts'])
bmesh.ops.transform(bm_mesh, matrix=Matrix.Translation(P_start + dir*(L_shaft*0.5)), verts=ret_shaft['verts'])
ret_head = bmesh.ops.create_cone(bm_mesh, cap_ends=True, cap_tris=False, segments=12, radius1=r_head, radius2=0.0, depth=L_head)
bmesh.ops.transform(bm_mesh, matrix=rot_mat, verts=ret_head['verts'])
bmesh.ops.transform(bm_mesh, matrix=Matrix.Translation(P_start + dir*(L_shaft + L_head*0.5)), verts=ret_head['verts'])
# --- 1. 球体関連 (チェックボックスでON/OFF) ---
if self.show_sphere:
obj_sphere = get_or_create_object("Sphere_Base", coll)
bm_sphere = bmesh.new()
bmesh.ops.create_uvsphere(bm_sphere, u_segments=32, v_segments=16, radius=R)
for f in bm_sphere.faces: f.smooth = True
safe_replace_mesh(bm_sphere, obj_sphere)
assign_material(obj_sphere, "Mat_Sphere", self.sphere_color, self.sphere_alpha)
obj_cap = get_or_create_object("SphericalCap", coll)
bm_cap = bmesh.new()
bmesh.ops.create_uvsphere(bm_cap, u_segments=32, v_segments=16, radius=R)
bm_cap.verts.ensure_lookup_table()
to_remove = [v for v in bm_cap.verts if (v.co - Cc).dot(N) < -1e-6]
if to_remove:
bmesh.ops.delete(bm_cap, geom=to_remove, context='VERTS')
for f in bm_cap.faces: f.smooth = True
safe_replace_mesh(bm_cap, obj_cap)
assign_material(obj_cap, "Mat_Cap", self.sphere_color, self.sphere_alpha)
else:
# OFFなら球体オブジェクトをコレクションから削除
remove_object_by_name("Sphere_Base")
remove_object_by_name("SphericalCap")
# --- 2. 円錐スカート vs 矢印複数 (別オブジェクト・別色) ---
if self.draw_mode == 'CONE':
# 矢印側を削除
remove_object_by_name("Arrows_Tangent")
remove_object_by_name("Cone_Tangent") # 旧名互換削除
obj_cone = get_or_create_object("Cone_Skirt", coll)
bm_cone = bmesh.new()
apex_v = bm_cone.verts.new(P)
cv = [bm_cone.verts.new(p) for p in circle_verts]
bm_cone.verts.ensure_lookup_table()
for i in range(segments):
bm_cone.faces.new((apex_v, cv[i], cv[(i+1)%segments]))
for f in bm_cone.faces: f.smooth = True
safe_replace_mesh(bm_cone, obj_cone)
assign_material(obj_cone, "Mat_Cone", self.cone_color, self.cone_alpha)
else: # ARROWS
remove_object_by_name("Cone_Skirt")
remove_object_by_name("Cone_Tangent")
obj_arrows = get_or_create_object("Arrows_Tangent", coll)
bm_arrows = bmesh.new()
arrows = self.arrow_count
for i in range(arrows):
theta = 2 * math.pi * i / arrows
v_target = Cc + rc * (U * math.cos(theta) + W * math.sin(theta))
add_arrow(bm_arrows, P, v_target, R)
for f in bm_arrows.faces: f.smooth = True
safe_replace_mesh(bm_arrows, obj_arrows)
assign_material(obj_arrows, "Mat_Arrows", self.arrows_color, self.arrows_alpha)
# --- 3. Contact Circle (常に表示) ---
obj_circle = get_or_create_object("ContactCircle", coll)
bm_circle = bmesh.new()
cv_circ = [bm_circle.verts.new(p) for p in circle_verts]
bm_circle.verts.ensure_lookup_table()
for i in range(segments):
bm_circle.edges.new((cv_circ[i], cv_circ[(i+1)%segments]))
safe_replace_mesh(bm_circle, obj_circle)
obj_circle.show_wire = True
assign_material(obj_circle, "Mat_Circle", (1.0, 0.05, 0.05), 1.0, blend='OPAQUE')
# --- 4. 最短距離矢印 ---
obj_short = get_or_create_object("ShortestArrow", coll)
bm_short = bmesh.new()
if self.show_short_arrow:
v_target_short = C + N * R
add_arrow(bm_short, P, v_target_short, R)
for f in bm_short.faces: f.smooth = True
safe_replace_mesh(bm_short, obj_short)
assign_material(obj_short, "Mat_ShortArrow", self.short_arrow_color, self.short_arrow_alpha)
# 計算結果
self.res_apex_z = f"{P.z:.2f}"
self.res_cone_h = f"{cone_h:.2f}"
self.res_len_tangent = f"{len_tangent:.2f}"
self.res_len_shortest = f"{len_shortest:.2f}"
self.res_circle_z = f"{Cc.z:.2f}"
self.res_circle_r = f"{rc:.2f}"
self.res_circle_circum = f"{(2 * math.pi * rc):.2f}"
self.res_plane_eq = get_plane_eq(N, Cc)
self.res_sphere_eq = get_sphere_eq(Cc, rc)
except Exception as e:
print(f"[Tangent Cone] update error: {e}")
import traceback
traceback.print_exc()
self.res_error = f"更新エラー: {e}"
finally:
_IS_UPDATING = False
# ==========================================
# オペレーター
# ==========================================
class TANGENTCONE_OT_init(bpy.types.Operator):
bl_idname = "mesh.tangent_cone_init"
bl_label = "作成 (新規生成)"
def execute(self, context):
props = context.scene.tangent_cone_props
props.is_calculated = True
update_tangent_cone_logic(props, context)
if props.res_error:
props.is_calculated = False
self.report({'WARNING'}, props.res_error)
return {'CANCELLED'}
return {'FINISHED'}
class TANGENTCONE_OT_detach(bpy.types.Operator):
bl_idname = "mesh.tangent_cone_detach"
bl_label = "切り離し (独立化)"
bl_description = "現在の生成物を独立させ、色も固定して保持します"
def execute(self, context):
global _IS_UPDATING
if _IS_UPDATING:
self.report({'WARNING'}, "更新中のため少し待ってください")
return {'CANCELLED'}
props = context.scene.tangent_cone_props
suffix = f"_Fixed_{random.randint(1000, 9999)}"
names = ["Sphere_Base", "Cone_Skirt", "Arrows_Tangent", "Cone_Tangent", "ContactCircle", "SphericalCap", "ShortestArrow"]
coll = bpy.data.collections.get(MAIN_COLL_NAME)
if not coll:
self.report({'WARNING'}, "対象が見つかりません")
props.is_calculated = False
return {'CANCELLED'}
fixed_coll = bpy.data.collections.get(FIXED_COLL_NAME)
if not fixed_coll:
fixed_coll = bpy.data.collections.new(FIXED_COLL_NAME)
try:
context.scene.collection.children.link(fixed_coll)
except RuntimeError:
pass
for name in names:
obj = coll.objects.get(name) or bpy.data.objects.get(name)
if not obj:
continue
if obj.users_collection:
for c in list(obj.users_collection):
if c.name == MAIN_COLL_NAME:
try:
c.objects.unlink(obj)
except:
pass
if fixed_coll.objects.get(obj.name) is None:
try:
fixed_coll.objects.link(obj)
except RuntimeError:
pass
old_name = obj.name
obj.name = name + suffix
if obj.data:
try:
obj.data.name = old_name + suffix + "_Mesh"
except:
pass
for slot in obj.material_slots:
if slot.material:
if slot.material.users > 1:
new_mat = slot.material.copy()
new_mat.name = slot.material.name + suffix
slot.material = new_mat
else:
slot.material.name = slot.material.name + suffix
props.is_calculated = False
self.report({'INFO'}, f"切り離し完了: {suffix}")
return {'FINISHED'}
class TANGENTCONE_OT_copy_text(bpy.types.Operator):
bl_idname = "wm.copy_tangent_cone_text"
bl_label = "Copy"
text_to_copy: bpy.props.StringProperty()
def execute(self, context):
context.window_manager.clipboard = self.text_to_copy
self.report({'INFO'}, f"コピー: {self.text_to_copy}")
return {'FINISHED'}
class TANGENTCONE_OT_copy_all(bpy.types.Operator):
bl_idname = "wm.copy_tangent_cone_all"
bl_label = "全結果をコピー"
def execute(self, context):
props = context.scene.tangent_cone_props
lines = [
f"【計算結果】",
f"頂点のZ座標: {props.res_apex_z}",
f"円錐の高さ: {props.res_cone_h}",
f"接線矢印の長さ(母線): {props.res_len_tangent}",
f"最短矢印の長さ(球表面へ): {props.res_len_shortest}",
f"接円のZ座標: {props.res_circle_z}",
f"接円の半径: {props.res_circle_r}",
f"接円の円周: {props.res_circle_circum}",
f"",
f"【接する円の方程式】",
f"平面: {props.res_plane_eq}",
f"球面: {props.res_sphere_eq}",
]
context.window_manager.clipboard = "\n".join(lines)
self.report({'INFO'}, "コピーしました")
return {'FINISHED'}
class TANGENTCONE_OT_open_url(bpy.types.Operator):
bl_idname = "wm.tangent_cone_open_url"
bl_label = "リンクを開く"
url: bpy.props.StringProperty()
def execute(self, context):
if self.url: webbrowser.open(self.url)
return {'FINISHED'}
class TANGENTCONE_OT_remove_addon(bpy.types.Operator):
bl_idname = "wm.tangent_cone_remove_addon"
bl_label = "アドオン削除"
def execute(self, context):
unregister()
return {'FINISHED'}
# ==========================================
# プロパティ
# ==========================================
class TangentConeProperties(bpy.types.PropertyGroup):
apex_loc: bpy.props.FloatVectorProperty(name="Apex (X,Y,Z)", default=INIT_APEX_LOC, subtype='XYZ', update=update_tangent_cone_logic)
radius: bpy.props.FloatProperty(name="Sphere Radius", default=INIT_RADIUS, min=0.001, update=update_tangent_cone_logic)
segments: bpy.props.IntProperty(name="Segments", default=INIT_SEGMENTS, min=16, max=512, update=update_tangent_cone_logic)
show_sphere: bpy.props.BoolProperty(name="球体を作成", description="Sphere_Base と SphericalCap の生成ON/OFF", default=INIT_SHOW_SPHERE, update=update_tangent_cone_logic)
draw_mode: bpy.props.EnumProperty(
name="描画モード",
items=[('CONE', "円錐スカート", ""), ('ARROWS', "矢印複数", "")],
default=INIT_DRAW_MODE, update=update_tangent_cone_logic
)
arrow_count: bpy.props.IntProperty(name="矢印の本数", default=INIT_ARROW_CNT, min=1, max=12, update=update_tangent_cone_logic)
show_short_arrow: bpy.props.BoolProperty(name="最短距離矢印を表示", default=INIT_SHOW_SHORT_ARROW, update=update_tangent_cone_logic)
sphere_color: bpy.props.FloatVectorProperty(name="色", subtype='COLOR', default=INIT_SPHERE_COLOR, min=0.0, max=1.0, update=update_tangent_cone_logic)
sphere_alpha: bpy.props.FloatProperty(name="透明度", default=INIT_SPHERE_ALPHA, min=0.0, max=1.0, update=update_tangent_cone_logic)
cone_color: bpy.props.FloatVectorProperty(name="色", subtype='COLOR', default=INIT_CONE_COLOR, min=0.0, max=1.0, update=update_tangent_cone_logic)
cone_alpha: bpy.props.FloatProperty(name="透明度", default=INIT_CONE_ALPHA, min=0.0, max=1.0, update=update_tangent_cone_logic)
arrows_color: bpy.props.FloatVectorProperty(name="色", subtype='COLOR', default=INIT_ARROWS_COLOR, min=0.0, max=1.0, update=update_tangent_cone_logic)
arrows_alpha: bpy.props.FloatProperty(name="透明度", default=INIT_ARROWS_ALPHA, min=0.0, max=1.0, update=update_tangent_cone_logic)
short_arrow_color: bpy.props.FloatVectorProperty(name="色", subtype='COLOR', default=INIT_SHORT_ARROW_COLOR, min=0.0, max=1.0, update=update_tangent_cone_logic)
short_arrow_alpha: bpy.props.FloatProperty(name="透明度", default=INIT_SHORT_ARROW_ALPHA, min=0.0, max=1.0, update=update_tangent_cone_logic)
is_calculated: bpy.props.BoolProperty(default=False)
res_error: bpy.props.StringProperty(default="")
res_apex_z: bpy.props.StringProperty()
res_cone_h: bpy.props.StringProperty()
res_len_tangent: bpy.props.StringProperty()
res_len_shortest: bpy.props.StringProperty()
res_circle_z: bpy.props.StringProperty()
res_circle_r: bpy.props.StringProperty()
res_circle_circum: bpy.props.StringProperty()
res_plane_eq: bpy.props.StringProperty()
res_sphere_eq: bpy.props.StringProperty()
# ==========================================
# UI
# ==========================================
class VIEW3D_PT_tangent_cone(bpy.types.Panel):
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = 'Tangent Cone'
bl_label = "Tangent Cone Generator"
def draw(self, context):
layout = self.layout
props = context.scene.tangent_cone_props
# 形状
box_shape = layout.box()
box_shape.label(text="形状設定", icon='MESH_DATA')
col = box_shape.column(align=True)
col.prop(props, "apex_loc")
col.prop(props, "radius")
col.prop(props, "segments")
col.separator()
col.prop(props, "show_sphere", toggle=True, icon='SPHERE')
layout.separator()
# 描画モード
box_mode = layout.box()
box_mode.label(text="生成形状の選択", icon='MODIFIER')
box_mode.prop(props, "draw_mode", expand=True)
if props.draw_mode == 'ARROWS':
box_mode.prop(props, "arrow_count")
box_mode.separator()
box_mode.prop(props, "show_short_arrow", toggle=True, icon='TRACKING_FORWARDS')
layout.separator()
# マテリアル (別色対応)
box_mat = layout.box()
box_mat.label(text="マテリアル (別色設定)", icon='MATERIAL')
# 球体
row = box_mat.row(align=True)
row.enabled = props.show_sphere
row.label(text="球体:")
row.prop(props, "sphere_color", text="")
row.prop(props, "sphere_alpha", slider=True)
# 円錐スカート専用
row_cone = box_mat.row(align=True)
row_cone.label(text="円錐スカート:")
row_cone.prop(props, "cone_color", text="")
row_cone.prop(props, "cone_alpha", slider=True)
if props.draw_mode != 'CONE':
row_cone.enabled = False
# 矢印複数専用 (新設)
row_arrows = box_mat.row(align=True)
row_arrows.label(text="矢印複数:")
row_arrows.prop(props, "arrows_color", text="")
row_arrows.prop(props, "arrows_alpha", slider=True)
if props.draw_mode != 'ARROWS':
row_arrows.enabled = False
# 最短矢印
if props.show_short_arrow:
row_short = box_mat.row(align=True)
row_short.label(text="最短矢印:")
row_short.prop(props, "short_arrow_color", text="")
row_short.prop(props, "short_arrow_alpha", slider=True)
layout.separator()
if not props.is_calculated:
layout.operator("mesh.tangent_cone_init", text="作成 (新規生成)", icon='MESH_CONE')
else:
layout.operator("mesh.tangent_cone_detach", text="切り離し (独立化)", icon='UNLINKED')
if props.res_error:
layout.separator()
layout.label(text=props.res_error, icon='ERROR')
return
if props.is_calculated:
layout.separator()
box = layout.box()
box.label(text="【計算結果】", icon='INFO')
def draw_result(parent, label_text, val_text):
row = parent.row(align=True)
split = row.split(factor=0.55)
split.label(text=label_text)
sub = split.row(align=True)
sub.label(text=val_text)
op = sub.operator("wm.copy_tangent_cone_text", text="", icon='COPYDOWN')
op.text_to_copy = val_text
col = box.column(align=True)
draw_result(col, "接線矢印の長さ (母線)", props.res_len_tangent)
draw_result(col, "最短矢印の長さ", props.res_len_shortest)
col.separator()
draw_result(col, "頂点 Z", props.res_apex_z)
draw_result(col, "高さ", props.res_cone_h)
draw_result(col, "接円 Z", props.res_circle_z)
draw_result(col, "接円 半径", props.res_circle_r)
draw_result(col, "接円 円周", props.res_circle_circum)
col.separator()
col.label(text="【接する円の方程式】")
draw_result(col, "平面", props.res_plane_eq)
draw_result(col, "球面", props.res_sphere_eq)
col.separator()
col.operator("wm.copy_tangent_cone_all", text="すべての結果をコピー", icon='DOCUMENTS')
# コレクション情報
layout.separator()
box_coll = layout.box()
box_coll.label(text=f"コレクション: {MAIN_COLL_NAME}", icon='OUTLINER_COLLECTION')
coll = bpy.data.collections.get(MAIN_COLL_NAME)
if coll:
for obj in coll.objects:
box_coll.label(text=f" - {obj.name}", icon='OBJECT_DATA')
class VIEW3D_PT_tangent_cone_links(bpy.types.Panel):
bl_label = "Links"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = 'Tangent Cone'
def draw(self, context):
layout = self.layout
for name, url in ADDON_LINKS.items():
if name and url:
layout.operator("wm.tangent_cone_open_url", text=name, icon='URL').url = url
class VIEW3D_PT_tangent_cone_remove(bpy.types.Panel):
bl_label = "Addon Remove"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = 'Tangent Cone'
def draw(self, context):
layout = self.layout
layout.operator("wm.tangent_cone_remove_addon", icon='CANCEL')
# ==========================================
# 登録
# ==========================================
classes = (
TANGENTCONE_OT_init,
TANGENTCONE_OT_detach,
TANGENTCONE_OT_copy_text,
TANGENTCONE_OT_copy_all,
TANGENTCONE_OT_open_url,
TANGENTCONE_OT_remove_addon,
TangentConeProperties,
VIEW3D_PT_tangent_cone,
VIEW3D_PT_tangent_cone_links,
VIEW3D_PT_tangent_cone_remove,
)
def register():
for cls in classes:
bpy.utils.register_class(cls)
bpy.types.Scene.tangent_cone_props = bpy.props.PointerProperty(type=TangentConeProperties)
if setup_workspace_handler not in bpy.app.handlers.load_post:
bpy.app.handlers.load_post.append(setup_workspace_handler)
try:
bpy.app.timers.register(delayed_setup, first_interval=0.5)
except:
pass
def unregister():
global _IS_UPDATING
_IS_UPDATING = False
for cls in reversed(classes):
try:
bpy.utils.unregister_class(cls)
except:
pass
if hasattr(bpy.types.Scene, "tangent_cone_props"):
del bpy.types.Scene.tangent_cone_props
if setup_workspace_handler in bpy.app.handlers.load_post:
bpy.app.handlers.load_post.remove(setup_workspace_handler)
if __name__ == "__main__":
try: unregister()
except: pass
register()