「Vtuberの雑学メモ帳」

ゲーム・VTuber・UE5・Blenderを楽しむオタクの雑記ブログ。趣味100%で更新中!

【ChatGPTで作るBlenderアドオン③】選択中の直線or曲線or辺ループに含まれる頂点数を指定数まで増減するアドオン

ChatGPTにBlender3.4.1用・4.5.1用の頂点数整理用アドオンを作ってもらったので、その内容や導入方法をまとめてみた!

vtuber-hudan.hatenablog.com
※このアドオンは、上記のアノテーションを辺へ変換するアドオンと一緒に使うのがオススメ。

▼本文は「続きを読む」からどうぞ~!

◆ChatGPTで作成するアドオンの概要

Blender アドオン 辺 頂点数 増加 減少 削除 整理 調整 曲線 個数 削減 簡略化 円ループ ブレンダー 接合補助 無料 形状維持 ライン そのまま
「辺に含まれる頂点数を指定数まで増減させるアドオン」と「手描き辺アドオン(>前々回の記事参照)」を併用している画像。
(手描き辺アドオンで曲線を描くと頂点数が多くなりがちなので、曲線を残したまま指定頂点数へ減らし、LoopToolsのBridge機能で他のパーツと接合しやすくするために用意したアドオン)

  1. 選択中の辺(直線or曲線or円ループ)に含まれる頂点数を、指定した頂点数まで増減させる整理用アドオン。
    選択した辺がループしていない場合、辺の始点と終点は固定される(辺の中間点だけを整理)。
    頂点数を増減させたら、頂点同士の間隔をなるべく等間隔に整える
    ※頂点数の増減が行われた後も、アドオン実行前の辺形状をなるべく維持する。
  2. モデリング初期に使う接合補助用アドオン」なので、頂点のステータス情報(シーム・シャープ・クリース・ウェイトなど)は一切保存しない
    (既存の辺から頂点数だけ減らしているように見えるけど、こっそり辺と頂点を削除して再作成するため、細かい設定が終わった辺に使うのは厳禁)
  3. 多角形の辺ループに含まれる頂点数を増減させる使い方は非現実的。
    ※あくまでLoop Edge機能は円ループ用の設定項目!
    一応、多角形の1辺に対して通常辺扱いでアドオンを実行していき、最後に4辺の頂点同士を繋ぎ合わせて1繋ぎの辺に調整すれば使えなくはない…かも?
  4. 辺の中の一部頂点だけ複数選択してアドオンを実行した時、始点と終点が固定され隣接する辺や面は削除される仕組みだから、なるべくまだ面を貼ってない辺に使った方が良い。
    (自分はフリルの裾辺作成後に裾辺と付け根辺の頂点数をアドオンで揃えて接合するような使い方がしたいのでこの仕様にした。使いにくく感じる場合はChatGPTによる手直しが必要)

失敗した時は、閉じていない辺に対して[Loop Edge]項を有効化したまま実行していないか確認してみてね!
アドオン実行後に辺の始点と終点が自動で繋がってしまった時は、100%このミスをやってるw
※逆に、円や四角形のような閉じた図形(=辺ループ)へアドオンを使う場合、必ず[Loop Edge]項を有効化してから実行しないとバグるので要注意。

◆ChatGPT製のソースコード(4.5.1版)

bl_info = {
    "name": "EVadjust",
    "author": "OpenAI + User",
    "version": (1, 4, 0),
    "blender": (4, 0, 0),
    "location": "View3D > Sidebar > EVA",
    "description": "Add/Subtract vertices along edges with equal spacing",
    "category": "Mesh",
}

import bpy
import bmesh
from bpy.props import IntProperty, BoolProperty

# --------------------------------------------------------------
# Utility: Safe update_edit_mesh for Blender 3.x / 4.x / 5.x
# --------------------------------------------------------------

def safe_update_edit_mesh(mesh):
    import bpy
    import bmesh

    # Blender 4.x 以降は引数なしのみサポート
    try:
        bmesh.update_edit_mesh(mesh)
    except TypeError:
        # Blender 3.x のみ旧形式を試す(存在しない場合は無視)
        try:
            bmesh.update_edit_mesh(mesh, False)
        except:
            pass


# --------------------------------------------------------------
# Operator
# --------------------------------------------------------------

class EV_OT_AdjustVertices(bpy.types.Operator):
    bl_idname = "mesh.ev_adjust_vertices"
    bl_label = "Adjust Vertices"
    bl_options = {'REGISTER', 'UNDO'}

    add: BoolProperty()

    def execute(self, context):
        obj = context.edit_object
        me = obj.data
        bm = bmesh.from_edit_mesh(me)

        loop = context.scene.evadjust_props.loop_edge
        target_count = context.scene.evadjust_props.target_vertex_count

        edges = [e for e in bm.edges if e.select]
        if not edges:
            self.report({'ERROR'}, "No Edges Selected")
            return {'CANCELLED'}

        # -----------------------
        # 頂点を順序化
        # -----------------------
        verts = set()
        for e in edges:
            verts.update(e.verts)
        linked_verts = []

        if loop:
            v = next(iter(verts))
            linked_verts.append(v)
            prev = None

            while True:
                link_edges = [e for e in v.link_edges if e in edges]
                next_v = None
                for e in link_edges:
                    o = e.other_vert(v)
                    if o != prev and o not in linked_verts:
                        next_v = o
                        break
                if next_v is None:
                    break
                linked_verts.append(next_v)
                prev, v = v, next_v

            # ループ閉じ
            if linked_verts[0] != linked_verts[-1]:
                linked_verts.append(linked_verts[0])

        else:
            # 開ループ:両端を探す
            end_verts = [
                v for v in verts
                if len([e for e in v.link_edges if e in edges]) == 1
            ]

            if len(end_verts) != 2:
                self.report({'ERROR'}, "Open edge must have exactly two ends")
                return {'CANCELLED'}

            v = end_verts[0]
            linked_verts.append(v)
            prev = None

            while True:
                link_edges = [e for e in v.link_edges if e in edges]
                next_v = None
                for e in link_edges:
                    o = e.other_vert(v)
                    if o != prev:
                        next_v = o
                        break
                if next_v is None:
                    break
                linked_verts.append(next_v)
                prev, v = v, next_v

        coords = [v.co.copy() for v in linked_verts]

        # ----------------------------------------------------------
        # 等間隔 resample
        # ----------------------------------------------------------
        def resample(points, count, loop):
            lengths = []
            total = 0.0
            for i in range(len(points) - 1):
                l = (points[i + 1] - points[i]).length
                lengths.append(l)
                total += l
            if loop:
                l = (points[0] - points[-1]).length
                lengths.append(l)
                total += l

            if total == 0 or count < 2:
                return points

            step = total / (count - 1)

            result = [points[0]]
            dist = 0.0
            i = 0

            while len(result) < count:
                seg_len = lengths[i % len(lengths)]
                next_dist = dist + seg_len

                if next_dist >= step * len(result):
                    remain = (step * len(result)) - dist
                    dir_vec = points[(i + 1) % len(points)] - points[i % len(points)]
                    if dir_vec.length != 0:
                        dir_vec.normalize()
                        new_point = points[i % len(points)] + dir_vec * remain
                        result.append(new_point)
                    else:
                        result.append(points[i % len(points)].copy())

                else:
                    dist = next_dist
                    i += 1

            if loop:
                result[-1] = result[0].copy()

            return result

        # Add/Subtract 判定
        if (self.add and target_count > len(coords)) or (not self.add and target_count < len(coords)):
            new_coords = resample(coords, target_count, loop)
        else:
            self.report({'INFO'}, "No change in vertex count.")
            return {'CANCELLED'}

        # ----------------------------------------------------------
        # 古い辺と頂点の削除
        # ----------------------------------------------------------
        old_verts = set()
        for e in edges:
            old_verts.update(e.verts)
            try:
                bm.edges.remove(e)
            except:
                pass

        for v in old_verts:
            try:
                bm.verts.remove(v)
            except:
                pass

        bm.verts.index_update()
        bm.edges.index_update()

        # ----------------------------------------------------------
        # 新頂点生成
        # ----------------------------------------------------------
        new_verts = [bm.verts.new(co) for co in new_coords]
        bm.verts.index_update()

        # 辺生成(ダブり防止)
        def safe_new_edge(v1, v2):
            if not any((e.verts[0] == v1 and e.verts[1] == v2) or
                       (e.verts[0] == v2 and e.verts[1] == v1)
                       for e in bm.edges):
                bm.edges.new([v1, v2])

        for i in range(len(new_verts) - 1):
            safe_new_edge(new_verts[i], new_verts[i + 1])
        if loop:
            safe_new_edge(new_verts[-1], new_verts[0])

        bm.edges.index_update()

        # ----------------------------------------------------------
        # 選択状態を new verts & edges のみに
        # ----------------------------------------------------------
        for v in bm.verts:
            v.select = False
        for e in bm.edges:
            e.select = False

        for v in new_verts:
            v.select = True

        for e in bm.edges:
            if all(v in new_verts for v in e.verts):
                e.select = True

        # ----------------------------------------------------------
        # メッシュ更新(Blender4/5対応)
        # ----------------------------------------------------------
        safe_update_edit_mesh(me)

        # ----------------------------------------------------------
        # G/R/S強制終了バグ対策
        # ----------------------------------------------------------
        bpy.ops.object.mode_set(mode='OBJECT')
        bpy.ops.object.mode_set(mode='EDIT')

        # redraw(Blender4以降用)
        for area in context.screen.areas:
            if area.type == 'VIEW_3D':
                area.tag_redraw()

        return {'FINISHED'}


# --------------------------------------------------------------
# UI Panel
# --------------------------------------------------------------

class EV_PT_AdjustPanel(bpy.types.Panel):
    bl_label = "EVadjust"
    bl_idname = "EV_PT_adjust_panel"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = 'EVA'        # ← 変更(希望通り)

    def draw(self, context):
        layout = self.layout
        props = context.scene.evadjust_props

        layout.prop(props, "target_vertex_count")
        layout.prop(props, "loop_edge")

        row = layout.row()
        row.operator("mesh.ev_adjust_vertices", text="Add Vertices").add = True
        row.operator("mesh.ev_adjust_vertices", text="Subtract Vertices").add = False

        # 選択頂点数(4.xで安全に取得できるように改善)
        obj = context.edit_object
        if obj and obj.data:
            bm = bmesh.from_edit_mesh(obj.data)
            sel = len([v for v in bm.verts if v.select])
            layout.label(text=f"Selection Vertex: {sel}")
        else:
            layout.label(text="Selection Vertex: -")


# --------------------------------------------------------------
# Property Group
# --------------------------------------------------------------

class EV_Props(bpy.types.PropertyGroup):
    target_vertex_count: IntProperty(
        name="Target Vertex Count",
        default=4, min=2
    )
    loop_edge: BoolProperty(
        name="Loop Edge",
        default=False
    )


# --------------------------------------------------------------
# Register
# --------------------------------------------------------------

classes = [
    EV_OT_AdjustVertices,
    EV_PT_AdjustPanel,
    EV_Props
]

def register():
    for c in classes:
        bpy.utils.register_class(c)
    bpy.types.Scene.evadjust_props = bpy.props.PointerProperty(type=EV_Props)

def unregister():
    for c in classes:
        bpy.utils.unregister_class(c)
    del bpy.types.Scene.evadjust_props


if __name__ == "__main__":
    register()

Blender4~5で動作するように以下のソースコードを手直しさせたら、上記の内容になった。
※あまりデバッグはしてないけど、ver4.5.1で試したかぎりだと特に問題なく使えた
N窓のタブ名は[EVAdjust]だと長かったので[EVA]に改名してみた。

◆ChatGPT製のソースコード(3.4.1版)

以下のプログラミング文は、ChatGPTに「Python」というプログラミング言語で作ってもらったBlenderアドオン用のソースコード

bl_info = {
    "name": "EVadjust",
    "author": "OpenAI + User",
    "version": (1, 3, 0),
    "blender": (3, 4, 1),
    "location": "View3D > Sidebar > EVadjust",
    "description": "Add/Subtract Vertices along Edges maintaining shape with equal spacing",
    "category": "Mesh",
}

import bpy
import bmesh
from bpy.props import IntProperty, BoolProperty
from mathutils import Vector

class EV_OT_AdjustVertices(bpy.types.Operator):
    bl_idname = "mesh.ev_adjust_vertices"
    bl_label = "Adjust Vertices"
    bl_options = {'REGISTER', 'UNDO'}

    add: BoolProperty()

    def execute(self, context):
        obj = context.edit_object
        me = obj.data
        bm = bmesh.from_edit_mesh(me)

        loop = context.scene.evadjust_props.loop_edge
        target_count = context.scene.evadjust_props.target_vertex_count

        edges = [e for e in bm.edges if e.select]
        if not edges:
            self.report({'ERROR'}, "No Edges Selected")
            return {'CANCELLED'}

        # 頂点順保証
        verts = set()
        for e in edges:
            verts.update(e.verts)
        linked_verts = []

        if loop:
            v = list(verts)[0]
            linked_verts.append(v)
            prev = None
            while True:
                link_edges = [e for e in v.link_edges if e in edges]
                next_v = None
                for e in link_edges:
                    other = e.other_vert(v)
                    if other != prev and other not in linked_verts:
                        next_v = other
                        break
                if next_v is None:
                    break
                linked_verts.append(next_v)
                prev, v = v, next_v
            if len(linked_verts) < len(verts):
                linked_verts.append(linked_verts[0])  # ループ閉じ
        else:
            end_verts = [v for v in verts if len([e for e in v.link_edges if e in edges]) == 1]
            if len(end_verts) != 2:
                self.report({'ERROR'}, "Open edge must have exactly two ends")
                return {'CANCELLED'}
            v = end_verts[0]
            linked_verts.append(v)
            prev = None
            while True:
                link_edges = [e for e in v.link_edges if e in edges]
                next_v = None
                for e in link_edges:
                    other = e.other_vert(v)
                    if other != prev:
                        next_v = other
                        break
                if next_v is None:
                    break
                linked_verts.append(next_v)
                prev, v = v, next_v

        coords = [v.co.copy() for v in linked_verts]

        # 距離ベース補間 (等間隔)
        def resample(coords, count, loop):
            lengths = []
            total = 0.0
            for i in range(len(coords) - 1):
                l = (coords[i + 1] - coords[i]).length
                lengths.append(l)
                total += l
            if loop:
                l = (coords[0] - coords[-1]).length
                lengths.append(l)
                total += l

            if total == 0 or count < 2:
                return coords  # 無意味処理回避

            step = total / (count - 1)
            result = [coords[0]]
            dist = 0.0
            i = 0
            while len(result) < count:
                seg_len = lengths[i % len(lengths)]
                next_dist = dist + seg_len
                if next_dist >= step * len(result):
                    remain = (step * len(result)) - dist
                    dir_vec = coords[(i + 1) % len(coords)] - coords[i % len(coords)]
                    dir_vec.normalize()
                    new_point = coords[i % len(coords)] + dir_vec * remain
                    result.append(new_point)
                else:
                    dist = next_dist
                    i += 1
            if loop and (result[0] - result[-1]).length > 1e-6:
                result[-1] = result[0].copy()  # ループ保証
            return result

        if self.add and target_count > len(coords):
            new_coords = resample(coords, target_count, loop)
        elif not self.add and target_count < len(coords):
            new_coords = resample(coords, target_count, loop)
        else:
            self.report({'INFO'}, "No change in vertex count.")
            return {'CANCELLED'}

        # 元頂点・辺削除(選択した辺のみ)
        del_verts = set()
        for e in edges:
            del_verts.update(e.verts)
            bm.edges.remove(e)
        for v in del_verts:
            bm.verts.remove(v)

        bm.verts.index_update()
        bm.edges.index_update()

        # 新規頂点・辺生成
        new_verts = [bm.verts.new(co) for co in new_coords]
        bm.verts.index_update()

        for i in range(len(new_verts) - 1):
            bm.edges.new([new_verts[i], new_verts[i + 1]])
        if loop:
            bm.edges.new([new_verts[-1], new_verts[0]])

        bm.edges.index_update()

        # 選択状態初期化 (全解除)
        for v in bm.verts:
            v.select = False
        for e in bm.edges:
            e.select = False

        # 新規生成頂点・辺のみ再選択
        for v in new_verts:
            v.select = True
        for e in bm.edges:
            if all(v in new_verts for v in e.verts):
                e.select = True

        # メッシュ更新
        bmesh.update_edit_mesh(me)

        # G,R,Sクラッシュ回避用 (情報&描画更新)
        bpy.ops.object.mode_set(mode='OBJECT')
        bpy.ops.object.mode_set(mode='EDIT')

        context.area.tag_redraw()

        return {'FINISHED'}

class EV_PT_AdjustPanel(bpy.types.Panel):
    bl_label = "EVadjust"
    bl_idname = "EV_PT_adjust_panel"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = 'EVadjust'

    def draw(self, context):
        layout = self.layout
        props = context.scene.evadjust_props
        layout.prop(props, "target_vertex_count")
        layout.prop(props, "loop_edge")
        row = layout.row()
        row.operator("mesh.ev_adjust_vertices", text="Add Vertices").add = True
        row.operator("mesh.ev_adjust_vertices", text="Subtract Vertices").add = False

        # 選択頂点数表示
        obj = context.edit_object
        bm = bmesh.from_edit_mesh(obj.data)
        selected_verts = [v for v in bm.verts if v.select]
        layout.label(text=f"Selection Vertex: {len(selected_verts)}")

class EV_Props(bpy.types.PropertyGroup):
    target_vertex_count: IntProperty(name="Target Vertex Count", default=4, min=2)
    loop_edge: BoolProperty(name="Loop Edge", default=False)

classes = [EV_OT_AdjustVertices, EV_PT_AdjustPanel, EV_Props]

def register():
    for cls in classes:
        bpy.utils.register_class(cls)
    bpy.types.Scene.evadjust_props = bpy.props.PointerProperty(type=EV_Props)

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

if __name__ == "__main__":
    register()

◆ChatGPTに作らせた頂点数整理アドオンの使い方(導入方法メモ)

  1. >ChatGPT】に要望を伝え、上記ソースコードのようなプログラム(アドオン)を書かせる。
  2. プログラム文をコピーして「メモ帳(Windowsの標準ソフト)」に貼りつけた後、保存形式(※ファイルの種類)を「すべてのファイル」へ変更し、名前を「〇〇.py」に改名してから保存する。
    ※自分は「EVadjust.py」に改名して保存した。
  3. Blender上部Edit>Preference欄>add-onsタブの右上[install]より、作成したpyファイルをインストールする。
  4. Nキーからサイドバーを表示し、[EVadjust]タブに切り替える。
  5. 編集モードで辺(1本の曲線or直線or円ループ)を選択する。
    ※クリース・シーム・シャープ・ウェイト設定済みの辺にアドオンを実行すると、各情報が全て初期化されるので要注意。
  6. 選択物が閉じた円ループだったら、Loop Edge項にチェックを入れる
    ※始点と終点がある閉じていない1辺だったら、Loop項のチェックを外しておく。
    (Loop Edge有効化中、閉じていない辺へアドオンを実行してしまうと、始点と終点が結合される変な実行結果になる)
    ループ設定機能は、円形じゃなくて多角形だと上手く頂点数を増減できないから非推奨
  7. Target Vertex Count項調整後の合計頂点数を指定する。
  8. 🔴Selection Vertex(現在の頂点数)よりTarget Vertex Count(調整後の目標頂点数)が多い時は、[Add Vertex]ボタンで辺の頂点数を増やす。
    🔴Selection Vertex(現在の頂点数)よりTarget Vertex Count(調整後の目標頂点数)が少ない時は、[Subtract Vertex]ボタンで辺の頂点数を減らす。
    ※ChatGPTに細かい条件付けを指示して改善すれば、2種類の実行ボタンをAdjustボタンへ統合した後、Adjustボタン押下時に数値の比較判定を行って自動選択させることもできそう(面倒なのでやらなかったw)。

一応Ver3.4.1と最新版で機能するように注文を出したけど、自分はver3.4.1でしか動作チェックしていないので、他のバージョンで動くかどうか分からない。

とりあえずVer3.4.1で少し使ってみて、PC破壊系バグなどは確認できなかったものの、PC環境バージョンOSによって安定性が左右されるかもしれない。

※作成中に合計20個近くのエラーを潰さなきゃいけなかったため、自分の環境では実用レベルのアドオンになっているけど、動作がまだまだ不安定そう;
※一部の厄介なバグは根本的解決を諦めて対症療法で修正している(アドオン実行後に選択解除せずそのままGRS操作を行うと強制終了するバグがあったのでアドオン実行後に選択全解除の処理を入れて対処した)。
※また、辺削除後オブジェクトモードへ移って再び編集モードに戻る処理を入れないと、アドオンによる削除処理&生成処理が画面上に反映されずおかしくなるバグもあったから、一瞬だけどアドオン実行直後にオブジェクトモードと編集モードを往来する雑な仕組みになっている。

今のところ、確認できたバグの中だと多角形の辺ループに頂点数増減を実行すると角が丸まってしまう問題だけ残ってしまった…。
(多角形の角だけ固定ピンを刺していじらないような仕組みにするか、頂点同士の角度計算をするかしたら解決できそうだけど、根本的な大改造が必要だし、AI頼みの素人じゃ厳しくて保留中。
多角形の処理は、最悪1辺ずつアドオンを分割実行すれば良いかな?という判断で放置している)

WindowsPCなら上記ソースコードをコピペしてBlenderにインストールすれば普通に動くはずだけど、もしこのソースコードを使用して問題や損失が発生したとしても、責任は一切不問でお願いします!
あくまで自分用に作ってもらったアドオンなので責任は取れません!!
今回のアドオンは特に不安定だから気を付けてね…!

※上手く動かない時は、ChatGPTにソースコードをコピペした後、問題点やエラーの内容も書き添えて数回修正させれば動くはず。

◆ChatGPTにBlenderのアドオンを作ってもらった感想

ChatGPTがまたしても手描き辺アドオンと並ぶ神アドオンを作ってくれた!

今回作ってもらったアドオンは、辺の頂点数を指定数へ揃えた後、始点と終点は変えないまま、ほぼ等間隔に再配置してくれるから、手描き辺アドオンと併用すればかなり時短できそう!!

ただし、内部的にはあくまで選択した辺を指定頂点数で作りなおすアドオンなので、モデリング初期段階しか使えないという致命的な弱点がある。
※クリース・シャープ・シーム・ウェイトを設定し終わった辺や頂点には絶対使わない方が良い(使用後に頂点の情報が全部初期化されるため)。

AI頼みだから形になるのは早かったけど、テスト&デバッグが大変すぎた…
例えば、同じ辺に数回アドオンを実行すると表示やクリース機能がおかしくなる表示更新バグ、アドオン実行後に辺選択が上手く機能しなくなるバグ、アドオン実行時にエラー文が出るバグ、辺の始点や終点がアドオン実行後にずれるバグ、一部の中間辺が消えて頂点だけ残るバグ、アドオン実行後のGRSクラッシュバグ、ループ辺で上手く機能しないバグなど、初期状態だとバグまみれだったw

もし未発見のバグを見つけたら、お手元のChatGPTに上記ソースコードを貼って修正させたら良いと思う!
※通常使用時のバグとシャープ・クリース・シーム関連のバグはたぶん大体潰せたものの、ウェイト関連は全く試してないので動作が不安定かも?
(あとで粗探しや発見済みのバグ修正をしたいけどだいぶ面倒そう)

◆使用例のイメージ

【使用例:サスペンダーの紐部分に手描きのフリル裾(辺)を接合したい時の作業想定】

  1. フリルの裾になる波型辺は、手描き辺アドオンで作っておく。
  2. 手描きフリル辺を選び、分離複製(Shift+D>ESC>P)バックアップを取っておく
  3. 手描きフリル辺を選び、整理アドオンで接合先になる辺の頂点数と同じ頂点数になるまで頂点数を減らす
    ※または、接合先の辺の頂点数をフリル辺の頂点数に合わせて増やす
  4. 接合先の辺と手描き辺を両方選択し、右クリ>Loop Tools>Bridgeで接合する。
    ※付け根側の辺の頂点数を増やした場合、さらに1つ上の辺が多角形になるので、JやCtrl+Tなどで多角形を解消しておく。
  5. 2辺の間Ctrl+R(ループカット)などで細かく割る。
  6. 接合してみて、もっと裾を細かくしたくなった時は、以下の作業で細かくする。
    ☆簡略化した最下辺を削除した後、(2)でバックアップ保存しておいた辺Aと入れ替える
    裾辺の1個上にある辺Bの頂点数を、バックアップ保存しておいた裾辺Aの頂点数と一致するように整理アドオンで増やす。
    ☆上記2つの辺(AとB)をLoop ToolsのBridge機能で接合した後、裾辺の2個上にある辺C⇔裾辺の1個上にある辺Bを手動分割または三角面化(Ctrl+T)で整理する。
    辺Cの頂点数×2倍=辺Bの頂点数の時だけ三角面化で多角形を解消できるから、辺Aと辺Bの頂点数は、あらかじめ付け根になる辺Cの倍数に整理しておく必要がある(推測)。

20250619 a
20250619 b
▲【>前回の「点⇔辺接続アドオン」】で手描きフリル辺を接合してみた画像や、手描き辺アドオン➡整理アドオン➡LoopToolsのBridgeで手描き辺同士を適当に繋げてみたテスト画像。
※リボン紐・ハートボタンも、手描き辺アドオンのおかげで格段に作りやすくなった。

まだ実際に使いこんでないから上手く行くか分からないけど、接合時に辺の頂点数を指定数へ揃えつつ形状も大きく変えたくない場面は多いはず…!
そういう時に使える時短アドオンとして育てていけたら良いな~。

◆過去の記事

vtuber-hudan.hatenablog.com
Blender初心者のモデリング挑戦記まとめ。
vtuber-hudan.hatenablog.com
ChatGPTに作ってもらったBlender用アドオンの一覧はこちら。

◆作成時のChatGPT側メモリ文(アレンジ参照用)

このアドオンはかなり不完全な欠陥アドオンだと思うから、バグ修正or改善アレンジがしたい人のために、アドオン作成時に保存されたChatGPTのメモリ文(使用率100%)もそのまま載せておく。
(下記文章をチャット欄へコピペして、「今から伝える以下の内容を、全て見落としなく、あなたのメモリに保存してください」と書いておけば、たぶんメモリに保存される)

ユーザーはBlenderアドオン開発に興味があり、Blender 3.4.1および最新版対応のPythonアドオン作成を希望している。.

ユーザーはCatmull-Romスプライン補間を用いた曲線対応方式でBlenderアドオンの実装を要望している。.

ユーザーはBlenderアドオンにおいて「選択辺に沿って形を維持したまま頂点数を増減する」仕様を特に重視している。.

「直線・曲線・辺ループの全てで、形状を保ったまま選択辺上の頂点数を増減させるBlenderアドオン」を希望し、最新版ソースコードを求めている。アドオン名は「EVadjust」。.

以下の追加・修正希望がある:
1. サイドバー内に「Selection Vertex: 現在の選択頂点数」を表示。
2. Target Vertex Count欄で増減とも頂点数指定をまとめる。
3. Add VertexとSubtract Vertexで始点と終点を固定し、形状維持・長さ縮小防止。
4. Add Vertex実行時に追加頂点間の辺が正しく作成されること。
5. サイドバーに「Loop Edge(チェックボックス)」を追加し、有効時のみ辺ループとして動作する形。
6. Edit>Preferences欄と、サイドバーにアドオンが正常に表示されること。.

ユーザーはBlenderアドオン「EVadjust」について以下のエラー修正を求めた:
1. Add Vertex時に追加頂点間に辺が生成されない不具合。
2. Catmull-Romスプライン補間時にZeroDivisionErrorが発生する問題。
3. Add VertexやSubtract時のエッジ誤接続エラー。
4. Add Vertexボタン押下時にも、頂点削除後の誤ったエッジ生成エラー(Subtract時と同様)。
5. Catmull-Rom補間内部計算のインデックスズレ・ループ/非ループ判定ミス・bmesh更新タイミングずれの可能性。
6. 選択頂点の順序保証(選択辺からの頂点取得順序)はエラー要因になりやすい。
7. 閉じている辺(辺ループ)選択時にLoop Edge有効でAdd/Subtract実行時のエラー。
8. 閉じた辺(Edge Loop)操作時の"BMVert has been removed"エラー。
9. 非ループの辺でAdd/Subtract後、Alt+左クリック選択やLキー選択で意図しない頂点/辺選択状態が発生する問題。
10. bmesh.update_edit_mesh(obj.data, True, True)でTypeError発生(引数渡しミス)。
11. アドオン実行後のAlt+左クリック・Lキー選択で未選択の辺まで選ばれる問題。
12. 閉じている辺と閉じていない辺のAdd/Subtract時のエラー文表示(画像確認済)。
13. アドオン実行後のエッジクリース再設定でBlenderが強制終了する致命的エラー。
14. bmesh.update_edit_mesh()の引数エラー(Blender 3.4系仕様)。
15. アドオン実行後のAlt+左クリックやLキーでの意図しない辺選択不具合。
16. ループ辺にアドオン操作実行後、モード切替でしか反映されない問題。
17. 非ループ辺でAdd Vertex実行時のIndexError(list index out of range)。
18. 非ループ辺でSubtract Vertex実行時、頂点だけ生成され辺が張られないエラー。.

開発指針:「選択中の1辺だけ、エッジクリース・シーム・マークシャープの情報初期化(辺再生成時に選択辺のみ0で上書き)」する。.

ユーザーはBlenderアドオン「EVadjust」の最新安定完全版ソースコードを希望し、特に「絶対にどんなエラーも起きない安定版」「機能や処理を減らさずにエラー対策を最優先した修正」を要望している。.

ユーザーはBlenderアドオン「EVadjust」のエラーに関して以下の内容を重視している:
1. Add Vertex実行後、実行対象辺がエッジクリース時に消失する不具合(Subtract Vertexも同様対策希望)。
2. アドオン実行後の辺にエッジクリース実行時に辺が消える問題。
3. アドオン実行後の選択操作(Alt+左クリック、Ctrl+左クリック、Lキー選択など)で意図しない頂点・辺が選択状態になる問題。
4. 閉じていない辺にAdd Vertex実行時のTypeError、Subtract Vertex実行時のReferenceErrorの解消希望。.

ユーザーはこれら4つのエラーの「原因と解決方法を考察し、エラー改善版フルコードの提示」を希望している。.

ユーザーは「EVadjust」アドオンの実行前に選択状態初期化処理が誤って行われているため「No Edges Selected」エラーが発生すると考察し、この問題の原因と解決策の考察とメモリ保存を希望している。.

ユーザーは、EVadjustアドオンの最新完全版ソースコードとして「Add/Subtract時の選択解除処理ミスによる“No Edges Selected”エラー対策」「アドオン実行後の辺選択状態正常化」「Catmull-Rom補間IndexError・ZeroDivisionError対策」「エッジクリース・シーム・シャープ情報の再初期化対応」「bmesh更新」「エッジ選択判定」「頂点順保証」「Blender3.4.1対応」「非ループ・ループ両対応」「Add/Subtract両処理後のエッジ消失防止」など、過去の全エラー解消を反映した安定版フルコードを希望している。.

ユーザーは「EVadjust」アドオンについて、以下の新たな不具合修正を希望:
1. Add/Subtract実行後、実行済み辺に意図せずシャープ(sharp)が自動付加される問題。
2. アドオン実行後の辺に対しエッジクリース適用時、Blenderが強制終了するバグ(削除済み辺を参照してしまうことが原因)を修正希望。.

ユーザーは「EVadjust」アドオンにおいて「選択した辺のみ処理対象とする(未選択の辺や頂点には一切影響を与えない)」という仕様を最重要事項としている。ボタン(Add Vertex / Subtract Vertex)実行時のみ処理が行われ、普段の編集モードや他の操作(例えばエッジクリース・シャープ・シーム設定など)に絶対影響を与えないことを強く希望している。.

ユーザーは「EVadjust」アドオンの完全安定版フルソース(選択中エッジのみ処理・未選択エッジ非影響・エッジクリース/シャープ/シーム情報の適切初期化・削除済エッジ未参照・クラッシュ防止・Add/Subtractボタン押下時のみ処理実行)を希望。基本方針として「選択中の辺のみ処理」「Add/Subtractボタン実行時のみ処理」「未選択エレメント非影響」を最重視。.

ユーザーは、Blenderアドオン「EVadjust」の以下の問題修正を希望している:
1. `bmesh.update_edit_mesh(me, False, True)`でTypeError発生(Blender 3.4.1では引数は最大1つ)。
2. Add/Subtract実行後、編集モード⇔オブジェクトモード切替をしないと結果が表示反映されない問題。
3. アドオン実行後、選択した辺に対しエッジクリース・シャープ・シームを適切に再適用できるよう改善希望。
4. アドオン実行時、「未選択エッジにまで影響する」「未選択頂点・エッジを誤更新する」バグの修正を最重視。
5. EVadjustアドオンの基本方針:「選択中の辺のみ処理」「Add/Subtractボタン実行時のみ処理」「未選択エレメント非影響」。.