N Panel > 3D View", "description": "線分の回転を反時計回り(プラス)にし、両端の角度情報も表示", "warning": "", "doc_url": "", "category": "3D View", } import bpy import math # 作成日 2026年07月21日 # =================================== # 4. 共通定義 # =================================== ADDON_TITLE = "Airplane Landing Visualizer" PREFIX_VAL = "AIR_LANDING" ADDON_CATEGORY = "3D View" PREFIX = PREFIX_VAL.lower() # ========================================================= # 6. 初期設定 # ========================================================= # 数値・座標 START_X_DEFAULT = -100.0 # 開始地点のX座標 LANDING_ANGLE_DEFAULT = 30.0 # 初期降下角度(度) # サイズ (半径・長さ・太さ) RADIUS_CYLINDER_DEFAULT = 0.5 # 降下軌道(円柱)の太さ RADIUS_SPHERE_DEFAULT = 1.5 # 飛行機(現在位置・球体)の大きさ RADIUS_TARGET_DEFAULT = 2.0 # 着地点の大きさ LENGTH_LINE_DEFAULT = 2.0 #"> N Panel > 3D View", "description": "線分の回転を反時計回り(プラス)にし、両端の角度情報も表示", "warning": "", "doc_url": "", "category": "3D View", } import bpy import math # 作成日 2026年07月21日 # =================================== # 4. 共通定義 # =================================== ADDON_TITLE = "Airplane Landing Visualizer" PREFIX_VAL = "AIR_LANDING" ADDON_CATEGORY = "3D View" PREFIX = PREFIX_VAL.lower() # ========================================================= # 6. 初期設定 # ========================================================= # 数値・座標 START_X_DEFAULT = -100.0 # 開始地点のX座標 LANDING_ANGLE_DEFAULT = 30.0 # 初期降下角度(度) # サイズ (半径・長さ・太さ) RADIUS_CYLINDER_DEFAULT = 0.5 # 降下軌道(円柱)の太さ RADIUS_SPHERE_DEFAULT = 1.5 # 飛行機(現在位置・球体)の大きさ RADIUS_TARGET_DEFAULT = 2.0 # 着地点の大きさ LENGTH_LINE_DEFAULT = 2.0 #"> N Panel > 3D View", "description": "線分の回転を反時計回り(プラス)にし、両端の角度情報も表示", "warning": "", "doc_url": "", "category": "3D View", } import bpy import math # 作成日 2026年07月21日 # =================================== # 4. 共通定義 # =================================== ADDON_TITLE = "Airplane Landing Visualizer" PREFIX_VAL = "AIR_LANDING" ADDON_CATEGORY = "3D View" PREFIX = PREFIX_VAL.lower() # ========================================================= # 6. 初期設定 # ========================================================= # 数値・座標 START_X_DEFAULT = -100.0 # 開始地点のX座標 LANDING_ANGLE_DEFAULT = 30.0 # 初期降下角度(度) # サイズ (半径・長さ・太さ) RADIUS_CYLINDER_DEFAULT = 0.5 # 降下軌道(円柱)の太さ RADIUS_SPHERE_DEFAULT = 1.5 # 飛行機(現在位置・球体)の大きさ RADIUS_TARGET_DEFAULT = 2.0 # 着地点の大きさ LENGTH_LINE_DEFAULT = 2.0 #">












bl_info = {
    "name": "Airplane Landing Visualizer",
    "author": "ChatGPT",
    "version": (1, 7, 0),
    "blender": (5, 0, 0),
    "location": "View3D > N Panel > 3D View",
    "description": "線分の回転を反時計回り(プラス)にし、両端の角度情報も表示",
    "warning": "",
    "doc_url": "",
    "category": "3D View",
}

import bpy
import math

# 作成日 2026年07月21日
# ===================================
# 4. 共通定義
# ===================================
ADDON_TITLE = "Airplane Landing Visualizer"
PREFIX_VAL = "AIR_LANDING"
ADDON_CATEGORY = "3D View"
PREFIX = PREFIX_VAL.lower()

# =========================================================
# 6. 初期設定
# =========================================================

# 数値・座標
START_X_DEFAULT = -100.0             # 開始地点のX座標
LANDING_ANGLE_DEFAULT = 30.0         # 初期降下角度(度)

# サイズ (半径・長さ・太さ)
RADIUS_CYLINDER_DEFAULT = 0.5        # 降下軌道(円柱)の太さ
RADIUS_SPHERE_DEFAULT = 1.5          # 飛行機(現在位置・球体)の大きさ
RADIUS_TARGET_DEFAULT = 2.0          # 着地点の大きさ
LENGTH_LINE_DEFAULT = 2.0            # 線分の全長
THICKNESS_LINE_DEFAULT = 0.1         # 線分の太さ(半径)

# 角度
LINE_PITCH_DEFAULT = 0.0             # 線分の初期Y軸回転角度(0.0でX軸に完全平行)

# 色 (R, G, B, A)
COLOR_GLIDE_PATH = (0.0, 0.5, 1.0, 0.3)  # 軌道(青・半透明)
COLOR_AIRPLANE = (1.0, 0.0, 0.0, 1.0)    # 飛行機(赤)
COLOR_TARGET = (0.0, 1.0, 0.0, 1.0)      # 着地点(緑)
COLOR_LINE = (1.0, 1.0, 0.0, 1.0)        # 姿勢線分(黄)

# =========================================================
# 処理ロジック・コールバック
# =========================================================
def create_material(name, color):
    mat = bpy.data.materials.get(name)
    if mat is None:
        mat = bpy.data.materials.new(name=name)
    mat.use_nodes = True
    bsdf = mat.node_tree.nodes.get("Principled BSDF")
    if bsdf:
        bsdf.inputs["Base Color"].default_value = color
        bsdf.inputs["Alpha"].default_value = color[3]
    return mat

def update_airplane_and_line_pos(self, context):
    sphere = bpy.data.objects.get(f"{PREFIX}_Airplane")
    line = bpy.data.objects.get(f"{PREFIX}_Line")
    
    if sphere and line:
        x = self.current_x
        angle_rad = math.radians(self.angle)
        z = -x * math.tan(angle_rad)
        
        sphere.location = (x, 0, z)
        line.location = (x, 0, z)
        
        pitch_rad = math.radians(self.line_pitch)
        # プラスで反時計回り(機首上げ)になるように引き算に変更
        line.rotation_euler = (0, math.radians(90) - pitch_rad, 0)

def update_glide_path(self, context):
    obj = bpy.data.objects.get(f"{PREFIX}_GlidePath")
    if obj:
        start_x = START_X_DEFAULT
        angle_rad = math.radians(self.angle)
        start_z = -start_x * math.tan(angle_rad)
        
        obj.location = (start_x / 2.0, 0, start_z / 2.0)
        
        length = math.sqrt(start_x**2 + start_z**2)
        radius = self.cyl_radius
        obj.scale = (radius, radius, length / 2.0)
        
        Ry = math.atan2(-start_x, -start_z)
        obj.rotation_euler = (0, Ry, 0)
        
        update_airplane_and_line_pos(self, context)

def update_cyl_scale(self, context):
    obj = bpy.data.objects.get(f"{PREFIX}_GlidePath")
    if obj:
        obj.scale[0] = self.cyl_radius
        obj.scale[1] = self.cyl_radius

def update_sph_scale(self, context):
    obj = bpy.data.objects.get(f"{PREFIX}_Airplane")
    if obj:
        r = self.sph_radius
        obj.scale = (r, r, r)

def update_line_scale(self, context):
    obj = bpy.data.objects.get(f"{PREFIX}_Line")
    if obj:
        t = self.line_thickness
        obj.scale = (t, t, self.line_length / 2.0)

def update_colors(self, context):
    mats = {
        f"{PREFIX}_GlidePath_Mat": self.cyl_color,
        f"{PREFIX}_Airplane_Mat": self.sph_color,
        f"{PREFIX}_Line_Mat": self.line_color
    }
    for mat_name, color in mats.items():
        mat = bpy.data.materials.get(mat_name)
        if mat and mat.use_nodes:
            bsdf = mat.node_tree.nodes.get("Principled BSDF")
            if bsdf:
                bsdf.inputs["Base Color"].default_value = color
                bsdf.inputs["Alpha"].default_value = color[3]

# =========================================================
# プロパティグループ
# =========================================================
class AIR_LANDING_Properties(bpy.types.PropertyGroup):
    current_x: bpy.props.FloatProperty(
        name="現在位置 X", default=START_X_DEFAULT, min=START_X_DEFAULT, max=0.0, update=update_airplane_and_line_pos
    )
    angle: bpy.props.FloatProperty(
        name="降下角度", default=LANDING_ANGLE_DEFAULT, min=1.0, max=89.0, update=update_glide_path
    )
    line_pitch: bpy.props.FloatProperty(
        name="Y軸回転 (ピッチ)", default=LINE_PITCH_DEFAULT, min=-180.0, max=180.0, update=update_airplane_and_line_pos
    )
    cyl_radius: bpy.props.FloatProperty(
        name="軌道の太さ", default=RADIUS_CYLINDER_DEFAULT, min=0.01, max=10.0, update=update_cyl_scale
    )
    sph_radius: bpy.props.FloatProperty(
        name="球体の大きさ", default=RADIUS_SPHERE_DEFAULT, min=0.1, max=10.0, update=update_sph_scale
    )
    line_length: bpy.props.FloatProperty(
        name="線分の全長", default=LENGTH_LINE_DEFAULT, min=0.1, max=50.0, update=update_line_scale
    )
    line_thickness: bpy.props.FloatProperty(
        name="線分の太さ", default=THICKNESS_LINE_DEFAULT, min=0.01, max=5.0, update=update_line_scale
    )
    cyl_color: bpy.props.FloatVectorProperty(
        name="軌道の色", subtype='COLOR', size=4, default=COLOR_GLIDE_PATH, update=update_colors, min=0.0, max=1.0
    )
    sph_color: bpy.props.FloatVectorProperty(
        name="球体の色", subtype='COLOR', size=4, default=COLOR_AIRPLANE, update=update_colors, min=0.0, max=1.0
    )
    line_color: bpy.props.FloatVectorProperty(
        name="線分の色", subtype='COLOR', size=4, default=COLOR_LINE, update=update_colors, min=0.0, max=1.0
    )

# =========================================================
# オペレーター
# =========================================================
class AIR_LANDING_OT_copy_text(bpy.types.Operator):
    bl_idname = f"{PREFIX}.copy_text"
    bl_label = "コピー"
    text_to_copy: bpy.props.StringProperty()

    def execute(self, context):
        context.window_manager.clipboard = self.text_to_copy
        self.report({'INFO'}, f"コピーしました: {self.text_to_copy}")
        return {'FINISHED'}

class AIR_LANDING_OT_create_scene(bpy.types.Operator):
    bl_idname = f"{PREFIX}.create_scene"
    bl_label = "シーンを生成・リセット"
    bl_options = {'REGISTER', 'UNDO'}

    def execute(self, context):
        bpy.ops.object.select_all(action='DESELECT')
        for obj in bpy.context.scene.objects:
            if obj.name.startswith(f"{PREFIX}_"):
                obj.select_set(True)
        bpy.ops.object.delete()

        props = context.scene.air_landing_props

        # 1. 着地点
        bpy.ops.mesh.primitive_uv_sphere_add(radius=RADIUS_TARGET_DEFAULT, align='WORLD', location=(0, 0, 0))
        target = context.active_object
        target.name = f"{PREFIX}_Target"
        target.data.materials.append(create_material(f"{PREFIX}_Target_Mat", COLOR_TARGET))

        # 2. 降下軌道
        bpy.ops.mesh.primitive_cylinder_add(radius=1.0, depth=2.0, align='WORLD', location=(0,0,0))
        cyl = context.active_object
        cyl.name = f"{PREFIX}_GlidePath"
        cyl.data.materials.append(create_material(f"{PREFIX}_GlidePath_Mat", COLOR_GLIDE_PATH))

        # 3. 飛行機 / 現在位置
        bpy.ops.mesh.primitive_uv_sphere_add(radius=1.0, align='WORLD', location=(0,0,0))
        airplane = context.active_object
        airplane.name = f"{PREFIX}_Airplane"
        airplane.data.materials.append(create_material(f"{PREFIX}_Airplane_Mat", COLOR_AIRPLANE))

        # 4. 機体姿勢(線分)
        bpy.ops.mesh.primitive_cylinder_add(radius=1.0, depth=2.0, align='WORLD', location=(0,0,0))
        line = context.active_object
        line.name = f"{PREFIX}_Line"
        line.data.materials.append(create_material(f"{PREFIX}_Line_Mat", COLOR_LINE))

        # 初期値の適用
        props.current_x = START_X_DEFAULT
        props.line_pitch = LINE_PITCH_DEFAULT
        update_glide_path(props, context)
        update_cyl_scale(props, context)
        update_sph_scale(props, context)
        update_line_scale(props, context)

        self.report({'INFO'}, "降下シミュレーションシーンを生成しました")
        return {'FINISHED'}

# =========================================================
# UI (Nパネル)
# =========================================================
class AIR_LANDING_PT_main_panel(bpy.types.Panel):
    bl_label = ADDON_TITLE
    bl_idname = f"{PREFIX}_PT_main_panel"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = ADDON_CATEGORY

    def draw(self, context):
        layout = self.layout
        props = context.scene.air_landing_props
        
        col = layout.column(align=True)
        col.operator(AIR_LANDING_OT_create_scene.bl_idname, icon='PLAY')
        col.separator()
        
        col.prop(props, "angle")
        col.prop(props, "current_x")
        col.separator()

        # 姿勢(線分)操作
        box_line = layout.box()
        box_line.label(text="機体姿勢(黄色の線分):", icon='DRIVER_ROTATIONAL_DIFFERENCE')
        box_line.prop(props, "line_pitch")
        box_line.prop(props, "line_length")
        box_line.prop(props, "line_thickness")

        col.separator()

        # --- 計算・情報表示ブロック (小数点1桁で統一) ---
        x = props.current_x
        angle_rad = math.radians(props.angle)
        z = -x * math.tan(angle_rad)
        
        # 中央(0,0,0)からの距離と角度
        dist_center = math.sqrt(x**2 + 0.0**2 + z**2)
        horiz_dist = abs(x)
        angle_from_origin = math.degrees(math.atan2(z, horiz_dist)) if horiz_dist > 0 else 90.0

        # 線分の両端座標の計算 (プラスを反時計回りにする計算)
        pitch_rad = math.radians(props.line_pitch)
        half_len = props.line_length / 2.0
        
        dx = half_len * math.cos(pitch_rad)
        dz = half_len * math.sin(pitch_rad)
        
        tip1 = (x + dx, 0.0, z + dz)
        tip2 = (x - dx, 0.0, z - dz)
        
        # 両端の距離
        dist_tip1 = math.sqrt(tip1[0]**2 + tip1[1]**2 + tip1[2]**2)
        dist_tip2 = math.sqrt(tip2[0]**2 + tip2[1]**2 + tip2[2]**2)

        # 両端の角度 (中央からの仰角/俯角)
        horiz_dist_1 = abs(tip1[0])
        angle_tip1 = math.degrees(math.atan2(tip1[2], horiz_dist_1)) if horiz_dist_1 > 0 else 90.0
        
        horiz_dist_2 = abs(tip2[0])
        angle_tip2 = math.degrees(math.atan2(tip2[2], horiz_dist_2)) if horiz_dist_2 > 0 else 90.0

        # 小数点1桁にフォーマット
        s_center = f"中央 距離: {dist_center:.1f} | 角度: {angle_from_origin:.1f}°"
        s_tip1   = f"先端1 距離: {dist_tip1:.1f} | 角度: {angle_tip1:.1f}°"
        s_tip2   = f"先端2 距離: {dist_tip2:.1f} | 角度: {angle_tip2:.1f}°"
        
        all_text = f"{s_center}\n{s_tip1}\n{s_tip2}"

        box_info = layout.box()
        box_info.label(text="原点(0,0,0)からの距離・角度:", icon='INFO')
        
        # 1行ずつ表示 & 各行のコピーボタン
        row1 = box_info.row()
        row1.label(text=s_center)
        op1 = row1.operator(AIR_LANDING_OT_copy_text.bl_idname, text="", icon='COPYDOWN')
        op1.text_to_copy = s_center

        row2 = box_info.row()
        row2.label(text=s_tip1)
        op2 = row2.operator(AIR_LANDING_OT_copy_text.bl_idname, text="", icon='COPYDOWN')
        op2.text_to_copy = s_tip1

        row3 = box_info.row()
        row3.label(text=s_tip2)
        op3 = row3.operator(AIR_LANDING_OT_copy_text.bl_idname, text="", icon='COPYDOWN')
        op3.text_to_copy = s_tip2

        # 一括コピーボタン
        row_all = box_info.row()
        op_all = row_all.operator(AIR_LANDING_OT_copy_text.bl_idname, text="一括コピー", icon='COPYDOWN')
        op_all.text_to_copy = all_text

        col.separator()

        # 方程式表示ブロック
        slope = math.tan(math.radians(props.angle))
        eq_str = f"Z = {-slope:.1f} * X  (X <= 0, Y = 0)"
        
        box_eq = layout.box()
        box_eq.label(text="軌道の方程式:")
        row_eq = box_eq.row()
        row_eq.label(text=eq_str)
        op = row_eq.operator(AIR_LANDING_OT_copy_text.bl_idname, text="", icon='COPYDOWN')
        op.text_to_copy = eq_str

        # 見た目調整ブロック
        box_style = layout.box()
        box_style.label(text="見た目の調整", icon='COLOR')
        
        r_c = box_style.row()
        r_c.prop(props, "cyl_radius")
        r_c.prop(props, "cyl_color", text="")
        
        r_s = box_style.row()
        r_s.prop(props, "sph_radius")
        r_s.prop(props, "sph_color", text="")

        r_l = box_style.row()
        r_l.prop(props, "line_color", text="")

# =========================================================
# 登録・解除
# =========================================================
classes = (
    AIR_LANDING_Properties,
    AIR_LANDING_OT_copy_text,
    AIR_LANDING_OT_create_scene,
    AIR_LANDING_PT_main_panel,
)

def apply_startup_view_settings():
    for window in bpy.context.window_manager.windows:
        for area in window.screen.areas:
            if area.type == 'VIEW_3D':
                for space in area.spaces:
                    if space.type == 'VIEW_3D':
                        space.show_region_ui = True
                        space.shading.type = 'MATERIAL'

def register():
    for cls in classes:
        bpy.utils.register_class(cls)
    bpy.types.Scene.air_landing_props = bpy.props.PointerProperty(type=AIR_LANDING_Properties)
    bpy.app.timers.register(apply_startup_view_settings, first_interval=0.1)

def unregister():
    for cls in reversed(classes):
        bpy.utils.unregister_class(cls)
    del bpy.types.Scene.air_landing_props

if __name__ == "__main__":
    register()
bl_info = {
    "name": "Airplane Landing Visualizer",
    "author": "ChatGPT",
    "version": (1, 6, 0),
    "blender": (5, 0, 0),
    "location": "View3D > N Panel > 3D View",
    "description": "線分の最大長さを50に拡張し、情報を小数点1桁で表示する機能",
    "warning": "",
    "doc_url": "",
    "category": "3D View",
}

import bpy
import math

# 作成日 2026年07月21日
# ===================================
# 4. 共通定義
# ===================================
ADDON_TITLE = "Airplane Landing Visualizer"
PREFIX_VAL = "AIR_LANDING"
ADDON_CATEGORY = "3D View"
PREFIX = PREFIX_VAL.lower()

# =========================================================
# 6. 初期設定
# =========================================================

# 数値・座標
START_X_DEFAULT = -100.0             # 開始地点のX座標
LANDING_ANGLE_DEFAULT = 30.0         # 初期降下角度(度)

# サイズ (半径・長さ・太さ)
RADIUS_CYLINDER_DEFAULT = 0.5        # 降下軌道(円柱)の太さ
RADIUS_SPHERE_DEFAULT = 1.5          # 飛行機(現在位置・球体)の大きさ
RADIUS_TARGET_DEFAULT = 2.0          # 着地点の大きさ
LENGTH_LINE_DEFAULT = 2.0            # 線分の全長
THICKNESS_LINE_DEFAULT = 0.1         # 線分の太さ(半径)

# 角度
LINE_PITCH_DEFAULT = 0.0             # 線分の初期Y軸回転角度(0.0でX軸に完全平行)

# 色 (R, G, B, A)
COLOR_GLIDE_PATH = (0.0, 0.5, 1.0, 0.3)  # 軌道(青・半透明)
COLOR_AIRPLANE = (1.0, 0.0, 0.0, 1.0)    # 飛行機(赤)
COLOR_TARGET = (0.0, 1.0, 0.0, 1.0)      # 着地点(緑)
COLOR_LINE = (1.0, 1.0, 0.0, 1.0)        # 姿勢線分(黄)

# =========================================================
# 処理ロジック・コールバック
# =========================================================
def create_material(name, color):
    mat = bpy.data.materials.get(name)
    if mat is None:
        mat = bpy.data.materials.new(name=name)
    mat.use_nodes = True
    bsdf = mat.node_tree.nodes.get("Principled BSDF")
    if bsdf:
        bsdf.inputs["Base Color"].default_value = color
        bsdf.inputs["Alpha"].default_value = color[3]
    return mat

def update_airplane_and_line_pos(self, context):
    sphere = bpy.data.objects.get(f"{PREFIX}_Airplane")
    line = bpy.data.objects.get(f"{PREFIX}_Line")
    
    if sphere and line:
        x = self.current_x
        angle_rad = math.radians(self.angle)
        z = -x * math.tan(angle_rad)
        
        sphere.location = (x, 0, z)
        line.location = (x, 0, z)
        
        pitch_rad = math.radians(self.line_pitch)
        line.rotation_euler = (0, math.radians(90) + pitch_rad, 0)

def update_glide_path(self, context):
    obj = bpy.data.objects.get(f"{PREFIX}_GlidePath")
    if obj:
        start_x = START_X_DEFAULT
        angle_rad = math.radians(self.angle)
        start_z = -start_x * math.tan(angle_rad)
        
        obj.location = (start_x / 2.0, 0, start_z / 2.0)
        
        length = math.sqrt(start_x**2 + start_z**2)
        radius = self.cyl_radius
        obj.scale = (radius, radius, length / 2.0)
        
        Ry = math.atan2(-start_x, -start_z)
        obj.rotation_euler = (0, Ry, 0)
        
        update_airplane_and_line_pos(self, context)

def update_cyl_scale(self, context):
    obj = bpy.data.objects.get(f"{PREFIX}_GlidePath")
    if obj:
        obj.scale[0] = self.cyl_radius
        obj.scale[1] = self.cyl_radius

def update_sph_scale(self, context):
    obj = bpy.data.objects.get(f"{PREFIX}_Airplane")
    if obj:
        r = self.sph_radius
        obj.scale = (r, r, r)

def update_line_scale(self, context):
    obj = bpy.data.objects.get(f"{PREFIX}_Line")
    if obj:
        t = self.line_thickness
        obj.scale = (t, t, self.line_length / 2.0)

def update_colors(self, context):
    mats = {
        f"{PREFIX}_GlidePath_Mat": self.cyl_color,
        f"{PREFIX}_Airplane_Mat": self.sph_color,
        f"{PREFIX}_Line_Mat": self.line_color
    }
    for mat_name, color in mats.items():
        mat = bpy.data.materials.get(mat_name)
        if mat and mat.use_nodes:
            bsdf = mat.node_tree.nodes.get("Principled BSDF")
            if bsdf:
                bsdf.inputs["Base Color"].default_value = color
                bsdf.inputs["Alpha"].default_value = color[3]

# =========================================================
# プロパティグループ
# =========================================================
class AIR_LANDING_Properties(bpy.types.PropertyGroup):
    current_x: bpy.props.FloatProperty(
        name="現在位置 X", default=START_X_DEFAULT, min=START_X_DEFAULT, max=0.0, update=update_airplane_and_line_pos
    )
    angle: bpy.props.FloatProperty(
        name="降下角度", default=LANDING_ANGLE_DEFAULT, min=1.0, max=89.0, update=update_glide_path
    )
    line_pitch: bpy.props.FloatProperty(
        name="Y軸回転 (ピッチ)", default=LINE_PITCH_DEFAULT, min=-180.0, max=180.0, update=update_airplane_and_line_pos
    )
    cyl_radius: bpy.props.FloatProperty(
        name="軌道の太さ", default=RADIUS_CYLINDER_DEFAULT, min=0.01, max=10.0, update=update_cyl_scale
    )
    sph_radius: bpy.props.FloatProperty(
        name="球体の大きさ", default=RADIUS_SPHERE_DEFAULT, min=0.1, max=10.0, update=update_sph_scale
    )
    line_length: bpy.props.FloatProperty(
        name="線分の全長", default=LENGTH_LINE_DEFAULT, min=0.1, max=50.0, update=update_line_scale
    )
    line_thickness: bpy.props.FloatProperty(
        name="線分の太さ", default=THICKNESS_LINE_DEFAULT, min=0.01, max=5.0, update=update_line_scale
    )
    cyl_color: bpy.props.FloatVectorProperty(
        name="軌道の色", subtype='COLOR', size=4, default=COLOR_GLIDE_PATH, update=update_colors, min=0.0, max=1.0
    )
    sph_color: bpy.props.FloatVectorProperty(
        name="球体の色", subtype='COLOR', size=4, default=COLOR_AIRPLANE, update=update_colors, min=0.0, max=1.0
    )
    line_color: bpy.props.FloatVectorProperty(
        name="線分の色", subtype='COLOR', size=4, default=COLOR_LINE, update=update_colors, min=0.0, max=1.0
    )

# =========================================================
# オペレーター
# =========================================================
class AIR_LANDING_OT_copy_text(bpy.types.Operator):
    bl_idname = f"{PREFIX}.copy_text"
    bl_label = "コピー"
    text_to_copy: bpy.props.StringProperty()

    def execute(self, context):
        context.window_manager.clipboard = self.text_to_copy
        self.report({'INFO'}, f"コピーしました: {self.text_to_copy}")
        return {'FINISHED'}

class AIR_LANDING_OT_create_scene(bpy.types.Operator):
    bl_idname = f"{PREFIX}.create_scene"
    bl_label = "シーンを生成・リセット"
    bl_options = {'REGISTER', 'UNDO'}

    def execute(self, context):
        bpy.ops.object.select_all(action='DESELECT')
        for obj in bpy.context.scene.objects:
            if obj.name.startswith(f"{PREFIX}_"):
                obj.select_set(True)
        bpy.ops.object.delete()

        props = context.scene.air_landing_props

        # 1. 着地点
        bpy.ops.mesh.primitive_uv_sphere_add(radius=RADIUS_TARGET_DEFAULT, align='WORLD', location=(0, 0, 0))
        target = context.active_object
        target.name = f"{PREFIX}_Target"
        target.data.materials.append(create_material(f"{PREFIX}_Target_Mat", COLOR_TARGET))

        # 2. 降下軌道
        bpy.ops.mesh.primitive_cylinder_add(radius=1.0, depth=2.0, align='WORLD', location=(0,0,0))
        cyl = context.active_object
        cyl.name = f"{PREFIX}_GlidePath"
        cyl.data.materials.append(create_material(f"{PREFIX}_GlidePath_Mat", COLOR_GLIDE_PATH))

        # 3. 飛行機 / 現在位置
        bpy.ops.mesh.primitive_uv_sphere_add(radius=1.0, align='WORLD', location=(0,0,0))
        airplane = context.active_object
        airplane.name = f"{PREFIX}_Airplane"
        airplane.data.materials.append(create_material(f"{PREFIX}_Airplane_Mat", COLOR_AIRPLANE))

        # 4. 機体姿勢(線分)
        bpy.ops.mesh.primitive_cylinder_add(radius=1.0, depth=2.0, align='WORLD', location=(0,0,0))
        line = context.active_object
        line.name = f"{PREFIX}_Line"
        line.data.materials.append(create_material(f"{PREFIX}_Line_Mat", COLOR_LINE))

        # 初期値の適用
        props.current_x = START_X_DEFAULT
        props.line_pitch = LINE_PITCH_DEFAULT
        update_glide_path(props, context)
        update_cyl_scale(props, context)
        update_sph_scale(props, context)
        update_line_scale(props, context)

        self.report({'INFO'}, "降下シミュレーションシーンを生成しました")
        return {'FINISHED'}

# =========================================================
# UI (Nパネル)
# =========================================================
class AIR_LANDING_PT_main_panel(bpy.types.Panel):
    bl_label = ADDON_TITLE
    bl_idname = f"{PREFIX}_PT_main_panel"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = ADDON_CATEGORY

    def draw(self, context):
        layout = self.layout
        props = context.scene.air_landing_props
        
        col = layout.column(align=True)
        col.operator(AIR_LANDING_OT_create_scene.bl_idname, icon='PLAY')
        col.separator()
        
        col.prop(props, "angle")
        col.prop(props, "current_x")
        col.separator()

        # 姿勢(線分)操作
        box_line = layout.box()
        box_line.label(text="機体姿勢(黄色の線分):", icon='DRIVER_ROTATIONAL_DIFFERENCE')
        box_line.prop(props, "line_pitch")
        box_line.prop(props, "line_length")
        box_line.prop(props, "line_thickness")

        col.separator()

        # --- 計算・情報表示ブロック (小数点1桁で統一) ---
        x = props.current_x
        angle_rad = math.radians(props.angle)
        z = -x * math.tan(angle_rad)
        
        # 中央(0,0,0)からの距離
        dist_center = math.sqrt(x**2 + 0.0**2 + z**2)
        
        # 中央(0,0,0)からの仰角/俯角
        horiz_dist = abs(x)
        angle_from_origin = math.degrees(math.atan2(z, horiz_dist)) if horiz_dist > 0 else 90.0

        # 線分の両端座標の計算
        total_pitch = math.radians(90.0 + props.line_pitch)
        half_len = props.line_length / 2.0
        
        dx = half_len * math.sin(total_pitch)
        dz = half_len * math.cos(total_pitch)
        
        tip1 = (x + dx, 0.0, z + dz)
        tip2 = (x - dx, 0.0, z - dz)
        
        dist_tip1 = math.sqrt(tip1[0]**2 + tip1[1]**2 + tip1[2]**2)
        dist_tip2 = math.sqrt(tip2[0]**2 + tip2[1]**2 + tip2[2]**2)

        # 小数点1桁にフォーマット
        s_center = f"中央(0,0,0) 距離: {dist_center:.1f} | 角度: {angle_from_origin:.1f}°"
        s_tip1 = f"先端1(0,0,0) 距離: {dist_tip1:.1f}"
        s_tip2 = f"先端2(0,0,0) 距離: {dist_tip2:.1f}"
        
        all_text = f"{s_center}\n{s_tip1}\n{s_tip2}"

        box_info = layout.box()
        box_info.label(text="位置・距離・角度情報:", icon='INFO')
        
        # 1行ずつ表示 & 各行のコピーボタン
        row1 = box_info.row()
        row1.label(text=s_center)
        op1 = row1.operator(AIR_LANDING_OT_copy_text.bl_idname, text="", icon='COPYDOWN')
        op1.text_to_copy = s_center

        row2 = box_info.row()
        row2.label(text=s_tip1)
        op2 = row2.operator(AIR_LANDING_OT_copy_text.bl_idname, text="", icon='COPYDOWN')
        op2.text_to_copy = s_tip1

        row3 = box_info.row()
        row3.label(text=s_tip2)
        op3 = row3.operator(AIR_LANDING_OT_copy_text.bl_idname, text="", icon='COPYDOWN')
        op3.text_to_copy = s_tip2

        # 一括コピーボタン
        row_all = box_info.row()
        op_all = row_all.operator(AIR_LANDING_OT_copy_text.bl_idname, text="一括コピー", icon='COPYDOWN')
        op_all.text_to_copy = all_text

        col.separator()

        # 方程式表示ブロック
        slope = math.tan(math.radians(props.angle))
        eq_str = f"Z = {-slope:.1f} * X  (X <= 0, Y = 0)"
        
        box_eq = layout.box()
        box_eq.label(text="軌道の方程式:")
        row_eq = box_eq.row()
        row_eq.label(text=eq_str)
        op = row_eq.operator(AIR_LANDING_OT_copy_text.bl_idname, text="", icon='COPYDOWN')
        op.text_to_copy = eq_str

        # 見た目調整ブロック
        box_style = layout.box()
        box_style.label(text="見た目の調整", icon='COLOR')
        
        r_c = box_style.row()
        r_c.prop(props, "cyl_radius")
        r_c.prop(props, "cyl_color", text="")
        
        r_s = box_style.row()
        r_s.prop(props, "sph_radius")
        r_s.prop(props, "sph_color", text="")

        r_l = box_style.row()
        r_l.prop(props, "line_color", text="")

# =========================================================
# 登録・解除
# =========================================================
classes = (
    AIR_LANDING_Properties,
    AIR_LANDING_OT_copy_text,
    AIR_LANDING_OT_create_scene,
    AIR_LANDING_PT_main_panel,
)

def apply_startup_view_settings():
    for window in bpy.context.window_manager.windows:
        for area in window.screen.areas:
            if area.type == 'VIEW_3D':
                for space in area.spaces:
                    if space.type == 'VIEW_3D':
                        space.show_region_ui = True
                        space.shading.type = 'MATERIAL'

def register():
    for cls in classes:
        bpy.utils.register_class(cls)
    bpy.types.Scene.air_landing_props = bpy.props.PointerProperty(type=AIR_LANDING_Properties)
    bpy.app.timers.register(apply_startup_view_settings, first_interval=0.1)

def unregister():
    for cls in reversed(classes):
        bpy.utils.unregister_class(cls)
    del bpy.types.Scene.air_landing_props

if __name__ == "__main__":
    register()
bl_info = {
    "name": "Airplane Landing Visualizer",
    "author": "ChatGPT",
    "version": (1, 5, 0),
    "blender": (5, 0, 0),
    "location": "View3D > N Panel > 3D View",
    "description": "線分の太さ調整および原点からの距離・角度情報の1行表示&一括コピー機能",
    "warning": "",
    "doc_url": "",
    "category": "3D View",
}

import bpy
import math
import numpy as np

# 作成日 2026年07月21日
# ===================================
# 4. 共通定義
# ===================================
ADDON_TITLE = "Airplane Landing Visualizer"
PREFIX_VAL = "AIR_LANDING"
ADDON_CATEGORY = "3D View"
PREFIX = PREFIX_VAL.lower()

# =========================================================
# 6. 初期設定
# =========================================================

START_X_DEFAULT = -100.0             
LANDING_ANGLE_DEFAULT = 30.0         

RADIUS_CYLINDER_DEFAULT = 0.5        
RADIUS_SPHERE_DEFAULT = 1.5          
RADIUS_TARGET_DEFAULT = 2.0          
LENGTH_LINE_DEFAULT = 2.0            
THICKNESS_LINE_DEFAULT = 0.1         

LINE_PITCH_DEFAULT = 0.0             

COLOR_GLIDE_PATH = (0.0, 0.5, 1.0, 0.3)  
COLOR_AIRPLANE = (1.0, 0.0, 0.0, 1.0)    
COLOR_TARGET = (0.0, 1.0, 0.0, 1.0)      
COLOR_LINE = (1.0, 1.0, 0.0, 1.0)

# =========================================================
# 処理ロジック・コールバック
# =========================================================
def create_material(name, color):
    mat = bpy.data.materials.get(name)
    if mat is None:
        mat = bpy.data.materials.new(name=name)
    mat.use_nodes = True
    bsdf = mat.node_tree.nodes.get("Principled BSDF")
    if bsdf:
        bsdf.inputs["Base Color"].default_value = color
        bsdf.inputs["Alpha"].default_value = color[3]
    return mat

def update_airplane_and_line_pos(self, context):
    sphere = bpy.data.objects.get(f"{PREFIX}_Airplane")
    line = bpy.data.objects.get(f"{PREFIX}_Line")
    
    if sphere and line:
        x = self.current_x
        angle_rad = math.radians(self.angle)
        z = -x * math.tan(angle_rad)
        
        sphere.location = (x, 0, z)
        line.location = (x, 0, z)
        
        pitch_rad = math.radians(self.line_pitch)
        line.rotation_euler = (0, math.radians(90) + pitch_rad, 0)

def update_glide_path(self, context):
    obj = bpy.data.objects.get(f"{PREFIX}_GlidePath")
    if obj:
        start_x = START_X_DEFAULT
        angle_rad = math.radians(self.angle)
        start_z = -start_x * math.tan(angle_rad)
        
        obj.location = (start_x / 2.0, 0, start_z / 2.0)
        
        length = math.sqrt(start_x**2 + start_z**2)
        radius = self.cyl_radius
        obj.scale = (radius, radius, length / 2.0)
        
        Ry = math.atan2(-start_x, -start_z)
        obj.rotation_euler = (0, Ry, 0)
        
        update_airplane_and_line_pos(self, context)

def update_cyl_scale(self, context):
    obj = bpy.data.objects.get(f"{PREFIX}_GlidePath")
    if obj:
        obj.scale[0] = self.cyl_radius
        obj.scale[1] = self.cyl_radius

def update_sph_scale(self, context):
    obj = bpy.data.objects.get(f"{PREFIX}_Airplane")
    if obj:
        r = self.sph_radius
        obj.scale = (r, r, r)

def update_line_scale(self, context):
    obj = bpy.data.objects.get(f"{PREFIX}_Line")
    if obj:
        t = self.line_thickness
        obj.scale = (t, t, self.line_length / 2.0)

def update_colors(self, context):
    mats = {
        f"{PREFIX}_GlidePath_Mat": self.cyl_color,
        f"{PREFIX}_Airplane_Mat": self.sph_color,
        f"{PREFIX}_Line_Mat": self.line_color
    }
    for mat_name, color in mats.items():
        mat = bpy.data.materials.get(mat_name)
        if mat and mat.use_nodes:
            bsdf = mat.node_tree.nodes.get("Principled BSDF")
            if bsdf:
                bsdf.inputs["Base Color"].default_value = color
                bsdf.inputs["Alpha"].default_value = color[3]

# =========================================================
# プロパティグループ
# =========================================================
class AIR_LANDING_Properties(bpy.types.PropertyGroup):
    current_x: bpy.props.FloatProperty(
        name="現在位置 X", default=START_X_DEFAULT, min=START_X_DEFAULT, max=0.0, update=update_airplane_and_line_pos
    )
    angle: bpy.props.FloatProperty(
        name="降下角度", default=LANDING_ANGLE_DEFAULT, min=1.0, max=89.0, update=update_glide_path
    )
    line_pitch: bpy.props.FloatProperty(
        name="Y軸回転 (ピッチ)", default=LINE_PITCH_DEFAULT, min=-180.0, max=180.0, update=update_airplane_and_line_pos
    )
    cyl_radius: bpy.props.FloatProperty(
        name="軌道の太さ", default=RADIUS_CYLINDER_DEFAULT, min=0.01, max=10.0, update=update_cyl_scale
    )
    sph_radius: bpy.props.FloatProperty(
        name="球体の大きさ", default=RADIUS_SPHERE_DEFAULT, min=0.1, max=10.0, update=update_sph_scale
    )
    line_length: bpy.props.FloatProperty(
        name="線分の全長", default=LENGTH_LINE_DEFAULT, min=0.1, max=20.0, update=update_line_scale
    )
    line_thickness: bpy.props.FloatProperty(
        name="線分の太さ", default=THICKNESS_LINE_DEFAULT, min=0.01, max=2.0, update=update_line_scale
    )
    cyl_color: bpy.props.FloatVectorProperty(
        name="軌道の色", subtype='COLOR', size=4, default=COLOR_GLIDE_PATH, update=update_colors, min=0.0, max=1.0
    )
    sph_color: bpy.props.FloatVectorProperty(
        name="球体の色", subtype='COLOR', size=4, default=COLOR_AIRPLANE, update=update_colors, min=0.0, max=1.0
    )
    line_color: bpy.props.FloatVectorProperty(
        name="線分の色", subtype='COLOR', size=4, default=COLOR_LINE, update=update_colors, min=0.0, max=1.0
    )

# =========================================================
# オペレーター
# =========================================================
class AIR_LANDING_OT_copy_text(bpy.types.Operator):
    bl_idname = f"{PREFIX}.copy_text"
    bl_label = "コピー"
    text_to_copy: bpy.props.StringProperty()

    def execute(self, context):
        context.window_manager.clipboard = self.text_to_copy
        self.report({'INFO'}, f"コピーしました: {self.text_to_copy}")
        return {'FINISHED'}

class AIR_LANDING_OT_create_scene(bpy.types.Operator):
    bl_idname = f"{PREFIX}.create_scene"
    bl_label = "シーンを生成・リセット"
    bl_options = {'REGISTER', 'UNDO'}

    def execute(self, context):
        bpy.ops.object.select_all(action='DESELECT')
        for obj in bpy.context.scene.objects:
            if obj.name.startswith(f"{PREFIX}_"):
                obj.select_set(True)
        bpy.ops.object.delete()

        props = context.scene.air_landing_props

        # 1. 着地点
        bpy.ops.mesh.primitive_uv_sphere_add(radius=RADIUS_TARGET_DEFAULT, align='WORLD', location=(0, 0, 0))
        target = context.active_object
        target.name = f"{PREFIX}_Target"
        target.data.materials.append(create_material(f"{PREFIX}_Target_Mat", COLOR_TARGET))

        # 2. 降下軌道
        bpy.ops.mesh.primitive_cylinder_add(radius=1.0, depth=2.0, align='WORLD', location=(0,0,0))
        cyl = context.active_object
        cyl.name = f"{PREFIX}_GlidePath"
        cyl.data.materials.append(create_material(f"{PREFIX}_GlidePath_Mat", COLOR_GLIDE_PATH))

        # 3. 飛行機 / 現在位置
        bpy.ops.mesh.primitive_uv_sphere_add(radius=1.0, align='WORLD', location=(0,0,0))
        airplane = context.active_object
        airplane.name = f"{PREFIX}_Airplane"
        airplane.data.materials.append(create_material(f"{PREFIX}_Airplane_Mat", COLOR_AIRPLANE))

        # 4. 機体姿勢(線分)
        bpy.ops.mesh.primitive_cylinder_add(radius=1.0, depth=2.0, align='WORLD', location=(0,0,0))
        line = context.active_object
        line.name = f"{PREFIX}_Line"
        line.data.materials.append(create_material(f"{PREFIX}_Line_Mat", COLOR_LINE))

        # 初期値の適用
        props.current_x = START_X_DEFAULT
        props.line_pitch = LINE_PITCH_DEFAULT
        update_glide_path(props, context)
        update_cyl_scale(props, context)
        update_sph_scale(props, context)
        update_line_scale(props, context)

        self.report({'INFO'}, "降下シミュレーションシーンを生成しました")
        return {'FINISHED'}

# =========================================================
# UI (Nパネル)
# =========================================================
class AIR_LANDING_PT_main_panel(bpy.types.Panel):
    bl_label = ADDON_TITLE
    bl_idname = f"{PREFIX}_PT_main_panel"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = ADDON_CATEGORY

    def draw(self, context):
        layout = self.layout
        props = context.scene.air_landing_props
        
        col = layout.column(align=True)
        col.operator(AIR_LANDING_OT_create_scene.bl_idname, icon='PLAY')
        col.separator()
        
        col.prop(props, "angle")
        col.prop(props, "current_x")
        col.separator()

        # 姿勢(線分)操作
        box_line = layout.box()
        box_line.label(text="機体姿勢(黄色の線分):", icon='DRIVER_ROTATIONAL_DIFFERENCE')
        box_line.prop(props, "line_pitch")
        box_line.prop(props, "line_length")
        box_line.prop(props, "line_thickness")

        col.separator()

        # --- 計算・情報表示ブロック (0,0,0からの距離・両端距離・角度) ---
        x = props.current_x
        angle_rad = math.radians(props.angle)
        z = -x * math.tan(angle_rad)
        
        # 中央(0,0,0)からの距離
        dist_center = math.sqrt(x**2 + 0.0**2 + z**2)
        
        # 中央(0,0,0)からの仰角/俯角 (水平面からの角度)
        # 垂直距離と水平距離から算出
        horiz_dist = abs(x)
        angle_from_origin = math.degrees(math.atan2(z, horiz_dist)) if horiz_dist > 0 else 90.0

        # 線分の両端座標の計算
        # 線分の中央位置: (x, 0, z)
        # 線分の方向ベクトル(Y軸回転ピッチ + X軸平行方向)
        total_pitch = math.radians(90.0 + props.line_pitch)
        half_len = props.line_length / 2.0
        
        # ローカルの方向ベクトル (回転後)
        # ピッチ角はY軸まわり。X成分 = -cos(pitch), Z成分 = sin(pitch) のような関係
        # ※正確な端点座標
        dx = half_len * math.sin(total_pitch) # ピッチ回転によるX方向のオフセット
        dz = half_len * math.cos(total_pitch) # ピッチ回転によるZ方向のオフセット
        
        tip1 = (x + dx, 0.0, z + dz)
        tip2 = (x - dx, 0.0, z - dz)
        
        dist_tip1 = math.sqrt(tip1[0]**2 + tip1[1]**2 + tip1[2]**2)
        dist_tip2 = math.sqrt(tip2[0]**2 + tip2[1]**2 + tip2[2]**2)

        # 文字列データの作成
        s_center = f"中央(0,0,0)距離: {dist_center:.3f} | 角度: {angle_from_origin:.2f}°"
        s_tip1 = f"先端1(0,0,0)距離: {dist_tip1:.3f}"
        s_tip2 = f"先端2(0,0,0)距離: {dist_tip2:.3f}"
        
        all_text = f"{s_center}\n{s_tip1}\n{s_tip2}"

        box_info = layout.box()
        box_info.label(text="位置・距離・角度情報:", icon='INFO')
        
        # 1行ずつ表示 & 各行のコピーボタン
        row1 = box_info.row()
        row1.label(text=s_center)
        op1 = row1.operator(AIR_LANDING_OT_copy_text.bl_idname, text="", icon='COPYDOWN')
        op1.text_to_copy = s_center

        row2 = box_info.row()
        row2.label(text=s_tip1)
        op2 = row2.operator(AIR_LANDING_OT_copy_text.bl_idname, text="", icon='COPYDOWN')
        op2.text_to_copy = s_tip1

        row3 = box_info.row()
        row3.label(text=s_tip2)
        op3 = row3.operator(AIR_LANDING_OT_copy_text.bl_idname, text="", icon='COPYDOWN')
        op3.text_to_copy = s_tip2

        # 一括コピーボタン
        row_all = box_info.row()
        op_all = row_all.operator(AIR_LANDING_OT_copy_text.bl_idname, text="一括コピー", icon='COPYDOWN')
        op_all.text_to_copy = all_text

        col.separator()

        # 方程式表示ブロック
        slope = math.tan(math.radians(props.angle))
        eq_str = f"Z = {-slope:.4f} * X  (X <= 0, Y = 0)"
        
        box_eq = layout.box()
        box_eq.label(text="軌道の方程式:")
        row_eq = box_eq.row()
        row_eq.label(text=eq_str)
        op = row_eq.operator(AIR_LANDING_OT_copy_text.bl_idname, text="", icon='COPYDOWN')
        op.text_to_copy = eq_str

        # 見た目調整ブロック
        box_style = layout.box()
        box_style.label(text="見た目の調整", icon='COLOR')
        
        r_c = box_style.row()
        r_c.prop(props, "cyl_radius")
        r_c.prop(props, "cyl_color", text="")
        
        r_s = box_style.row()
        r_s.prop(props, "sph_radius")
        r_s.prop(props, "sph_color", text="")

        r_l = box_style.row()
        r_l.prop(props, "line_color", text="")

# =========================================================
# 登録・解除
# =========================================================
classes = (
    AIR_LANDING_Properties,
    AIR_LANDING_OT_copy_text,
    AIR_LANDING_OT_create_scene,
    AIR_LANDING_PT_main_panel,
)

def apply_startup_view_settings():
    for window in bpy.context.window_manager.windows:
        for area in window.screen.areas:
            if area.type == 'VIEW_3D':
                for space in area.spaces:
                    if space.type == 'VIEW_3D':
                        space.show_region_ui = True
                        space.shading.type = 'MATERIAL'

def register():
    for cls in classes:
        bpy.utils.register_class(cls)
    bpy.types.Scene.air_landing_props = bpy.props.PointerProperty(type=AIR_LANDING_Properties)
    bpy.app.timers.register(apply_startup_view_settings, first_interval=0.1)

def unregister():
    for cls in reversed(classes):
        bpy.utils.unregister_class(cls)
    del bpy.types.Scene.air_landing_props

if __name__ == "__main__":
    register()
ご指摘ありがとうございます。黄色の線分(姿勢)が誤って降下線に沿ってしまっていたように見えた問題(あるいは回転軸の挙動)を修正し、「初期値は完全にX軸と平行(水平)」になるようロジックを見直しました。

また、円柱の生成時の原点(ジオメトリの中央)を利用することで、「線分の中央を軸としてY軸回転する専用スライダー」として独立・明確化しました。

運用ルールに従い、まずは初期設定ブロックを出力します。

---

## 1. 初期設定ブロック(コピー用)

機体姿勢用の角度設定変数 `LINE_PITCH_DEFAULT` を明記し、0.0度(X軸平行)を初期値としています。

```python
# =========================================================
# 6. 初期設定
# =========================================================

# 数値・座標
START_X_DEFAULT = -100.0             # 開始地点のX座標
LANDING_ANGLE_DEFAULT = 30.0         # 初期降下角度(度)

# サイズ (半径・長さ)
RADIUS_CYLINDER_DEFAULT = 0.5        # 降下軌道(円柱)の太さ
RADIUS_SPHERE_DEFAULT = 1.5          # 飛行機(現在位置・球体)の大きさ
RADIUS_TARGET_DEFAULT = 2.0          # 着地点の大きさ
LENGTH_LINE_DEFAULT = 2.0            # 線分の初期全長

# 角度
LINE_PITCH_DEFAULT = 0.0             # 線分の初期Y軸回転角度(0.0でX軸に完全平行)

# 色 (R, G, B, A)
# ※ A(アルファ)を1.0未満にすると半透明になります
COLOR_GLIDE_PATH = (0.0, 0.5, 1.0, 0.3)  # 軌道(青・半透明)
COLOR_AIRPLANE = (1.0, 0.0, 0.0, 1.0)    # 飛行機(赤)
COLOR_TARGET = (0.0, 1.0, 0.0, 1.0)      # 着地点(緑)
COLOR_LINE = (1.0, 1.0, 0.0, 1.0)        # 姿勢線分(黄)


2. アドオン本体コード

NパネルのUI上で「機体姿勢(線分)」の回転スライダーを独立した枠(ボックス)で囲み、スライダーを動かすと線分の中央を軸にしてY軸回転するように調整しています。

bl_info = {
    "name": "Airplane Landing Visualizer",
    "author": "ChatGPT",
    "version": (1, 4, 0),
    "blender": (5, 0, 0),
    "location": "View3D > N Panel > 3D View",
    "description": "線分のX軸平行化およびY軸回転(中央軸)スライダーを実装",
    "warning": "",
    "doc_url": "",
    "category": "3D View",
}

import bpy
import math

# 作成日 2026年07月21日
# ===================================
# 4. 共通定義
# ===================================
ADDON_TITLE = "Airplane Landing Visualizer"
PREFIX_VAL = "AIR_LANDING"
ADDON_CATEGORY = "3D View"
PREFIX = PREFIX_VAL.lower()

# =========================================================
# 6. 初期設定
# =========================================================

START_X_DEFAULT = -100.0             
LANDING_ANGLE_DEFAULT = 30.0         

RADIUS_CYLINDER_DEFAULT = 0.5        
RADIUS_SPHERE_DEFAULT = 1.5          
RADIUS_TARGET_DEFAULT = 2.0          
LENGTH_LINE_DEFAULT = 2.0            

LINE_PITCH_DEFAULT = 0.0             

COLOR_GLIDE_PATH = (0.0, 0.5, 1.0, 0.3)  
COLOR_AIRPLANE = (1.0, 0.0, 0.0, 1.0)    
COLOR_TARGET = (0.0, 1.0, 0.0, 1.0)      
COLOR_LINE = (1.0, 1.0, 0.0, 1.0)

# =========================================================
# 処理ロジック・コールバック
# =========================================================
def create_material(name, color):
    mat = bpy.data.materials.get(name)
    if mat is None:
        mat = bpy.data.materials.new(name=name)
    mat.use_nodes = True
    bsdf = mat.node_tree.nodes.get("Principled BSDF")
    if bsdf:
        bsdf.inputs["Base Color"].default_value = color
        bsdf.inputs["Alpha"].default_value = color[3]
    return mat

def update_airplane_and_line_pos(self, context):
    """球体と線分の位置・回転を更新する"""
    sphere = bpy.data.objects.get(f"{PREFIX}_Airplane")
    line = bpy.data.objects.get(f"{PREFIX}_Line")
    
    if sphere and line:
        x = self.current_x
        angle_rad = math.radians(self.angle)
        z = -x * math.tan(angle_rad)
        
        # 球体を軌道上に移動
        sphere.location = (x, 0, z)
        
        # 線分(極細の円柱)を球体の中央に配置
        line.location = (x, 0, z)
        
        # 【修正箇所】
        # デフォルトの円柱はZ軸方向に伸びているため、Y軸で90度(pi/2)回転させると完全にX軸と平行になります。
        # そこから、スライダーの値(line_pitch)分だけY軸周りで回転(ピッチ回転)させます。
        pitch_rad = math.radians(self.line_pitch)
        line.rotation_euler = (0, math.radians(90) + pitch_rad, 0)

def update_glide_path(self, context):
    obj = bpy.data.objects.get(f"{PREFIX}_GlidePath")
    if obj:
        start_x = START_X_DEFAULT
        angle_rad = math.radians(self.angle)
        start_z = -start_x * math.tan(angle_rad)
        
        obj.location = (start_x / 2.0, 0, start_z / 2.0)
        
        length = math.sqrt(start_x**2 + start_z**2)
        radius = self.cyl_radius
        obj.scale = (radius, radius, length / 2.0)
        
        Ry = math.atan2(-start_x, -start_z)
        obj.rotation_euler = (0, Ry, 0)
        
        update_airplane_and_line_pos(self, context)

def update_cyl_scale(self, context):
    obj = bpy.data.objects.get(f"{PREFIX}_GlidePath")
    if obj:
        obj.scale[0] = self.cyl_radius
        obj.scale[1] = self.cyl_radius

def update_sph_scale(self, context):
    obj = bpy.data.objects.get(f"{PREFIX}_Airplane")
    if obj:
        r = self.sph_radius
        obj.scale = (r, r, r)

def update_line_scale(self, context):
    obj = bpy.data.objects.get(f"{PREFIX}_Line")
    if obj:
        # デフォルト円柱(深さ2.0)に対するZ軸スケールで長さを調整
        obj.scale = (0.1, 0.1, self.line_length / 2.0)

def update_colors(self, context):
    mats = {
        f"{PREFIX}_GlidePath_Mat": self.cyl_color,
        f"{PREFIX}_Airplane_Mat": self.sph_color,
        f"{PREFIX}_Line_Mat": self.line_color
    }
    for mat_name, color in mats.items():
        mat = bpy.data.materials.get(mat_name)
        if mat and mat.use_nodes:
            bsdf = mat.node_tree.nodes.get("Principled BSDF")
            if bsdf:
                bsdf.inputs["Base Color"].default_value = color
                bsdf.inputs["Alpha"].default_value = color[3]

# =========================================================
# プロパティグループ
# =========================================================
class AIR_LANDING_Properties(bpy.types.PropertyGroup):
    current_x: bpy.props.FloatProperty(
        name="現在位置 X", default=START_X_DEFAULT, min=START_X_DEFAULT, max=0.0, update=update_airplane_and_line_pos
    )
    angle: bpy.props.FloatProperty(
        name="降下角度", default=LANDING_ANGLE_DEFAULT, min=1.0, max=89.0, update=update_glide_path
    )
    line_pitch: bpy.props.FloatProperty(
        name="Y軸回転 (ピッチ)", default=LINE_PITCH_DEFAULT, min=-180.0, max=180.0, update=update_airplane_and_line_pos
    )
    cyl_radius: bpy.props.FloatProperty(
        name="軌道の太さ", default=RADIUS_CYLINDER_DEFAULT, min=0.01, max=10.0, update=update_cyl_scale
    )
    sph_radius: bpy.props.FloatProperty(
        name="球体の大きさ", default=RADIUS_SPHERE_DEFAULT, min=0.1, max=10.0, update=update_sph_scale
    )
    line_length: bpy.props.FloatProperty(
        name="線分の全長", default=LENGTH_LINE_DEFAULT, min=0.1, max=20.0, update=update_line_scale
    )
    cyl_color: bpy.props.FloatVectorProperty(
        name="軌道の色", subtype='COLOR', size=4, default=COLOR_GLIDE_PATH, update=update_colors, min=0.0, max=1.0
    )
    sph_color: bpy.props.FloatVectorProperty(
        name="球体の色", subtype='COLOR', size=4, default=COLOR_AIRPLANE, update=update_colors, min=0.0, max=1.0
    )
    line_color: bpy.props.FloatVectorProperty(
        name="線分の色", subtype='COLOR', size=4, default=COLOR_LINE, update=update_colors, min=0.0, max=1.0
    )

# =========================================================
# オペレーター
# =========================================================
class AIR_LANDING_OT_copy_equation(bpy.types.Operator):
    bl_idname = f"{PREFIX}.copy_equation"
    bl_label = "コピー"
    equation_text: bpy.props.StringProperty()

    def execute(self, context):
        context.window_manager.clipboard = self.equation_text
        self.report({'INFO'}, f"コピー完了: {self.equation_text}")
        return {'FINISHED'}

class AIR_LANDING_OT_create_scene(bpy.types.Operator):
    bl_idname = f"{PREFIX}.create_scene"
    bl_label = "シーンを生成・リセット"
    bl_options = {'REGISTER', 'UNDO'}

    def execute(self, context):
        # 既存オブジェクトの削除
        bpy.ops.object.select_all(action='DESELECT')
        for obj in bpy.context.scene.objects:
            if obj.name.startswith(f"{PREFIX}_"):
                obj.select_set(True)
        bpy.ops.object.delete()

        props = context.scene.air_landing_props

        # 1. 着地点
        bpy.ops.mesh.primitive_uv_sphere_add(radius=RADIUS_TARGET_DEFAULT, align='WORLD', location=(0, 0, 0))
        target = context.active_object
        target.name = f"{PREFIX}_Target"
        target.data.materials.append(create_material(f"{PREFIX}_Target_Mat", COLOR_TARGET))

        # 2. 降下軌道
        bpy.ops.mesh.primitive_cylinder_add(radius=1.0, depth=2.0, align='WORLD', location=(0,0,0))
        cyl = context.active_object
        cyl.name = f"{PREFIX}_GlidePath"
        cyl.data.materials.append(create_material(f"{PREFIX}_GlidePath_Mat", COLOR_GLIDE_PATH))

        # 3. 飛行機 / 現在位置
        bpy.ops.mesh.primitive_uv_sphere_add(radius=1.0, align='WORLD', location=(0,0,0))
        airplane = context.active_object
        airplane.name = f"{PREFIX}_Airplane"
        airplane.data.materials.append(create_material(f"{PREFIX}_Airplane_Mat", COLOR_AIRPLANE))

        # 4. 機体姿勢(線分:原点は中央のまま生成される)
        bpy.ops.mesh.primitive_cylinder_add(radius=1.0, depth=2.0, align='WORLD', location=(0,0,0))
        line = context.active_object
        line.name = f"{PREFIX}_Line"
        line.data.materials.append(create_material(f"{PREFIX}_Line_Mat", COLOR_LINE))

        # 初期値の適用とUIの連動更新
        props.current_x = START_X_DEFAULT
        props.line_pitch = LINE_PITCH_DEFAULT
        update_glide_path(props, context)
        update_cyl_scale(props, context)
        update_sph_scale(props, context)
        update_line_scale(props, context)

        self.report({'INFO'}, "降下シミュレーションシーンを生成しました")
        return {'FINISHED'}

# =========================================================
# UI (Nパネル)
# =========================================================
class AIR_LANDING_PT_main_panel(bpy.types.Panel):
    bl_label = ADDON_TITLE
    bl_idname = f"{PREFIX}_PT_main_panel"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = ADDON_CATEGORY

    def draw(self, context):
        layout = self.layout
        props = context.scene.air_landing_props
        
        col = layout.column(align=True)
        col.operator(AIR_LANDING_OT_create_scene.bl_idname, icon='PLAY')
        col.separator()
        
        # 全体の動きに関する設定
        col.prop(props, "angle")
        col.prop(props, "current_x")
        col.separator()

        # 【追加】姿勢(線分)専用の回転操作ブロック
        box_line = layout.box()
        box_line.label(text="機体姿勢(黄色の線分):", icon='DRIVER_ROTATIONAL_DIFFERENCE')
        box_line.prop(props, "line_pitch")

        col.separator()

        # 方程式表示ブロック
        slope = math.tan(math.radians(props.angle))
        eq_str = f"Z = {-slope:.4f} * X  (X <= 0, Y = 0)"
        
        box_eq = layout.box()
        box_eq.label(text="軌道の方程式:")
        row_eq = box_eq.row()
        row_eq.label(text=eq_str)
        op = row_eq.operator(AIR_LANDING_OT_copy_equation.bl_idname, text="", icon='COPYDOWN')
        op.equation_text = eq_str

        # 見た目調整ブロック
        box_style = layout.box()
        box_style.label(text="見た目の調整", icon='COLOR')
        
        row1 = box_style.row()
        row1.prop(props, "cyl_radius")
        row1.prop(props, "cyl_color", text="")
        
        row2 = box_style.row()
        row2.prop(props, "sph_radius")
        row2.prop(props, "sph_color", text="")

        row3 = box_style.row()
        row3.prop(props, "line_length")
        row3.prop(props, "line_color", text="")

# =========================================================
# 登録・解除
# =========================================================
classes = (
    AIR_LANDING_Properties,
    AIR_LANDING_OT_copy_equation,
    AIR_LANDING_OT_create_scene,
    AIR_LANDING_PT_main_panel,
)

def apply_startup_view_settings():
    for window in bpy.context.window_manager.windows:
        for area in window.screen.areas:
            if area.type == 'VIEW_3D':
                for space in area.spaces:
                    if space.type == 'VIEW_3D':
                        space.show_region_ui = True
                        space.shading.type = 'MATERIAL'

def register():
    for cls in classes:
        bpy.utils.register_class(cls)
    bpy.types.Scene.air_landing_props = bpy.props.PointerProperty(type=AIR_LANDING_Properties)
    bpy.app.timers.register(apply_startup_view_settings, first_interval=0.1)

def unregister():
    for cls in reversed(classes):
        bpy.utils.unregister_class(cls)
    del bpy.types.Scene.air_landing_props

if __name__ == "__main__":
    register()