import bpy
from bpy.types import Operator, Panel, PropertyGroup
from bpy.props import StringProperty, PointerProperty, FloatVectorProperty, IntProperty
# =========================================================
# アドオン宣言
# =========================================================
bl_info = {
"name": "Grease Pencil Object Manager",
"author": "AI",
"version": (4, 3, 2),
"blender": (5, 0, 0),
"location": "View3D > Sidebar > GP Manager",
"description": "GPオブジェクト作成 → ツール/太さ/色 → レイヤー → エフェクト → 完成",
"category": "Grease Pencil",
}
PREFIX_VAL = "ZIONAD"
ADDON_TITLE = "GP Manager"
DEFAULT_COL_NAME = "GreaseObjects"
DEFAULT_BASE_NAME = "GP_"
# =========================================================
# 1. プロパティグループ (12色のカラー設定)
# =========================================================
class ZIONAD_Properties(PropertyGroup):
col_name: StringProperty(name="主コレクション", default=DEFAULT_COL_NAME)
obj_base_name: StringProperty(name="ベース名", default=DEFAULT_BASE_NAME)
col_01_name: StringProperty(default="黒"); col_01_stroke: FloatVectorProperty(subtype='COLOR', size=4, default=(0.05, 0.05, 0.05, 1.0), min=0.0, max=1.0); col_01_fill: FloatVectorProperty(subtype='COLOR', size=4, default=(0.3, 0.3, 0.3, 0.5), min=0.0, max=1.0)
col_02_name: StringProperty(default="白"); col_02_stroke: FloatVectorProperty(subtype='COLOR', size=4, default=(1.0, 1.0, 1.0, 1.0), min=0.0, max=1.0); col_02_fill: FloatVectorProperty(subtype='COLOR', size=4, default=(0.9, 0.9, 0.9, 0.5), min=0.0, max=1.0)
col_03_name: StringProperty(default="灰"); col_03_stroke: FloatVectorProperty(subtype='COLOR', size=4, default=(0.4, 0.4, 0.4, 1.0), min=0.0, max=1.0); col_03_fill: FloatVectorProperty(subtype='COLOR', size=4, default=(0.6, 0.6, 0.6, 0.5), min=0.0, max=1.0)
col_04_name: StringProperty(default="赤"); col_04_stroke: FloatVectorProperty(subtype='COLOR', size=4, default=(0.8, 0.1, 0.1, 1.0), min=0.0, max=1.0); col_04_fill: FloatVectorProperty(subtype='COLOR', size=4, default=(0.9, 0.4, 0.4, 0.5), min=0.0, max=1.0)
col_05_name: StringProperty(default="青"); col_05_stroke: FloatVectorProperty(subtype='COLOR', size=4, default=(0.1, 0.2, 0.8, 1.0), min=0.0, max=1.0); col_05_fill: FloatVectorProperty(subtype='COLOR', size=4, default=(0.4, 0.5, 0.9, 0.5), min=0.0, max=1.0)
col_06_name: StringProperty(default="緑"); col_06_stroke: FloatVectorProperty(subtype='COLOR', size=4, default=(0.1, 0.7, 0.2, 1.0), min=0.0, max=1.0); col_06_fill: FloatVectorProperty(subtype='COLOR', size=4, default=(0.4, 0.9, 0.5, 0.5), min=0.0, max=1.0)
col_07_name: StringProperty(default="黄"); col_07_stroke: FloatVectorProperty(subtype='COLOR', size=4, default=(0.9, 0.8, 0.1, 1.0), min=0.0, max=1.0); col_07_fill: FloatVectorProperty(subtype='COLOR', size=4, default=(0.9, 0.9, 0.5, 0.5), min=0.0, max=1.0)
col_08_name: StringProperty(default="水"); col_08_stroke: FloatVectorProperty(subtype='COLOR', size=4, default=(0.1, 0.8, 0.9, 1.0), min=0.0, max=1.0); col_08_fill: FloatVectorProperty(subtype='COLOR', size=4, default=(0.5, 0.9, 0.9, 0.5), min=0.0, max=1.0)
col_09_name: StringProperty(default="紫"); col_09_stroke: FloatVectorProperty(subtype='COLOR', size=4, default=(0.6, 0.1, 0.8, 1.0), min=0.0, max=1.0); col_09_fill: FloatVectorProperty(subtype='COLOR', size=4, default=(0.8, 0.5, 0.9, 0.5), min=0.0, max=1.0)
col_10_name: StringProperty(default="橙"); col_10_stroke: FloatVectorProperty(subtype='COLOR', size=4, default=(0.9, 0.4, 0.1, 1.0), min=0.0, max=1.0); col_10_fill: FloatVectorProperty(subtype='COLOR', size=4, default=(0.9, 0.6, 0.4, 0.5), min=0.0, max=1.0)
col_11_name: StringProperty(default="茶"); col_11_stroke: FloatVectorProperty(subtype='COLOR', size=4, default=(0.4, 0.2, 0.1, 1.0), min=0.0, max=1.0); col_11_fill: FloatVectorProperty(subtype='COLOR', size=4, default=(0.6, 0.4, 0.3, 0.5), min=0.0, max=1.0)
col_12_name: StringProperty(default="桃"); col_12_stroke: FloatVectorProperty(subtype='COLOR', size=4, default=(0.9, 0.3, 0.6, 1.0), min=0.0, max=1.0); col_12_fill: FloatVectorProperty(subtype='COLOR', size=4, default=(0.9, 0.6, 0.8, 0.5), min=0.0, max=1.0)
# =========================================================
# 2. ユーティリティ (ジオメトリノード自動生成)
# =========================================================
def is_gp_object(obj):
return obj and obj.type in {'GPENCIL', 'GREASEPENCIL'}
def get_next_name(base_name):
max_num = 0
for obj in bpy.data.objects:
if is_gp_object(obj) and obj.name.startswith(base_name):
try: max_num = max(max_num, int(obj.name.replace(base_name, "")))
except: pass
return f"{base_name}{max_num + 1:03d}"
# ★ ボイリング(震え) ノードツリー生成
def create_noise_node_group():
name = "GP_Boiling_Noise_FX"
if name in bpy.data.node_groups: return bpy.data.node_groups[name]
ng = bpy.data.node_groups.new(name, 'GeometryNodeTree')
ng.interface.new_socket("Geometry", in_out='INPUT', socket_type='NodeSocketGeometry')
# 【変更】震えの強さを -10.0 から 10.0 まで許可
sock_fac = ng.interface.new_socket("Factor", in_out='INPUT', socket_type='NodeSocketFloat')
sock_fac.default_value = 0.05
sock_fac.min_value = -10.0 # マイナスを許可
sock_fac.max_value = 10.0
ng.interface.new_socket("Geometry", in_out='OUTPUT', socket_type='NodeSocketGeometry')
node_in = ng.nodes.new('NodeGroupInput')
node_out = ng.nodes.new('NodeGroupOutput')
noise = ng.nodes.new('ShaderNodeTexNoise'); noise.noise_dimensions = '4D'
time = ng.nodes.new('GeometryNodeInputSceneTime')
math_mul = ng.nodes.new('ShaderNodeMath'); math_mul.operation = 'MULTIPLY'; math_mul.inputs[1].default_value = 15.0
vmath_sub = ng.nodes.new('ShaderNodeVectorMath'); vmath_sub.operation = 'SUBTRACT'; vmath_sub.inputs[1].default_value = (0.5, 0.5, 0.5)
vmath_scale = ng.nodes.new('ShaderNodeVectorMath'); vmath_scale.operation = 'SCALE'
set_pos = ng.nodes.new('GeometryNodeSetPosition')
ng.links.new(time.outputs['Seconds'], math_mul.inputs[0])
ng.links.new(math_mul.outputs['Value'], noise.inputs['W'])
ng.links.new(noise.outputs['Color'], vmath_sub.inputs[0])
ng.links.new(vmath_sub.outputs['Vector'], vmath_scale.inputs[0])
ng.links.new(node_in.outputs['Factor'], vmath_scale.inputs['Scale'])
ng.links.new(node_in.outputs['Geometry'], set_pos.inputs['Geometry'])
ng.links.new(vmath_scale.outputs['Vector'], set_pos.inputs['Offset'])
ng.links.new(set_pos.outputs['Geometry'], node_out.inputs['Geometry'])
return ng
# ★ 太さ追加 ノードツリー生成
def create_thickness_node_group():
name = "GP_Outline_Thickness_FX"
if name in bpy.data.node_groups: return bpy.data.node_groups[name]
ng = bpy.data.node_groups.new(name, 'GeometryNodeTree')
ng.interface.new_socket("Geometry", in_out='INPUT', socket_type='NodeSocketGeometry')
# 太さはマイナスにすると形状が破綻するため 0.0 固定が安全です
sock_thick = ng.interface.new_socket("Add_Thickness", in_out='INPUT', socket_type='NodeSocketFloat')
sock_thick.default_value = 0.02
sock_thick.min_value = 0.0
sock_thick.max_value = 10.0
ng.interface.new_socket("Geometry", in_out='OUTPUT', socket_type='NodeSocketGeometry')
node_in = ng.nodes.new('NodeGroupInput')
node_out = ng.nodes.new('NodeGroupOutput')
rad_in = ng.nodes.new('GeometryNodeInputRadius')
math_add = ng.nodes.new('ShaderNodeMath'); math_add.operation = 'ADD'
set_rad = ng.nodes.new('GeometryNodeSetCurveRadius')
ng.links.new(rad_in.outputs['Radius'], math_add.inputs[0])
ng.links.new(node_in.outputs['Add_Thickness'], math_add.inputs[1])
ng.links.new(node_in.outputs['Geometry'], set_rad.inputs['Curve'])
ng.links.new(math_add.outputs['Value'], set_rad.inputs['Radius'])
ng.links.new(set_rad.outputs['Curve'], node_out.inputs['Geometry'])
return ng
# =========================================================
# 3. オペレーター群
# =========================================================
class ZIONAD_OT_CreateGP(Operator):
bl_idname = f"{PREFIX_VAL.lower()}.create_gp"
bl_label = "① + 新規 GPオブジェクトを作成"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
props = context.scene.zionad_props
curr_frame = context.scene.frame_current
if context.mode != 'OBJECT':
try: bpy.ops.object.mode_set(mode='OBJECT')
except: pass
col = bpy.data.collections.get(props.col_name)
if not col:
col = bpy.data.collections.new(props.col_name)
context.scene.collection.children.link(col)
try: bpy.ops.object.grease_pencil_add(type='EMPTY')
except: bpy.ops.object.gpencil_add(type='EMPTY')
gp = context.active_object
gp.name = get_next_name(props.obj_base_name)
for c in gp.users_collection: c.objects.unlink(gp)
col.objects.link(gp)
gp_data = gp.data
if hasattr(gp_data, "layers"):
try:
if len(gp_data.layers) == 0:
layer_fill = gp_data.layers.new(name="Fill")
if hasattr(layer_fill, "frames"): layer_fill.frames.new(curr_frame)
layer_line = gp_data.layers.new(name="Line")
if hasattr(layer_line, "frames"): layer_line.frames.new(curr_frame)
gp_data.layers.active = layer_line
except: pass
mat_name = "GP_Mat_01"
mat = bpy.data.materials.get(mat_name)
if not mat:
mat = bpy.data.materials.new(name=mat_name)
try: bpy.data.materials.create_gpencil_data(mat)
except: pass
if hasattr(mat, "grease_pencil") and mat.grease_pencil:
mat.grease_pencil.color = props.col_01_stroke
mat.grease_pencil.fill_color = props.col_01_fill
mat.diffuse_color = props.col_01_stroke
gp.data.materials.append(mat)
gp.active_material_index = 0
try: bpy.ops.object.mode_set(mode='PAINT_GREASE_PENCIL')
except:
try: bpy.ops.object.mode_set(mode='PAINT_GPENCIL')
except: pass
try: bpy.ops.grease_pencil.insert_blank_frame()
except: pass
try: bpy.ops.gpencil.blank_frame_add()
except: pass
self.report({'INFO'}, f"'{gp.name}' を作成しました。")
return {'FINISHED'}
class ZIONAD_OT_SetBrushTool(Operator):
bl_idname = f"{PREFIX_VAL.lower()}.set_brush_tool"
bl_label = "ツール変更"
tool_type: StringProperty()
def execute(self, context):
if context.mode not in {'PAINT_GPENCIL', 'PAINT_GREASE_PENCIL'}:
try: bpy.ops.object.mode_set(mode='PAINT_GREASE_PENCIL')
except: pass
if self.tool_type == 'ERASER':
try: bpy.ops.wm.tool_set_by_id(name="builtin_brush.Erase")
except: pass
else:
try: bpy.ops.wm.tool_set_by_id(name="builtin_brush.Draw")
except: pass
ts = context.scene.tool_settings
paint = getattr(ts, "grease_pencil_paint", getattr(ts, "gpencil_paint", None))
if paint:
t_name = {"PEN":"Pen", "PENCIL":"Pencil", "MARKER":"Marker"}.get(self.tool_type, "Pen")
for b in bpy.data.brushes:
if t_name.lower() in b.name.lower():
try: paint.brush = b
except:
try: bpy.ops.brush.asset_activate(asset_id=b.name)
except: pass
break
return {'FINISHED'}
class ZIONAD_OT_ToggleCanvas(Operator):
bl_idname = f"{PREFIX_VAL.lower()}.toggle_canvas"
bl_label = "グリッド表示"
def execute(self, context):
for area in context.screen.areas:
if area.type == 'VIEW_3D':
for space in area.spaces:
if space.type == 'VIEW_3D':
state = space.overlay.show_floor
space.overlay.show_floor = not state
space.overlay.show_ortho_grid = not state
return {'FINISHED'}
class ZIONAD_OT_SetBrushSize(Operator):
bl_idname = f"{PREFIX_VAL.lower()}.set_brush_size"
bl_label = "太さ"
size: IntProperty(default=10)
def execute(self, context):
ts = context.scene.tool_settings
paint = getattr(ts, "grease_pencil_paint", getattr(ts, "gpencil_paint", None))
if paint and hasattr(paint, "brush") and paint.brush:
try: paint.brush.size = self.size
except: pass
try: ts.unified_paint_settings.size = self.size
except: pass
return {'FINISHED'}
class ZIONAD_OT_ApplyColor(Operator):
bl_idname = f"{PREFIX_VAL.lower()}.apply_color"
bl_label = "色"
color_index: IntProperty(default=1)
def execute(self, context):
obj = context.active_object
if not is_gp_object(obj): return {'CANCELLED'}
props = context.scene.zionad_props
stroke_col = getattr(props, f"col_{self.color_index:02d}_stroke")
fill_col = getattr(props, f"col_{self.color_index:02d}_fill")
mat_name = f"GP_Mat_{self.color_index:02d}"
mat = bpy.data.materials.get(mat_name)
if not mat:
mat = bpy.data.materials.new(name=mat_name)
try: bpy.data.materials.create_gpencil_data(mat)
except: pass
if hasattr(mat, "grease_pencil") and mat.grease_pencil:
mat.grease_pencil.color = stroke_col
mat.grease_pencil.fill_color = fill_col
mat.diffuse_color = stroke_col
found = False
for i, slot_mat in enumerate(obj.data.materials):
if slot_mat == mat:
obj.active_material_index = i; found = True; break
if not found:
obj.data.materials.append(mat)
obj.active_material_index = len(obj.data.materials) - 1
for area in context.screen.areas:
if area.type == 'VIEW_3D': area.tag_redraw()
return {'FINISHED'}
class ZIONAD_OT_AddModifier(Operator):
bl_idname = f"{PREFIX_VAL.lower()}.add_modifier"
bl_label = "エフェクト追加"
mod_type: StringProperty()
def execute(self, context):
obj = context.active_object
if not is_gp_object(obj): return {'CANCELLED'}
mods = getattr(obj, "modifiers", [])
if self.mod_type == 'NOISE':
m_name = "Boiling_Noise"
if m_name not in mods:
mod = mods.new(name=m_name, type='NODES')
mod.node_group = create_noise_node_group()
elif self.mod_type == 'THICKNESS':
m_name = "Outline_Thickness"
if m_name not in mods:
mod = mods.new(name=m_name, type='NODES')
mod.node_group = create_thickness_node_group()
return {'FINISHED'}
class ZIONAD_OT_ResetModifiers(Operator):
bl_idname = f"{PREFIX_VAL.lower()}.reset_modifiers"
bl_label = "設定を初期値に戻す"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
obj = context.active_object
if not is_gp_object(obj): return {'CANCELLED'}
mods = getattr(obj, "modifiers", [])
for m in mods:
if m.type == 'NODES' and m.node_group:
try:
if m.name == "Boiling_Noise":
id_name = [i.identifier for i in m.node_group.interface.items_tree if i.name == "Factor"][0]
m[id_name] = 0.05
elif m.name == "Outline_Thickness":
id_name = [i.identifier for i in m.node_group.interface.items_tree if i.name == "Add_Thickness"][0]
m[id_name] = 0.02
except: pass
self.report({'INFO'}, "エフェクトの数値を初期化しました")
return {'FINISHED'}
class ZIONAD_OT_AddLayer(Operator):
bl_idname = f"{PREFIX_VAL.lower()}.add_layer"
bl_label = "新規レイヤー"
def execute(self, context):
obj = context.active_object
if is_gp_object(obj) and hasattr(obj.data, "layers"):
try:
layer = obj.data.layers.new(name="Layer")
obj.data.layers.active = layer
if hasattr(layer, "frames"): layer.frames.new(context.scene.frame_current)
except: pass
return {'FINISHED'}
class ZIONAD_OT_SetLayerActive(Operator):
bl_idname = f"{PREFIX_VAL.lower()}.set_layer_active"
bl_label = "レイヤー選択"
layer_name: StringProperty()
def execute(self, context):
obj = context.active_object
if is_gp_object(obj) and hasattr(obj.data, "layers"):
layer = obj.data.layers.get(self.layer_name)
if layer: obj.data.layers.active = layer
return {'FINISHED'}
class ZIONAD_OT_FinishDrawing(Operator):
bl_idname = f"{PREFIX_VAL.lower()}.finish_drawing"
bl_label = "⑥ ✓ オブジェクト完成"
def execute(self, context):
if not is_gp_object(context.active_object): return {'CANCELLED'}
try: bpy.ops.object.mode_set(mode='OBJECT')
except: pass
try: getattr(bpy.ops, PREFIX_VAL.lower()).create_gp()
except: pass
return {'FINISHED'}
class ZIONAD_OT_EditPalette(Operator):
bl_idname = f"{PREFIX_VAL.lower()}.edit_palette"
bl_label = "🎨 パレットの色を編集"
def invoke(self, context, event): return context.window_manager.invoke_props_dialog(self, width=400)
def draw(self, context):
layout = self.layout
props = context.scene.zionad_props
layout.label(text="名前 / Stroke(線) / Fill(塗り)", icon='COLOR')
for i in range(1, 13):
row = layout.row(align=True)
row.prop(props, f"col_{i:02d}_name", text="")
row.prop(props, f"col_{i:02d}_stroke", text="")
row.prop(props, f"col_{i:02d}_fill", text="")
def execute(self, context): return {'FINISHED'}
class ZIONAD_OT_RemoveAllPanels(Operator):
bl_idname = f"{PREFIX_VAL.lower()}.remove_all_panels"
bl_label = "Unregister Addon"
def execute(self, context): unregister(); return {'FINISHED'}
# =========================================================
# 4. パネル UI
# =========================================================
class ZIONAD_PT_Workflow(Panel):
bl_label = "1. 描画ワークフロー"
bl_idname = "ZIONAD_PT_workflow"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = ADDON_TITLE
bl_order = 0
def draw(self, context):
layout = self.layout
props = context.scene.zionad_props
# ① 新規作成
col = layout.column(align=True)
col.scale_y = 1.5
col.operator(ZIONAD_OT_CreateGP.bl_idname, icon='ADD')
layout.separator()
# ② ツールとグリッド
box_tool = layout.box()
box_tool.label(text="② ツール & グリッド:", icon='TOOL_SETTINGS')
row_tool = box_tool.row(align=True)
row_tool.operator(ZIONAD_OT_SetBrushTool.bl_idname, text="ペン", icon='GREASEPENCIL').tool_type = 'PEN'
row_tool.operator(ZIONAD_OT_SetBrushTool.bl_idname, text="鉛筆", icon='EDITMODE_HLT').tool_type = 'PENCIL'
row_tool.operator(ZIONAD_OT_SetBrushTool.bl_idname, text="マーカー", icon='BRUSH_DATA').tool_type = 'MARKER'
row_tool.operator(ZIONAD_OT_SetBrushTool.bl_idname, text="消しゴム", icon='TRASH').tool_type = 'ERASER'
box_tool.operator(ZIONAD_OT_ToggleCanvas.bl_idname, text="マス目ガイド ON/OFF", icon='GRID')
# ③ 太さ
box_size = layout.box()
box_size.label(text="③ ペンの太さ:", icon='BRUSH_DATA')
r1 = box_size.row(align=True)
r1.operator(ZIONAD_OT_SetBrushSize.bl_idname, text="極細 (3)").size = 3
r1.operator(ZIONAD_OT_SetBrushSize.bl_idname, text="細 (10)").size = 10
r1.operator(ZIONAD_OT_SetBrushSize.bl_idname, text="中 (20)").size = 20
r2 = box_size.row(align=True)
r2.operator(ZIONAD_OT_SetBrushSize.bl_idname, text="太 (30)").size = 30
r2.operator(ZIONAD_OT_SetBrushSize.bl_idname, text="極太 (50)").size = 50
r2.operator(ZIONAD_OT_SetBrushSize.bl_idname, text="特大 (100)").size = 100
# ④ 色選択
box_col = layout.box()
r_col = box_col.row()
r_col.label(text="④ 色を選ぶ:", icon='COLOR')
r_col.operator(ZIONAD_OT_EditPalette.bl_idname, text="", icon='PREFERENCES')
grid = box_col.grid_flow(columns=3, align=True)
for i in range(1, 13):
name = getattr(props, f"col_{i:02d}_name")
c = grid.column(align=True)
r = c.row(align=True)
r.scale_x = 0.85
r.prop(props, f"col_{i:02d}_stroke", text="")
r.prop(props, f"col_{i:02d}_fill", text="")
op = r.operator(ZIONAD_OT_ApplyColor.bl_idname, text=name)
op.color_index = i
# ⑤ レイヤー & エフェクト
obj = context.active_object
if is_gp_object(obj):
box_layer = layout.box()
row_head = box_layer.row()
row_head.label(text="⑤ レイヤー:", icon='GROUP')
row_head.operator(ZIONAD_OT_AddLayer.bl_idname, text="追加", icon='ADD')
if hasattr(obj.data, "layers") and obj.data.layers:
layers_list = []
try: layers_list = list(obj.data.layers)
except: pass
for layer in reversed(layers_list):
row_layer = box_layer.row(align=True)
is_active = False
try: is_active = (obj.data.layers.active == layer)
except: pass
icon = 'RADIOBUT_ON' if is_active else 'RADIOBUT_OFF'
op_act = row_layer.operator(ZIONAD_OT_SetLayerActive.bl_idname, text=layer.name, icon=icon)
op_act.layer_name = layer.name
if hasattr(layer, "hide"): row_layer.prop(layer, "hide", text="", icon='HIDE_ON' if layer.hide else 'HIDE_OFF')
elif hasattr(layer, "hide_viewport"): row_layer.prop(layer, "hide_viewport", text="", icon='HIDE_ON' if layer.hide_viewport else 'HIDE_OFF')
if hasattr(layer, "lock"): row_layer.prop(layer, "lock", text="", icon='LOCKED' if layer.lock else 'UNLOCKED')
if hasattr(layer, "opacity"): row_layer.prop(layer, "opacity", text="", slider=True)
else:
box_layer.label(text="レイヤーなし")
# エフェクト (ジオメトリノード)
box_fx = layout.box()
box_fx.label(text="★ エフェクト追加 (再生で動きます):", icon='MODIFIER')
row_fx = box_fx.row(align=True)
row_fx.operator(ZIONAD_OT_AddModifier.bl_idname, text="ボイリング(震え)").mod_type = 'NOISE'
row_fx.operator(ZIONAD_OT_AddModifier.bl_idname, text="線を太くする").mod_type = 'THICKNESS'
mods = getattr(obj, "modifiers", [])
has_effect = False
for m in mods:
if m.type == 'NODES' and m.node_group:
if m.name in ["Boiling_Noise", "Outline_Thickness"]: has_effect = True
try:
if m.name == "Boiling_Noise":
id_name = [i.identifier for i in m.node_group.interface.items_tree if i.name == "Factor"][0]
if id_name not in m: m[id_name] = 0.05
# ラベルを -10〜10 に変更
box_fx.prop(m, f'["{id_name}"]', text="震えの強さ (-10〜10)", slider=True)
elif m.name == "Outline_Thickness":
id_name = [i.identifier for i in m.node_group.interface.items_tree if i.name == "Add_Thickness"][0]
if id_name not in m: m[id_name] = 0.02
box_fx.prop(m, f'["{id_name}"]', text="追加する太さ (0〜10)", slider=True)
except: pass
if has_effect:
box_fx.operator(ZIONAD_OT_ResetModifiers.bl_idname, text="設定を初期値に戻す", icon='FILE_REFRESH')
layout.separator()
col = layout.column(align=True)
col.scale_y = 1.5
col.operator(ZIONAD_OT_FinishDrawing.bl_idname, icon='CHECKMARK')
class ZIONAD_PT_Manager(Panel):
bl_label = "2. オブジェクト管理"
bl_idname = "ZIONAD_PT_manager"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = ADDON_TITLE
bl_order = 1
bl_options = {'DEFAULT_CLOSED'}
def draw(self, context):
layout = self.layout
box = layout.box()
props = context.scene.zionad_props
col = bpy.data.collections.get(props.col_name)
if col:
for obj in col.objects:
if is_gp_object(obj):
row = box.row(align=True)
icon = 'RADIOBUT_ON' if context.active_object == obj else 'RADIOBUT_OFF'
row.prop(obj, "name", text="", icon=icon)
row.prop(obj, "hide_viewport", text="", icon='HIDE_ON' if obj.hide_viewport else 'HIDE_OFF')
row.prop(obj, "hide_select", text="", icon='RESTRICT_SELECT_ON' if obj.hide_select else 'UNLOCKED')
else: box.label(text="まだ描画がありません")
class ZIONAD_PT_RemovePanel(Panel):
bl_label = "3. アドオン削除"
bl_idname = "ZIONAD_PT_remove"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = ADDON_TITLE
bl_order = 2
bl_options = {'DEFAULT_CLOSED'}
def draw(self, context):
layout = self.layout
layout.operator(ZIONAD_OT_RemoveAllPanels.bl_idname, icon='CANCEL')
# =========================================================
# 5. 登録・解除
# =========================================================
classes = (
ZIONAD_Properties,
ZIONAD_OT_CreateGP, ZIONAD_OT_SetBrushTool, ZIONAD_OT_ToggleCanvas,
ZIONAD_OT_SetBrushSize, ZIONAD_OT_ApplyColor, ZIONAD_OT_AddModifier,
ZIONAD_OT_ResetModifiers, ZIONAD_OT_AddLayer, ZIONAD_OT_SetLayerActive,
ZIONAD_OT_FinishDrawing, ZIONAD_OT_EditPalette, ZIONAD_OT_RemoveAllPanels,
ZIONAD_PT_Workflow, ZIONAD_PT_Manager, ZIONAD_PT_RemovePanel,
)
def register():
for cls in classes: bpy.utils.register_class(cls)
bpy.types.Scene.zionad_props = PointerProperty(type=ZIONAD_Properties)
def unregister():
if hasattr(bpy.types.Scene, 'zionad_props'): del bpy.types.Scene.zionad_props
for cls in reversed(classes):
try: bpy.utils.unregister_class(cls)
except: pass
if __name__ == "__main__": register()
import bpy
from bpy.types import Operator, Panel, PropertyGroup
from bpy.props import StringProperty, PointerProperty, FloatVectorProperty, IntProperty
# =========================================================
# アドオン宣言
# =========================================================
bl_info = {
"name": "Grease Pencil Object Manager",
"author": "AI",
"version": (4, 3, 1),
"blender": (5, 0, 0),
"location": "View3D > Sidebar > GP Manager",
"description": "GPオブジェクト作成 → ツール/太さ/色 → レイヤー → エフェクト → 完成",
"category": "Grease Pencil",
}
PREFIX_VAL = "ZIONAD"
ADDON_TITLE = "GP Manager"
DEFAULT_COL_NAME = "GreaseObjects"
DEFAULT_BASE_NAME = "GP_"
# =========================================================
# 1. プロパティグループ (12色のカラー設定)
# =========================================================
class ZIONAD_Properties(PropertyGroup):
col_name: StringProperty(name="主コレクション", default=DEFAULT_COL_NAME)
obj_base_name: StringProperty(name="ベース名", default=DEFAULT_BASE_NAME)
col_01_name: StringProperty(default="黒"); col_01_stroke: FloatVectorProperty(subtype='COLOR', size=4, default=(0.05, 0.05, 0.05, 1.0), min=0.0, max=1.0); col_01_fill: FloatVectorProperty(subtype='COLOR', size=4, default=(0.3, 0.3, 0.3, 0.5), min=0.0, max=1.0)
col_02_name: StringProperty(default="白"); col_02_stroke: FloatVectorProperty(subtype='COLOR', size=4, default=(1.0, 1.0, 1.0, 1.0), min=0.0, max=1.0); col_02_fill: FloatVectorProperty(subtype='COLOR', size=4, default=(0.9, 0.9, 0.9, 0.5), min=0.0, max=1.0)
col_03_name: StringProperty(default="灰"); col_03_stroke: FloatVectorProperty(subtype='COLOR', size=4, default=(0.4, 0.4, 0.4, 1.0), min=0.0, max=1.0); col_03_fill: FloatVectorProperty(subtype='COLOR', size=4, default=(0.6, 0.6, 0.6, 0.5), min=0.0, max=1.0)
col_04_name: StringProperty(default="赤"); col_04_stroke: FloatVectorProperty(subtype='COLOR', size=4, default=(0.8, 0.1, 0.1, 1.0), min=0.0, max=1.0); col_04_fill: FloatVectorProperty(subtype='COLOR', size=4, default=(0.9, 0.4, 0.4, 0.5), min=0.0, max=1.0)
col_05_name: StringProperty(default="青"); col_05_stroke: FloatVectorProperty(subtype='COLOR', size=4, default=(0.1, 0.2, 0.8, 1.0), min=0.0, max=1.0); col_05_fill: FloatVectorProperty(subtype='COLOR', size=4, default=(0.4, 0.5, 0.9, 0.5), min=0.0, max=1.0)
col_06_name: StringProperty(default="緑"); col_06_stroke: FloatVectorProperty(subtype='COLOR', size=4, default=(0.1, 0.7, 0.2, 1.0), min=0.0, max=1.0); col_06_fill: FloatVectorProperty(subtype='COLOR', size=4, default=(0.4, 0.9, 0.5, 0.5), min=0.0, max=1.0)
col_07_name: StringProperty(default="黄"); col_07_stroke: FloatVectorProperty(subtype='COLOR', size=4, default=(0.9, 0.8, 0.1, 1.0), min=0.0, max=1.0); col_07_fill: FloatVectorProperty(subtype='COLOR', size=4, default=(0.9, 0.9, 0.5, 0.5), min=0.0, max=1.0)
col_08_name: StringProperty(default="水"); col_08_stroke: FloatVectorProperty(subtype='COLOR', size=4, default=(0.1, 0.8, 0.9, 1.0), min=0.0, max=1.0); col_08_fill: FloatVectorProperty(subtype='COLOR', size=4, default=(0.5, 0.9, 0.9, 0.5), min=0.0, max=1.0)
col_09_name: StringProperty(default="紫"); col_09_stroke: FloatVectorProperty(subtype='COLOR', size=4, default=(0.6, 0.1, 0.8, 1.0), min=0.0, max=1.0); col_09_fill: FloatVectorProperty(subtype='COLOR', size=4, default=(0.8, 0.5, 0.9, 0.5), min=0.0, max=1.0)
col_10_name: StringProperty(default="橙"); col_10_stroke: FloatVectorProperty(subtype='COLOR', size=4, default=(0.9, 0.4, 0.1, 1.0), min=0.0, max=1.0); col_10_fill: FloatVectorProperty(subtype='COLOR', size=4, default=(0.9, 0.6, 0.4, 0.5), min=0.0, max=1.0)
col_11_name: StringProperty(default="茶"); col_11_stroke: FloatVectorProperty(subtype='COLOR', size=4, default=(0.4, 0.2, 0.1, 1.0), min=0.0, max=1.0); col_11_fill: FloatVectorProperty(subtype='COLOR', size=4, default=(0.6, 0.4, 0.3, 0.5), min=0.0, max=1.0)
col_12_name: StringProperty(default="桃"); col_12_stroke: FloatVectorProperty(subtype='COLOR', size=4, default=(0.9, 0.3, 0.6, 1.0), min=0.0, max=1.0); col_12_fill: FloatVectorProperty(subtype='COLOR', size=4, default=(0.9, 0.6, 0.8, 0.5), min=0.0, max=1.0)
# =========================================================
# 2. ユーティリティ (ジオメトリノード自動生成)
# =========================================================
def is_gp_object(obj):
return obj and obj.type in {'GPENCIL', 'GREASEPENCIL'}
def get_next_name(base_name):
max_num = 0
for obj in bpy.data.objects:
if is_gp_object(obj) and obj.name.startswith(base_name):
try: max_num = max(max_num, int(obj.name.replace(base_name, "")))
except: pass
return f"{base_name}{max_num + 1:03d}"
# ★ ボイリング(震え) ノードツリー生成
def create_noise_node_group():
name = "GP_Boiling_Noise_FX"
if name in bpy.data.node_groups: return bpy.data.node_groups[name]
ng = bpy.data.node_groups.new(name, 'GeometryNodeTree')
ng.interface.new_socket("Geometry", in_out='INPUT', socket_type='NodeSocketGeometry')
# 【制限追加】震えの強さのパラメータ設定
sock_fac = ng.interface.new_socket("Factor", in_out='INPUT', socket_type='NodeSocketFloat')
sock_fac.default_value = 0.05
sock_fac.min_value = 0.0 # マイナス禁止
sock_fac.max_value = 10.0 # 最大10まで
ng.interface.new_socket("Geometry", in_out='OUTPUT', socket_type='NodeSocketGeometry')
node_in = ng.nodes.new('NodeGroupInput')
node_out = ng.nodes.new('NodeGroupOutput')
noise = ng.nodes.new('ShaderNodeTexNoise'); noise.noise_dimensions = '4D'
time = ng.nodes.new('GeometryNodeInputSceneTime')
math_mul = ng.nodes.new('ShaderNodeMath'); math_mul.operation = 'MULTIPLY'; math_mul.inputs[1].default_value = 15.0
vmath_sub = ng.nodes.new('ShaderNodeVectorMath'); vmath_sub.operation = 'SUBTRACT'; vmath_sub.inputs[1].default_value = (0.5, 0.5, 0.5)
vmath_scale = ng.nodes.new('ShaderNodeVectorMath'); vmath_scale.operation = 'SCALE'
set_pos = ng.nodes.new('GeometryNodeSetPosition')
ng.links.new(time.outputs['Seconds'], math_mul.inputs[0])
ng.links.new(math_mul.outputs['Value'], noise.inputs['W'])
ng.links.new(noise.outputs['Color'], vmath_sub.inputs[0])
ng.links.new(vmath_sub.outputs['Vector'], vmath_scale.inputs[0])
ng.links.new(node_in.outputs['Factor'], vmath_scale.inputs['Scale'])
ng.links.new(node_in.outputs['Geometry'], set_pos.inputs['Geometry'])
ng.links.new(vmath_scale.outputs['Vector'], set_pos.inputs['Offset'])
ng.links.new(set_pos.outputs['Geometry'], node_out.inputs['Geometry'])
return ng
# ★ 太さ追加 ノードツリー生成
def create_thickness_node_group():
name = "GP_Outline_Thickness_FX"
if name in bpy.data.node_groups: return bpy.data.node_groups[name]
ng = bpy.data.node_groups.new(name, 'GeometryNodeTree')
ng.interface.new_socket("Geometry", in_out='INPUT', socket_type='NodeSocketGeometry')
# 【制限追加】太さのパラメータ設定
sock_thick = ng.interface.new_socket("Add_Thickness", in_out='INPUT', socket_type='NodeSocketFloat')
sock_thick.default_value = 0.02
sock_thick.min_value = 0.0 # マイナス禁止
sock_thick.max_value = 10.0 # 最大10まで
ng.interface.new_socket("Geometry", in_out='OUTPUT', socket_type='NodeSocketGeometry')
node_in = ng.nodes.new('NodeGroupInput')
node_out = ng.nodes.new('NodeGroupOutput')
rad_in = ng.nodes.new('GeometryNodeInputRadius')
math_add = ng.nodes.new('ShaderNodeMath'); math_add.operation = 'ADD'
set_rad = ng.nodes.new('GeometryNodeSetCurveRadius')
ng.links.new(rad_in.outputs['Radius'], math_add.inputs[0])
ng.links.new(node_in.outputs['Add_Thickness'], math_add.inputs[1])
ng.links.new(node_in.outputs['Geometry'], set_rad.inputs['Curve'])
ng.links.new(math_add.outputs['Value'], set_rad.inputs['Radius'])
ng.links.new(set_rad.outputs['Curve'], node_out.inputs['Geometry'])
return ng
# =========================================================
# 3. オペレーター群
# =========================================================
class ZIONAD_OT_CreateGP(Operator):
bl_idname = f"{PREFIX_VAL.lower()}.create_gp"
bl_label = "① + 新規 GPオブジェクトを作成"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
props = context.scene.zionad_props
curr_frame = context.scene.frame_current
if context.mode != 'OBJECT':
try: bpy.ops.object.mode_set(mode='OBJECT')
except: pass
col = bpy.data.collections.get(props.col_name)
if not col:
col = bpy.data.collections.new(props.col_name)
context.scene.collection.children.link(col)
try: bpy.ops.object.grease_pencil_add(type='EMPTY')
except: bpy.ops.object.gpencil_add(type='EMPTY')
gp = context.active_object
gp.name = get_next_name(props.obj_base_name)
for c in gp.users_collection: c.objects.unlink(gp)
col.objects.link(gp)
gp_data = gp.data
if hasattr(gp_data, "layers"):
try:
if len(gp_data.layers) == 0:
layer_fill = gp_data.layers.new(name="Fill")
if hasattr(layer_fill, "frames"): layer_fill.frames.new(curr_frame)
layer_line = gp_data.layers.new(name="Line")
if hasattr(layer_line, "frames"): layer_line.frames.new(curr_frame)
gp_data.layers.active = layer_line
except: pass
mat_name = "GP_Mat_01"
mat = bpy.data.materials.get(mat_name)
if not mat:
mat = bpy.data.materials.new(name=mat_name)
try: bpy.data.materials.create_gpencil_data(mat)
except: pass
if hasattr(mat, "grease_pencil") and mat.grease_pencil:
mat.grease_pencil.color = props.col_01_stroke
mat.grease_pencil.fill_color = props.col_01_fill
mat.diffuse_color = props.col_01_stroke
gp.data.materials.append(mat)
gp.active_material_index = 0
try: bpy.ops.object.mode_set(mode='PAINT_GREASE_PENCIL')
except:
try: bpy.ops.object.mode_set(mode='PAINT_GPENCIL')
except: pass
try: bpy.ops.grease_pencil.insert_blank_frame()
except: pass
try: bpy.ops.gpencil.blank_frame_add()
except: pass
self.report({'INFO'}, f"'{gp.name}' を作成しました。")
return {'FINISHED'}
class ZIONAD_OT_SetBrushTool(Operator):
bl_idname = f"{PREFIX_VAL.lower()}.set_brush_tool"
bl_label = "ツール変更"
tool_type: StringProperty()
def execute(self, context):
if context.mode not in {'PAINT_GPENCIL', 'PAINT_GREASE_PENCIL'}:
try: bpy.ops.object.mode_set(mode='PAINT_GREASE_PENCIL')
except: pass
if self.tool_type == 'ERASER':
try: bpy.ops.wm.tool_set_by_id(name="builtin_brush.Erase")
except: pass
else:
try: bpy.ops.wm.tool_set_by_id(name="builtin_brush.Draw")
except: pass
ts = context.scene.tool_settings
paint = getattr(ts, "grease_pencil_paint", getattr(ts, "gpencil_paint", None))
if paint:
t_name = {"PEN":"Pen", "PENCIL":"Pencil", "MARKER":"Marker"}.get(self.tool_type, "Pen")
for b in bpy.data.brushes:
if t_name.lower() in b.name.lower():
try: paint.brush = b
except:
try: bpy.ops.brush.asset_activate(asset_id=b.name)
except: pass
break
return {'FINISHED'}
class ZIONAD_OT_ToggleCanvas(Operator):
bl_idname = f"{PREFIX_VAL.lower()}.toggle_canvas"
bl_label = "グリッド表示"
def execute(self, context):
for area in context.screen.areas:
if area.type == 'VIEW_3D':
for space in area.spaces:
if space.type == 'VIEW_3D':
state = space.overlay.show_floor
space.overlay.show_floor = not state
space.overlay.show_ortho_grid = not state
return {'FINISHED'}
class ZIONAD_OT_SetBrushSize(Operator):
bl_idname = f"{PREFIX_VAL.lower()}.set_brush_size"
bl_label = "太さ"
size: IntProperty(default=10)
def execute(self, context):
ts = context.scene.tool_settings
paint = getattr(ts, "grease_pencil_paint", getattr(ts, "gpencil_paint", None))
if paint and hasattr(paint, "brush") and paint.brush:
try: paint.brush.size = self.size
except: pass
try: ts.unified_paint_settings.size = self.size
except: pass
return {'FINISHED'}
class ZIONAD_OT_ApplyColor(Operator):
bl_idname = f"{PREFIX_VAL.lower()}.apply_color"
bl_label = "色"
color_index: IntProperty(default=1)
def execute(self, context):
obj = context.active_object
if not is_gp_object(obj): return {'CANCELLED'}
props = context.scene.zionad_props
stroke_col = getattr(props, f"col_{self.color_index:02d}_stroke")
fill_col = getattr(props, f"col_{self.color_index:02d}_fill")
mat_name = f"GP_Mat_{self.color_index:02d}"
mat = bpy.data.materials.get(mat_name)
if not mat:
mat = bpy.data.materials.new(name=mat_name)
try: bpy.data.materials.create_gpencil_data(mat)
except: pass
if hasattr(mat, "grease_pencil") and mat.grease_pencil:
mat.grease_pencil.color = stroke_col
mat.grease_pencil.fill_color = fill_col
mat.diffuse_color = stroke_col
found = False
for i, slot_mat in enumerate(obj.data.materials):
if slot_mat == mat:
obj.active_material_index = i; found = True; break
if not found:
obj.data.materials.append(mat)
obj.active_material_index = len(obj.data.materials) - 1
for area in context.screen.areas:
if area.type == 'VIEW_3D': area.tag_redraw()
return {'FINISHED'}
class ZIONAD_OT_AddModifier(Operator):
bl_idname = f"{PREFIX_VAL.lower()}.add_modifier"
bl_label = "エフェクト追加"
mod_type: StringProperty()
def execute(self, context):
obj = context.active_object
if not is_gp_object(obj): return {'CANCELLED'}
mods = getattr(obj, "modifiers", [])
if self.mod_type == 'NOISE':
m_name = "Boiling_Noise"
if m_name not in mods:
mod = mods.new(name=m_name, type='NODES')
mod.node_group = create_noise_node_group()
elif self.mod_type == 'THICKNESS':
m_name = "Outline_Thickness"
if m_name not in mods:
mod = mods.new(name=m_name, type='NODES')
mod.node_group = create_thickness_node_group()
return {'FINISHED'}
# ★ 【新規追加】設定を初期値に戻すボタン処理
class ZIONAD_OT_ResetModifiers(Operator):
bl_idname = f"{PREFIX_VAL.lower()}.reset_modifiers"
bl_label = "設定を初期値に戻す"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
obj = context.active_object
if not is_gp_object(obj): return {'CANCELLED'}
mods = getattr(obj, "modifiers", [])
for m in mods:
if m.type == 'NODES' and m.node_group:
try:
if m.name == "Boiling_Noise":
id_name = [i.identifier for i in m.node_group.interface.items_tree if i.name == "Factor"][0]
m[id_name] = 0.05
elif m.name == "Outline_Thickness":
id_name = [i.identifier for i in m.node_group.interface.items_tree if i.name == "Add_Thickness"][0]
m[id_name] = 0.02
except: pass
self.report({'INFO'}, "エフェクトの数値を初期化しました")
return {'FINISHED'}
class ZIONAD_OT_AddLayer(Operator):
bl_idname = f"{PREFIX_VAL.lower()}.add_layer"
bl_label = "新規レイヤー"
def execute(self, context):
obj = context.active_object
if is_gp_object(obj) and hasattr(obj.data, "layers"):
try:
layer = obj.data.layers.new(name="Layer")
obj.data.layers.active = layer
if hasattr(layer, "frames"): layer.frames.new(context.scene.frame_current)
except: pass
return {'FINISHED'}
class ZIONAD_OT_SetLayerActive(Operator):
bl_idname = f"{PREFIX_VAL.lower()}.set_layer_active"
bl_label = "レイヤー選択"
layer_name: StringProperty()
def execute(self, context):
obj = context.active_object
if is_gp_object(obj) and hasattr(obj.data, "layers"):
layer = obj.data.layers.get(self.layer_name)
if layer: obj.data.layers.active = layer
return {'FINISHED'}
class ZIONAD_OT_FinishDrawing(Operator):
bl_idname = f"{PREFIX_VAL.lower()}.finish_drawing"
bl_label = "⑥ ✓ オブジェクト完成"
def execute(self, context):
if not is_gp_object(context.active_object): return {'CANCELLED'}
try: bpy.ops.object.mode_set(mode='OBJECT')
except: pass
try: getattr(bpy.ops, PREFIX_VAL.lower()).create_gp()
except: pass
return {'FINISHED'}
class ZIONAD_OT_EditPalette(Operator):
bl_idname = f"{PREFIX_VAL.lower()}.edit_palette"
bl_label = "🎨 パレットの色を編集"
def invoke(self, context, event): return context.window_manager.invoke_props_dialog(self, width=400)
def draw(self, context):
layout = self.layout
props = context.scene.zionad_props
layout.label(text="名前 / Stroke(線) / Fill(塗り)", icon='COLOR')
for i in range(1, 13):
row = layout.row(align=True)
row.prop(props, f"col_{i:02d}_name", text="")
row.prop(props, f"col_{i:02d}_stroke", text="")
row.prop(props, f"col_{i:02d}_fill", text="")
def execute(self, context): return {'FINISHED'}
class ZIONAD_OT_RemoveAllPanels(Operator):
bl_idname = f"{PREFIX_VAL.lower()}.remove_all_panels"
bl_label = "Unregister Addon"
def execute(self, context): unregister(); return {'FINISHED'}
# =========================================================
# 4. パネル UI
# =========================================================
class ZIONAD_PT_Workflow(Panel):
bl_label = "1. 描画ワークフロー"
bl_idname = "ZIONAD_PT_workflow"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = ADDON_TITLE
bl_order = 0
def draw(self, context):
layout = self.layout
props = context.scene.zionad_props
# ① 新規作成
col = layout.column(align=True)
col.scale_y = 1.5
col.operator(ZIONAD_OT_CreateGP.bl_idname, icon='ADD')
layout.separator()
# ② ツールとグリッド
box_tool = layout.box()
box_tool.label(text="② ツール & グリッド:", icon='TOOL_SETTINGS')
row_tool = box_tool.row(align=True)
row_tool.operator(ZIONAD_OT_SetBrushTool.bl_idname, text="ペン", icon='GREASEPENCIL').tool_type = 'PEN'
row_tool.operator(ZIONAD_OT_SetBrushTool.bl_idname, text="鉛筆", icon='EDITMODE_HLT').tool_type = 'PENCIL'
row_tool.operator(ZIONAD_OT_SetBrushTool.bl_idname, text="マーカー", icon='BRUSH_DATA').tool_type = 'MARKER'
row_tool.operator(ZIONAD_OT_SetBrushTool.bl_idname, text="消しゴム", icon='TRASH').tool_type = 'ERASER'
box_tool.operator(ZIONAD_OT_ToggleCanvas.bl_idname, text="マス目ガイド ON/OFF", icon='GRID')
# ③ 太さ
box_size = layout.box()
box_size.label(text="③ ペンの太さ:", icon='BRUSH_DATA')
r1 = box_size.row(align=True)
r1.operator(ZIONAD_OT_SetBrushSize.bl_idname, text="極細 (3)").size = 3
r1.operator(ZIONAD_OT_SetBrushSize.bl_idname, text="細 (10)").size = 10
r1.operator(ZIONAD_OT_SetBrushSize.bl_idname, text="中 (20)").size = 20
r2 = box_size.row(align=True)
r2.operator(ZIONAD_OT_SetBrushSize.bl_idname, text="太 (30)").size = 30
r2.operator(ZIONAD_OT_SetBrushSize.bl_idname, text="極太 (50)").size = 50
r2.operator(ZIONAD_OT_SetBrushSize.bl_idname, text="特大 (100)").size = 100
# ④ 色選択
box_col = layout.box()
r_col = box_col.row()
r_col.label(text="④ 色を選ぶ:", icon='COLOR')
r_col.operator(ZIONAD_OT_EditPalette.bl_idname, text="", icon='PREFERENCES')
grid = box_col.grid_flow(columns=3, align=True)
for i in range(1, 13):
name = getattr(props, f"col_{i:02d}_name")
c = grid.column(align=True)
r = c.row(align=True)
r.scale_x = 0.85
r.prop(props, f"col_{i:02d}_stroke", text="")
r.prop(props, f"col_{i:02d}_fill", text="")
op = r.operator(ZIONAD_OT_ApplyColor.bl_idname, text=name)
op.color_index = i
# ⑤ レイヤー & エフェクト
obj = context.active_object
if is_gp_object(obj):
box_layer = layout.box()
row_head = box_layer.row()
row_head.label(text="⑤ レイヤー:", icon='GROUP')
row_head.operator(ZIONAD_OT_AddLayer.bl_idname, text="追加", icon='ADD')
if hasattr(obj.data, "layers") and obj.data.layers:
layers_list = []
try: layers_list = list(obj.data.layers)
except: pass
for layer in reversed(layers_list):
row_layer = box_layer.row(align=True)
is_active = False
try: is_active = (obj.data.layers.active == layer)
except: pass
icon = 'RADIOBUT_ON' if is_active else 'RADIOBUT_OFF'
op_act = row_layer.operator(ZIONAD_OT_SetLayerActive.bl_idname, text=layer.name, icon=icon)
op_act.layer_name = layer.name
if hasattr(layer, "hide"): row_layer.prop(layer, "hide", text="", icon='HIDE_ON' if layer.hide else 'HIDE_OFF')
elif hasattr(layer, "hide_viewport"): row_layer.prop(layer, "hide_viewport", text="", icon='HIDE_ON' if layer.hide_viewport else 'HIDE_OFF')
if hasattr(layer, "lock"): row_layer.prop(layer, "lock", text="", icon='LOCKED' if layer.lock else 'UNLOCKED')
if hasattr(layer, "opacity"): row_layer.prop(layer, "opacity", text="", slider=True)
else:
box_layer.label(text="レイヤーなし")
# エフェクト (ジオメトリノード)
box_fx = layout.box()
box_fx.label(text="★ エフェクト追加 (再生で動きます):", icon='MODIFIER')
row_fx = box_fx.row(align=True)
row_fx.operator(ZIONAD_OT_AddModifier.bl_idname, text="ボイリング(震え)").mod_type = 'NOISE'
row_fx.operator(ZIONAD_OT_AddModifier.bl_idname, text="線を太くする").mod_type = 'THICKNESS'
mods = getattr(obj, "modifiers", [])
has_effect = False
for m in mods:
if m.type == 'NODES' and m.node_group:
if m.name in ["Boiling_Noise", "Outline_Thickness"]: has_effect = True
try:
if m.name == "Boiling_Noise":
id_name = [i.identifier for i in m.node_group.interface.items_tree if i.name == "Factor"][0]
if id_name not in m: m[id_name] = 0.05
box_fx.prop(m, f'["{id_name}"]', text="震えの強さ (0〜10)", slider=True)
elif m.name == "Outline_Thickness":
id_name = [i.identifier for i in m.node_group.interface.items_tree if i.name == "Add_Thickness"][0]
if id_name not in m: m[id_name] = 0.02
box_fx.prop(m, f'["{id_name}"]', text="追加する太さ (0〜10)", slider=True)
except: pass
# 初期化ボタンの表示
if has_effect:
box_fx.operator(ZIONAD_OT_ResetModifiers.bl_idname, text="設定を初期値に戻す", icon='FILE_REFRESH')
layout.separator()
col = layout.column(align=True)
col.scale_y = 1.5
col.operator(ZIONAD_OT_FinishDrawing.bl_idname, icon='CHECKMARK')
class ZIONAD_PT_Manager(Panel):
bl_label = "2. オブジェクト管理"
bl_idname = "ZIONAD_PT_manager"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = ADDON_TITLE
bl_order = 1
bl_options = {'DEFAULT_CLOSED'}
def draw(self, context):
layout = self.layout
box = layout.box()
props = context.scene.zionad_props
col = bpy.data.collections.get(props.col_name)
if col:
for obj in col.objects:
if is_gp_object(obj):
row = box.row(align=True)
icon = 'RADIOBUT_ON' if context.active_object == obj else 'RADIOBUT_OFF'
row.prop(obj, "name", text="", icon=icon)
row.prop(obj, "hide_viewport", text="", icon='HIDE_ON' if obj.hide_viewport else 'HIDE_OFF')
row.prop(obj, "hide_select", text="", icon='RESTRICT_SELECT_ON' if obj.hide_select else 'UNLOCKED')
else: box.label(text="まだ描画がありません")
class ZIONAD_PT_RemovePanel(Panel):
bl_label = "3. アドオン削除"
bl_idname = "ZIONAD_PT_remove"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = ADDON_TITLE
bl_order = 2
bl_options = {'DEFAULT_CLOSED'}
def draw(self, context):
layout = self.layout
layout.operator(ZIONAD_OT_RemoveAllPanels.bl_idname, icon='CANCEL')
# =========================================================
# 5. 登録・解除
# =========================================================
classes = (
ZIONAD_Properties,
ZIONAD_OT_CreateGP, ZIONAD_OT_SetBrushTool, ZIONAD_OT_ToggleCanvas,
ZIONAD_OT_SetBrushSize, ZIONAD_OT_ApplyColor, ZIONAD_OT_AddModifier,
ZIONAD_OT_ResetModifiers, ZIONAD_OT_AddLayer, ZIONAD_OT_SetLayerActive,
ZIONAD_OT_FinishDrawing, ZIONAD_OT_EditPalette, ZIONAD_OT_RemoveAllPanels,
ZIONAD_PT_Workflow, ZIONAD_PT_Manager, ZIONAD_PT_RemovePanel,
)
def register():
for cls in classes: bpy.utils.register_class(cls)
bpy.types.Scene.zionad_props = PointerProperty(type=ZIONAD_Properties)
def unregister():
if hasattr(bpy.types.Scene, 'zionad_props'): del bpy.types.Scene.zionad_props
for cls in reversed(classes):
try: bpy.utils.unregister_class(cls)
except: pass
if __name__ == "__main__": register()
import bpy
from bpy.types import Operator, Panel, PropertyGroup
from bpy.props import StringProperty, PointerProperty, FloatVectorProperty, IntProperty
# =========================================================
# アドオン宣言
# =========================================================
bl_info = {
"name": "Grease Pencil Object Manager",
"author": "AI",
"version": (4, 2, 1),
"blender": (5, 0, 0),
"location": "View3D > Sidebar > GP Manager",
"description": "GPオブジェクト作成 → ツール/太さ/色 → レイヤー → エフェクト → 完成",
"category": "Grease Pencil",
}
PREFIX_VAL = "ZIONAD"
ADDON_TITLE = "GP Manager"
DEFAULT_COL_NAME = "GreaseObjects"
DEFAULT_BASE_NAME = "GP_"
# =========================================================
# 1. プロパティグループ (12色のカラー設定)
# =========================================================
class ZIONAD_Properties(PropertyGroup):
col_name: StringProperty(name="主コレクション", default=DEFAULT_COL_NAME)
obj_base_name: StringProperty(name="ベース名", default=DEFAULT_BASE_NAME)
col_01_name: StringProperty(default="黒"); col_01_stroke: FloatVectorProperty(subtype='COLOR', size=4, default=(0.05, 0.05, 0.05, 1.0), min=0.0, max=1.0); col_01_fill: FloatVectorProperty(subtype='COLOR', size=4, default=(0.3, 0.3, 0.3, 0.5), min=0.0, max=1.0)
col_02_name: StringProperty(default="白"); col_02_stroke: FloatVectorProperty(subtype='COLOR', size=4, default=(1.0, 1.0, 1.0, 1.0), min=0.0, max=1.0); col_02_fill: FloatVectorProperty(subtype='COLOR', size=4, default=(0.9, 0.9, 0.9, 0.5), min=0.0, max=1.0)
col_03_name: StringProperty(default="灰"); col_03_stroke: FloatVectorProperty(subtype='COLOR', size=4, default=(0.4, 0.4, 0.4, 1.0), min=0.0, max=1.0); col_03_fill: FloatVectorProperty(subtype='COLOR', size=4, default=(0.6, 0.6, 0.6, 0.5), min=0.0, max=1.0)
col_04_name: StringProperty(default="赤"); col_04_stroke: FloatVectorProperty(subtype='COLOR', size=4, default=(0.8, 0.1, 0.1, 1.0), min=0.0, max=1.0); col_04_fill: FloatVectorProperty(subtype='COLOR', size=4, default=(0.9, 0.4, 0.4, 0.5), min=0.0, max=1.0)
col_05_name: StringProperty(default="青"); col_05_stroke: FloatVectorProperty(subtype='COLOR', size=4, default=(0.1, 0.2, 0.8, 1.0), min=0.0, max=1.0); col_05_fill: FloatVectorProperty(subtype='COLOR', size=4, default=(0.4, 0.5, 0.9, 0.5), min=0.0, max=1.0)
col_06_name: StringProperty(default="緑"); col_06_stroke: FloatVectorProperty(subtype='COLOR', size=4, default=(0.1, 0.7, 0.2, 1.0), min=0.0, max=1.0); col_06_fill: FloatVectorProperty(subtype='COLOR', size=4, default=(0.4, 0.9, 0.5, 0.5), min=0.0, max=1.0)
col_07_name: StringProperty(default="黄"); col_07_stroke: FloatVectorProperty(subtype='COLOR', size=4, default=(0.9, 0.8, 0.1, 1.0), min=0.0, max=1.0); col_07_fill: FloatVectorProperty(subtype='COLOR', size=4, default=(0.9, 0.9, 0.5, 0.5), min=0.0, max=1.0)
col_08_name: StringProperty(default="水"); col_08_stroke: FloatVectorProperty(subtype='COLOR', size=4, default=(0.1, 0.8, 0.9, 1.0), min=0.0, max=1.0); col_08_fill: FloatVectorProperty(subtype='COLOR', size=4, default=(0.5, 0.9, 0.9, 0.5), min=0.0, max=1.0)
col_09_name: StringProperty(default="紫"); col_09_stroke: FloatVectorProperty(subtype='COLOR', size=4, default=(0.6, 0.1, 0.8, 1.0), min=0.0, max=1.0); col_09_fill: FloatVectorProperty(subtype='COLOR', size=4, default=(0.8, 0.5, 0.9, 0.5), min=0.0, max=1.0)
col_10_name: StringProperty(default="橙"); col_10_stroke: FloatVectorProperty(subtype='COLOR', size=4, default=(0.9, 0.4, 0.1, 1.0), min=0.0, max=1.0); col_10_fill: FloatVectorProperty(subtype='COLOR', size=4, default=(0.9, 0.6, 0.4, 0.5), min=0.0, max=1.0)
col_11_name: StringProperty(default="茶"); col_11_stroke: FloatVectorProperty(subtype='COLOR', size=4, default=(0.4, 0.2, 0.1, 1.0), min=0.0, max=1.0); col_11_fill: FloatVectorProperty(subtype='COLOR', size=4, default=(0.6, 0.4, 0.3, 0.5), min=0.0, max=1.0)
col_12_name: StringProperty(default="桃"); col_12_stroke: FloatVectorProperty(subtype='COLOR', size=4, default=(0.9, 0.3, 0.6, 1.0), min=0.0, max=1.0); col_12_fill: FloatVectorProperty(subtype='COLOR', size=4, default=(0.9, 0.6, 0.8, 0.5), min=0.0, max=1.0)
# =========================================================
# 2. ユーティリティ
# =========================================================
def is_gp_object(obj):
return obj and obj.type in {'GPENCIL', 'GREASEPENCIL'}
def get_next_name(base_name):
max_num = 0
for obj in bpy.data.objects:
if is_gp_object(obj) and obj.name.startswith(base_name):
try: max_num = max(max_num, int(obj.name.replace(base_name, "")))
except: pass
return f"{base_name}{max_num + 1:03d}"
# =========================================================
# 3. オペレーター群
# =========================================================
class ZIONAD_OT_CreateGP(Operator):
bl_idname = f"{PREFIX_VAL.lower()}.create_gp"
bl_label = "① + 新規 GPオブジェクトを作成"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
props = context.scene.zionad_props
curr_frame = context.scene.frame_current
if context.mode != 'OBJECT':
try: bpy.ops.object.mode_set(mode='OBJECT')
except: pass
col = bpy.data.collections.get(props.col_name)
if not col:
col = bpy.data.collections.new(props.col_name)
context.scene.collection.children.link(col)
try: bpy.ops.object.grease_pencil_add(type='EMPTY')
except: bpy.ops.object.gpencil_add(type='EMPTY')
gp = context.active_object
gp.name = get_next_name(props.obj_base_name)
for c in gp.users_collection: c.objects.unlink(gp)
col.objects.link(gp)
gp_data = gp.data
if hasattr(gp_data, "layers"):
try:
if len(gp_data.layers) == 0:
layer_fill = gp_data.layers.new(name="Fill")
if hasattr(layer_fill, "frames"): layer_fill.frames.new(curr_frame)
layer_line = gp_data.layers.new(name="Line")
if hasattr(layer_line, "frames"): layer_line.frames.new(curr_frame)
gp_data.layers.active = layer_line
except: pass
mat_name = "GP_Mat_01"
mat = bpy.data.materials.get(mat_name)
if not mat:
mat = bpy.data.materials.new(name=mat_name)
try: bpy.data.materials.create_gpencil_data(mat)
except: pass
if hasattr(mat, "grease_pencil") and mat.grease_pencil:
mat.grease_pencil.color = props.col_01_stroke
mat.grease_pencil.fill_color = props.col_01_fill
mat.grease_pencil.show_stroke = True
mat.grease_pencil.show_fill = True
mat.diffuse_color = props.col_01_stroke
gp.data.materials.append(mat)
gp.active_material_index = 0
try: bpy.ops.object.mode_set(mode='PAINT_GREASE_PENCIL')
except:
try: bpy.ops.object.mode_set(mode='PAINT_GPENCIL')
except: pass
try: bpy.ops.grease_pencil.insert_blank_frame()
except: pass
try: bpy.ops.gpencil.blank_frame_add()
except: pass
ts = context.scene.tool_settings
paint = getattr(ts, "grease_pencil_paint", getattr(ts, "gpencil_paint", None))
if paint and hasattr(paint, "brush") and paint.brush:
try: paint.brush.use_material = True
except: pass
try: paint.brush.color_mode = 'MATERIAL'
except: pass
self.report({'INFO'}, f"'{gp.name}' を作成しました。")
return {'FINISHED'}
class ZIONAD_OT_SetBrushTool(Operator):
bl_idname = f"{PREFIX_VAL.lower()}.set_brush_tool"
bl_label = "ツール変更"
tool_type: StringProperty()
def execute(self, context):
if context.mode not in {'PAINT_GPENCIL', 'PAINT_GREASE_PENCIL'}:
try: bpy.ops.object.mode_set(mode='PAINT_GREASE_PENCIL')
except:
try: bpy.ops.object.mode_set(mode='PAINT_GPENCIL')
except: pass
if self.tool_type == 'ERASER':
try: bpy.ops.wm.tool_set_by_id(name="builtin_brush.Erase")
except: pass
else:
try: bpy.ops.wm.tool_set_by_id(name="builtin_brush.Draw")
except: pass
ts = context.scene.tool_settings
paint = getattr(ts, "grease_pencil_paint", getattr(ts, "gpencil_paint", None))
if paint:
t_name = {"PEN":"Pen", "PENCIL":"Pencil", "MARKER":"Marker"}.get(self.tool_type, "Pen")
for b in bpy.data.brushes:
if t_name.lower() in b.name.lower():
# 【修正箇所】 Blender 4.3以降での Read-only エラーを回避
try:
paint.brush = b
except AttributeError:
# アセットブラシのアクティベートによるフォールバック
try: bpy.ops.brush.asset_activate(asset_id=b.name)
except: pass
except Exception:
pass
break
return {'FINISHED'}
class ZIONAD_OT_ToggleCanvas(Operator):
bl_idname = f"{PREFIX_VAL.lower()}.toggle_canvas"
bl_label = "グリッド表示"
def execute(self, context):
for area in context.screen.areas:
if area.type == 'VIEW_3D':
for space in area.spaces:
if space.type == 'VIEW_3D':
if hasattr(space.overlay, "show_gpencil_canvas"):
space.overlay.show_gpencil_canvas = not space.overlay.show_gpencil_canvas
elif hasattr(space.overlay, "show_canvas"):
space.overlay.show_canvas = not space.overlay.show_canvas
return {'FINISHED'}
class ZIONAD_OT_SetBrushSize(Operator):
bl_idname = f"{PREFIX_VAL.lower()}.set_brush_size"
bl_label = "太さ"
size: IntProperty(default=10)
def execute(self, context):
ts = context.scene.tool_settings
paint = getattr(ts, "grease_pencil_paint", getattr(ts, "gpencil_paint", None))
if paint and hasattr(paint, "brush") and paint.brush:
try: paint.brush.size = self.size
except: pass
# Unified Paint 設定へのフォールバック
try: ts.unified_paint_settings.size = self.size
except: pass
return {'FINISHED'}
class ZIONAD_OT_ApplyColor(Operator):
bl_idname = f"{PREFIX_VAL.lower()}.apply_color"
bl_label = "色"
color_index: IntProperty(default=1)
def execute(self, context):
obj = context.active_object
if not is_gp_object(obj): return {'CANCELLED'}
props = context.scene.zionad_props
stroke_col = getattr(props, f"col_{self.color_index:02d}_stroke")
fill_col = getattr(props, f"col_{self.color_index:02d}_fill")
mat_name = f"GP_Mat_{self.color_index:02d}"
mat = bpy.data.materials.get(mat_name)
if not mat:
mat = bpy.data.materials.new(name=mat_name)
try: bpy.data.materials.create_gpencil_data(mat)
except: pass
if hasattr(mat, "grease_pencil") and mat.grease_pencil:
mat.grease_pencil.color = stroke_col
mat.grease_pencil.fill_color = fill_col
mat.grease_pencil.show_stroke = True
mat.grease_pencil.show_fill = True
mat.diffuse_color = stroke_col
found = False
for i, slot_mat in enumerate(obj.data.materials):
if slot_mat == mat:
obj.active_material_index = i
found = True
break
if not found:
obj.data.materials.append(mat)
obj.active_material_index = len(obj.data.materials) - 1
ts = context.scene.tool_settings
paint = getattr(ts, "grease_pencil_paint", getattr(ts, "gpencil_paint", None))
if paint and hasattr(paint, "brush") and paint.brush:
try: paint.brush.use_material = True; paint.brush.color_mode = 'MATERIAL'
except: pass
for area in context.screen.areas:
if area.type == 'VIEW_3D': area.tag_redraw()
return {'FINISHED'}
class ZIONAD_OT_AddModifier(Operator):
bl_idname = f"{PREFIX_VAL.lower()}.add_modifier"
bl_label = "エフェクト追加"
mod_type: StringProperty()
def execute(self, context):
obj = context.active_object
if not is_gp_object(obj): return {'CANCELLED'}
m_name = "Boiling_Noise" if self.mod_type == 'NOISE' else "Outline_Thickness"
m_type_2 = 'GP_NOISE' if self.mod_type == 'NOISE' else 'GP_THICKNESS'
m_type_3 = 'GREASE_PENCIL_NOISE' if self.mod_type == 'NOISE' else 'GREASE_PENCIL_THICKNESS'
mod = None
mods = getattr(obj, "grease_pencil_modifiers", getattr(obj, "modifiers", None))
if mods:
mod = mods.get(m_name)
if not mod:
try: mod = mods.new(name=m_name, type=m_type_3)
except:
try: mod = mods.new(name=m_name, type=m_type_2)
except: pass
return {'FINISHED'}
class ZIONAD_OT_AddLayer(Operator):
bl_idname = f"{PREFIX_VAL.lower()}.add_layer"
bl_label = "新規レイヤー"
def execute(self, context):
obj = context.active_object
if is_gp_object(obj) and hasattr(obj.data, "layers"):
try:
layer = obj.data.layers.new(name="Layer")
obj.data.layers.active = layer
if hasattr(layer, "frames"): layer.frames.new(context.scene.frame_current)
except: pass
return {'FINISHED'}
class ZIONAD_OT_SetLayerActive(Operator):
bl_idname = f"{PREFIX_VAL.lower()}.set_layer_active"
bl_label = "レイヤー選択"
layer_name: StringProperty()
def execute(self, context):
obj = context.active_object
if is_gp_object(obj) and hasattr(obj.data, "layers"):
layer = obj.data.layers.get(self.layer_name)
if layer: obj.data.layers.active = layer
return {'FINISHED'}
class ZIONAD_OT_FinishDrawing(Operator):
bl_idname = f"{PREFIX_VAL.lower()}.finish_drawing"
bl_label = "⑥ ✓ オブジェクト完成"
def execute(self, context):
if not is_gp_object(context.active_object): return {'CANCELLED'}
try: bpy.ops.object.mode_set(mode='OBJECT')
except: pass
try: getattr(bpy.ops, PREFIX_VAL.lower()).create_gp()
except: pass
return {'FINISHED'}
class ZIONAD_OT_EditPalette(Operator):
bl_idname = f"{PREFIX_VAL.lower()}.edit_palette"
bl_label = "🎨 パレットの色を編集"
def invoke(self, context, event):
return context.window_manager.invoke_props_dialog(self, width=400)
def draw(self, context):
layout = self.layout
props = context.scene.zionad_props
layout.label(text="名前 / Stroke(線) / Fill(塗り)", icon='COLOR')
for i in range(1, 13):
row = layout.row(align=True)
row.prop(props, f"col_{i:02d}_name", text="")
row.prop(props, f"col_{i:02d}_stroke", text="")
row.prop(props, f"col_{i:02d}_fill", text="")
def execute(self, context): return {'FINISHED'}
class ZIONAD_OT_RemoveAllPanels(Operator):
bl_idname = f"{PREFIX_VAL.lower()}.remove_all_panels"
bl_label = "Unregister Addon"
def execute(self, context):
unregister()
return {'FINISHED'}
# =========================================================
# 4. パネル UI
# =========================================================
class ZIONAD_PT_Workflow(Panel):
bl_label = "1. 描画ワークフロー"
bl_idname = "ZIONAD_PT_workflow"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = ADDON_TITLE
bl_order = 0
def draw(self, context):
layout = self.layout
props = context.scene.zionad_props
# ① 新規作成
col = layout.column(align=True)
col.scale_y = 1.5
col.operator(ZIONAD_OT_CreateGP.bl_idname, icon='ADD')
layout.separator()
# ② ツールとグリッド
box_tool = layout.box()
box_tool.label(text="② ツール & グリッド:", icon='TOOL_SETTINGS')
row_tool = box_tool.row(align=True)
row_tool.operator(ZIONAD_OT_SetBrushTool.bl_idname, text="ペン", icon='GREASEPENCIL').tool_type = 'PEN'
row_tool.operator(ZIONAD_OT_SetBrushTool.bl_idname, text="鉛筆", icon='EDITMODE_HLT').tool_type = 'PENCIL'
row_tool.operator(ZIONAD_OT_SetBrushTool.bl_idname, text="マーカー", icon='BRUSH_DATA').tool_type = 'MARKER'
row_tool.operator(ZIONAD_OT_SetBrushTool.bl_idname, text="消しゴム", icon='TRASH').tool_type = 'ERASER'
box_tool.operator(ZIONAD_OT_ToggleCanvas.bl_idname, text="グリッド ON/OFF", icon='GRID')
# ③ 太さ (6種)
box_size = layout.box()
box_size.label(text="③ ペンの太さ:", icon='BRUSH_DATA')
r1 = box_size.row(align=True)
r1.operator(ZIONAD_OT_SetBrushSize.bl_idname, text="極細 (3)").size = 3
r1.operator(ZIONAD_OT_SetBrushSize.bl_idname, text="細 (10)").size = 10
r1.operator(ZIONAD_OT_SetBrushSize.bl_idname, text="中 (20)").size = 20
r2 = box_size.row(align=True)
r2.operator(ZIONAD_OT_SetBrushSize.bl_idname, text="太 (30)").size = 30
r2.operator(ZIONAD_OT_SetBrushSize.bl_idname, text="極太 (50)").size = 50
r2.operator(ZIONAD_OT_SetBrushSize.bl_idname, text="特大 (100)").size = 100
# ④ 色選択 (3列レイアウトで小型化)
box_col = layout.box()
r_col = box_col.row()
r_col.label(text="④ 色を選ぶ:", icon='COLOR')
r_col.operator(ZIONAD_OT_EditPalette.bl_idname, text="", icon='PREFERENCES')
grid = box_col.grid_flow(columns=3, align=True)
for i in range(1, 13):
name = getattr(props, f"col_{i:02d}_name")
c = grid.column(align=True)
r = c.row(align=True)
r.scale_x = 0.85
r.prop(props, f"col_{i:02d}_stroke", text="")
r.prop(props, f"col_{i:02d}_fill", text="")
op = r.operator(ZIONAD_OT_ApplyColor.bl_idname, text=name)
op.color_index = i
# ⑤ レイヤー & エフェクト
obj = context.active_object
if is_gp_object(obj):
# レイヤー管理
box_layer = layout.box()
row_head = box_layer.row()
row_head.label(text="⑤ レイヤー:", icon='GROUP')
row_head.operator(ZIONAD_OT_AddLayer.bl_idname, text="追加", icon='ADD')
if hasattr(obj.data, "layers") and obj.data.layers:
layers_list = []
try: layers_list = list(obj.data.layers)
except: pass
for layer in reversed(layers_list):
row_layer = box_layer.row(align=True)
is_active = False
try: is_active = (obj.data.layers.active == layer)
except: pass
icon = 'RADIOBUT_ON' if is_active else 'RADIOBUT_OFF'
op_act = row_layer.operator(ZIONAD_OT_SetLayerActive.bl_idname, text=layer.name, icon=icon)
op_act.layer_name = layer.name
if hasattr(layer, "hide"): row_layer.prop(layer, "hide", text="", icon='HIDE_ON' if layer.hide else 'HIDE_OFF')
elif hasattr(layer, "hide_viewport"): row_layer.prop(layer, "hide_viewport", text="", icon='HIDE_ON' if layer.hide_viewport else 'HIDE_OFF')
if hasattr(layer, "lock"): row_layer.prop(layer, "lock", text="", icon='LOCKED' if layer.lock else 'UNLOCKED')
if hasattr(layer, "opacity"): row_layer.prop(layer, "opacity", text="", slider=True)
else:
box_layer.label(text="レイヤーなし")
# エフェクト
box_fx = layout.box()
box_fx.label(text="★ エフェクト追加:", icon='MODIFIER')
row_fx = box_fx.row(align=True)
row_fx.operator(ZIONAD_OT_AddModifier.bl_idname, text="ボイリング(震え)").mod_type = 'NOISE'
row_fx.operator(ZIONAD_OT_AddModifier.bl_idname, text="アウトライン(フチ)").mod_type = 'THICKNESS'
mods = getattr(obj, "grease_pencil_modifiers", getattr(obj, "modifiers", []))
for m in mods:
if m.name == "Boiling_Noise":
box_fx.prop(m, "factor", text="ボイリング強度", slider=True)
if m.name == "Outline_Thickness":
box_fx.prop(m, "thickness", text="フチの太さ", slider=True)
layout.separator()
col = layout.column(align=True)
col.scale_y = 1.5
col.operator(ZIONAD_OT_FinishDrawing.bl_idname, icon='CHECKMARK')
class ZIONAD_PT_Manager(Panel):
bl_label = "2. オブジェクト管理"
bl_idname = "ZIONAD_PT_manager"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = ADDON_TITLE
bl_order = 1
bl_options = {'DEFAULT_CLOSED'}
def draw(self, context):
layout = self.layout
box = layout.box()
props = context.scene.zionad_props
col = bpy.data.collections.get(props.col_name)
if col:
for obj in col.objects:
if is_gp_object(obj):
row = box.row(align=True)
icon = 'RADIOBUT_ON' if context.active_object == obj else 'RADIOBUT_OFF'
row.prop(obj, "name", text="", icon=icon)
row.prop(obj, "hide_viewport", text="", icon='HIDE_ON' if obj.hide_viewport else 'HIDE_OFF')
row.prop(obj, "hide_select", text="", icon='RESTRICT_SELECT_ON' if obj.hide_select else 'UNLOCKED')
else: box.label(text="まだ描画がありません")
class ZIONAD_PT_RemovePanel(Panel):
bl_label = "3. アドオン削除"
bl_idname = "ZIONAD_PT_remove"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = ADDON_TITLE
bl_order = 2
bl_options = {'DEFAULT_CLOSED'}
def draw(self, context):
layout = self.layout
layout.operator(ZIONAD_OT_RemoveAllPanels.bl_idname, icon='CANCEL')
# =========================================================
# 5. 登録・解除
# =========================================================
classes = (
ZIONAD_Properties,
ZIONAD_OT_CreateGP, ZIONAD_OT_SetBrushTool, ZIONAD_OT_ToggleCanvas,
ZIONAD_OT_SetBrushSize, ZIONAD_OT_ApplyColor, ZIONAD_OT_AddModifier,
ZIONAD_OT_AddLayer, ZIONAD_OT_SetLayerActive, ZIONAD_OT_FinishDrawing,
ZIONAD_OT_EditPalette, ZIONAD_OT_RemoveAllPanels,
ZIONAD_PT_Workflow, ZIONAD_PT_Manager, ZIONAD_PT_RemovePanel,
)
def register():
for cls in classes: bpy.utils.register_class(cls)
bpy.types.Scene.zionad_props = PointerProperty(type=ZIONAD_Properties)
def unregister():
if hasattr(bpy.types.Scene, 'zionad_props'): del bpy.types.Scene.zionad_props
for cls in reversed(classes):
try: bpy.utils.unregister_class(cls)
except: pass
if __name__ == "__main__": register()
import bpy
import math
from bpy.types import Operator, Panel, PropertyGroup
from bpy.props import StringProperty, PointerProperty, FloatVectorProperty, IntProperty
# =========================================================
# アドオン宣言
# =========================================================
bl_info = {
"name": "Grease Pencil Object Manager",
"author": "AI",
"version": (4, 1, 3),
"blender": (5, 0, 0), # Blender 5.x / GPv3 対応
"location": "View3D > Sidebar > GP Manager",
"description": "GPオブジェクト作成 → 太さ・色選択 → レイヤー追加 → 完成",
"category": "Grease Pencil",
}
PREFIX_VAL = "ZIONAD"
ADDON_TITLE = "GP Manager"
DEFAULT_COL_NAME = "GreaseObjects"
DEFAULT_BASE_NAME = "GP_"
# =========================================================
# 1. プロパティグループ (12色のカラー設定)
# =========================================================
class ZIONAD_Properties(PropertyGroup):
col_name: StringProperty(name="主コレクション", default=DEFAULT_COL_NAME)
obj_base_name: StringProperty(name="ベース名", default=DEFAULT_BASE_NAME)
# 12色パレット
col_01_name: StringProperty(default="黒")
col_01_stroke: FloatVectorProperty(name="線", subtype='COLOR', size=4, default=(0.05, 0.05, 0.05, 1.0), min=0.0, max=1.0)
col_01_fill: FloatVectorProperty(name="塗", subtype='COLOR', size=4, default=(0.3, 0.3, 0.3, 0.5), min=0.0, max=1.0)
col_02_name: StringProperty(default="白")
col_02_stroke: FloatVectorProperty(name="線", subtype='COLOR', size=4, default=(1.0, 1.0, 1.0, 1.0), min=0.0, max=1.0)
col_02_fill: FloatVectorProperty(name="塗", subtype='COLOR', size=4, default=(0.9, 0.9, 0.9, 0.5), min=0.0, max=1.0)
col_03_name: StringProperty(default="灰")
col_03_stroke: FloatVectorProperty(name="線", subtype='COLOR', size=4, default=(0.4, 0.4, 0.4, 1.0), min=0.0, max=1.0)
col_03_fill: FloatVectorProperty(name="塗", subtype='COLOR', size=4, default=(0.6, 0.6, 0.6, 0.5), min=0.0, max=1.0)
col_04_name: StringProperty(default="赤")
col_04_stroke: FloatVectorProperty(name="線", subtype='COLOR', size=4, default=(0.8, 0.1, 0.1, 1.0), min=0.0, max=1.0)
col_04_fill: FloatVectorProperty(name="塗", subtype='COLOR', size=4, default=(0.9, 0.4, 0.4, 0.5), min=0.0, max=1.0)
col_05_name: StringProperty(default="青")
col_05_stroke: FloatVectorProperty(name="線", subtype='COLOR', size=4, default=(0.1, 0.2, 0.8, 1.0), min=0.0, max=1.0)
col_05_fill: FloatVectorProperty(name="塗", subtype='COLOR', size=4, default=(0.4, 0.5, 0.9, 0.5), min=0.0, max=1.0)
col_06_name: StringProperty(default="緑")
col_06_stroke: FloatVectorProperty(name="線", subtype='COLOR', size=4, default=(0.1, 0.7, 0.2, 1.0), min=0.0, max=1.0)
col_06_fill: FloatVectorProperty(name="塗", subtype='COLOR', size=4, default=(0.4, 0.9, 0.5, 0.5), min=0.0, max=1.0)
col_07_name: StringProperty(default="黄")
col_07_stroke: FloatVectorProperty(name="線", subtype='COLOR', size=4, default=(0.9, 0.8, 0.1, 1.0), min=0.0, max=1.0)
col_07_fill: FloatVectorProperty(name="塗", subtype='COLOR', size=4, default=(0.9, 0.9, 0.5, 0.5), min=0.0, max=1.0)
col_08_name: StringProperty(default="水")
col_08_stroke: FloatVectorProperty(name="線", subtype='COLOR', size=4, default=(0.1, 0.8, 0.9, 1.0), min=0.0, max=1.0)
col_08_fill: FloatVectorProperty(name="塗", subtype='COLOR', size=4, default=(0.5, 0.9, 0.9, 0.5), min=0.0, max=1.0)
col_09_name: StringProperty(default="紫")
col_09_stroke: FloatVectorProperty(name="線", subtype='COLOR', size=4, default=(0.6, 0.1, 0.8, 1.0), min=0.0, max=1.0)
col_09_fill: FloatVectorProperty(name="塗", subtype='COLOR', size=4, default=(0.8, 0.5, 0.9, 0.5), min=0.0, max=1.0)
col_10_name: StringProperty(default="橙")
col_10_stroke: FloatVectorProperty(name="線", subtype='COLOR', size=4, default=(0.9, 0.4, 0.1, 1.0), min=0.0, max=1.0)
col_10_fill: FloatVectorProperty(name="塗", subtype='COLOR', size=4, default=(0.9, 0.6, 0.4, 0.5), min=0.0, max=1.0)
col_11_name: StringProperty(default="茶")
col_11_stroke: FloatVectorProperty(name="線", subtype='COLOR', size=4, default=(0.4, 0.2, 0.1, 1.0), min=0.0, max=1.0)
col_11_fill: FloatVectorProperty(name="塗", subtype='COLOR', size=4, default=(0.6, 0.4, 0.3, 0.5), min=0.0, max=1.0)
col_12_name: StringProperty(default="桃")
col_12_stroke: FloatVectorProperty(name="線", subtype='COLOR', size=4, default=(0.9, 0.3, 0.6, 1.0), min=0.0, max=1.0)
col_12_fill: FloatVectorProperty(name="塗", subtype='COLOR', size=4, default=(0.9, 0.6, 0.8, 0.5), min=0.0, max=1.0)
# =========================================================
# 2. ユーティリティ
# =========================================================
def is_gp_object(obj):
return obj and obj.type in {'GPENCIL', 'GREASEPENCIL'}
def get_next_name(base_name):
max_num = 0
for obj in bpy.data.objects:
if is_gp_object(obj) and obj.name.startswith(base_name):
try: max_num = max(max_num, int(obj.name.replace(base_name, "")))
except: pass
return f"{base_name}{max_num + 1:03d}"
# =========================================================
# 3. オペレーター (ワークフロー)
# =========================================================
class ZIONAD_OT_CreateGP(Operator):
bl_idname = f"{PREFIX_VAL.lower()}.create_gp"
bl_label = "① + 新規 GPオブジェクトを作成"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
props = context.scene.zionad_props
curr_frame = context.scene.frame_current
if context.mode != 'OBJECT':
try: bpy.ops.object.mode_set(mode='OBJECT')
except: pass
# コレクション作成
col = bpy.data.collections.get(props.col_name)
if not col:
col = bpy.data.collections.new(props.col_name)
context.scene.collection.children.link(col)
# GP作成
try: bpy.ops.object.grease_pencil_add(type='EMPTY')
except: bpy.ops.object.gpencil_add(type='EMPTY')
gp = context.active_object
gp.name = get_next_name(props.obj_base_name)
for c in gp.users_collection: c.objects.unlink(gp)
col.objects.link(gp)
# 【レイヤー&フレーム自動生成】※フレームが無いと描画できないエラーを防止
gp_data = gp.data
if hasattr(gp_data, "layers"):
try:
if len(gp_data.layers) == 0:
layer_fill = gp_data.layers.new(name="Fill")
if hasattr(layer_fill, "frames"):
layer_fill.frames.new(curr_frame)
layer_line = gp_data.layers.new(name="Line")
if hasattr(layer_line, "frames"):
layer_line.frames.new(curr_frame)
gp_data.layers.active = layer_line
except: pass
# 【マテリアル設定】
mat_name = "GP_Mat_01"
mat = bpy.data.materials.get(mat_name)
if not mat:
mat = bpy.data.materials.new(name=mat_name)
try: bpy.data.materials.create_gpencil_data(mat)
except: pass
if hasattr(mat, "grease_pencil") and mat.grease_pencil:
mat.grease_pencil.color = props.col_01_stroke
mat.grease_pencil.fill_color = props.col_01_fill
mat.grease_pencil.show_stroke = True
mat.grease_pencil.show_fill = True
mat.diffuse_color = props.col_01_stroke
if not mat.use_nodes:
mat.use_nodes = True
if mat.node_tree:
for node in mat.node_tree.nodes:
if node.type == 'BSDF_PRINCIPLED':
node.inputs[0].default_value = props.col_01_stroke
gp.data.materials.append(mat)
gp.active_material_index = 0
# ドローモードへ
try: bpy.ops.object.mode_set(mode='PAINT_GREASE_PENCIL')
except:
try: bpy.ops.object.mode_set(mode='PAINT_GPENCIL')
except: pass
# 【エラー対策】GPv3などの仕様変更用フォールバック(強制的に空白フレームを挿入)
try: bpy.ops.grease_pencil.insert_blank_frame()
except: pass
try: bpy.ops.gpencil.blank_frame_add()
except: pass
# ブラシをマテリアルモードに強制
ts = context.scene.tool_settings
paint = getattr(ts, "grease_pencil_paint", getattr(ts, "gpencil_paint", None))
if paint and hasattr(paint, "brush") and paint.brush:
try: paint.brush.use_material = True
except: pass
try: paint.brush.color_mode = 'MATERIAL'
except: pass
self.report({'INFO'}, f"キャンバス '{gp.name}' を作成しました。")
return {'FINISHED'}
class ZIONAD_OT_SetBrushSize(Operator):
bl_idname = f"{PREFIX_VAL.lower()}.set_brush_size"
bl_label = "太さを変更"
bl_options = {'REGISTER', 'UNDO'}
size: IntProperty(default=10)
def execute(self, context):
ts = context.scene.tool_settings
paint = getattr(ts, "grease_pencil_paint", getattr(ts, "gpencil_paint", None))
if paint and hasattr(paint, "brush") and paint.brush:
paint.brush.size = self.size
return {'FINISHED'}
class ZIONAD_OT_ApplyColor(Operator):
bl_idname = f"{PREFIX_VAL.lower()}.apply_color"
bl_label = "色を適用"
bl_options = {'REGISTER', 'UNDO'}
color_index: IntProperty(default=1)
def execute(self, context):
obj = context.active_object
if not is_gp_object(obj):
return {'CANCELLED'}
props = context.scene.zionad_props
stroke_col = getattr(props, f"col_{self.color_index:02d}_stroke")
fill_col = getattr(props, f"col_{self.color_index:02d}_fill")
mat_name = f"GP_Mat_{self.color_index:02d}"
mat = bpy.data.materials.get(mat_name)
if not mat:
mat = bpy.data.materials.new(name=mat_name)
try: bpy.data.materials.create_gpencil_data(mat)
except: pass
if hasattr(mat, "grease_pencil") and mat.grease_pencil:
mat.grease_pencil.color = stroke_col
mat.grease_pencil.fill_color = fill_col
mat.grease_pencil.show_stroke = True
mat.grease_pencil.show_fill = True
mat.diffuse_color = stroke_col
if not mat.use_nodes:
mat.use_nodes = True
if mat.node_tree:
for node in mat.node_tree.nodes:
if node.type == 'BSDF_PRINCIPLED':
node.inputs[0].default_value = stroke_col
found = False
for i, slot_mat in enumerate(obj.data.materials):
if slot_mat == mat:
obj.active_material_index = i
found = True
break
if not found:
obj.data.materials.append(mat)
obj.active_material_index = len(obj.data.materials) - 1
ts = context.scene.tool_settings
paint = getattr(ts, "grease_pencil_paint", getattr(ts, "gpencil_paint", None))
if paint and hasattr(paint, "brush") and paint.brush:
try: paint.brush.use_material = True
except: pass
try: paint.brush.color_mode = 'MATERIAL'
except: pass
for area in context.screen.areas:
if area.type == 'VIEW_3D': area.tag_redraw()
if context.mode not in {'PAINT_GPENCIL', 'PAINT_GREASE_PENCIL'}:
try: bpy.ops.object.mode_set(mode='PAINT_GREASE_PENCIL')
except:
try: bpy.ops.object.mode_set(mode='PAINT_GPENCIL')
except: pass
return {'FINISHED'}
class ZIONAD_OT_AddLayer(Operator):
bl_idname = f"{PREFIX_VAL.lower()}.add_layer"
bl_label = "新規レイヤー追加"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
obj = context.active_object
curr_frame = context.scene.frame_current
if is_gp_object(obj) and hasattr(obj.data, "layers"):
try:
layer = obj.data.layers.new(name="Layer")
obj.data.layers.active = layer
# レイヤー作成と同時にフレーム(キャンバス)を追加
if hasattr(layer, "frames"):
layer.frames.new(curr_frame)
except: pass
# ドローモード中の場合は念のためオペレーターでフレーム追加処理を呼び出す
if context.mode in {'PAINT_GPENCIL', 'PAINT_GREASE_PENCIL'}:
try: bpy.ops.grease_pencil.insert_blank_frame()
except: pass
try: bpy.ops.gpencil.blank_frame_add()
except: pass
return {'FINISHED'}
class ZIONAD_OT_SetLayerActive(Operator):
bl_idname = f"{PREFIX_VAL.lower()}.set_layer_active"
bl_label = "レイヤーを選択"
layer_name: StringProperty()
def execute(self, context):
obj = context.active_object
if is_gp_object(obj) and hasattr(obj.data, "layers"):
layer = obj.data.layers.get(self.layer_name)
if layer: obj.data.layers.active = layer
return {'FINISHED'}
class ZIONAD_OT_FinishDrawing(Operator):
bl_idname = f"{PREFIX_VAL.lower()}.finish_drawing"
bl_label = "⑤ ✓ オブジェクト完成 (確定して次を作る)"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
if not is_gp_object(context.active_object): return {'CANCELLED'}
try: bpy.ops.object.mode_set(mode='OBJECT')
except: pass
try:
op = getattr(bpy.ops, PREFIX_VAL.lower())
op.create_gp()
except: pass
return {'FINISHED'}
class ZIONAD_OT_EditPalette(Operator):
bl_idname = f"{PREFIX_VAL.lower()}.edit_palette"
bl_label = "🎨 パレットの12色をカスタマイズ"
def invoke(self, context, event):
return context.window_manager.invoke_props_dialog(self, width=400)
def draw(self, context):
layout = self.layout
props = context.scene.zionad_props
layout.label(text="名前 / Stroke(線の色) / Fill(塗りの色)", icon='COLOR')
layout.separator()
for i in range(1, 13):
row = layout.row(align=True)
row.prop(props, f"col_{i:02d}_name", text="")
row.prop(props, f"col_{i:02d}_stroke", text="")
row.prop(props, f"col_{i:02d}_fill", text="")
def execute(self, context): return {'FINISHED'}
class ZIONAD_OT_RemoveAllPanels(Operator):
bl_idname = f"{PREFIX_VAL.lower()}.remove_all_panels"
bl_label = "Unregister Addon"
def execute(self, context):
unregister()
return {'FINISHED'}
# =========================================================
# 4. パネル UI
# =========================================================
class ZIONAD_PT_Workflow(Panel):
bl_label = "1. 描画ワークフロー"
bl_idname = "ZIONAD_PT_workflow"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = ADDON_TITLE
bl_order = 0
def draw(self, context):
layout = self.layout
props = context.scene.zionad_props
# ① 新規作成
col = layout.column(align=True)
col.scale_y = 1.5
col.operator(ZIONAD_OT_CreateGP.bl_idname, icon='ADD')
layout.separator()
# ② ペンの太さ
box_size = layout.box()
box_size.label(text="② ペンの太さ:", icon='BRUSH_DATA')
row_size = box_size.row(align=True)
op_s = row_size.operator(ZIONAD_OT_SetBrushSize.bl_idname, text="細 (5)")
op_s.size = 5
op_m = row_size.operator(ZIONAD_OT_SetBrushSize.bl_idname, text="中 (20)")
op_m.size = 20
op_l = row_size.operator(ZIONAD_OT_SetBrushSize.bl_idname, text="太 (50)")
op_l.size = 50
layout.separator()
# ③ 色選択
box = layout.box()
row = box.row()
row.label(text="③ 色を選ぶ (選択以降に描く線に適用):", icon='COLOR')
row.operator(ZIONAD_OT_EditPalette.bl_idname, text="", icon='PREFERENCES')
grid = box.grid_flow(columns=2, align=True)
for i in range(1, 13):
name = getattr(props, f"col_{i:02d}_name")
c = grid.column(align=True)
r = c.row(align=True)
r.prop(props, f"col_{i:02d}_stroke", text="")
r.prop(props, f"col_{i:02d}_fill", text="")
op = r.operator(ZIONAD_OT_ApplyColor.bl_idname, text=name)
op.color_index = i
layout.separator()
# ④ レイヤー管理
obj = context.active_object
if is_gp_object(obj):
box_layer = layout.box()
row_head = box_layer.row()
row_head.label(text="④ レイヤー (同一オブジェクト内):", icon='GROUP')
row_head.operator(ZIONAD_OT_AddLayer.bl_idname, text="追加", icon='ADD')
if hasattr(obj.data, "layers") and obj.data.layers:
# 【エラー対策】GPv3でリスト化が失敗した場合にパネルが消えないように保護
layers_list = []
try: layers_list = list(obj.data.layers)
except: pass
for layer in reversed(layers_list):
row_layer = box_layer.row(align=True)
is_active = False
try: is_active = (obj.data.layers.active == layer)
except: pass
icon = 'RADIOBUT_ON' if is_active else 'RADIOBUT_OFF'
op_act = row_layer.operator(ZIONAD_OT_SetLayerActive.bl_idname, text=layer.name, icon=icon)
op_act.layer_name = layer.name
if hasattr(layer, "hide"):
row_layer.prop(layer, "hide", text="", icon='HIDE_ON' if layer.hide else 'HIDE_OFF')
elif hasattr(layer, "hide_viewport"):
row_layer.prop(layer, "hide_viewport", text="", icon='HIDE_ON' if layer.hide_viewport else 'HIDE_OFF')
if hasattr(layer, "lock"):
row_layer.prop(layer, "lock", text="", icon='LOCKED' if layer.lock else 'UNLOCKED')
if hasattr(layer, "opacity"):
row_layer.prop(layer, "opacity", text="", slider=True)
else:
box_layer.label(text="レイヤーがありません")
layout.separator()
# ⑤ 完成ボタン
col = layout.column(align=True)
col.scale_y = 1.5
col.operator(ZIONAD_OT_FinishDrawing.bl_idname, icon='CHECKMARK')
class ZIONAD_PT_Manager(Panel):
bl_label = "2. オブジェクト管理"
bl_idname = "ZIONAD_PT_manager"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = ADDON_TITLE
bl_order = 1
bl_options = {'DEFAULT_CLOSED'}
def draw(self, context):
layout = self.layout
box = layout.box()
props = context.scene.zionad_props
col = bpy.data.collections.get(props.col_name)
if col:
for obj in col.objects:
if is_gp_object(obj):
row = box.row(align=True)
icon = 'RADIOBUT_ON' if context.active_object == obj else 'RADIOBUT_OFF'
row.prop(obj, "name", text="", icon=icon)
row.prop(obj, "hide_viewport", text="", icon='HIDE_ON' if obj.hide_viewport else 'HIDE_OFF')
row.prop(obj, "hide_select", text="", icon='RESTRICT_SELECT_ON' if obj.hide_select else 'UNLOCKED')
else:
box.label(text="まだ描画がありません")
class ZIONAD_PT_RemovePanel(Panel):
bl_label = "3. アドオン削除"
bl_idname = "ZIONAD_PT_remove"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = ADDON_TITLE
bl_order = 2
bl_options = {'DEFAULT_CLOSED'}
def draw(self, context):
layout = self.layout
box = layout.box()
box.label(text="アドオンの登録を即座に解除します", icon='ERROR')
box.operator(ZIONAD_OT_RemoveAllPanels.bl_idname, icon='CANCEL')
# =========================================================
# 5. 登録・解除
# =========================================================
classes = (
ZIONAD_Properties,
ZIONAD_OT_CreateGP,
ZIONAD_OT_SetBrushSize,
ZIONAD_OT_ApplyColor,
ZIONAD_OT_AddLayer,
ZIONAD_OT_SetLayerActive,
ZIONAD_OT_FinishDrawing,
ZIONAD_OT_EditPalette,
ZIONAD_OT_RemoveAllPanels,
ZIONAD_PT_Workflow,
ZIONAD_PT_Manager,
ZIONAD_PT_RemovePanel,
)
def register():
for cls in classes:
bpy.utils.register_class(cls)
bpy.types.Scene.zionad_props = PointerProperty(type=ZIONAD_Properties)
def unregister():
if hasattr(bpy.types.Scene, 'zionad_props'):
del bpy.types.Scene.zionad_props
for cls in reversed(classes):
try:
bpy.utils.unregister_class(cls)
except:
pass
if __name__ == "__main__":
register()