From 18fbc71d17b4e51660cc77861f7969042ffae0d8 Mon Sep 17 00:00:00 2001 From: hc-psy Date: Mon, 29 May 2023 00:23:43 +0800 Subject: [PATCH] [feature] ui and chatgpt openai complete --- blendergpt-zh/__init__.py | 89 +-------- blendergpt-zh/gpt_gpt.py | 120 ++++++++++++ blendergpt-zh/gpt_opt.py | 165 +++++++++++++++++ blendergpt-zh/gpt_pkg.py | 55 ++++++ blendergpt-zh/gpt_pnl.py | 202 +++++++++++++++++++++ blendergpt-zh/{gptzh_prf.py => gpt_prf.py} | 10 +- blendergpt-zh/gptzh_opt.py | 120 ------------ blendergpt-zh/gptzh_pnl.py | 61 ------- 8 files changed, 554 insertions(+), 268 deletions(-) create mode 100644 blendergpt-zh/gpt_gpt.py create mode 100644 blendergpt-zh/gpt_opt.py create mode 100644 blendergpt-zh/gpt_pkg.py create mode 100644 blendergpt-zh/gpt_pnl.py rename blendergpt-zh/{gptzh_prf.py => gpt_prf.py} (66%) delete mode 100644 blendergpt-zh/gptzh_opt.py delete mode 100644 blendergpt-zh/gptzh_pnl.py diff --git a/blendergpt-zh/__init__.py b/blendergpt-zh/__init__.py index 57ecf2e..e2d23ba 100644 --- a/blendergpt-zh/__init__.py +++ b/blendergpt-zh/__init__.py @@ -1,110 +1,35 @@ import bpy -from .gptzh_pnl import BLENDERGPT_PT_PANEL -from .gptzh_prf import BLENDERGPT_AddonPreferences -from .gptzh_opt import BLENDERGPT_OT_DEL_ALL_MSG, BLENDERGPT_OT_DEL_MSG, BLENDERGPT_OT_GPT_CODE, BLENDERGPT_OT_SEND_MSG +from .gpt_pkg import * +from .gpt_pnl import BLENDERGPT_PT_PANEL, props_initialization, props_clear +from .gpt_prf import BLENDERGPT_AddonPreferences +from .gpt_opt import BLENDERGPT_OT_DEL_ALL_MSG, BLENDERGPT_OT_DEL_MSG, BLENDERGPT_OT_GPT_CODE, BLENDERGPT_OT_SEND_MSG bl_info = { - "name": "Blender GPT (ZH) 中文用戶專屬", + "name": "Blender GPT", "author": "Ryvn (@hc-psy) (@@hao-chenglo2049)", "description": "", "blender": (2, 82, 0), "version": (0, 0, 1), - "location": "3D View (三維視圖) > UI (使用者介面) > BlenderGptZH", "warning": "", "category": "Object" } -system_prompt = """You are an assistant made for the purposes of helping the user with Blender, the 3D software. -- Respond with your answers in markdown (```). -- Preferably import entire modules instead of bits. -- Do not perform destructive operations on the meshes. -- Do not use cap_ends. Do not do more than what is asked (setting up render settings, adding cameras, etc) -- Do not respond with anything that is not Python code. - -Example: - -user: create 10 cubes in random locations from -10 to 10 -assistant: -``` -import bpy -import random -bpy.ops.mesh.primitive_cube_add() - -#how many cubes you want to add -count = 10 - -for c in range(0,count): - x = random.randint(-10,10) - y = random.randint(-10,10) - z = random.randint(-10,10) - bpy.ops.mesh.primitive_cube_add(location=(x,y,z)) -```""" - Classes = (BLENDERGPT_PT_PANEL, BLENDERGPT_OT_DEL_ALL_MSG, BLENDERGPT_OT_DEL_MSG, BLENDERGPT_OT_GPT_CODE, BLENDERGPT_OT_SEND_MSG, BLENDERGPT_AddonPreferences) -def init_props(): - bpy.types.Scene.history = bpy.props.CollectionProperty( - type=bpy.types.PropertyGroup) - - bpy.types.Scene.model = bpy.props.EnumProperty( - name="GPT模型", - description="請選擇欲使用的Chat-GPT模型", - items=[ - ("gpt3.5", "GPT-3.5 (便宜但較容易出錯)", "使用 GPT-3.5 (便宜但較容易出錯)"), - ("gpt4", "GPT-4 (昂貴但較詳細準確)", "使用 GPT-4 (昂貴但較詳細準確)"), - ], - default="gpt3.5", - ) - - bpy.types.Scene.lan = bpy.props.EnumProperty( - name="語言", - description="請選擇Chat-GPT所回饋的語言", - items=[ - ("traditional", "繁體中文", "繁體中文"), - ("simplified", "简体中文", "简体中文"), - ("english", "English", "英文"), - ], - default="traditional", - ) - - bpy.types.Scene.prompt_input = bpy.props.StringProperty( - name="指令", - description="請輸入你的指令", - default="", - ) - - bpy.types.Scene.on_finish = bpy.props.BoolProperty(default=False) - bpy.types.PropertyGroup.type = bpy.props.StringProperty() - bpy.types.PropertyGroup.content = bpy.props.StringProperty() - - -def get_api_key(context, addon_name): - preferences = context.preferences - addon_prefs = preferences.addons[addon_name].preferences - return addon_prefs.api_key - - -def clear_props(): - del bpy.types.Scene.history - del bpy.types.Scene.model - del bpy.types.Scene.prompt_input - del bpy.types.Scene.on_finish - - def register(): for cls in Classes: bpy.utils.register_class(cls) - init_props() + props_initialization() def unregister(): for cls in Classes: bpy.utils.unregister_class(cls) - clear_props() + props_clear() diff --git a/blendergpt-zh/gpt_gpt.py b/blendergpt-zh/gpt_gpt.py new file mode 100644 index 0000000..024f0a8 --- /dev/null +++ b/blendergpt-zh/gpt_gpt.py @@ -0,0 +1,120 @@ +import openai +import re + + +def SYS_MAIN_PROMPT(language): return f""" +I want you to act as a professional 3D artist who is proficient in writing scripts in Blender, the 3D software. +Here are some rules you have to heed and follow: +- Respond with your answers in markdown (```). +- Preferably import entire modules instead of bits. +- Do not perform destructive operations on the meshes. +- Do not use cap_ends. Do not do more than what is asked (setting up render settings, adding cameras, etc) +- Do not respond with anything that is not Python code. +- Please write comments in {language}. +""" + + +EX_1_USER = """create 10 cubes in random locations from -1 to 1""" + +EX_1_ASSISTANT = """``` +import bpy +import random +bpy.ops.mesh.primitive_cube_add() + +count = 10 + +for _ in range(count): + x = random.randint(-1,1) + y = random.randint(-1,1) + z = random.randint(-1,1) + bpy.ops.mesh.primitive_cube_add(location=(x,y,z)) +```""" + +EX_2_USER = """delete all mesh objects in the scene and create a 5x5x5 ball in the scence""" + +EX_2_ASSISTANT = """``` +import bpy + +bpy.ops.object.select_all(action='DESELECT') +bpy.ops.object.select_by_type(type='MESH') +bpy.ops.object.delete() + +bpy.ops.mesh.primitive_uv_sphere_add(location=(0, 0, 0), radius=2.5) +```""" + + +def post_process(final_txt): + final_txt = re.findall( + r'```(.*?)```', final_txt, re.DOTALL)[0] + + final_txt = re.sub( + r'^python', '', final_txt, flags=re.MULTILINE) + + return final_txt + + +def chatgpt(context): + scene = context.scene + lan = int(scene.lan) + + languages = ['traditional chinese', 'simplified chinese', 'english'] + models = [scene.model_0, scene.model_1, scene.model_2] + prompts = [scene.prompt_input_0, + scene.prompt_input_1, scene.prompt_input_2] + temperatures = [scene.t_0, + scene.t_1, scene.t_2] + + # sys data preparation + messages = [{"role": "system", "content": SYS_MAIN_PROMPT(languages[lan])}] + messages.append( + {"role": "system", "name": "example_user", "content": EX_1_USER}) + messages.append( + {"role": "system", "name": "example_assistant", "content": EX_1_ASSISTANT}) + messages.append( + {"role": "system", "name": "example_user", "content": EX_2_USER}) + messages.append( + {"role": "system", "name": "example_assistant", "content": EX_2_ASSISTANT}) + + # add previous messages + for msg in scene.history[-8:]: + if msg.type == "GPT": + messages.append( + {"role": "assistant", "content": "```\n" + msg.content + "\n```"}) + else: + messages.append({"role": "user", + "content": msg.content}) + + if messages[-1]["role"] != "user": + # add the current user message + messages.append({"role": "user", "content": "Please provide me with Blender (3D software) code regarding the following task: " + + prompts[lan] + ". \n. Do not respond with anything that is not Python code. Do not provide explanations. " + f"Wite code comment in {languages[lan]}."}) + + response = openai.ChatCompletion.create( + model=models[lan], + messages=messages, + temperature=temperatures[lan], + stream=True, + max_tokens=2000, + ) + + try: + events = [] + final_txt = '' + + # becuase stream = true so use delta to concatentate + for e in response: + if len(e['choices'][0]['delta']) == 0: + continue + + if 'role' in e['choices'][0]['delta']: + continue + + events.append(e) + event_text = e['choices'][0]['delta']['content'] + final_txt += event_text + print(final_txt, flush=True, end='\r') + + return post_process(final_txt) + + except IndexError: + return None diff --git a/blendergpt-zh/gpt_opt.py b/blendergpt-zh/gpt_opt.py new file mode 100644 index 0000000..6a1b553 --- /dev/null +++ b/blendergpt-zh/gpt_opt.py @@ -0,0 +1,165 @@ +import bpy + +from bpy.types import Operator +import openai + +from .gpt_gpt import chatgpt + + +class BLENDERGPT_OT_DEL_MSG(Operator): + bl_idname = "gpt.del_msg" + bl_label = "delete message" + bl_description = "delete message" + bl_options = {"REGISTER", "UNDO"} + + msg_idx: bpy.props.IntProperty(name="訊息索引", default=0) + + def execute(self, context): + scene = context.scene + history = scene.history + history.remove(self.msg_idx) + return {"FINISHED"} + + +class BLENDERGPT_OT_DEL_ALL_MSG(Operator): + bl_idname = "gpt.del_all_msg" + bl_label = "delete all messages" + bl_description = "delete all messages" + bl_options = {"REGISTER", "UNDO"} + + def execute(self, context): + scene = context.scene + history = scene.history + history.clear() + return {"FINISHED"} + + +class BLENDERGPT_OT_GPT_CODE(Operator): + bl_idname = "gpt.gpt_code" + bl_label = "show GPT code" + bl_description = "show GPT code" + bl_options = {"REGISTER", "UNDO"} + + code: bpy.props.StringProperty( + name="GPT Code", description="GPT Code", default="") + + def execute(self, context): + + # text area + if int(context.scene.lan) == 0: + txt_name = '指令腳本.py' + elif int(context.scene.lan) == 1: + txt_name = '指令脚本.py' + else: + txt_name = 'prompt_script.py' + + txt = bpy.data.texts.get(txt_name) + + if txt is None: + txt = bpy.data.texts.new(txt_name) + + txt.clear() + txt.write(self.code) + + txt_edit_area = None + for area in bpy.context.screen.areas: + if area.type == 'TEXT_EDITOR': + txt_edit_area = area + break + + if txt_edit_area is None: + cxt_area = context.area + for region in cxt_area.regions: + if region.type == 'WINDOW': + bpy.ops.screen.area_split( + {'area': cxt_area, 'region': region}, direction='VERTICAL', factor=0.5) + break + + new_area = context.screen.areas[-1] + new_area.type = 'TEXT_EDITOR' + txt_edit_area = new_area + + txt_edit_area.spaces.active.text = txt + + return {"FINISHED"} + + +class BLENDERGPT_OT_SEND_MSG(Operator): + bl_idname = "gpt.send_msg" + bl_label = "send message" + bl_description = "send message" + bl_options = {"REGISTER", "UNDO"} + + def execute(self, context): + scene = context.scene + + # TODO: connect to GPT + prf = context.preferences + openai.api_key = prf.addons["blendergpt-zh"].preferences.openai_key + + if not openai.api_key: + if int(context.scene.lan) == 0: + self.report( + {'ERROR'}, "錯誤: 沒有偵測到 OPENAI API Key,請在插件設定中設定 OPENAI API Key") + elif int(context.scene.lan) == 1: + self.report( + {'ERROR'}, "错误: 没有检测到 OPENAI API Key,请在插件设置中设置 OPENAI API Key") + else: + self.report( + {'ERROR'}, "Error: No OPENAI API Key detected, please set OPENAI API Key in the add-on preferences") + return {'CANCELLED'} + + scene.on_finish = True + # bpy.ops.wm.redraw_timer(type='DRAW_WIN_SWAP', iterations=1) + + if len(scene.history) == 0 or scene.history[-1].type == 'GPT': + if int(context.scene.lan) == 0: + if scene.prompt_input_0 == "": + self.report({'ERROR'}, f"錯誤: 請輸入指令") + scene.on_finish = False + return {'CANCELLED'} + + msg = scene.history.add() + msg.type = 'USER' + msg.content = scene.prompt_input_0 + elif int(context.scene.lan) == 1: + if scene.prompt_input_1 == "": + self.report({'ERROR'}, f"错误: 请输入指令") + scene.on_finish = False + return {'CANCELLED'} + + msg = scene.history.add() + msg.type = 'USER' + msg.content = scene.prompt_input_1 + else: + if scene.prompt_input_2 == "": + self.report({'ERROR'}, f"Error: Please enter the prompt") + scene.on_finish = False + return {'CANCELLED'} + + msg = scene.history.add() + msg.type = 'USER' + msg.content = scene.prompt_input_2 + + code_exe_blender = chatgpt(context) + + scene.prompt_input_0 = "" + scene.prompt_input_1 = "" + scene.prompt_input_2 = "" + + if code_exe_blender: + msg = scene.history.add() + msg.type = 'GPT' + msg.content = code_exe_blender + + global_namespace = globals().copy() + + try: + exec(code_exe_blender, global_namespace) + except Exception as e: + self.report({'ERROR'}, f"Error: {e}") + scene.on_finish = False + return {'CANCELLED'} + + scene.on_finish = False + return {"FINISHED"} diff --git a/blendergpt-zh/gpt_pkg.py b/blendergpt-zh/gpt_pkg.py new file mode 100644 index 0000000..fc5ba47 --- /dev/null +++ b/blendergpt-zh/gpt_pkg.py @@ -0,0 +1,55 @@ +import sys +import subprocess +import os +import platform +import bpy + + +def isWindows(): + return os.name == 'nt' + + +def isMacOS(): + return os.name == 'posix' and platform.system() == "Darwin" + + +def isLinux(): + return os.name == 'posix' and platform.system() == "Linux" + + +def python_exec(): + + if isWindows(): + return os.path.join(sys.prefix, 'bin', 'python.exe') + elif isMacOS(): + + try: + # 2.92 and older + path = bpy.app.binary_path_python + except AttributeError: + # 2.93 and later + import sys + path = sys.executable + return os.path.abspath(path) + elif isLinux(): + return os.path.join(sys.prefix, 'sys.prefix/bin', 'python') + else: + print("sorry, still not implemented for ", + os.name, " - ", platform.system) + + +def installModule(packageName): + + try: + subprocess.call([python_exe, "import ", packageName]) + except: + python_exe = python_exec() + # upgrade pip + subprocess.call([python_exe, "-m", "ensurepip"]) + subprocess.call( + [python_exe, "-m", "pip", "install", "--upgrade", "pip"]) + # install required packages + subprocess.call([python_exe, "-m", "pip", "install", packageName]) + + +installModule('openai') diff --git a/blendergpt-zh/gpt_pnl.py b/blendergpt-zh/gpt_pnl.py new file mode 100644 index 0000000..afd21c8 --- /dev/null +++ b/blendergpt-zh/gpt_pnl.py @@ -0,0 +1,202 @@ +import bpy + +from bpy.types import Panel + + +UI_lan = { + 'language': ['繁體中文', '简体中文', 'English'], + 'label_language': ['語言', '语言', 'Language'], + 'label_model': ['Chat-GPT 模型', 'Chat-GPT 模型', 'Chat-GPT Model'], + 'label_model_description': ['請選擇欲使用的Chat-GPT模型', '请选择要使用的Chat-GPT模型', 'Please select the Chat-GPT model'], + 'model_options': { + 'gpt3.5': ['GPT-3.5 (便宜但較容易出錯)', 'GPT-3.5 (便宜但較容易出错)', 'GPT-3.5 (Affordable but less accurate)'], + 'gpt4': ['GPT-4 (昂貴但較詳細準確)', 'GPT-4 (昂贵但较详细准确)', 'GPT-4 (Expensive but more accurate)'], + }, + 'label_history': ['對話歷史紀錄', '对话历史纪录', 'Chat History'], + 'label_show_code': ['顯示程式碼', '显示代码', 'Show Code'], + 'label_user': ['指令>', '指令>', 'Prompt>'], + 'button_send': ['請稍候,模型正在編寫腳本...', '请稍候,模型正在编写脚本...', 'Please wait, the model is writing the script...'], + 'button_submit': ['送出指令', '提交指令', 'Submit Prompt'], + 'button_regenerate': ['重新生成', '重新生成', 'Regenerate Response'], + 'command': ['指令', '指令', 'Prompt'], + 'command_instruction': ['請輸入指令', '请输入指令', 'Please enter the command'], + 'button_delete_all': ['刪除所有對話', '删除所有对话', 'Delete History'], + 'button_delete': ['刪除此回答', '删除此回答', 'Delete This Response'], + 'creativity': ['創意度', '创意度', 'Creativity'], +} + + +class BLENDERGPT_PT_PANEL(Panel): + bl_label = 'Blender GPT' + bl_idname = 'GPT_PT_PANEL' + bl_space_type = 'VIEW_3D' + bl_region_type = 'UI' + bl_category = 'Blender GPT' + + def draw(self, context): + layout = self.layout + + lan_idx = int(context.scene.lan) + + column = layout.column(align=True) + + # language usage + row = column.row(align=True) + row.label(text=UI_lan['label_language'][lan_idx]) + row.prop(context.scene, "lan", text="") + + column.separator() + + # model of chat gpt + column.label(text=UI_lan['label_model'][lan_idx]) + if lan_idx == 0: + column.prop(context.scene, "model_0", text="") + elif lan_idx == 1: + column.prop(context.scene, "model_1", text="") + else: + column.prop(context.scene, "model_2", text="") + + column.separator() + + # creativity + column.label(text=UI_lan['creativity'][lan_idx]) + if lan_idx == 0: + column.prop(context.scene, "t_0", text="") + elif lan_idx == 1: + column.prop(context.scene, "t_1", text="") + else: + column.prop(context.scene, "t_2", text="") + + column.separator() + # history of chat + if len(context.scene.history) > 0: + column.label(text=UI_lan['label_history'][lan_idx]) + box = column.box() + for index, message in enumerate(context.scene.history): + if message.type == 'GPT': + row = box.row() + row.label(text="GPT>") + + code_op = row.operator( + "gpt.gpt_code", text="", icon="TEXT", emboss=False) + code_op.code = message.content + + if index == len(context.scene.history) - 1: + del_msg_op = row.operator( + 'gpt.del_msg', text="", icon='TRASH', emboss=False) + del_msg_op.msg_idx = index + + else: + row = box.row() + row.label( + text=f"{UI_lan['label_user'][lan_idx]}{message.content}") + + if index == len(context.scene.history) - 2: + del_msg_op = row.operator( + 'gpt.del_msg', text="", icon='TRASH', emboss=False) + del_msg_op.msg_idx = index + + column.separator() + + # input of chat + if len(context.scene.history) == 0 or (len(context.scene.history) > 0 and context.scene.history[-1].type != 'USER'): + column.label(text=UI_lan['command'][lan_idx]) + if lan_idx == 0: + column.prop(context.scene, "prompt_input_0", text="") + elif lan_idx == 1: + column.prop(context.scene, "prompt_input_1", text="") + else: + column.prop(context.scene, "prompt_input_2", text="") + + # send message + if len(context.scene.history) > 0 and context.scene.history[-1].type == 'USER': + button_label = UI_lan['button_send'][lan_idx] if context.scene.on_finish else UI_lan['button_regenerate'][lan_idx] + else: + button_label = UI_lan['button_send'][lan_idx] if context.scene.on_finish else UI_lan['button_submit'][lan_idx] + + column.operator("gpt.send_msg", text=button_label, icon="PLAY") + + column.separator() + column.operator("gpt.del_all_msg", + text=UI_lan['button_delete_all'][lan_idx], icon="TRASH") + + +def model_props_generator(idx): + return bpy.props.EnumProperty( + name=UI_lan['label_model'][idx], + description=UI_lan['label_model_description'][idx], + items=[ + ("gpt-3.5-turbo", UI_lan['model_options']['gpt3.5'] + [idx], UI_lan['model_options']['gpt3.5'][idx]), + ("gpt-4", UI_lan['model_options']['gpt4'] + [idx], UI_lan['model_options']['gpt4'][idx]), + ], + default="gpt-3.5-turbo", + ) + + +def prompt_input_generator(idx): + return bpy.props.StringProperty( + name=UI_lan['command'][idx], + description=UI_lan['command_instruction'][idx], + default="", + ) + + +def temperature_generator(idx): + return bpy.props.FloatProperty( + name=UI_lan['creativity'][idx], + description=UI_lan['creativity'][idx], + default=0, + min=0, + max=1, + ) + + +def props_initialization(): + + bpy.types.Scene.history = bpy.props.CollectionProperty( + type=bpy.types.PropertyGroup) + + bpy.types.Scene.lan = bpy.props.EnumProperty( + name="語言", + description="請選擇語言", + items=[ + ("0", "繁體中文", "繁體中文"), + ("1", "简体中文", "简体中文"), + ("2", "English", "英文"), + ], + default="0", + ) + + bpy.types.Scene.model_0 = model_props_generator(0) + bpy.types.Scene.model_1 = model_props_generator(1) + bpy.types.Scene.model_2 = model_props_generator(2) + + bpy.types.Scene.prompt_input_0 = prompt_input_generator(0) + bpy.types.Scene.prompt_input_1 = prompt_input_generator(1) + bpy.types.Scene.prompt_input_2 = prompt_input_generator(2) + + bpy.types.Scene.t_0 = temperature_generator(0) + bpy.types.Scene.t_1 = temperature_generator(1) + bpy.types.Scene.t_2 = temperature_generator(2) + + bpy.types.Scene.on_finish = bpy.props.BoolProperty(default=False) + + bpy.types.PropertyGroup.type = bpy.props.StringProperty() + bpy.types.PropertyGroup.content = bpy.props.StringProperty() + + +def props_clear(): + del bpy.types.Scene.history + del bpy.types.Scene.lan + del bpy.types.Scene.model_0 + del bpy.types.Scene.model_1 + del bpy.types.Scene.model_2 + del bpy.types.Scene.prompt_input_0 + del bpy.types.Scene.prompt_input_1 + del bpy.types.Scene.prompt_input_2 + del bpy.types.Scene.t_0 + del bpy.types.Scene.t_1 + del bpy.types.Scene.t_2 + del bpy.types.Scene.on_finish diff --git a/blendergpt-zh/gptzh_prf.py b/blendergpt-zh/gpt_prf.py similarity index 66% rename from blendergpt-zh/gptzh_prf.py rename to blendergpt-zh/gpt_prf.py index 77ffef0..8062e03 100644 --- a/blendergpt-zh/gptzh_prf.py +++ b/blendergpt-zh/gpt_prf.py @@ -3,10 +3,10 @@ from bpy.types import AddonPreferences class BLENDERGPT_AddonPreferences(AddonPreferences): - bl_idname = __name__ - print(__name__) - api_key: props.StringProperty( - name="API Key", + bl_idname = "blendergpt-zh" + + openai_key: props.StringProperty( + name="OPENAI API Key", description="Enter your OpenAI API Key", default="", subtype="PASSWORD", @@ -14,4 +14,4 @@ class BLENDERGPT_AddonPreferences(AddonPreferences): def draw(self, context): layout = self.layout - layout.prop(self, "api_key") + layout.prop(self, "openai_key") diff --git a/blendergpt-zh/gptzh_opt.py b/blendergpt-zh/gptzh_opt.py deleted file mode 100644 index 6a2487f..0000000 --- a/blendergpt-zh/gptzh_opt.py +++ /dev/null @@ -1,120 +0,0 @@ -import bpy - -from bpy.types import Operator - - -class BLENDERGPT_OT_DEL_MSG(Operator): - bl_idname = "gpt.del_msg" - bl_label = "刪除訊息" - bl_description = "刪除訊息" - bl_options = {"REGISTER", "UNDO"} - - msg_idx: bpy.props.IntProperty(name="訊息索引", default=0) - - def execute(self, context): - scene = context.scene - history = scene.history - history.remove(self.msg_idx) - return {"FINISHED"} - - -class BLENDERGPT_OT_DEL_ALL_MSG(Operator): - bl_idname = "gpt.del_all_msg" - bl_label = "刪除所有訊息" - bl_description = "刪除所有訊息" - bl_options = {"REGISTER", "UNDO"} - - def execute(self, context): - scene = context.scene - history = scene.history - history.clear() - return {"FINISHED"} - - -class BLENDERGPT_OT_GPT_CODE(Operator): - bl_idname = "gpt.gpt_code" - bl_label = "展示GPT程式碼" - bl_description = "展示GPT程式碼" - bl_options = {"REGISTER", "UNDO"} - - code: bpy.props.StringProperty( - name="GPT程式碼", description="GPT所產生的程式碼", default="") - - def execute(self, context): - # text area - txt_name = '指令腳本.py' - txt = bpy.data.texts.get(txt_name) - - if txt is None: - txt = bpy.data.texts.new(txt_name) - - txt.clear() - txt.write(self.code) - - txt_edit_area = None - for area in bpy.context.screen.areas: - if area.type == 'TEXT_EDITOR': - txt_edit_area = area - break - - cxt_area = context.area - for region in cxt_area.regions: - if region.type == 'WINDOW': - override = {'area': cxt_area, 'region': region} - bpy.ops.screen.area_split( - override, direction='VERTICAL', factor=0.5) - break - - new_area = context.screen.areas[-1] - new_area.type = 'TEXT_EDITOR' - - if txt_edit_area is None: - txt_edit_area = new_area - - txt_edit_area.spaces.active.text = txt - - return {"FINISHED"} - - -class BLENDERGPT_OT_SEND_MSG(Operator): - bl_idname = "gpt.send_msg" - bl_label = "送出訊息" - bl_description = "送出訊息" - bl_options = {"REGISTER", "UNDO"} - - prompt_input: bpy.props.StringProperty( - name="指令", description="指令", default="") - - def execute(self, context): - # TODO: connect to GPT - - scene = context.scene - - scene.on_finish = True - bpy.ops.wm.redraw_timer(type='DRAW_WIN_SWAP', iterations=1) - - blender_code = "print('Hello World')" # TODO: get from GPT - - msg = scene.history.add() - msg.type = 'USER' - msg.content = scene.prompt_input - - # clear prompt input - scene.prompt_input = "" - - if blender_code: - msg = scene.history.add() - msg.type = 'GPT' - msg.content = blender_code - - global_namespace = globals().copy() - - try: - exec(blender_code, global_namespace) - except Exception as e: - self.report({'ERROR'}, f"Error: {e}") - scene.on_finish = False - return {'CANCELLED'} - - scene.on_finish = False - return {"FINISHED"} diff --git a/blendergpt-zh/gptzh_pnl.py b/blendergpt-zh/gptzh_pnl.py deleted file mode 100644 index ea22f57..0000000 --- a/blendergpt-zh/gptzh_pnl.py +++ /dev/null @@ -1,61 +0,0 @@ -import bpy - -from bpy.types import Panel - - -class BLENDERGPT_PT_PANEL(Panel): - bl_label = 'Blender GPT ZH' - bl_idname = 'GPT_PT_PANEL' - bl_space_type = 'VIEW_3D' - bl_region_type = 'UI' - bl_category = 'Blender GPT ZH' - - def draw(self, context): - layout = self.layout - - column = layout.column(align=True) - - # language usage - row = column.row(align=True) - row.label(text="回饋語言:") - row.prop(context.scene, "lan", text="") - - column.separator() - - # history of chat - column.label(text="對話歷史紀錄:") - box = column.box() - for index, message in enumerate(context.scene.history): - if message.type == 'GPT': - row = box.row() - row.label(text="GPT: ") - show_code_op = row.operator( - "gpt.gpt_code", text="展示程式碼", icon="TEXT") - show_code_op.code = message.content - delete_message_op = row.operator( - 'gpt.del_msg', text="", icon='TRASH', emboss=False) - delete_message_op.msg_idx = index - else: - row = box.row() - row.label(text=f"USER: {message.content}") - delete_message_op = row.operator( - 'gpt.del_msg', text="", icon='TRASH', emboss=False) - delete_message_op.msg_idx = index - - column.separator() - - # model of chat gpt - column.label(text="Chat-GPT 模型:") - column.prop(context.scene, "model", text="") - - # input of chat - column.label(text="指令:") - column.prop(context.scene, "prompt_input", text="") - - button_label = "請稍候,模型正在編寫腳本..." if context.scene.on_finish else "送出指令" - - row = column.row(align=True) - row.operator("gpt.send_msg", text=button_label) - row.operator("gpt.del_all_msg", text="Clear Chat") - - column.separator()