diff --git a/.gitignore b/.gitignore index 68bc17f..170717e 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,9 @@ __pycache__/ # C extensions *.so +.DS_Store/ +.DS_Store + # Distribution / packaging .Python build/ diff --git a/blendergpt-zh/__init__.py b/blendergpt-zh/__init__.py new file mode 100644 index 0000000..57ecf2e --- /dev/null +++ b/blendergpt-zh/__init__.py @@ -0,0 +1,110 @@ + +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 + + +bl_info = { + "name": "Blender GPT (ZH) 中文用戶專屬", + "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() + + +def unregister(): + for cls in Classes: + bpy.utils.unregister_class(cls) + + clear_props() diff --git a/blendergpt-zh/gptzh_opt.py b/blendergpt-zh/gptzh_opt.py new file mode 100644 index 0000000..6a2487f --- /dev/null +++ b/blendergpt-zh/gptzh_opt.py @@ -0,0 +1,120 @@ +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 new file mode 100644 index 0000000..ea22f57 --- /dev/null +++ b/blendergpt-zh/gptzh_pnl.py @@ -0,0 +1,61 @@ +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() diff --git a/blendergpt-zh/gptzh_prf.py b/blendergpt-zh/gptzh_prf.py new file mode 100644 index 0000000..77ffef0 --- /dev/null +++ b/blendergpt-zh/gptzh_prf.py @@ -0,0 +1,17 @@ +from bpy import props +from bpy.types import AddonPreferences + + +class BLENDERGPT_AddonPreferences(AddonPreferences): + bl_idname = __name__ + print(__name__) + api_key: props.StringProperty( + name="API Key", + description="Enter your OpenAI API Key", + default="", + subtype="PASSWORD", + ) + + def draw(self, context): + layout = self.layout + layout.prop(self, "api_key")