blender Million 2026
# Copied: 15:00:01
import bpy
import bmesh
import math
import webbrowser
from bpy.props import FloatVectorProperty, FloatProperty, BoolProperty, PointerProperty, StringProperty
from bpy.types import Operator, Panel, PropertyGroup
from mathutils import Vector
from datetime import datetime
# ==============================================================================
# 設定エリア & ID管理
# ==============================================================================
PREFIX = "GeoSplit20260308"
TAB_NAME = " [ Geo Split ] "
# ### ZIONAD_SOURCE_ID: GEO_SPLIT_2026_03_08_COLOR ###
bl_info = {
"name": f"zionad 520 [ Geo Split ] {PREFIX}",
"author": "zionadchat",
"version": (3, 3, 0),
"blender": (3, 0, 0),
"location": "3D View > Sidebar",
"description": "Earth Splitter & Cone Generator with Multi-Color",
"category": "3D View",
}
OP_PREFIX = PREFIX.lower()
PROPS_NAME = f"{PREFIX}_props"
SOURCE_ID_TAG = "### ZIONAD_SOURCE_ID: GEO_SPLIT_2026_03_08_COLOR ###"
ADDON_LINKS = (
{"label": "Code Copy Template", "url": "<https://www.notion.so/Code-copy-20260221>"},
{"label": "Theory Background", "url": "<https://www.notion.so/Einstein-from-20260119>"},
)
# ==============================================================================
# デフォルト値設定 (コピー機能で書き換わるエリア)
# ==============================================================================
# <BEGIN_DICT>
CURRENT_DEFAULTS = {
"show_preview": True,
"color_upper": (0.05, 0.40, 0.80, 0.80), # 青系
"color_lower": (0.10, 0.60, 0.20, 0.80), # 緑系
"color_cone_lat": (0.90, 0.80, 0.10, 0.80), # 黄色系
"color_cone_polar": (0.90, 0.20, 0.20, 0.80),# 赤系
"sphere_loc": (0.0000, 0.0000, 0.0000),
"sphere_radius": 5.0000,
"split_latitude": 60.0000,
"show_upper": True,
"show_lower": True,
"show_cone_lat": True,
"show_cone_polar": True,
}
# <END_DICT>
# ==============================================================================
# ジオメトリ生成ロジック (BMesh)
# ==============================================================================
def build_upper_sphere(radius, lat_deg):
bm = bmesh.new()
bmesh.ops.create_uvsphere(bm, u_segments=64, v_segments=32, radius=radius)
z_cut = radius * math.sin(math.radians(lat_deg))
geom = bm.verts[:] + bm.edges[:] + bm.faces[:]
res = bmesh.ops.bisect_plane(bm, geom=geom, plane_co=(0,0,z_cut), plane_no=(0,0,1), clear_inner=True, clear_outer=False)
cut_edges = [e for e in res['geom_cut'] if isinstance(e, bmesh.types.BMEdge)]
if cut_edges:
try: bmesh.ops.holes_fill(bm, edges=cut_edges)
except: pass
return bm
def build_lower_sphere(radius, lat_deg):
bm = bmesh.new()
bmesh.ops.create_uvsphere(bm, u_segments=64, v_segments=32, radius=radius)
z_cut = radius * math.sin(math.radians(lat_deg))
geom = bm.verts[:] + bm.edges[:] + bm.faces[:]
res = bmesh.ops.bisect_plane(bm, geom=geom, plane_co=(0,0,z_cut), plane_no=(0,0,1), clear_inner=False, clear_outer=True)
cut_edges = [e for e in res['geom_cut'] if isinstance(e, bmesh.types.BMEdge)]
if cut_edges:
try: bmesh.ops.holes_fill(bm, edges=cut_edges)
except: pass
return bm
def build_cone_lat(radius, lat_deg):
bm = bmesh.new()
lat_rad = math.radians(lat_deg)
z_cut = radius * math.sin(lat_rad)
r_base = radius * math.cos(lat_rad)
depth = abs(z_cut)
if depth < 0.0001: depth = 0.0001
if z_cut >= 0:
bmesh.ops.create_cone(bm, segments=64, radius1=0, radius2=r_base, depth=depth)
bmesh.ops.translate(bm, vec=(0,0, depth/2), verts=bm.verts)
else:
bmesh.ops.create_cone(bm, segments=64, radius1=r_base, radius2=0, depth=depth)
bmesh.ops.translate(bm, vec=(0,0, -depth/2), verts=bm.verts)
return bm
def build_cone_polar(radius):
bm = bmesh.new()
bmesh.ops.create_cone(bm, segments=64, radius1=radius, radius2=0, depth=radius)
bmesh.ops.translate(bm, vec=(0,0, radius/2), verts=bm.verts)
return bm
def create_unique_material(color, name_prefix="Mat"):
timestamp = datetime.now().strftime('%M%S%f')[:5]
mat_name = f"{name_prefix}_{timestamp}"
mat = bpy.data.materials.new(name=mat_name)
mat.use_nodes = True
mat.blend_method = 'BLEND'
if mat.use_nodes:
tree = mat.node_tree
tree.nodes.clear()
bsdf = tree.nodes.new("ShaderNodeBsdfPrincipled")
bsdf.location = (0, 0)
out = tree.nodes.new("ShaderNodeOutputMaterial")
out.location = (300, 0)
tree.links.new(bsdf.outputs[0], out.inputs[0])
if "Base Color" in bsdf.inputs: bsdf.inputs['Base Color'].default_value = color
if "Alpha" in bsdf.inputs: bsdf.inputs['Alpha'].default_value = color[3]
mat.diffuse_color = color
return mat
# ==============================================================================
# プレビュー用ロジック
# ==============================================================================
PREVIEW_COL_NAME = f"{PREFIX}_Preview_Zone"
PREVIEW_TAG = f"{PREFIX}_preview_tag"
def update_preview_geometry(context):
props = getattr(context.scene, PROPS_NAME, None)
if not props: return
col = bpy.data.collections.get(PREVIEW_COL_NAME)
if not col:
col = bpy.data.collections.new(PREVIEW_COL_NAME)
context.scene.collection.children.link(col)
for o in[o for o in col.objects if o.get(PREVIEW_TAG)]:
bpy.data.objects.remove(o, do_unlink=True)
if not props.show_preview: return
# オブジェクトと個別の色を一緒にリストにまとめる
parts =[]
if props.show_upper:
parts.append(("UpperSphere", build_upper_sphere(props.sphere_radius, props.split_latitude), props.color_upper))
if props.show_lower:
parts.append(("LowerSphere", build_lower_sphere(props.sphere_radius, props.split_latitude), props.color_lower))
if props.show_cone_lat:
parts.append(("ConeCenterLat", build_cone_lat(props.sphere_radius, props.split_latitude), props.color_cone_lat))
if props.show_cone_polar:
parts.append(("ConePolarEq", build_cone_polar(props.sphere_radius), props.color_cone_polar))
for name, bm, color in parts:
bmesh.ops.translate(bm, vec=Vector(props.sphere_loc), verts=bm.verts)
mesh = bpy.data.meshes.new(f"PreviewMesh_{name}")
bm.to_mesh(mesh)
bm.free()
obj = bpy.data.objects.new(f"[Preview] {name}", mesh)
obj[PREVIEW_TAG] = True
col.objects.link(obj)
# 個別カラーでマテリアル生成
mat = create_unique_material(color, f"Mat_Prev_{name}")
obj.data.materials.append(mat)
obj.show_wire = True
obj.show_all_edges = True
obj.select_set(False)
_timer = None
def delayed_update():
global _timer
_timer = None
if bpy.context and bpy.context.scene:
update_preview_geometry(bpy.context)
return None
def on_update(self, context):
global _timer
if _timer:
try: bpy.app.timers.unregister(_timer)
except: pass
_timer = bpy.app.timers.register(delayed_update, first_interval=0.05)
# ==============================================================================
# PROPERTIES
# ==============================================================================
class PG_SphereProps(PropertyGroup):
show_preview: BoolProperty(name="Show Preview", default=CURRENT_DEFAULTS['show_preview'], update=on_update)
sphere_loc: FloatVectorProperty(name="Location", size=3, default=CURRENT_DEFAULTS['sphere_loc'], update=on_update)
sphere_radius: FloatProperty(name="Radius", default=CURRENT_DEFAULTS['sphere_radius'], min=0.01, update=on_update)
split_latitude: FloatProperty(name="Split Latitude", default=CURRENT_DEFAULTS['split_latitude'], min=-90.0, max=90.0, update=on_update)
# 個別カラープロパティ
color_upper: FloatVectorProperty(name="Upper Color", subtype='COLOR', size=4, min=0, max=1, default=CURRENT_DEFAULTS['color_upper'], update=on_update)
color_lower: FloatVectorProperty(name="Lower Color", subtype='COLOR', size=4, min=0, max=1, default=CURRENT_DEFAULTS['color_lower'], update=on_update)
color_cone_lat: FloatVectorProperty(name="Cone (Lat) Color", subtype='COLOR', size=4, min=0, max=1, default=CURRENT_DEFAULTS['color_cone_lat'], update=on_update)
color_cone_polar: FloatVectorProperty(name="Cone (Polar) Color", subtype='COLOR', size=4, min=0, max=1, default=CURRENT_DEFAULTS['color_cone_polar'], update=on_update)
show_upper: BoolProperty(name="Show Upper Sphere", default=CURRENT_DEFAULTS['show_upper'], update=on_update)
show_lower: BoolProperty(name="Show Lower Sphere", default=CURRENT_DEFAULTS['show_lower'], update=on_update)
show_cone_lat: BoolProperty(name="Show Cone (Lat)", default=CURRENT_DEFAULTS['show_cone_lat'], update=on_update)
show_cone_polar: BoolProperty(name="Show Cone (Polar)", default=CURRENT_DEFAULTS['show_cone_polar'], update=on_update)
# ==============================================================================
# OPERATORS
# ==============================================================================
class OT_CreateSphere(Operator):
bl_idname = f"{OP_PREFIX}.create_sphere"
bl_label = "Create Objects"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
props = getattr(context.scene, PROPS_NAME, None)
parts =[]
if props.show_upper:
parts.append(("UpperSphere", build_upper_sphere(props.sphere_radius, props.split_latitude), props.color_upper))
if props.show_lower:
parts.append(("LowerSphere", build_lower_sphere(props.sphere_radius, props.split_latitude), props.color_lower))
if props.show_cone_lat:
parts.append(("ConeCenterLat", build_cone_lat(props.sphere_radius, props.split_latitude), props.color_cone_lat))
if props.show_cone_polar:
parts.append(("ConePolarEq", build_cone_polar(props.sphere_radius), props.color_cone_polar))
if not parts:
self.report({'WARNING'}, "No visibility options selected.")
return {'CANCELLED'}
created_objs =[]
timestamp = datetime.now().strftime('%H%M%S')
for name, bm, color in parts:
bmesh.ops.translate(bm, vec=Vector(props.sphere_loc), verts=bm.verts)
mesh = bpy.data.meshes.new(f"{name}_Mesh")
bm.to_mesh(mesh)
bm.free()
obj = bpy.data.objects.new(f"{name}_{timestamp}", mesh)
if context.collection: context.collection.objects.link(obj)
else: context.scene.collection.objects.link(obj)
# 個別カラーのマテリアルを適用
unique_mat = create_unique_material(color, f"Mat_{name}")
obj.data.materials.append(unique_mat)
created_objs.append(obj)
bpy.ops.object.select_all(action='DESELECT')
for obj in created_objs:
obj.select_set(True)
if created_objs:
context.view_layer.objects.active = created_objs[-1]
self.report({'INFO'}, f"Created {len(created_objs)} Parts Successfully")
return {'FINISHED'}
class OT_CopyFullScript(Operator):
bl_idname = f"{OP_PREFIX}.copy_script"
bl_label = "Copy Script"
def execute(self, context):
props = getattr(context.scene, PROPS_NAME, None)
target_text = None
for t in bpy.data.texts:
if SOURCE_ID_TAG in t.as_string():
target_text = t; break
if not target_text:
self.report({'ERROR'}, "Script source not found.")
return {'CANCELLED'}
code = target_text.as_string()
l = props.sphere_loc
cu = props.color_upper
cl = props.color_lower
ccl = props.color_cone_lat
ccp = props.color_cone_polar
new_dict = "CURRENT_DEFAULTS = {\n"
new_dict += f' "show_preview": {props.show_preview},\n'
new_dict += f' "color_upper": ({cu[0]:.4f}, {cu[1]:.4f}, {cu[2]:.4f}, {cu[3]:.4f}),\n'
new_dict += f' "color_lower": ({cl[0]:.4f}, {cl[1]:.4f}, {cl[2]:.4f}, {cl[3]:.4f}),\n'
new_dict += f' "color_cone_lat": ({ccl[0]:.4f}, {ccl[1]:.4f}, {ccl[2]:.4f}, {ccl[3]:.4f}),\n'
new_dict += f' "color_cone_polar": ({ccp[0]:.4f}, {ccp[1]:.4f}, {ccp[2]:.4f}, {ccp[3]:.4f}),\n'
new_dict += f' "sphere_loc": ({l[0]:.4f}, {l[1]:.4f}, {l[2]:.4f}),\n'
new_dict += f' "sphere_radius": {props.sphere_radius:.4f},\n'
new_dict += f' "split_latitude": {props.split_latitude:.4f},\n'
new_dict += f' "show_upper": {props.show_upper},\n'
new_dict += f' "show_lower": {props.show_lower},\n'
new_dict += f' "show_cone_lat": {props.show_cone_lat},\n'
new_dict += f' "show_cone_polar": {props.show_cone_polar},\n'
new_dict += "}\n"
try:
start, end = "# <BEGIN" + "_DICT>", "# <END" + "_DICT>"
pre, post = code.split(start)[0], code.split(end)[1]
final = f"# Copied: {datetime.now().strftime('%H:%M:%S')}\n" + pre + start + "\n" + new_dict + end + post
context.window_manager.clipboard = final
self.report({'INFO'}, "Code copied!")
except: return {'CANCELLED'}
return {'FINISHED'}
class OT_Reset(Operator):
bl_idname = f"{OP_PREFIX}.reset"
bl_label = "Reset"
def execute(self, context):
p = getattr(context.scene, PROPS_NAME)
p.sphere_loc = (0,0,0)
p.sphere_radius = 5.0
p.split_latitude = 60.0
p.show_upper = True; p.show_lower = True
p.show_cone_lat = True; p.show_cone_polar = True
return {'FINISHED'}
class OT_OpenUrl(Operator):
bl_idname = f"{OP_PREFIX}.open_url"; bl_label = "Open URL"; url: StringProperty()
def execute(self, context): webbrowser.open(self.url); return {'FINISHED'}
class OT_RemoveAddon(Operator):
bl_idname = f"{OP_PREFIX}.remove_addon"; bl_label = "Remove Addon"
def execute(self, context):
bpy.app.timers.register(lambda: unregister(), first_interval=0.1)
return {'FINISHED'}
# ==============================================================================
# PANELS
# ==============================================================================
class PT_MainPanel(Panel):
bl_label = "Earth / Geo Splitter"
bl_idname = f"{PREFIX}_PT_main"
bl_space_type = 'VIEW_3D'; bl_region_type = 'UI'; bl_category = TAB_NAME
def draw(self, context):
layout = self.layout
props = getattr(context.scene, PROPS_NAME, None)
if not props: layout.label(text="Reload Script"); return
row = layout.row()
row.scale_y = 1.2
row.operator(OT_CopyFullScript.bl_idname, icon='COPY_ID', text="Copy Code with Values")
layout.separator()
layout.prop(props, "show_preview", icon='RESTRICT_VIEW_OFF' if props.show_preview else 'RESTRICT_VIEW_ON')
box = layout.box()
if not props.show_preview:
box.label(text="Preview is Hidden", icon='INFO')
box.prop(props, "sphere_loc")
box.prop(props, "sphere_radius")
box.prop(props, "split_latitude", text="Latitude (Degree)")
box.label(text="Visibility & Colors:")
# Upper Sphere
row = box.row(align=True)
row.prop(props, "show_upper", text="Upper Sphere (North)", icon='HIDE_OFF' if props.show_upper else 'HIDE_ON')
if props.show_upper: row.prop(props, "color_upper", text="")
# Lower Sphere
row = box.row(align=True)
row.prop(props, "show_lower", text="Lower Sphere (South)", icon='HIDE_OFF' if props.show_lower else 'HIDE_ON')
if props.show_lower: row.prop(props, "color_lower", text="")
# Cone Center to Lat
row = box.row(align=True)
row.prop(props, "show_cone_lat", text="Cone (Center to Lat)", icon='HIDE_OFF' if props.show_cone_lat else 'HIDE_ON')
if props.show_cone_lat: row.prop(props, "color_cone_lat", text="")
# Cone Pole to Eq
row = box.row(align=True)
row.prop(props, "show_cone_polar", text="Cone (Pole to Eq)", icon='HIDE_OFF' if props.show_cone_polar else 'HIDE_ON')
if props.show_cone_polar: row.prop(props, "color_cone_polar", text="")
box.operator(OT_Reset.bl_idname, icon='LOOP_BACK', text="Reset Params")
layout.separator()
col = layout.column()
col.scale_y = 1.5
col.operator(OT_CreateSphere.bl_idname, icon='MESH_UVSPHERE', text="Create Objects")
class PT_LinksPanel(Panel):
bl_label = "Links"; bl_idname = f"{PREFIX}_PT_links"; bl_space_type = 'VIEW_3D'; bl_region_type = 'UI'; bl_category = TAB_NAME; bl_options = {'DEFAULT_CLOSED'}
def draw(self, context):
for l in ADDON_LINKS: self.layout.operator(OT_OpenUrl.bl_idname, text=l["label"]).url = l["url"]
class PT_RemovePanel(Panel):
bl_label = "System"; bl_idname = f"{PREFIX}_PT_remove"; bl_space_type = 'VIEW_3D'; bl_region_type = 'UI'; bl_category = TAB_NAME; bl_options = {'DEFAULT_CLOSED'}
def draw(self, context): self.layout.operator(OT_RemoveAddon.bl_idname, icon='CANCEL', text="Remove Addon")
# ==============================================================================
# REGISTER
# ==============================================================================
classes = (PG_SphereProps, OT_CreateSphere, OT_CopyFullScript, OT_Reset, OT_OpenUrl, OT_RemoveAddon, PT_MainPanel, PT_LinksPanel, PT_RemovePanel)
def register():
for c in classes: bpy.utils.register_class(c)
setattr(bpy.types.Scene, PROPS_NAME, PointerProperty(type=PG_SphereProps))
def unregister():
if hasattr(bpy.types.Scene, PROPS_NAME): delattr(bpy.types.Scene, PROPS_NAME)
for c in reversed(classes): bpy.utils.unregister_class(c)
if __name__ == "__main__": register()