From 9a997c4036f6bfba329f858bb17c8bd07fb974e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E6=A8=AA?= <2938139566@qq.com> Date: Wed, 17 Sep 2025 11:11:46 +0800 Subject: [PATCH 1/2] =?UTF-8?q?1.=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- RenderPipelineFile/config/daytime.yaml | 2 +- Start_Run.py | 8 +- core/world.py | 4 +- gui/gui_manager.py | 1 + icons/logo.png | Bin 0 -> 5110 bytes main.py | 4 +- project/project_manager.py | 2 +- scene/scene_manager.py | 38 +-- ui/icon_manager.py | 322 ++++++++++++++++++++ ui/icon_manager_gui.py | 405 +++++++++++++++++++++++++ ui/main_window.py | 112 +++++-- ui/widgets.py | 19 +- 12 files changed, 875 insertions(+), 42 deletions(-) create mode 100644 icons/logo.png create mode 100644 ui/icon_manager.py create mode 100644 ui/icon_manager_gui.py diff --git a/RenderPipelineFile/config/daytime.yaml b/RenderPipelineFile/config/daytime.yaml index af9861ed..58bbc54e 100644 --- a/RenderPipelineFile/config/daytime.yaml +++ b/RenderPipelineFile/config/daytime.yaml @@ -18,7 +18,7 @@ control_points: sun_intensity: [[[0.0000000000,0.0000000000],[0.0041666667,0.0000000000],[0.0083333333,0.0000000000],[0.0125000000,0.0000000000],[0.0166666667,0.0000000000],[0.0208333333,0.0000000000],[0.0250000000,0.0000000000],[0.0291666667,0.0000000000],[0.0333333333,0.0000000000],[0.0375000000,0.0000000000],[0.0416666667,0.0000000000],[0.0458333333,0.0000000000],[0.0500000000,0.0000000000],[0.0541666667,0.0000000000],[0.0583333333,0.0000000000],[0.0625000000,0.0000000000],[0.0666666667,0.0000000000],[0.0708333333,0.0000000000],[0.0750000000,0.0000000000],[0.0791666667,0.0000000000],[0.0833333333,0.0000000000],[0.0875000000,0.0000000000],[0.0916666667,0.0000000000],[0.0958333333,0.0000000000],[0.1000000000,0.0000000000],[0.1041666667,0.0000000000],[0.1083333333,0.0000000000],[0.1125000000,0.0000000000],[0.1166666667,0.0000000000],[0.1208333333,0.0000000000],[0.1250000000,0.0000000000],[0.1291666667,0.0000000000],[0.1333333333,0.0000000000],[0.1375000000,0.0000000000],[0.1416666667,0.0000000000],[0.1458333333,0.0000000000],[0.1500000000,0.0000000000],[0.1541666667,0.0000000000],[0.1583333333,0.0000028805],[0.1625000000,0.0003577724],[0.1666666667,0.0013331400],[0.1708333333,0.0029671803],[0.1750000000,0.0052963381],[0.1791666667,0.0083550556],[0.1833333333,0.0121755589],[0.1875000000,0.0167876159],[0.1916666667,0.0222183530],[0.1958333333,0.0284919947],[0.2000000000,0.0356297193],[0.2041666667,0.0436494349],[0.2083333333,0.0525656099],[0.2125000000,0.0623891610],[0.2166666667,0.0731272461],[0.2208333333,0.0847831708],[0.2250000000,0.0973563167],[0.2291666667,0.1108419698],[0.2333333333,0.1252313631],[0.2375000000,0.1405115250],[0.2416666667,0.1566653434],[0.2458333333,0.1736715009],[0.2500000000,0.1915046014],[0.2541666667,0.2101350464],[0.2583333333,0.2295292930],[0.2625000000,0.2496498145],[0.2666666667,0.2704552670],[0.2708333333,0.2919006662],[0.2750000000,0.3139375192],[0.2791666667,0.3365139497],[0.2833333333,0.3595750662],[0.2875000000,0.3830630359],[0.2916666667,0.4069173972],[0.2958333333,0.4310753462],[0.3000000000,0.4554720417],[0.3041666667,0.4800408236],[0.3083333333,0.5047136020],[0.3125000000,0.5294212108],[0.3166666667,0.5540936424],[0.3208333333,0.5786605298],[0.3250000000,0.6030514553],[0.3291666667,0.6271963182],[0.3333333333,0.6510256858],[0.3375000000,0.6744711982],[0.3416666667,0.6974659988],[0.3458333333,0.7199450163],[0.3500000000,0.7418453485],[0.3541666667,0.7631067095],[0.3583333333,0.7836717291],[0.3625000000,0.8034862953],[0.3666666667,0.8224999302],[0.3708333333,0.8406661079],[0.3750000000,0.8579425235],[0.3791666667,0.8742914270],[0.3833333333,0.8896799131],[0.3875000000,0.9040801386],[0.3916666667,0.9174695289],[0.3958333333,0.9298310650],[0.4000000000,0.9411533765],[0.4041666667,0.9514309312],[0.4083333333,0.9606641691],[0.4125000000,0.9688595571],[0.4166666667,0.9760296330],[0.4208333333,0.9821930708],[0.4250000000,0.9873746114],[0.4291666667,0.9916050060],[0.4333333333,0.9949209310],[0.4375000000,0.9973647924],[0.4416666667,0.9989845508],[0.4458333333,0.9998334497],[0.4500000000,0.9999696949],[0.4541666667,0.9994560801],[0.4583333333,0.9983595429],[0.4625000000,0.9967506613],[0.4666666667,0.9947030614],[0.4708333333,0.9922927758],[0.4750000000,0.9895975125],[0.4791666667,0.9866958610],[0.4833333333,0.9836664262],[0.4875000000,0.9805868867],[0.4916666667,0.9775330316],[0.4958333333,0.9745777179],[0.5000000000,0.9717898417],[0.5041666667,0.9692332877],[0.5083333333,0.9669658924],[0.5125000000,0.9650384806],[0.5089595376,0.9690650222],[0.5208333333,0.9623666659],[0.5250000000,0.9616814371],[0.5291666667,0.9614534423],[0.5333333333,0.9616877089],[0.5375000000,0.9623790807],[0.5416666667,0.9635123329],[0.5458333333,0.9650624244],[0.5500000000,0.9669949804],[0.5541666667,0.9692669864],[0.5583333333,0.9718275065],[0.5625000000,0.9746185969],[0.5666666667,0.9775762863],[0.5708333333,0.9806315864],[0.5750000000,0.9837115661],[0.5791666667,0.9867403433],[0.5833333333,0.9896401655],[0.5875000000,0.9923323562],[0.5916666667,0.9947382579],[0.5958333333,0.9967800977],[0.6000000000,0.9983817820],[0.6041666667,0.9994696263],[0.6083333333,0.9999730028],[0.6125000000,0.9998249266],[0.6166666667,0.9989625601],[0.6208333333,0.9973276624],[0.6250000000,0.9948669567],[0.6291666667,0.9915324664],[0.6333333333,0.9872817545],[0.6375000000,0.9820781426],[0.6416666667,0.9758908775],[0.6458333333,0.9686952146],[0.6500000000,0.9604725211],[0.6541666667,0.9512102537],[0.6583333333,0.9409019858],[0.6625000000,0.9295473441],[0.6666666667,0.9171518878],[0.6708333333,0.9037270619],[0.6750000000,0.8892899902],[0.6791666667,0.8738633008],[0.6833333333,0.8574749656],[0.6875000000,0.8401579787],[0.6916666667,0.8219502453],[0.6958333333,0.8028941798],[0.7000000000,0.7830364456],[0.7041666667,0.7624277344],[0.7083333333,0.7411222520],[0.7125000000,0.7191776044],[0.7166666667,0.6966542563],[0.7208333333,0.6736152714],[0.7250000000,0.6501259629],[0.7291666667,0.6262533880],[0.7333333333,0.6020661121],[0.7375000000,0.5776338043],[0.7416666667,0.5530267796],[0.7458333333,0.5283156992],[0.7500000000,0.5035711751],[0.7541666667,0.4788634341],[0.7583333333,0.4542618347],[0.7625000000,0.4298347613],[0.7666666667,0.4056490351],[0.7708333333,0.3817697830],[0.7750000000,0.3582600107],[0.7791666667,0.3351803495],[0.7833333333,0.3125888445],[0.7875000000,0.2905406366],[0.7916666667,0.2690876955],[0.7958333333,0.2482787388],[0.8000000000,0.2281588906],[0.8041666667,0.2087696425],[0.8083333333,0.1901486315],[0.8125000000,0.1723295359],[0.8166666667,0.1553419918],[0.8208333333,0.1392115328],[0.8250000000,0.1239595144],[0.8291666667,0.1096030703],[0.8333333333,0.0961551918],[0.8375000000,0.0836246599],[0.8416666667,0.0720161369],[0.8458333333,0.0613302273],[0.8500000000,0.0515635598],[0.8541666667,0.0427088803],[0.8583333333,0.0347551990],[0.8625000000,0.0276878920],[0.8666666667,0.0214889271],[0.8708333333,0.0161369711],[0.8750000000,0.0116076130],[0.8791666667,0.0078735477],[0.8833333333,0.0049047927],[0.8875000000,0.0026688977],[0.8916666667,0.0011311782],[0.8958333333,0.0002549473],[0.9000000000,0.0000000000],[0.9041666667,0.0000000000],[0.9083333333,0.0000000000],[0.9125000000,0.0000000000],[0.9166666667,0.0000000000],[0.9208333333,0.0000000000],[0.9250000000,0.0000000000],[0.9291666667,0.0000000000],[0.9333333333,0.0000000000],[0.9375000000,0.0000000000],[0.9416666667,0.0000000000],[0.9458333333,0.0000000000],[0.9500000000,0.0000000000],[0.9541666667,0.0000000000],[0.9583333333,0.0000000000],[0.9625000000,0.0000000000],[0.9666666667,0.0000000000],[0.9708333333,0.0000000000],[0.9750000000,0.0000000000],[0.9791666667,0.0000000000],[0.9833333333,0.0000000000],[0.9875000000,0.0000000000],[0.9916666667,0.0000000000],[0.9958333333,0.0000000000]]] sun_color: [[[0.5010435645,0.5818710306],[0.0433100000,0.8999700000],[0.8635787716,0.9130000000],[0.1785000000,0.8973600000],[0.8099800000,0.8651100000],[0.2360800000,0.7712700000],[0.6583432177,0.8485126184],[0.1266806142,0.9648102053],[0.9558541267,0.9090909091],[0.5568400771,0.7353760446]],[[0.5001318426,0.5160300000],[0.0572700000,0.6541600000],[0.2395000000,0.5976800000],[0.8104600000,0.6009000000],[0.6967400000,0.5483900000]],[[0.0862400000,0.4257800000],[0.4955600000,0.4033000000],[0.8234200000,0.4340200000]]] sun_azimuth: [[[0.5000000000,0.5000000000]]] - sun_altitude: [[[0.5000000000,1.0000000000]]] + sun_altitude: [[[0.5000000000,0.8555555556]]] extinction: [[[0.4913294798,0.6378830084]]] volumetrics: fog_ramp_size: [[[0.5510597303,0.7409470752]]] diff --git a/Start_Run.py b/Start_Run.py index fa515efa..8e6a1214 100644 --- a/Start_Run.py +++ b/Start_Run.py @@ -21,8 +21,14 @@ sys.path.insert(0, icons_path) if __name__ == "__main__": args = sys.argv[1:] # args = "/home/tiger/桌面/Test1" + # args = "C:/Users/29381/Desktop/1" + print(f'Path is {args}') + # 将整个列表转换为字符串(包括方括号) + args_str = ''.join(args) + from main import run if args: - run(args[0]) + run(args_str) + # run(args) else: run() \ No newline at end of file diff --git a/core/world.py b/core/world.py index 0538877f..b241f9ea 100644 --- a/core/world.py +++ b/core/world.py @@ -288,7 +288,8 @@ class CoreWorld(Panda3DWorld): self.cam.setPos(0, -50, 20) self.cam.lookAt(0, 0, 0) self.camLens.setFov(80) - # self.cam.setTag("is_scene_element", "1") + self.cam.setTag("is_scene_element", "1") + self.cam.setTag("tree_item_type", "CAMERA_NODE") print("✓ 相机设置完成") def _setupLighting(self): @@ -323,6 +324,7 @@ class CoreWorld(Panda3DWorld): self.ground.setZ(-0.1) self.ground.setColor(0.8, 0.8, 0.8, 1) # self.ground.setTag("is_scene_element", "1") + # self.ground.setTag("tree_item_type", "SCENE_NODE") # 创建支持贴图的材质 mat = Material() diff --git a/gui/gui_manager.py b/gui/gui_manager.py index 83737e6f..bbd0beff 100644 --- a/gui/gui_manager.py +++ b/gui/gui_manager.py @@ -723,6 +723,7 @@ class GUIManager: textNodePath.setTag("is_scene_element", "1") textNodePath.setTag("tree_item_type", "GUI_3DTEXT") textNodePath.setTag("created_by_user", "1") + textNodePath.setTag("name", text_name) # 添加到GUI元素列表 self.gui_elements.append(textNodePath) diff --git a/icons/logo.png b/icons/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..361b35c7a5081715cfe0351d74b970bc37a242f5 GIT binary patch literal 5110 zcmbVQ=RX_V`wk6av`u2G5)nK0Zl59qi5)c?YS-3UT_|Gjh`o1<*sD~jDm7}ATD3<@ zW0XF$ROvU*fAIVKUYv8kxbN#a=iHz3;<~Rm6C>TrOgu~g0N^qfgEpnc!T%PBp1Rjs z)fG|$jlZd`CZKMN_ZtAf+JHr)%pTv}v$gi)Rfdkrsctm{C&Uqj(dT%G|D^4PO~@<#sQ)Re?+T(mmiDH^S?m@sQFpU+QoArKcz9MW<*EhY>S8Iu=c&Pc06)B}3I z+Z3<|uC3j(Gbe;W%Au$QnJ3iB$L;f$88CZb6?#I+fR9S`|EIu-ZvVYl=;C^Hd}cR_ zbKoR@BSFd=DpxKBx+3@wcmO`1>l7Zq2dX@;Bv15wq~WdOUrS3BE}BS$oi?a>ew(%2 z-)H>uYvk+IiJlka&{cb92YBqC5fA~WS3S3owZD<3_*lfuPCq?HJuj%_cTMhp?=lcy z+lc=@dvLg^hi%(gy`bC-mhpr$dRS&b(0o$~JfR_9e0ajtNlIT4Z% z6pe}h3Tx<|d`X1$W@pxnf#8YVH#nJlkeJBCBp77Xngz~M*h`^iHF2&eBqoK_06{RR zox3l;2-`mY+pWMK8Dni2r~`JudE!MC2z`u?If3r=V>E!R)h!5bQ9XZ2*KP4F3+5=` zlYiexj|vzWQqG^I0o{!$swcHevNs>4d_osK;}{JrGSj!1y*pOCz6FSf_T66&ejUyE ztZ7&khYf!tMmW;7`VMa+@l!jXR8bty+id$WI!4;ig1g8zQd#{;;Eb2xuct0_($UkU zLHRuGhJoL5tM~S=Bww}5vvYhf5WJ9l%Cw*TE95$F4@0d_@l(Rle)=1)y&rl;iAR|q z5Y58zrd>mvjQuux6_0|dgfaAwgcz~tTH8JZ@kWG@no%gjq45D~%KB+5a7ZKB=oo5~ zRwkU5v5mw`;^FwzZR<`Pmgu#~ba3DTw(fzeGUbz?BK~+Z{u`@KCri#53OaHm}5O+z6y=g0#kp^A_?8q@zxw! z4ageSuPK%v2=m+f&Hm_3d+ePR@xYK&9s*m^EshrC!>{68F~#WtNr^~5@Oa^o#6hS% z1}ee4-&Yn5TTd{uK?|?+gPx~<;F@w(56)C{rJrIWr8mHHL8W_55v=Cz1TprQ2WLU> z0ruj1$6GGt?Z+G~zj_c3BQ{k8SPi$ty-%O+=b(a8eYNXCO(Z+a>9~J8B}H>i%`;#MaPpGvoq9B$yYA>UP#jCl zF82?aL4!m0Uw5r|XsNe+K;_n|0>FzByI!{fB671=1NgsDN)Epsp%hU~?Ey5P|&p%1x%#-+3$@lP!=RFG( zfr{w`WVg3awwO|))nv(;s`y~XPNJibYRH}6I@eJo(Nu(^exQK-tw~R~Rey<JzfvSCI@|J0K;7KH(YVGRH|Z9g z0@Wh&O9j>7A~RNJfT2N2!(@^qCuj~UHLf?pI!pmCFhZ|gN&w0R-{cu=1jeYGFFBp=XA8H~xO!6hR46TTVtDlkJA1-22qR^G7#f&-o zTWtBaYji8?^&cNOZQuP3J@Gvgm^gA|&l4QXtiifj++)~@jl#A>Y103Wpf9*fBsFqq zwR-S>oJXL9d*hjax5v~tDJEE|M^=vVeeJ&zSlGPY>e`jinUOIFZQ=)08v zQICrf8gQwXeK#H^GOKS5dz0BzUUNHAX%|wf8+3-fV*0qlD~T&YYL5hCa$KuzTW z?3Zaai<~%y)M|}B#~SAMrgggob_x%Mv26{9;Llf-a40eqVBe4>h4hwog}|T+e)I|ThIaSx?)-7YX@_Wi-BJix~``Ctp23% ztNgjJ^k=KJw040sT~%}V)Pn@kSKzpfGnMWg`ecWG&i03a zJhd&_GGgys73?qN;6lX$b2wb_oRRE|H9h0+B`Vjr1WaU3HFdxWW@`lW4%Egr^yKs6 zSgT$epH6JU0f!zJYHlJ*odpsdRHM+o?+^)MX0Y!4Pe7Df8FSf9gWsJj(7M+4nFevr zl;OM7lrI1A4sVT2AKNi8B+k6yC#>=xyxRM%BKi~cW3oPciw-$^-f3Ch7Egci@ceJ* zv&fB1j!t@mM3x`U*<{>h)L5IXaUAgyvvGEz#+6kWqMIjB?{daV`;^%O$L#j}h@!kh zw4#B1`y1hr1Zer-GtqAP)4jC|n~1+#uF~kgEBGe0?2@Y-l=73iYaQRb z3?g=1u3WGricA~}28)LWLI{ycU;8(5TcerR)4_P?-g?MVVmYF z*&n^W<9~*<=hojCmWqxSY z#s6jY$mK`m@0I0X+wDcQ7o*NV*;x-A-f*(gt$T~B1y5sq%RByDa^xpcFhk36*j zT)V-&%`aWhB|gwuWtsBe8a$<6Nb>7$yH7F`7UVdy!Ant`QXe~z%m_LgR((?%Ox1QU zjNg>XfwR+_-E_CH5ex5aHD^6t61Z>jRTqd>YWmY~9dy%72*^M7O{8-FdInD@EhCYp z%?!9!^l%|XWA-Y#%P-V`olA?z!MCZ+q}XXw!Vulykr@eVeen?T%W8CN-;EG3sa*@7 z&1K5;Yc_>!VrSSddZnffFRe-r?51Fd%h?)1`e!_!&0pDQX5C9*Enf$OOjq>wx$qJK z_$*bV4!?N#bu?s0KKB*GfCTzhv^pN`^3Y|$E@5R{g{yr#B54B!K?Hz=Ho+Gl#Frp| zSsAbojcybTNs=()*{Dx~`B^o2FrBm`--MQoO-zAY&vomvj-+Ov%^I{o`z{$nMfGnc zJY&nRm_I8+fF@7zmD>ICp7668e;f^Ny~#wk-?ru!D}v24|K67jgXwCGa*&*Y=HTY8 zl#)PVh@r4MNUj;g;aE+1v%&e*VygYEtR8D5aqTnSi;y!y_b*(Ou1#fp(~1ICl_>l~4j|tVuw6N%=Cs@hXob_v>y~|GpzT|3KCGuvLc{@eHpq@yw2~%o$x^}9;j*;)M zBPzH|s0qfW;;|oUlxo_~o7ToLo`w*KEf!KuP?rb(TZPoM;PiAurU|*%4~CvJcVx+E zU}-l`lcVbBehbSM+GIMrZ3UEt5|njJ(**Mp6xsDi&7dWGNRj4%v6x=wr(Z=65k#D4 z{l(C~PA|f=mRN&i;&MV*j_>b(pSP)T-+Rg|0RcU@~1BR**nqA z1Dl86Cj%ZmDNuj?+j>&I=co$90;S9yXgJ&Lw}xV-{&t4?efTRMK(?=ZHnNi7-NI4> z5HXaM2C%{<>Bnoc#C%W+G}Lw#&{=nNi`*98)8uVU>jTM|#zC8pCW5zxdlKhcKB($H z4o!QGmHosih)FT2;^xsO)^N9S*QUOzk|`&CzVHEZOPO7IY-gYnYxvNbgLxJJw(*vZ zmM>eQFq+yN-YB74X%Xe-FF2~!B$*k4+4W*DhgX<#Z%*G-UYp@)u{0!-P^VU>4SFxa zVcjbwiPzqx;m)wFK(So*+{#N9YkDHk_e*s`ZuKPC9UC%gtqnuWcIxyP?Kc>xM#OT= zGUct`>W2*{%Q)G-lY}^ao~3|Fhgv^rNqDzyf#Z?8yC2-rs#NUB_7Jwi&y9Z-emD2Q}r;W$_DJh0Zfql<2* zQ#f`eUDP%F0KOz~Ofe%cIvV>9QnB-vFX}?QTy8}+pX7vS*?j?0pK*7e9yWjyxwYY>M-cSXOJWJxC(<}eSqbAlq*xbUY5Wmtzcc^7v zqQRIUGPRI`Dp<-3`odsChf)CURAsz4!7WCzY7lc6na#9_z0F>vIj0)VsDYOYI~cM6 z25YH*!@pI*A=6LjHhv(I9&QzA4iwWQp~Fu}&ddk|2|rNGA%Zlyj%p$yL7$#vf8 z6xKCI{SF|xFF{78rWWU`0IJb-UtvRqOVy2oi)OqKlZ(S;)){5JXhO3 zbuPOiI-*lbo&L>`;4X@+lnFF5{7(!X;7#w^Yv7TrPZE`?_o7l3B>$?fNFD&L^8OoPQ5D{4G|M?{G0^SyV*_kUk<1)4rFSkaF`3lQ9e(q z28R%Uue7$1Av*h03-_0l0uLK}XX zQ;jN|B;SsWJ1`MeA!NTt*69J2-uY*yW0Gn;>FM`;woD3xpj#i;_*Xz=2hte7N?r=23zl$u^|9Lu0$P4CdobbC=( w6^n6av^uM%l2vk~1lB1~Eu#PHMSA|y82dW;K#2mDR3i_7)iy%cY1$M12k3@VLI3~& literal 0 HcmV?d00001 diff --git a/main.py b/main.py index 9bca08bc..f46c47e8 100644 --- a/main.py +++ b/main.py @@ -105,7 +105,7 @@ class MyWorld(CoreWorld): self.collision_manager = CollisionManager(self) # 调试选项 - self.debug_collision = False # 是否显示碰撞体 + self.debug_collision = True # 是否显示碰撞体 # 默认启用模型间碰撞检测(可选) self.enableModelCollisionDetection(enable=True, frequency=0.1, threshold=0.5) @@ -850,7 +850,7 @@ def run(args = None): # 使用新的UI模块创建主窗口 from ui.main_window import setup_main_window - + print(f'Path is {args}') app, main_window = setup_main_window(world, args) # 启动应用程序 diff --git a/project/project_manager.py b/project/project_manager.py index c3370792..31b2df70 100644 --- a/project/project_manager.py +++ b/project/project_manager.py @@ -217,7 +217,7 @@ class ProjectManager: config_file = os.path.join(project_path, "project.json") if not os.path.exists(config_file): if parent_window: - QMessageBox.warning(parent_window, "警告", "选择的不是有效的项目文件夹!") + QMessageBox.warning(parent_window, "警告", f"选择的不是有效的项目文件夹!{project_path}") else: print("警告: 选择的不是有效的项目文件夹!") return False diff --git a/scene/scene_manager.py b/scene/scene_manager.py index 745e6194..2c839f6d 100644 --- a/scene/scene_manager.py +++ b/scene/scene_manager.py @@ -674,7 +674,7 @@ class SceneManager: # 根据调试设置决定是否显示碰撞体 if hasattr(self.world, 'debug_collision') and self.world.debug_collision: - cNodePath.hide() + cNodePath.show() else: cNodePath.hide() @@ -1143,22 +1143,26 @@ class SceneManager: tree_widget = self._get_tree_widget() # 清除当前场景 print("\n清除当前场景...") - for model in self.models: - tree_widget.delete_item(model) + # for model in self.models: + # tree_widget.delete_item(model) + # # 清除灯光 + # for light_node in self.Spotlight: + # tree_widget.delete_item(light_node) + # + # for light_node in self.Pointlight: + # tree_widget.delete_item(light_node) + # + # for terrain in self.world.terrain_manager.terrains: + # tree_widget.delete_item(terrain) + # + # for gui in self.world.gui_elements: + # if not gui.isEmpty(): + # tree_widget.delete_item(gui) - # 清除灯光 - for light_node in self.Spotlight: - tree_widget.delete_item(light_node) - - for light_node in self.Pointlight: - tree_widget.delete_item(light_node) - - for terrain in self.world.terrain_manager.terrains: - tree_widget.delete_item(terrain) - - # 清除tilesets - for tileset_info in self.tilesets: - tree_widget.delete_item(tileset_info['node']) + # # 清除tilesets + # for tileset_info in self.tilesets: + # tree_widget.delete_item(tileset_info['node']) + tree_widget.clear_tree() for light in self.Spotlight: if not light.isEmpty(): @@ -1415,7 +1419,7 @@ class SceneManager: print(f" 发现 {len(gui_data)} 个GUI元素需要重建") # 使用gui_manager重新创建GUI元素 - self._recreateGUIElementsFromData(gui_data) + # self._recreateGUIElementsFromData(gui_data) else: print("ℹ️ GUI信息文件为空") except json.JSONDecodeError as e: diff --git a/ui/icon_manager.py b/ui/icon_manager.py new file mode 100644 index 00000000..944c3aa6 --- /dev/null +++ b/ui/icon_manager.py @@ -0,0 +1,322 @@ +""" +图标管理工具 + +负责统一管理应用程序中的所有图标: +- 图标路径解析 +- 图标缓存 +- 图标预加载 +- 图标错误处理 +""" +import os +import sys +from typing import Dict, Optional +from PyQt5.QtGui import QIcon, QPixmap +from PyQt5.QtCore import QSize + + +class IconManager: + """图标管理器类""" + + def __init__(self): + """初始化图标管理器""" + self.icon_cache: Dict[str, QIcon] = {} + self.icon_directory = self._get_icon_directory() + self.default_icon = None + + # 预定义的图标映射 + self.icon_map = { + # 主窗口图标 + 'app_logo': 'logo.png', + + # 工具栏图标 + 'select_tool': 'select_tool.png', + 'move_tool': 'move_tool.png', + 'rotate_tool': 'rotate_tool.png', + 'scale_tool': 'scale_tool.png', + + # 菜单图标(如果有的话) + 'new_file': 'new_file.png', + 'open_file': 'open_file.png', + 'save_file': 'save_file.png', + 'exit': 'exit.png', + + # 对象类型图标 + 'object_3d': 'object_3d.png', + 'light': 'light.png', + 'camera': 'camera.png', + 'terrain': 'terrain.png', + 'script': 'script.png', + + # 状态图标 + 'success': 'success.png', + 'warning': 'warning.png', + 'error': 'error.png', + 'info': 'info.png', + } + + # 初始化默认图标 + self._create_default_icon() + + # 预加载常用图标 + self._preload_icons() + + def _get_icon_directory(self) -> str: + """获取图标目录的绝对路径""" + # 获取当前文件的目录(ui目录) + current_dir = os.path.dirname(os.path.abspath(__file__)) + # 获取项目根目录(ui的父目录) + project_root = os.path.dirname(current_dir) + # 拼接icons目录路径 + icon_dir = os.path.join(project_root, "icons") + + print(f"🔍 图标目录路径: {icon_dir}") + + # 检查目录是否存在 + if not os.path.exists(icon_dir): + print(f"⚠️ 图标目录不存在: {icon_dir}") + # 尝试创建目录 + try: + os.makedirs(icon_dir, exist_ok=True) + print(f"✅ 已创建图标目录: {icon_dir}") + except Exception as e: + print(f"❌ 创建图标目录失败: {e}") + + return icon_dir + + def _create_default_icon(self): + """创建默认图标""" + # 创建一个简单的默认图标 + pixmap = QPixmap(16, 16) + pixmap.fill() # 填充为白色 + self.default_icon = QIcon(pixmap) + + def _preload_icons(self): + """预加载常用图标""" + print("🔄 开始预加载图标...") + + for icon_name, file_name in self.icon_map.items(): + icon_path = os.path.join(self.icon_directory, file_name) + if os.path.exists(icon_path): + try: + icon = QIcon(icon_path) + self.icon_cache[icon_name] = icon + print(f"✅ 已加载图标: {icon_name} -> {file_name}") + except Exception as e: + print(f"❌ 加载图标失败: {icon_name} -> {file_name}, 错误: {e}") + else: + print(f"⚠️ 图标文件不存在: {icon_path}") + + print(f"📊 预加载完成,共加载 {len(self.icon_cache)} 个图标") + + def get_icon(self, icon_name: str, size: Optional[QSize] = None) -> QIcon: + """ + 获取图标 + + Args: + icon_name: 图标名称(可以是预定义名称或文件名) + size: 图标尺寸 + + Returns: + QIcon对象 + """ + # 首先检查缓存 + if icon_name in self.icon_cache: + icon = self.icon_cache[icon_name] + if size: + # 如果指定了尺寸,返回指定尺寸的图标 + pixmap = icon.pixmap(size) + return QIcon(pixmap) + return icon + + # 如果不在缓存中,尝试从映射中获取 + if icon_name in self.icon_map: + file_name = self.icon_map[icon_name] + icon_path = os.path.join(self.icon_directory, file_name) + else: + # 直接使用文件名 + icon_path = os.path.join(self.icon_directory, icon_name) + if not icon_name.endswith(('.png', '.jpg', '.jpeg', '.svg', '.ico')): + icon_path += '.png' # 默认添加.png扩展名 + + # 尝试加载图标 + if os.path.exists(icon_path): + try: + icon = QIcon(icon_path) + # 缓存图标 + self.icon_cache[icon_name] = icon + print(f"✅ 动态加载图标: {icon_name} -> {os.path.basename(icon_path)}") + + if size: + pixmap = icon.pixmap(size) + return QIcon(pixmap) + return icon + except Exception as e: + print(f"❌ 加载图标失败: {icon_path}, 错误: {e}") + else: + print(f"⚠️ 图标文件不存在: {icon_path}") + + # 返回默认图标 + return self.default_icon + + def get_icon_path(self, icon_name: str) -> str: + """ + 获取图标文件的完整路径 + + Args: + icon_name: 图标名称 + + Returns: + 图标文件的完整路径 + """ + if icon_name in self.icon_map: + file_name = self.icon_map[icon_name] + else: + file_name = icon_name + if not file_name.endswith(('.png', '.jpg', '.jpeg', '.svg', '.ico')): + file_name += '.png' + + icon_path = os.path.join(self.icon_directory, file_name) + + if os.path.exists(icon_path): + return icon_path + else: + print(f"⚠️ 图标文件不存在: {icon_path}") + return "" + + def has_icon(self, icon_name: str) -> bool: + """ + 检查图标是否存在 + + Args: + icon_name: 图标名称 + + Returns: + 是否存在 + """ + return bool(self.get_icon_path(icon_name)) + + def add_icon(self, icon_name: str, icon_path: str) -> bool: + """ + 添加新图标到缓存 + + Args: + icon_name: 图标名称 + icon_path: 图标文件路径 + + Returns: + 是否添加成功 + """ + try: + if os.path.exists(icon_path): + icon = QIcon(icon_path) + self.icon_cache[icon_name] = icon + print(f"✅ 已添加图标到缓存: {icon_name} -> {icon_path}") + return True + else: + print(f"❌ 图标文件不存在: {icon_path}") + return False + except Exception as e: + print(f"❌ 添加图标失败: {icon_name} -> {icon_path}, 错误: {e}") + return False + + def refresh_cache(self): + """刷新图标缓存""" + print("🔄 刷新图标缓存...") + self.icon_cache.clear() + self._preload_icons() + + def get_available_icons(self) -> list: + """获取所有可用的图标列表""" + available_icons = [] + + # 添加预定义的图标 + available_icons.extend(self.icon_map.keys()) + + # 扫描图标目录中的所有图标文件 + if os.path.exists(self.icon_directory): + for file_name in os.listdir(self.icon_directory): + if file_name.lower().endswith(('.png', '.jpg', '.jpeg', '.svg', '.ico')): + icon_name = os.path.splitext(file_name)[0] + if icon_name not in available_icons: + available_icons.append(icon_name) + + return sorted(available_icons) + + def get_cache_info(self) -> dict: + """获取缓存信息""" + return { + 'cached_icons': len(self.icon_cache), + 'icon_directory': self.icon_directory, + 'available_icons': len(self.get_available_icons()), + 'cache_keys': list(self.icon_cache.keys()) + } + + def debug_info(self): + """打印调试信息""" + print("=" * 50) + print("📋 图标管理器调试信息") + print("=" * 50) + + info = self.get_cache_info() + print(f"图标目录: {info['icon_directory']}") + print(f"目录存在: {os.path.exists(info['icon_directory'])}") + print(f"缓存图标数: {info['cached_icons']}") + print(f"可用图标数: {info['available_icons']}") + + if info['cache_keys']: + print("\n已缓存的图标:") + for key in info['cache_keys']: + print(f" - {key}") + + print("\n图标目录内容:") + if os.path.exists(self.icon_directory): + for file_name in os.listdir(self.icon_directory): + file_path = os.path.join(self.icon_directory, file_name) + size = os.path.getsize(file_path) if os.path.isfile(file_path) else 0 + print(f" - {file_name} ({size} bytes)") + else: + print(" 目录不存在") + + print("=" * 50) + + +# 全局图标管理器实例 +_icon_manager = None + + +def get_icon_manager() -> IconManager: + """获取全局图标管理器实例""" + global _icon_manager + if _icon_manager is None: + _icon_manager = IconManager() + return _icon_manager + + +def get_icon(icon_name: str, size: Optional[QSize] = None) -> QIcon: + """便捷函数:获取图标""" + return get_icon_manager().get_icon(icon_name, size) + + +def get_icon_path(icon_name: str) -> str: + """便捷函数:获取图标路径""" + return get_icon_manager().get_icon_path(icon_name) + + +def has_icon(icon_name: str) -> bool: + """便捷函数:检查图标是否存在""" + return get_icon_manager().has_icon(icon_name) + + +if __name__ == "__main__": + # 测试代码 + print("🧪 测试图标管理器...") + + manager = IconManager() + manager.debug_info() + + # 测试获取图标 + logo_icon = manager.get_icon('app_logo') + print(f"\n📱 应用图标是否有效: {not logo_icon.isNull()}") + + move_tool_icon = manager.get_icon('move_tool') + print(f"🔧 移动工具图标是否有效: {not move_tool_icon.isNull()}") \ No newline at end of file diff --git a/ui/icon_manager_gui.py b/ui/icon_manager_gui.py new file mode 100644 index 00000000..bda6524a --- /dev/null +++ b/ui/icon_manager_gui.py @@ -0,0 +1,405 @@ +""" +图标管理器GUI工具 + +提供图形界面来管理和查看图标: +- 显示所有可用图标 +- 图标预览 +- 图标信息 +- 图标刷新 +""" +import os +from PyQt5.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QPushButton, + QListWidget, QListWidgetItem, QLabel, QGroupBox, + QTextEdit, QSplitter, QDialog, QDialogButtonBox, + QFileDialog, QMessageBox, QScrollArea, QGridLayout) +from PyQt5.QtGui import QIcon, QPixmap, QFont +from PyQt5.QtCore import Qt, QSize + +from ui.icon_manager import get_icon_manager + + +class IconPreviewWidget(QWidget): + """图标预览控件""" + + def __init__(self): + super().__init__() + self.setupUI() + + def setupUI(self): + """设置UI""" + layout = QVBoxLayout(self) + + # 图标显示区域 + self.icon_label = QLabel() + self.icon_label.setAlignment(Qt.AlignCenter) + self.icon_label.setStyleSheet(""" + QLabel { + border: 2px dashed #8b5cf6; + border-radius: 8px; + background-color: #2d2d44; + color: #e0e0ff; + min-height: 100px; + margin: 10px; + } + """) + self.icon_label.setText("选择图标查看预览") + layout.addWidget(self.icon_label) + + # 图标信息 + self.info_label = QLabel() + self.info_label.setStyleSheet(""" + QLabel { + background-color: #252538; + color: #e0e0ff; + padding: 10px; + border-radius: 4px; + font-family: monospace; + } + """) + self.info_label.setText("图标信息将在此显示") + layout.addWidget(self.info_label) + + def showIcon(self, icon_name: str, icon: QIcon): + """显示图标""" + if not icon.isNull(): + # 显示不同尺寸的图标 + sizes = [16, 24, 32, 48, 64] + pixmaps = [] + + # 创建组合图标显示 + for size in sizes: + pixmap = icon.pixmap(QSize(size, size)) + if not pixmap.isNull(): + pixmaps.append((size, pixmap)) + + if pixmaps: + # 创建合成图片显示多个尺寸 + total_width = sum(size for size, _ in pixmaps) + 20 * (len(pixmaps) - 1) + max_height = max(size for size, _ in pixmaps) + + combined_pixmap = QPixmap(total_width, max_height + 40) + combined_pixmap.fill(Qt.transparent) + + from PyQt5.QtGui import QPainter, QPen + painter = QPainter(combined_pixmap) + + x = 0 + for size, pixmap in pixmaps: + # 绘制图标 + y = (max_height - size) // 2 + painter.drawPixmap(x, y, pixmap) + + # 绘制尺寸标签 + painter.setPen(QPen(Qt.white)) + painter.drawText(x, max_height + 15, f"{size}x{size}") + + x += size + 20 + + painter.end() + + self.icon_label.setPixmap(combined_pixmap) + else: + self.icon_label.setText("无法加载图标") + else: + self.icon_label.setText("图标无效") + + # 更新信息 + info_text = f"图标名称: {icon_name}\n" + info_text += f"图标有效: {'是' if not icon.isNull() else '否'}\n" + + # 获取图标管理器信息 + icon_manager = get_icon_manager() + icon_path = icon_manager.get_icon_path(icon_name) + if icon_path: + info_text += f"文件路径: {icon_path}\n" + if os.path.exists(icon_path): + size = os.path.getsize(icon_path) + info_text += f"文件大小: {size} bytes\n" + + # 获取可用尺寸 + if not icon.isNull(): + available_sizes = icon.availableSizes() + if available_sizes: + sizes_text = ", ".join(f"{s.width()}x{s.height()}" for s in available_sizes) + info_text += f"可用尺寸: {sizes_text}\n" + + self.info_label.setText(info_text) + + +class IconManagerDialog(QDialog): + """图标管理器对话框""" + + def __init__(self, parent=None): + super().__init__(parent) + self.icon_manager = get_icon_manager() + self.setupUI() + self.loadIcons() + + def setupUI(self): + """设置UI""" + self.setWindowTitle("图标管理器") + self.setModal(False) + self.resize(800, 600) + + # 设置样式 + self.setStyleSheet(""" + QDialog { + background-color: #1e1e2e; + color: #e0e0ff; + } + QListWidget { + background-color: #252538; + color: #e0e0ff; + border: 1px solid #3a3a4a; + border-radius: 4px; + alternate-background-color: #2d2d44; + } + QListWidget::item { + padding: 8px; + border-bottom: 1px solid #3a3a4a; + } + QListWidget::item:hover { + background-color: #3a3a4a; + } + QListWidget::item:selected { + background-color: rgba(139, 92, 246, 100); + color: white; + } + QPushButton { + background-color: #8b5cf6; + color: white; + border: none; + padding: 8px 16px; + border-radius: 4px; + font-weight: 500; + } + QPushButton:hover { + background-color: #7c3aed; + } + QPushButton:pressed { + background-color: #6d28d9; + } + QGroupBox { + background-color: #252538; + border: 1px solid #3a3a4a; + border-radius: 6px; + margin-top: 1ex; + color: #e0e0ff; + font-weight: 500; + padding-top: 10px; + } + QGroupBox::title { + subline-offset: -2px; + padding: 0 8px; + color: #c0c0e0; + font-weight: 500; + } + QTextEdit { + background-color: #252538; + color: #e0e0ff; + border: 1px solid #3a3a4a; + border-radius: 4px; + font-family: monospace; + } + """) + + layout = QVBoxLayout(self) + + # 顶部按钮栏 + button_layout = QHBoxLayout() + + self.refresh_btn = QPushButton("刷新图标") + self.refresh_btn.clicked.connect(self.refreshIcons) + button_layout.addWidget(self.refresh_btn) + + self.add_icon_btn = QPushButton("添加图标") + self.add_icon_btn.clicked.connect(self.addIcon) + button_layout.addWidget(self.add_icon_btn) + + self.debug_btn = QPushButton("调试信息") + self.debug_btn.clicked.connect(self.showDebugInfo) + button_layout.addWidget(self.debug_btn) + + button_layout.addStretch() + layout.addLayout(button_layout) + + # 主分割器 + splitter = QSplitter(Qt.Horizontal) + + # 左侧:图标列表 + left_widget = QWidget() + left_layout = QVBoxLayout(left_widget) + + list_group = QGroupBox("可用图标") + list_layout = QVBoxLayout(list_group) + + self.icon_list = QListWidget() + self.icon_list.itemSelectionChanged.connect(self.onIconSelected) + list_layout.addWidget(self.icon_list) + + left_layout.addWidget(list_group) + splitter.addWidget(left_widget) + + # 右侧:图标预览 + right_widget = QWidget() + right_layout = QVBoxLayout(right_widget) + + preview_group = QGroupBox("图标预览") + preview_layout = QVBoxLayout(preview_group) + + self.preview_widget = IconPreviewWidget() + preview_layout.addWidget(self.preview_widget) + + right_layout.addWidget(preview_group) + splitter.addWidget(right_widget) + + # 设置分割器比例 + splitter.setSizes([300, 500]) + layout.addWidget(splitter) + + # 底部按钮 + button_box = QDialogButtonBox(QDialogButtonBox.Close) + button_box.rejected.connect(self.close) + layout.addWidget(button_box) + + def loadIcons(self): + """加载图标列表""" + self.icon_list.clear() + + available_icons = self.icon_manager.get_available_icons() + + for icon_name in available_icons: + item = QListWidgetItem() + + # 获取图标 + icon = self.icon_manager.get_icon(icon_name, QSize(24, 24)) + + # 设置图标和文本 + if not icon.isNull(): + item.setIcon(icon) + item.setText(f"🎨 {icon_name}") + else: + item.setText(f"❌ {icon_name}") + + item.setData(Qt.UserRole, icon_name) + self.icon_list.addItem(item) + + print(f"📊 加载了 {len(available_icons)} 个图标") + + def onIconSelected(self): + """当选择图标时""" + current_item = self.icon_list.currentItem() + if current_item: + icon_name = current_item.data(Qt.UserRole) + icon = self.icon_manager.get_icon(icon_name) + self.preview_widget.showIcon(icon_name, icon) + + def refreshIcons(self): + """刷新图标""" + print("🔄 刷新图标缓存...") + self.icon_manager.refresh_cache() + self.loadIcons() + QMessageBox.information(self, "完成", "图标缓存已刷新") + + def addIcon(self): + """添加新图标""" + file_path, _ = QFileDialog.getOpenFileName( + self, + "选择图标文件", + "", + "图像文件 (*.png *.jpg *.jpeg *.svg *.ico);;所有文件 (*)" + ) + + if file_path: + # 获取文件名作为图标名称 + file_name = os.path.basename(file_path) + icon_name = os.path.splitext(file_name)[0] + + # 复制文件到图标目录 + import shutil + target_path = os.path.join(self.icon_manager.icon_directory, file_name) + + try: + shutil.copy2(file_path, target_path) + + # 添加到缓存 + success = self.icon_manager.add_icon(icon_name, target_path) + + if success: + QMessageBox.information(self, "成功", f"图标 '{icon_name}' 已添加") + self.loadIcons() + else: + QMessageBox.warning(self, "失败", "添加图标失败") + + except Exception as e: + QMessageBox.critical(self, "错误", f"复制文件失败:\n{str(e)}") + + def showDebugInfo(self): + """显示调试信息""" + debug_dialog = QDialog(self) + debug_dialog.setWindowTitle("图标管理器调试信息") + debug_dialog.resize(600, 400) + + layout = QVBoxLayout(debug_dialog) + + text_edit = QTextEdit() + text_edit.setFont(QFont("Consolas", 10)) + + # 获取调试信息 + info = self.icon_manager.get_cache_info() + debug_text = "图标管理器调试信息\n" + debug_text += "=" * 50 + "\n\n" + debug_text += f"图标目录: {info['icon_directory']}\n" + debug_text += f"目录存在: {os.path.exists(info['icon_directory'])}\n" + debug_text += f"缓存图标数: {info['cached_icons']}\n" + debug_text += f"可用图标数: {info['available_icons']}\n\n" + + debug_text += "已缓存的图标:\n" + for key in info['cache_keys']: + debug_text += f" - {key}\n" + + debug_text += "\n图标目录内容:\n" + if os.path.exists(info['icon_directory']): + for file_name in os.listdir(info['icon_directory']): + file_path = os.path.join(info['icon_directory'], file_name) + if os.path.isfile(file_path): + size = os.path.getsize(file_path) + debug_text += f" - {file_name} ({size} bytes)\n" + else: + debug_text += " 目录不存在\n" + + text_edit.setPlainText(debug_text) + layout.addWidget(text_edit) + + button_box = QDialogButtonBox(QDialogButtonBox.Close) + button_box.rejected.connect(debug_dialog.close) + layout.addWidget(button_box) + + debug_dialog.exec_() + + +def show_icon_manager(parent=None): + """显示图标管理器对话框""" + dialog = IconManagerDialog(parent) + dialog.show() + return dialog + + +if __name__ == "__main__": + from PyQt5.QtWidgets import QApplication + import sys + + app = QApplication(sys.argv) + + # 设置全局样式 + app.setStyleSheet(""" + QApplication { + background-color: #1e1e2e; + color: #e0e0ff; + } + """) + + dialog = IconManagerDialog() + dialog.show() + + sys.exit(app.exec_()) \ No newline at end of file diff --git a/ui/main_window.py b/ui/main_window.py index 92af669d..aae2643e 100644 --- a/ui/main_window.py +++ b/ui/main_window.py @@ -20,6 +20,7 @@ from PyQt5.QtCore import Qt, QDir, QTimer, QSize, QPoint from direct.showbase.ShowBaseGlobal import aspect2d from ui.widgets import CustomPanda3DWidget, CustomFileView, CustomTreeWidget,CustomAssetsTreeWidget, CustomConsoleDockWidget +from ui.icon_manager import get_icon_manager, get_icon class MainWindow(QMainWindow): """主窗口类""" @@ -28,6 +29,11 @@ class MainWindow(QMainWindow): super().__init__() self.world = world self.world.main_window = self # 关键:让world对象能访问主窗口 + + # 初始化图标管理器并打印调试信息 + self.icon_manager = get_icon_manager() + print("🔧 图标管理器初始化完成") + self.icon_manager.debug_info() self.setStyleSheet(""" QMainWindow { @@ -325,7 +331,13 @@ class MainWindow(QMainWindow): def setupCenterWidget(self): """设置窗口基本属性""" self.setWindowTitle("引擎编辑器") - + # 使用图标管理器设置窗口图标 + app_icon = get_icon('app_logo') + if not app_icon.isNull(): + self.setWindowIcon(app_icon) + print("✅ 应用图标设置成功") + else: + print("⚠️ 应用图标设置失败,使用默认图标") # 使用自定义的 Panda3D 部件作为中央部件 self.pandaWidget = CustomPanda3DWidget(self.world) self.setCentralWidget(self.pandaWidget) @@ -425,9 +437,9 @@ class MainWindow(QMainWindow): # 选择工具 self.selectTool = QToolButton() - icon_path = self.get_icon_path("select_tool.png") - if icon_path and os.path.exists(icon_path): - self.selectTool.setIcon(QIcon(icon_path)) + select_icon = get_icon('select_tool', QSize(16, 16)) + if not select_icon.isNull(): + self.selectTool.setIcon(select_icon) else: self.selectTool.setText('选择') # 如果没有图标则显示文字 self.selectTool.setIconSize(QSize(16, 16)) @@ -439,9 +451,9 @@ class MainWindow(QMainWindow): # 移动工具 self.moveTool = QToolButton() - icon_path = self.get_icon_path("move_tool.png") - if icon_path and os.path.exists(icon_path): - self.moveTool.setIcon(QIcon(icon_path)) + move_icon = get_icon('move_tool', QSize(16, 16)) + if not move_icon.isNull(): + self.moveTool.setIcon(move_icon) else: self.moveTool.setText('移动') self.moveTool.setIconSize(QSize(16, 16)) @@ -453,9 +465,9 @@ class MainWindow(QMainWindow): # 旋转工具 self.rotateTool = QToolButton() - icon_path = self.get_icon_path("rotate_tool.png") - if icon_path and os.path.exists(icon_path): - self.rotateTool.setIcon(QIcon(icon_path)) + rotate_icon = get_icon('rotate_tool', QSize(16, 16)) + if not rotate_icon.isNull(): + self.rotateTool.setIcon(rotate_icon) else: self.rotateTool.setText('旋转') self.rotateTool.setIconSize(QSize(16, 16)) @@ -467,9 +479,9 @@ class MainWindow(QMainWindow): # 缩放工具 self.scaleTool = QToolButton() - icon_path = self.get_icon_path("scale_tool.png") - if icon_path and os.path.exists(icon_path): - self.scaleTool.setIcon(QIcon(icon_path)) + scale_icon = get_icon('scale_tool', QSize(16, 16)) + if not scale_icon.isNull(): + self.scaleTool.setIcon(scale_icon) else: self.scaleTool.setText('缩放') self.scaleTool.setIconSize(QSize(16, 16)) @@ -637,8 +649,12 @@ class MainWindow(QMainWindow): self.moveAction = self.toolsMenu.addAction('移动工具') self.rotateAction = self.toolsMenu.addAction('旋转工具') self.scaleAction = self.toolsMenu.addAction('缩放工具') + self.toolsMenu.addSeparator() self.sunsetAction = self.toolsMenu.addAction('光照编辑') self.pluginAction = self.toolsMenu.addAction('图形编辑') + # self.toolsMenu.addSeparator() + # self.iconManagerAction = self.toolsMenu.addAction('图标管理器') + # self.iconManagerAction.triggered.connect(self.onOpenIconManager) # 统一创建菜单 - 关键修改 self.createMenu = menubar.addMenu('创建') @@ -843,11 +859,21 @@ class MainWindow(QMainWindow): padding: 0px 0px; /* 增加内边距,提供更多的垂直空间 */ border-bottom: 0px solid #3a3a4a; } - QDockWidget::close-button, QDockWidget::float-button { + QDockWidget::close-button { background-color: #8b5cf6; border: none; icon-size: 8px; /* 调整图标大小 */ border-radius: 4px; /* 增加圆角 */ + right: 5px; + top: 2px; + } + QDockWidget::float-button { + background-color: #8b5cf6; + border: none; + icon-size: 8px; /* 调整图标大小 */ + border-radius: 4px; /* 增加圆角 */ + right: 25px; + top: 2px; } QDockWidget::close-button:hover, QDockWidget::float-button:hover { background-color: #7c3aed; /* 悬停时显示较亮的背景 */ @@ -887,11 +913,21 @@ class MainWindow(QMainWindow): padding: 0px 0px; /* 增加内边距,提供更多的垂直空间 */ border-bottom: 0px solid #3a3a4a; } - QDockWidget::close-button, QDockWidget::float-button { + QDockWidget::close-button { background-color: #8b5cf6; border: none; icon-size: 8px; /* 调整图标大小 */ border-radius: 4px; /* 增加圆角 */ + right: 5px; + top: 2px; + } + QDockWidget::float-button { + background-color: #8b5cf6; + border: none; + icon-size: 8px; /* 调整图标大小 */ + border-radius: 4px; /* 增加圆角 */ + right: 25px; + top: 2px; } QDockWidget::close-button:hover, QDockWidget::float-button:hover { background-color: #7c3aed; /* 悬停时显示较亮的背景 */ @@ -984,11 +1020,21 @@ class MainWindow(QMainWindow): padding: 0px 0px; /* 增加内边距,提供更多的垂直空间 */ border-bottom: 0px solid #3a3a4a; } - QDockWidget::close-button, QDockWidget::float-button { + QDockWidget::close-button { background-color: #8b5cf6; border: none; icon-size: 8px; /* 调整图标大小 */ border-radius: 4px; /* 增加圆角 */ + right: 5px; + top: 2px; + } + QDockWidget::float-button { + background-color: #8b5cf6; + border: none; + icon-size: 8px; /* 调整图标大小 */ + border-radius: 4px; /* 增加圆角 */ + right: 25px; + top: 2px; } QDockWidget::close-button:hover, QDockWidget::float-button:hover { background-color: #7c3aed; /* 悬停时显示较亮的背景 */ @@ -1086,11 +1132,21 @@ class MainWindow(QMainWindow): padding: 0px 0px; /* 增加内边距,提供更多的垂直空间 */ border-bottom: 0px solid #3a3a4a; } - QDockWidget::close-button, QDockWidget::float-button { + QDockWidget::close-button { background-color: #8b5cf6; border: none; icon-size: 8px; /* 调整图标大小 */ border-radius: 4px; /* 增加圆角 */ + right: 5px; + top: 2px; + } + QDockWidget::float-button { + background-color: #8b5cf6; + border: none; + icon-size: 8px; /* 调整图标大小 */ + border-radius: 4px; /* 增加圆角 */ + right: 25px; + top: 2px; } QDockWidget::close-button:hover, QDockWidget::float-button:hover { background-color: #7c3aed; /* 悬停时显示较亮的背景 */ @@ -1139,11 +1195,21 @@ class MainWindow(QMainWindow): padding: 0px 0px; /* 增加内边距,提供更多的垂直空间 */ border-bottom: 0px solid #3a3a4a; } - QDockWidget::close-button, QDockWidget::float-button { + QDockWidget::close-button { background-color: #8b5cf6; border: none; icon-size: 8px; /* 调整图标大小 */ border-radius: 4px; /* 增加圆角 */ + right: 5px; + top: 2px; + } + QDockWidget::float-button { + background-color: #8b5cf6; + border: none; + icon-size: 8px; /* 调整图标大小 */ + border-radius: 4px; /* 增加圆角 */ + right: 25px; + top: 2px; } QDockWidget::close-button:hover, QDockWidget::float-button:hover { background-color: #7c3aed; /* 悬停时显示较亮的背景 */ @@ -2494,6 +2560,16 @@ class MainWindow(QMainWindow): except Exception as e: QMessageBox.critical(self, "错误", f"卸载脚本时出错: {str(e)}") + def onOpenIconManager(self): + """打开图标管理器""" + try: + from ui.icon_manager_gui import show_icon_manager + self.icon_manager_dialog = show_icon_manager(self) + print("🎨 图标管理器已打开") + except Exception as e: + print(f"❌ 打开图标管理器失败: {e}") + QMessageBox.warning(self, "错误", f"打开图标管理器失败:\n{str(e)}") + def closeEvent(self, event): """处理窗口关闭事件""" try: diff --git a/ui/widgets.py b/ui/widgets.py index a2347cfb..16dd9d3c 100644 --- a/ui/widgets.py +++ b/ui/widgets.py @@ -2217,7 +2217,6 @@ class CustomTreeWidget(QTreeWidget): if not item: print(f"✅ Panda3D节点 '{node_name_for_logging}' 已清理并移除。UI树中未找到对应项。") return - try: # 2. 过滤受保护节点 node_type = item.data(0, Qt.UserRole + 1) @@ -2255,6 +2254,24 @@ class CustomTreeWidget(QTreeWidget): import traceback traceback.print_exc() + def clear_tree(self): + """清空UI树""" + print("Clear") + self.clear() + # 创建场景根节点 + sceneRoot = QTreeWidgetItem(self, ['场景']) + sceneRoot.setData(0, Qt.UserRole, self.world.render) + sceneRoot.setData(0, Qt.UserRole + 1, "SCENE_ROOT") + # 添加相机节点 + cameraItem = QTreeWidgetItem(sceneRoot, ['相机']) + cameraItem.setData(0, Qt.UserRole, self.world.cam) + cameraItem.setData(0, Qt.UserRole + 1, "CAMERA_NODE") + # 添加地板节点 + if hasattr(self.world, 'ground') and self.world.ground: + groundItem = QTreeWidgetItem(sceneRoot, ['地板']) + groundItem.setData(0, Qt.UserRole, self.world.ground) + groundItem.setData(0,Qt.UserRole + 1, "SCENE_NODE") + def _cleanup_panda_node_resources(self, panda_node): """一个集中的辅助函数,用于清理与Panda3D节点相关的所有资源。""" if not panda_node or panda_node.is_empty(): From 39a52b3953ebc56cd7eadc0ddfb3e4f4b3e9a843 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E6=A8=AA?= <2938139566@qq.com> Date: Wed, 17 Sep 2025 15:41:55 +0800 Subject: [PATCH 2/2] =?UTF-8?q?1.=E5=A2=9E=E5=8A=A0=E7=A2=B0=E6=92=9E?= =?UTF-8?q?=E9=9D=A2=E6=9D=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- RenderPipelineFile/config/daytime.yaml | 2 +- core/collision_manager.py | 29 +- ui/property_panel.py | 1086 +++++++++++++++++++++++- 3 files changed, 1106 insertions(+), 11 deletions(-) diff --git a/RenderPipelineFile/config/daytime.yaml b/RenderPipelineFile/config/daytime.yaml index 58bbc54e..ac5efef2 100644 --- a/RenderPipelineFile/config/daytime.yaml +++ b/RenderPipelineFile/config/daytime.yaml @@ -17,7 +17,7 @@ control_points: scattering: sun_intensity: [[[0.0000000000,0.0000000000],[0.0041666667,0.0000000000],[0.0083333333,0.0000000000],[0.0125000000,0.0000000000],[0.0166666667,0.0000000000],[0.0208333333,0.0000000000],[0.0250000000,0.0000000000],[0.0291666667,0.0000000000],[0.0333333333,0.0000000000],[0.0375000000,0.0000000000],[0.0416666667,0.0000000000],[0.0458333333,0.0000000000],[0.0500000000,0.0000000000],[0.0541666667,0.0000000000],[0.0583333333,0.0000000000],[0.0625000000,0.0000000000],[0.0666666667,0.0000000000],[0.0708333333,0.0000000000],[0.0750000000,0.0000000000],[0.0791666667,0.0000000000],[0.0833333333,0.0000000000],[0.0875000000,0.0000000000],[0.0916666667,0.0000000000],[0.0958333333,0.0000000000],[0.1000000000,0.0000000000],[0.1041666667,0.0000000000],[0.1083333333,0.0000000000],[0.1125000000,0.0000000000],[0.1166666667,0.0000000000],[0.1208333333,0.0000000000],[0.1250000000,0.0000000000],[0.1291666667,0.0000000000],[0.1333333333,0.0000000000],[0.1375000000,0.0000000000],[0.1416666667,0.0000000000],[0.1458333333,0.0000000000],[0.1500000000,0.0000000000],[0.1541666667,0.0000000000],[0.1583333333,0.0000028805],[0.1625000000,0.0003577724],[0.1666666667,0.0013331400],[0.1708333333,0.0029671803],[0.1750000000,0.0052963381],[0.1791666667,0.0083550556],[0.1833333333,0.0121755589],[0.1875000000,0.0167876159],[0.1916666667,0.0222183530],[0.1958333333,0.0284919947],[0.2000000000,0.0356297193],[0.2041666667,0.0436494349],[0.2083333333,0.0525656099],[0.2125000000,0.0623891610],[0.2166666667,0.0731272461],[0.2208333333,0.0847831708],[0.2250000000,0.0973563167],[0.2291666667,0.1108419698],[0.2333333333,0.1252313631],[0.2375000000,0.1405115250],[0.2416666667,0.1566653434],[0.2458333333,0.1736715009],[0.2500000000,0.1915046014],[0.2541666667,0.2101350464],[0.2583333333,0.2295292930],[0.2625000000,0.2496498145],[0.2666666667,0.2704552670],[0.2708333333,0.2919006662],[0.2750000000,0.3139375192],[0.2791666667,0.3365139497],[0.2833333333,0.3595750662],[0.2875000000,0.3830630359],[0.2916666667,0.4069173972],[0.2958333333,0.4310753462],[0.3000000000,0.4554720417],[0.3041666667,0.4800408236],[0.3083333333,0.5047136020],[0.3125000000,0.5294212108],[0.3166666667,0.5540936424],[0.3208333333,0.5786605298],[0.3250000000,0.6030514553],[0.3291666667,0.6271963182],[0.3333333333,0.6510256858],[0.3375000000,0.6744711982],[0.3416666667,0.6974659988],[0.3458333333,0.7199450163],[0.3500000000,0.7418453485],[0.3541666667,0.7631067095],[0.3583333333,0.7836717291],[0.3625000000,0.8034862953],[0.3666666667,0.8224999302],[0.3708333333,0.8406661079],[0.3750000000,0.8579425235],[0.3791666667,0.8742914270],[0.3833333333,0.8896799131],[0.3875000000,0.9040801386],[0.3916666667,0.9174695289],[0.3958333333,0.9298310650],[0.4000000000,0.9411533765],[0.4041666667,0.9514309312],[0.4083333333,0.9606641691],[0.4125000000,0.9688595571],[0.4166666667,0.9760296330],[0.4208333333,0.9821930708],[0.4250000000,0.9873746114],[0.4291666667,0.9916050060],[0.4333333333,0.9949209310],[0.4375000000,0.9973647924],[0.4416666667,0.9989845508],[0.4458333333,0.9998334497],[0.4500000000,0.9999696949],[0.4541666667,0.9994560801],[0.4583333333,0.9983595429],[0.4625000000,0.9967506613],[0.4666666667,0.9947030614],[0.4708333333,0.9922927758],[0.4750000000,0.9895975125],[0.4791666667,0.9866958610],[0.4833333333,0.9836664262],[0.4875000000,0.9805868867],[0.4916666667,0.9775330316],[0.4958333333,0.9745777179],[0.5000000000,0.9717898417],[0.5041666667,0.9692332877],[0.5083333333,0.9669658924],[0.5125000000,0.9650384806],[0.5089595376,0.9690650222],[0.5208333333,0.9623666659],[0.5250000000,0.9616814371],[0.5291666667,0.9614534423],[0.5333333333,0.9616877089],[0.5375000000,0.9623790807],[0.5416666667,0.9635123329],[0.5458333333,0.9650624244],[0.5500000000,0.9669949804],[0.5541666667,0.9692669864],[0.5583333333,0.9718275065],[0.5625000000,0.9746185969],[0.5666666667,0.9775762863],[0.5708333333,0.9806315864],[0.5750000000,0.9837115661],[0.5791666667,0.9867403433],[0.5833333333,0.9896401655],[0.5875000000,0.9923323562],[0.5916666667,0.9947382579],[0.5958333333,0.9967800977],[0.6000000000,0.9983817820],[0.6041666667,0.9994696263],[0.6083333333,0.9999730028],[0.6125000000,0.9998249266],[0.6166666667,0.9989625601],[0.6208333333,0.9973276624],[0.6250000000,0.9948669567],[0.6291666667,0.9915324664],[0.6333333333,0.9872817545],[0.6375000000,0.9820781426],[0.6416666667,0.9758908775],[0.6458333333,0.9686952146],[0.6500000000,0.9604725211],[0.6541666667,0.9512102537],[0.6583333333,0.9409019858],[0.6625000000,0.9295473441],[0.6666666667,0.9171518878],[0.6708333333,0.9037270619],[0.6750000000,0.8892899902],[0.6791666667,0.8738633008],[0.6833333333,0.8574749656],[0.6875000000,0.8401579787],[0.6916666667,0.8219502453],[0.6958333333,0.8028941798],[0.7000000000,0.7830364456],[0.7041666667,0.7624277344],[0.7083333333,0.7411222520],[0.7125000000,0.7191776044],[0.7166666667,0.6966542563],[0.7208333333,0.6736152714],[0.7250000000,0.6501259629],[0.7291666667,0.6262533880],[0.7333333333,0.6020661121],[0.7375000000,0.5776338043],[0.7416666667,0.5530267796],[0.7458333333,0.5283156992],[0.7500000000,0.5035711751],[0.7541666667,0.4788634341],[0.7583333333,0.4542618347],[0.7625000000,0.4298347613],[0.7666666667,0.4056490351],[0.7708333333,0.3817697830],[0.7750000000,0.3582600107],[0.7791666667,0.3351803495],[0.7833333333,0.3125888445],[0.7875000000,0.2905406366],[0.7916666667,0.2690876955],[0.7958333333,0.2482787388],[0.8000000000,0.2281588906],[0.8041666667,0.2087696425],[0.8083333333,0.1901486315],[0.8125000000,0.1723295359],[0.8166666667,0.1553419918],[0.8208333333,0.1392115328],[0.8250000000,0.1239595144],[0.8291666667,0.1096030703],[0.8333333333,0.0961551918],[0.8375000000,0.0836246599],[0.8416666667,0.0720161369],[0.8458333333,0.0613302273],[0.8500000000,0.0515635598],[0.8541666667,0.0427088803],[0.8583333333,0.0347551990],[0.8625000000,0.0276878920],[0.8666666667,0.0214889271],[0.8708333333,0.0161369711],[0.8750000000,0.0116076130],[0.8791666667,0.0078735477],[0.8833333333,0.0049047927],[0.8875000000,0.0026688977],[0.8916666667,0.0011311782],[0.8958333333,0.0002549473],[0.9000000000,0.0000000000],[0.9041666667,0.0000000000],[0.9083333333,0.0000000000],[0.9125000000,0.0000000000],[0.9166666667,0.0000000000],[0.9208333333,0.0000000000],[0.9250000000,0.0000000000],[0.9291666667,0.0000000000],[0.9333333333,0.0000000000],[0.9375000000,0.0000000000],[0.9416666667,0.0000000000],[0.9458333333,0.0000000000],[0.9500000000,0.0000000000],[0.9541666667,0.0000000000],[0.9583333333,0.0000000000],[0.9625000000,0.0000000000],[0.9666666667,0.0000000000],[0.9708333333,0.0000000000],[0.9750000000,0.0000000000],[0.9791666667,0.0000000000],[0.9833333333,0.0000000000],[0.9875000000,0.0000000000],[0.9916666667,0.0000000000],[0.9958333333,0.0000000000]]] sun_color: [[[0.5010435645,0.5818710306],[0.0433100000,0.8999700000],[0.8635787716,0.9130000000],[0.1785000000,0.8973600000],[0.8099800000,0.8651100000],[0.2360800000,0.7712700000],[0.6583432177,0.8485126184],[0.1266806142,0.9648102053],[0.9558541267,0.9090909091],[0.5568400771,0.7353760446]],[[0.5001318426,0.5160300000],[0.0572700000,0.6541600000],[0.2395000000,0.5976800000],[0.8104600000,0.6009000000],[0.6967400000,0.5483900000]],[[0.0862400000,0.4257800000],[0.4955600000,0.4033000000],[0.8234200000,0.4340200000]]] - sun_azimuth: [[[0.5000000000,0.5000000000]]] + sun_azimuth: [[[0.5000000000,0.4944444444]]] sun_altitude: [[[0.5000000000,0.8555555556]]] extinction: [[[0.4913294798,0.6378830084]]] volumetrics: diff --git a/core/collision_manager.py b/core/collision_manager.py index 041571bf..853ad45b 100644 --- a/core/collision_manager.py +++ b/core/collision_manager.py @@ -325,18 +325,29 @@ class CollisionManager: return CollisionSphere(center, kwargs.get('radius', radius)) elif shape_type == 'box': - # 创建包围盒 - min_point = bounds.getMin() - max_point = bounds.getMax() + # 创建自定义尺寸的包围盒 + width = kwargs.get('width', bounds.getMax().x - bounds.getMin().x) + length = kwargs.get('length', bounds.getMax().y - bounds.getMin().y) + height = kwargs.get('height', bounds.getMax().z - bounds.getMin().z) + + # 计算盒子的最小和最大点 + half_width = width / 2 + half_length = length / 2 + half_height = height / 2 + + min_point = Point3(center.x - half_width, center.y - half_length, center.z - half_height) + max_point = Point3(center.x + half_width, center.y + half_length, center.z + half_height) return CollisionBox(min_point, max_point) elif shape_type == 'capsule': - # 创建胶囊体(适合角色) - height = kwargs.get('height', (bounds.getMax().z - bounds.getMin().z)) - radius = kwargs.get('radius', min(bounds.getRadius() * 0.5, height * 0.3)) - point_a = Point3(center.x, center.y, bounds.getMin().z + radius) - point_b = Point3(center.x, center.y, bounds.getMax().z - radius) - return CollisionCapsule(point_a, point_b, radius) + # 创建自定义参数的胶囊体 + custom_height = kwargs.get('height', (bounds.getMax().z - bounds.getMin().z)) + custom_radius = kwargs.get('radius', min(bounds.getRadius() * 0.5, custom_height * 0.3)) + + # 计算胶囊体的两个端点 + point_a = Point3(center.x, center.y, center.z - custom_height/2 + custom_radius) + point_b = Point3(center.x, center.y, center.z + custom_height/2 - custom_radius) + return CollisionCapsule(point_a, point_b, custom_radius) elif shape_type == 'plane': # 创建平面(适合地面、墙面) diff --git a/ui/property_panel.py b/ui/property_panel.py index d7262ee6..063f1a25 100644 --- a/ui/property_panel.py +++ b/ui/property_panel.py @@ -1213,6 +1213,9 @@ class PropertyPanelManager: self.transform_group.setLayout(transform_layout) self._propertyLayout.addWidget(self.transform_group) + # 碰撞检测面板 + self._addCollisionPanel(model) + # 动画和太阳方位角面板 self._addAnimationPanel(model) self._addSunAzimuthPanel() @@ -8683,4 +8686,1085 @@ except Exception as e: if actor: actor.stop() actor.cleanup() - actor.removeNode() \ No newline at end of file + actor.removeNode() + + def _addCollisionPanel(self, model): + """添加碰撞检测面板""" + try: + # 创建碰撞检测组 + collision_group = QGroupBox("碰撞检测") + collision_layout = QGridLayout() + + # 检查模型是否已有碰撞 + has_collision = self._hasCollision(model) + + # 碰撞状态标签 + status_label = QLabel("状态:") + collision_layout.addWidget(status_label, 0, 0) + + # 状态文本(需要保存引用以便更新) + self.collision_status_text = QLabel("已启用" if has_collision else "未启用") + self.collision_status_text.setStyleSheet("color: green;" if has_collision else "color: red;") + collision_layout.addWidget(self.collision_status_text, 0, 1) + + # 形状选择标签(始终显示) + self.collision_shape_label = QLabel("碰撞形状:") + collision_layout.addWidget(self.collision_shape_label, 1, 0) + + # 形状选择下拉框(始终显示) + self.collision_shape_combo = QComboBox() + self.collision_shape_combo.addItems([ + "球形 (Sphere)", + "盒型 (Box)", + "胶囊体 (Capsule)", + "平面 (Plane)", + "自动选择 (Auto)" + ]) + collision_layout.addWidget(self.collision_shape_combo, 1, 1) + + # 保存布局引用,用于动态添加/移除控件 + self.collision_layout = collision_layout + self.collision_group = collision_group + + current_row = 2 # 下一行的索引 + + # 显示/隐藏切换按钮(只有有碰撞时才显示) + if has_collision: + # 检查碰撞的当前可见性 + is_collision_visible = self._isCollisionVisible(model) + + # 显示当前碰撞类型并设置为只读 + current_shape = self._getCurrentCollisionShape(model) + self._setComboToShape(current_shape) + self.collision_shape_combo.setEnabled(False) + + # 添加碰撞参数调整控件 + current_row = self._addCollisionParameterControls(model, collision_layout, current_row, current_shape) + + # 显示/隐藏切换按钮 + self.collision_visibility_button = QPushButton("隐藏碰撞" if is_collision_visible else "显示碰撞") + self.collision_visibility_button.clicked.connect(lambda: self._toggleCollisionVisibility(model)) + collision_layout.addWidget(self.collision_visibility_button, current_row, 0, 1, 2) + current_row += 1 + + # 移除碰撞按钮 + self.collision_button = QPushButton("移除碰撞") + self.collision_button.clicked.connect(lambda: self._removeCollisionAndUpdate(model)) + collision_layout.addWidget(self.collision_button, current_row, 0, 1, 2) + else: + # 如果没有碰撞,设置默认选择并允许编辑 + self.collision_shape_combo.setCurrentText("球形 (Sphere)") + self.collision_shape_combo.setEnabled(True) + + # 清理之前的参数控件 + self._clearCollisionParameterControls() + + # 隐藏显示/隐藏按钮 + if hasattr(self, 'collision_visibility_button'): + self.collision_visibility_button.setVisible(False) + + # 添加碰撞按钮 + self.collision_button = QPushButton("添加碰撞") + self.collision_button.clicked.connect(lambda: self._addCollisionAndUpdate(model)) + collision_layout.addWidget(self.collision_button, current_row, 0, 1, 2) + collision_group.setLayout(collision_layout) + self._propertyLayout.addWidget(collision_group) + + except Exception as e: + print(f"创建碰撞面板失败: {e}") + def _addCollisionParameterControls(self, model, layout, start_row, shape_type): + """添加碰撞参数调整控件""" + try: + current_row = start_row + + # 位置调整控件(所有类型都有) + pos_label = QLabel("位置偏移:") + layout.addWidget(pos_label, current_row, 0) + current_row += 1 + + # X, Y, Z 位置调整 + self.collision_pos_x = self._createCollisionSpinBox(-100, 100, 2) + self.collision_pos_y = self._createCollisionSpinBox(-100, 100, 2) + self.collision_pos_z = self._createCollisionSpinBox(-100, 100, 2) + + layout.addWidget(QLabel("X:"), current_row, 0) + layout.addWidget(self.collision_pos_x, current_row, 1) + current_row += 1 + + layout.addWidget(QLabel("Y:"), current_row, 0) + layout.addWidget(self.collision_pos_y, current_row, 1) + current_row += 1 + + layout.addWidget(QLabel("Z:"), current_row, 0) + layout.addWidget(self.collision_pos_z, current_row, 1) + current_row += 1 + + # 连接位置变化信号 + self.collision_pos_x.valueChanged.connect(lambda v: self._updateCollisionPosition(model, 'x', v)) + self.collision_pos_y.valueChanged.connect(lambda v: self._updateCollisionPosition(model, 'y', v)) + self.collision_pos_z.valueChanged.connect(lambda v: self._updateCollisionPosition(model, 'z', v)) + + # 根据形状类型添加特定参数 + if shape_type == 'sphere': + current_row = self._addSphereParameters(model, layout, current_row) + elif shape_type == 'box': + current_row = self._addBoxParameters(model, layout, current_row) + elif shape_type == 'capsule': + current_row = self._addCapsuleParameters(model, layout, current_row) + elif shape_type == 'plane': + current_row = self._addPlaneParameters(model, layout, current_row) + + # 获取并设置当前参数值 + self._loadCurrentCollisionParameters(model, shape_type) + + return current_row + + except Exception as e: + print(f"添加碰撞参数控件失败: {e}") + return start_row + + def _createCollisionSpinBox(self, min_val, max_val, decimals=2): + """创建碰撞参数调整用的SpinBox""" + spinbox = QDoubleSpinBox() + spinbox.setRange(min_val, max_val) + spinbox.setDecimals(decimals) + spinbox.setSingleStep(0.1) + return spinbox + + def _addSphereParameters(self, model, layout, start_row): + """添加球形碰撞参数""" + current_row = start_row + + # 半径调整 + radius_label = QLabel("半径:") + layout.addWidget(radius_label, current_row, 0) + + self.collision_radius = self._createCollisionSpinBox(0.1, 100, 2) + self.collision_radius.valueChanged.connect(lambda v: self._updateSphereRadius(model, v)) + layout.addWidget(self.collision_radius, current_row, 1) + current_row += 1 + + return current_row + + def _addBoxParameters(self, model, layout, start_row): + """添加盒型碰撞参数""" + current_row = start_row + + size_label = QLabel("尺寸:") + layout.addWidget(size_label, current_row, 0) + current_row += 1 + + # 宽度、长度、高度 + self.collision_width = self._createCollisionSpinBox(0.1, 100, 2) + self.collision_length = self._createCollisionSpinBox(0.1, 100, 2) + self.collision_height = self._createCollisionSpinBox(0.1, 100, 2) + + layout.addWidget(QLabel("宽度:"), current_row, 0) + layout.addWidget(self.collision_width, current_row, 1) + current_row += 1 + + layout.addWidget(QLabel("长度:"), current_row, 0) + layout.addWidget(self.collision_length, current_row, 1) + current_row += 1 + + layout.addWidget(QLabel("高度:"), current_row, 0) + layout.addWidget(self.collision_height, current_row, 1) + current_row += 1 + + # 连接信号 + self.collision_width.valueChanged.connect(lambda v: self._updateBoxSize(model, 'width', v)) + self.collision_length.valueChanged.connect(lambda v: self._updateBoxSize(model, 'length', v)) + self.collision_height.valueChanged.connect(lambda v: self._updateBoxSize(model, 'height', v)) + + return current_row + + def _addCapsuleParameters(self, model, layout, start_row): + """添加胶囊体碰撞参数""" + current_row = start_row + + # 半径和高度 + radius_label = QLabel("半径:") + layout.addWidget(radius_label, current_row, 0) + + self.collision_capsule_radius = self._createCollisionSpinBox(0.1, 100, 2) + self.collision_capsule_radius.valueChanged.connect(lambda v: self._updateCapsuleRadius(model, v)) + layout.addWidget(self.collision_capsule_radius, current_row, 1) + current_row += 1 + + height_label = QLabel("高度:") + layout.addWidget(height_label, current_row, 0) + + self.collision_capsule_height = self._createCollisionSpinBox(0.1, 100, 2) + self.collision_capsule_height.valueChanged.connect(lambda v: self._updateCapsuleHeight(model, v)) + layout.addWidget(self.collision_capsule_height, current_row, 1) + current_row += 1 + + return current_row + + def _addPlaneParameters(self, model, layout, start_row): + """添加平面碰撞参数""" + current_row = start_row + + # 法向量 + normal_label = QLabel("法向量:") + layout.addWidget(normal_label, current_row, 0) + current_row += 1 + + self.collision_normal_x = self._createCollisionSpinBox(-1, 1, 2) + self.collision_normal_y = self._createCollisionSpinBox(-1, 1, 2) + self.collision_normal_z = self._createCollisionSpinBox(-1, 1, 2) + + layout.addWidget(QLabel("Nx:"), current_row, 0) + layout.addWidget(self.collision_normal_x, current_row, 1) + current_row += 1 + + layout.addWidget(QLabel("Ny:"), current_row, 0) + layout.addWidget(self.collision_normal_y, current_row, 1) + current_row += 1 + + layout.addWidget(QLabel("Nz:"), current_row, 0) + layout.addWidget(self.collision_normal_z, current_row, 1) + current_row += 1 + + # 连接信号 + self.collision_normal_x.valueChanged.connect(lambda v: self._updatePlaneNormal(model, 'x', v)) + self.collision_normal_y.valueChanged.connect(lambda v: self._updatePlaneNormal(model, 'y', v)) + self.collision_normal_z.valueChanged.connect(lambda v: self._updatePlaneNormal(model, 'z', v)) + + return current_row + def _hasCollision(self, model): + """检查模型是否已有碰撞体""" + try: + from panda3d.core import CollisionNode + + # 检查模型及其子节点是否有碰撞节点 + collision_nodes = model.findAllMatches("**/+CollisionNode") + has_collision = collision_nodes.getNumPaths() > 0 + + print(f"碰撞检查:模型 {model.getName()} - {'有' if has_collision else '无'}碰撞 (找到{collision_nodes.getNumPaths()}个碰撞节点)") + + return has_collision + except Exception as e: + print(f"检查碰撞失败: {e}") + return False + + def _isCollisionVisible(self, model): + """检查碰撞是否可见""" + try: + collision_nodes = model.findAllMatches("**/+CollisionNode") + for collision_np in collision_nodes: + # 检查碰撞节点是否隐藏 + return not collision_np.isHidden() + return False + except Exception as e: + print(f"检查碰撞可见性失败: {e}") + return False + + def _toggleCollisionVisibility(self, model): + """切换碰撞可见性""" + try: + collision_nodes = model.findAllMatches("**/+CollisionNode") + is_visible = self._isCollisionVisible(model) + + for collision_np in collision_nodes: + if is_visible: + collision_np.hide() + print(f"隐藏碰撞:{model.getName()}") + else: + collision_np.show() + print(f"显示碰撞:{model.getName()}") + + # 立即更新按钮状态 + self._updateCollisionVisibilityButton(model) + + except Exception as e: + print(f"切换碰撞可见性失败: {e}") + + def _updateCollisionVisibilityButton(self, model): + """更新碰撞可见性按钮状态""" + try: + if hasattr(self, 'collision_visibility_button'): + is_visible = self._isCollisionVisible(model) + self.collision_visibility_button.setText("隐藏碰撞" if is_visible else "显示碰撞") + print(f"更新可见性按钮:{model.getName()} - {'可见' if is_visible else '隐藏'}") + except Exception as e: + print(f"更新碰撞可见性按钮失败: {e}") + + def _getCurrentCollisionShape(self, model): + """获取当前模型的碰撞形状类型""" + try: + from panda3d.core import CollisionNode, CollisionSphere, CollisionBox, CollisionCapsule, CollisionPlane + + collision_nodes = model.findAllMatches("**/+CollisionNode") + for collision_np in collision_nodes: + collision_node = collision_np.node() + if collision_node.getNumSolids() > 0: + solid = collision_node.getSolid(0) + solid_type = type(solid).__name__ + + if solid_type == "CollisionSphere": + return "sphere" + elif solid_type == "CollisionBox": + return "box" + elif solid_type == "CollisionCapsule": + return "capsule" + elif solid_type == "CollisionPlane": + return "plane" + + return "sphere" # 默认返回球形 + except Exception as e: + print(f"获取碰撞形状失败: {e}") + return "sphere" + + def _setComboToShape(self, shape_type): + """根据形状类型设置下拉框选择""" + shape_map = { + "sphere": "球形 (Sphere)", + "box": "盒型 (Box)", + "capsule": "胶囊体 (Capsule)", + "plane": "平面 (Plane)", + "auto": "自动选择 (Auto)" + } + + if shape_type in shape_map: + self.collision_shape_combo.setCurrentText(shape_map[shape_type]) + else: + self.collision_shape_combo.setCurrentText("球形 (Sphere)") + + def _getSelectedCollisionShape(self): + """获取选中的碰撞形状类型""" + if hasattr(self, 'collision_shape_combo'): + shape_text = self.collision_shape_combo.currentText() + # 从显示文本中提取形状类型 + if "球形" in shape_text: + return 'sphere' + elif "盒型" in shape_text: + return 'box' + elif "胶囊体" in shape_text: + return 'capsule' + elif "平面" in shape_text: + return 'plane' + elif "自动选择" in shape_text: + return 'auto' + return 'sphere' # 默认返回球形 + + def _addCollisionAndUpdate(self, model): + """添加指定形状的碰撞体并更新界面""" + try: + # 防止重复调用 + if getattr(self, '_adding_collision', False): + print("正在添加碰撞,跳过重复调用") + return + + self._adding_collision = True + + if hasattr(self.world, 'scene_manager'): + # 获取选中的碰撞形状 + shape_type = self._getSelectedCollisionShape() + + # 参考scene_manager的setupCollision方法 + from panda3d.core import CollisionNode, BitMask32 + + # 创建碰撞节点 + cNode = CollisionNode(f'modelCollision_{model.getName()}') + + # 设置碰撞掩码 + cNode.setIntoCollideMask(BitMask32.bit(2)) # 用于鼠标选择 + + # 如果启用了模型间碰撞检测,添加额外的掩码 + if (hasattr(self.world, 'collision_manager') and + hasattr(self.world.collision_manager, 'model_collision_enabled') and + self.world.collision_manager.model_collision_enabled): + current_mask = cNode.getIntoCollideMask() + model_collision_mask = BitMask32.bit(6) # MODEL_COLLISION + cNode.setIntoCollideMask(current_mask | model_collision_mask) + + # 创建指定形状的碰撞体 + if hasattr(self.world, 'collision_manager'): + collision_shape = self.world.collision_manager.createCollisionShape(model, shape_type) + else: + # 回退方案:创建简单球体 + from panda3d.core import CollisionSphere, Point3 + bounds = model.getBounds() + if bounds.isEmpty(): + collision_shape = CollisionSphere(Point3(0, 0, 0), 1.0) + else: + center = bounds.getCenter() + radius = bounds.getRadius() + if radius <= 0: + radius = 1.0 + collision_shape = CollisionSphere(center, radius) + + cNode.addSolid(collision_shape) + + # 将碰撞节点附加到模型上 + cNodePath = model.attachNewNode(cNode) + + # 根据调试设置决定是否显示碰撞体 + if hasattr(self.world, 'debug_collision') and self.world.debug_collision: + cNodePath.show() + else: + cNodePath.hide() + + # 为模型添加碰撞相关标签 + model.setTag("has_collision", "true") + model.setTag("collision_shape", shape_type) + if 'radius' in locals(): + model.setTag("collision_radius", str(radius)) + + print(f"✅ 为模型 {model.getName()} 添加了 {shape_type} 碰撞体") + + # 简单更新按钮状态,不调用完整的状态更新 + if hasattr(self, 'collision_button'): + self.collision_button.setText("移除碰撞") + try: + self.collision_button.clicked.disconnect() + except: + pass + self.collision_button.clicked.connect(lambda: self._removeCollisionAndUpdate(model)) + + if hasattr(self, 'collision_status_text'): + self.collision_status_text.setText("已启用") + self.collision_status_text.setStyleSheet("color: green;") + + if hasattr(self, 'collision_shape_combo'): + self.collision_shape_combo.setEnabled(False) + + else: + print("场景管理器未初始化") + + except Exception as e: + print(f"添加碰撞失败: {e}") + import traceback + traceback.print_exc() + finally: + # 确保标志被重置 + self._adding_collision = False + + def _removeCollisionAndUpdate(self, model): + """移除模型的碰撞体并更新界面""" + try: + # 查找并移除碰撞节点 + collision_nodes = model.findAllMatches("**/+CollisionNode") + for collision_np in collision_nodes: + collision_np.removeNode() + + print(f"移除了模型 {model.getName()} 的碰撞体") + + # 重置状态并更新界面 + self._previous_collision_state = True # 强制刷新 + self._updateCollisionPanelState(model) + + except Exception as e: + print(f"移除碰撞失败: {e}") + + def _updateCollisionPanelState(self, model): + """更新碰撞面板状态""" + try: + # 防止重复调用 + if getattr(self, '_updating_collision_panel', False): + return + + self._updating_collision_panel = True + + if hasattr(self, 'collision_button') and hasattr(self, 'collision_status_text') and hasattr(self, 'collision_shape_combo'): + has_collision = self._hasCollision(model) + + # 更新状态文本和颜色 + self.collision_status_text.setText("已启用" if has_collision else "未启用") + self.collision_status_text.setStyleSheet("color: green;" if has_collision else "color: red;") + + if has_collision: + # 有碰撞:显示移除按钮,下拉框变为只读并显示当前类型 + self.collision_button.setText("移除碰撞") + + # 先断开所有连接,再重新连接 + try: + self.collision_button.clicked.disconnect() + except: + pass + self.collision_button.clicked.connect(lambda: self._removeCollisionAndUpdate(model)) + + # 获取并显示当前碰撞类型,设置为只读 + current_shape = self._getCurrentCollisionShape(model) + self._setComboToShape(current_shape) + self.collision_shape_combo.setEnabled(False) + + # 确保参数控件存在 - 只在没有参数控件时才添加 + if not hasattr(self, 'collision_pos_x'): + print("添加碰撞参数控件") + self._addParameterControlsToExistingPanel(model, current_shape) + + # 显示/隐藏按钮状态更新 + if not hasattr(self, 'collision_visibility_button'): + # 创建可见性按钮 + self._addVisibilityButtonToExistingPanel(model) + else: + self.collision_visibility_button.setVisible(True) + self._updateCollisionVisibilityButton(model) + + print(f"碰撞面板状态更新:有碰撞 - {current_shape}") + + else: + # 无碰撞:显示添加按钮,下拉框变为可编辑 + self.collision_button.setText("添加碰撞") + + # 先断开所有连接,再重新连接 + try: + self.collision_button.clicked.disconnect() + except: + pass + self.collision_button.clicked.connect(lambda: self._addCollisionAndUpdate(model)) + + # 恢复为可编辑状态 + self.collision_shape_combo.setEnabled(True) + + # 隐藏并清理参数控件 - 只在有参数控件时才清理 + if hasattr(self, 'collision_pos_x'): + print("清理碰撞参数控件") + self._hideCollisionParameterControls() + + # 隐藏显示/隐藏按钮 + if hasattr(self, 'collision_visibility_button'): + self.collision_visibility_button.setVisible(False) + + print(f"碰撞面板状态更新:无碰撞 - 可编辑") + + except Exception as e: + print(f"更新碰撞面板状态失败: {e}") + import traceback + traceback.print_exc() + finally: + # 确保标志被重置 + self._updating_collision_panel = False + + def _loadCurrentCollisionParameters(self, model, shape_type): + """加载当前碰撞参数到界面""" + try: + collision_nodes = model.findAllMatches("**/+CollisionNode") + for collision_np in collision_nodes: + collision_node = collision_np.node() + if collision_node.getNumSolids() > 0: + solid = collision_node.getSolid(0) + + # 获取碰撞节点的位置 + pos = collision_np.getPos() + self.collision_pos_x.setValue(pos.x) + self.collision_pos_y.setValue(pos.y) + self.collision_pos_z.setValue(pos.z) + + if shape_type == 'sphere': + self._loadSphereParameters(solid) + elif shape_type == 'box': + self._loadBoxParameters(solid) + elif shape_type == 'capsule': + self._loadCapsuleParameters(solid) + elif shape_type == 'plane': + self._loadPlaneParameters(solid) + break + + except Exception as e: + print(f"加载碰撞参数失败: {e}") + + def _loadSphereParameters(self, solid): + """加载球形参数""" + try: + from panda3d.core import CollisionSphere + if isinstance(solid, CollisionSphere): + radius = solid.getRadius() + self.collision_radius.setValue(radius) + except Exception as e: + print(f"加载球形参数失败: {e}") + + def _loadBoxParameters(self, solid): + """加载盒型参数""" + try: + from panda3d.core import CollisionBox + if isinstance(solid, CollisionBox): + min_point = solid.getMin() + max_point = solid.getMax() + width = max_point.x - min_point.x + length = max_point.y - min_point.y + height = max_point.z - min_point.z + + self.collision_width.setValue(width) + self.collision_length.setValue(length) + self.collision_height.setValue(height) + except Exception as e: + print(f"加载盒型参数失败: {e}") + + def _loadCapsuleParameters(self, solid): + """加载胶囊体参数""" + try: + from panda3d.core import CollisionCapsule + if isinstance(solid, CollisionCapsule): + radius = solid.getRadius() + point_a = solid.getPointA() + point_b = solid.getPointB() + height = (point_b - point_a).length() + + self.collision_capsule_radius.setValue(radius) + self.collision_capsule_height.setValue(height) + except Exception as e: + print(f"加载胶囊体参数失败: {e}") + + def _loadPlaneParameters(self, solid): + """加载平面参数""" + try: + from panda3d.core import CollisionPlane + if isinstance(solid, CollisionPlane): + plane = solid.getPlane() + normal = plane.getNormal() + + self.collision_normal_x.setValue(normal.x) + self.collision_normal_y.setValue(normal.y) + self.collision_normal_z.setValue(normal.z) + except Exception as e: + print(f"加载平面参数失败: {e}") + + def _updateCollisionPosition(self, model, axis, value): + """更新碰撞位置""" + try: + collision_nodes = model.findAllMatches("**/+CollisionNode") + for collision_np in collision_nodes: + current_pos = collision_np.getPos() + if axis == 'x': + collision_np.setPos(value, current_pos.y, current_pos.z) + elif axis == 'y': + collision_np.setPos(current_pos.x, value, current_pos.z) + elif axis == 'z': + collision_np.setPos(current_pos.x, current_pos.y, value) + print(f"更新碰撞位置 {axis}: {value}") + break + except Exception as e: + print(f"更新碰撞位置失败: {e}") + + def _updateSphereRadius(self, model, radius): + """更新球形半径""" + try: + self._recreateCollisionShape(model, 'sphere', radius=radius) + print(f"更新球形半径: {radius}") + except Exception as e: + print(f"更新球形半径失败: {e}") + + def _updateBoxSize(self, model, dimension, value): + """更新盒型尺寸""" + try: + # 获取当前所有尺寸 + width = self.collision_width.value() if hasattr(self, 'collision_width') else value + length = self.collision_length.value() if hasattr(self, 'collision_length') else value + height = self.collision_height.value() if hasattr(self, 'collision_height') else value + + self._recreateCollisionShape(model, 'box', width=width, length=length, height=height) + print(f"更新盒型{dimension}: {value}") + except Exception as e: + print(f"更新盒型尺寸失败: {e}") + + def _updateCapsuleRadius(self, model, radius): + """更新胶囊体半径""" + try: + height = self.collision_capsule_height.value() if hasattr(self, 'collision_capsule_height') else 2.0 + self._recreateCollisionShape(model, 'capsule', radius=radius, height=height) + print(f"更新胶囊体半径: {radius}") + except Exception as e: + print(f"更新胶囊体半径失败: {e}") + + def _updateCapsuleHeight(self, model, height): + """更新胶囊体高度""" + try: + radius = self.collision_capsule_radius.value() if hasattr(self, 'collision_capsule_radius') else 0.5 + self._recreateCollisionShape(model, 'capsule', radius=radius, height=height) + print(f"更新胶囊体高度: {height}") + except Exception as e: + print(f"更新胶囊体高度失败: {e}") + + def _updatePlaneNormal(self, model, axis, value): + """更新平面法向量""" + try: + # 获取当前法向量 + normal_x = self.collision_normal_x.value() if hasattr(self, 'collision_normal_x') else 0 + normal_y = self.collision_normal_y.value() if hasattr(self, 'collision_normal_y') else 0 + normal_z = self.collision_normal_z.value() if hasattr(self, 'collision_normal_z') else 1 + + self._recreateCollisionShape(model, 'plane', normal=(normal_x, normal_y, normal_z)) + print(f"更新平面法向量 {axis}: {value}") + except Exception as e: + print(f"更新平面法向量失败: {e}") + + def _recreateCollisionShape(self, model, shape_type, **kwargs): + """重新创建碰撞形状(保持位置和可见性)""" + try: + # 保存当前状态 + collision_nodes = model.findAllMatches("**/+CollisionNode") + if not collision_nodes: + return + + collision_np = collision_nodes[0] + current_pos = collision_np.getPos() + is_visible = not collision_np.isHidden() + + # 移除旧的碰撞体 + collision_np.removeNode() + + # 创建新的碰撞体 + from panda3d.core import CollisionNode, BitMask32 + cNode = CollisionNode(f'modelCollision_{model.getName()}') + cNode.setIntoCollideMask(BitMask32.bit(2)) + + # 创建新形状 + if hasattr(self.world, 'collision_manager'): + collision_shape = self.world.collision_manager.createCollisionShape(model, shape_type, **kwargs) + else: + # 回退方案 + from panda3d.core import CollisionSphere, Point3 + collision_shape = CollisionSphere(Point3(0, 0, 0), kwargs.get('radius', 1.0)) + + cNode.addSolid(collision_shape) + + # 重新附加并恢复状态 + new_collision_np = model.attachNewNode(cNode) + new_collision_np.setPos(current_pos) + + if is_visible: + new_collision_np.show() + else: + new_collision_np.hide() + + except Exception as e: + print(f"重新创建碰撞形状失败: {e}") + import traceback + traceback.print_exc() + + def _clearCollisionParameterControls(self): + """清理碰撞参数控件""" + try: + # 位置控件 + for attr in ['collision_pos_x', 'collision_pos_y', 'collision_pos_z']: + if hasattr(self, attr): + widget = getattr(self, attr) + if widget and widget.parent(): + widget.setParent(None) + widget.deleteLater() + delattr(self, attr) + + # 球形参数控件 + if hasattr(self, 'collision_radius'): + if self.collision_radius and self.collision_radius.parent(): + self.collision_radius.setParent(None) + self.collision_radius.deleteLater() + delattr(self, 'collision_radius') + + # 盒型参数控件 + for attr in ['collision_width', 'collision_length', 'collision_height']: + if hasattr(self, attr): + widget = getattr(self, attr) + if widget and widget.parent(): + widget.setParent(None) + widget.deleteLater() + delattr(self, attr) + + # 胶囊体参数控件 + for attr in ['collision_capsule_radius', 'collision_capsule_height']: + if hasattr(self, attr): + widget = getattr(self, attr) + if widget and widget.parent(): + widget.setParent(None) + widget.deleteLater() + delattr(self, attr) + + # 平面参数控件 + for attr in ['collision_normal_x', 'collision_normal_y', 'collision_normal_z']: + if hasattr(self, attr): + widget = getattr(self, attr) + if widget and widget.parent(): + widget.setParent(None) + widget.deleteLater() + delattr(self, attr) + + print("清理碰撞参数控件完成") + + except Exception as e: + print(f"清理碰撞参数控件失败: {e}") + + def _refreshCollisionPanel(self, model): + """刷新整个碰撞面板(简化版)""" + try: + print("使用简化的面板刷新") + # 直接调用状态更新,不删除面板 + self._updateCollisionPanelState(model) + + except Exception as e: + print(f"刷新碰撞面板失败: {e}") + import traceback + traceback.print_exc() + + def _addParameterControlsToExistingPanel(self, model, shape_type): + """向现有面板添加参数控件""" + try: + if not hasattr(self, 'collision_layout'): + return + + layout = self.collision_layout + + # 首先清理可能存在的旧控件 + self._hideCollisionParameterControls() + + # 找到插入位置(在按钮之前) + current_row = 2 + + # 位置调整控件 + pos_label = QLabel("位置偏移:") + pos_label.setVisible(True) # 确保可见 + layout.addWidget(pos_label, current_row, 0) + current_row += 1 + + # X, Y, Z 位置调整 + self.collision_pos_x = self._createCollisionSpinBox(-100, 100, 2) + self.collision_pos_y = self._createCollisionSpinBox(-100, 100, 2) + self.collision_pos_z = self._createCollisionSpinBox(-100, 100, 2) + + x_label = QLabel("X:") + x_label.setVisible(True) + layout.addWidget(x_label, current_row, 0) + self.collision_pos_x.setVisible(True) + layout.addWidget(self.collision_pos_x, current_row, 1) + current_row += 1 + + y_label = QLabel("Y:") + y_label.setVisible(True) + layout.addWidget(y_label, current_row, 0) + self.collision_pos_y.setVisible(True) + layout.addWidget(self.collision_pos_y, current_row, 1) + current_row += 1 + + z_label = QLabel("Z:") + z_label.setVisible(True) + layout.addWidget(z_label, current_row, 0) + self.collision_pos_z.setVisible(True) + layout.addWidget(self.collision_pos_z, current_row, 1) + current_row += 1 + + # 连接位置变化信号 + self.collision_pos_x.valueChanged.connect(lambda v: self._updateCollisionPosition(model, 'x', v)) + self.collision_pos_y.valueChanged.connect(lambda v: self._updateCollisionPosition(model, 'y', v)) + self.collision_pos_z.valueChanged.connect(lambda v: self._updateCollisionPosition(model, 'z', v)) + + # 根据形状类型添加特定参数 + if shape_type == 'sphere': + current_row = self._addSphereParametersToExisting(model, layout, current_row) + elif shape_type == 'box': + current_row = self._addBoxParametersToExisting(model, layout, current_row) + elif shape_type == 'capsule': + current_row = self._addCapsuleParametersToExisting(model, layout, current_row) + elif shape_type == 'plane': + current_row = self._addPlaneParametersToExisting(model, layout, current_row) + + # 重新定位按钮 + self._repositionButtons(current_row) + + # 加载参数值 + self._loadCurrentCollisionParameters(model, shape_type) + + except Exception as e: + print(f"添加参数控件到现有面板失败: {e}") + import traceback + traceback.print_exc() + + def _addSphereParametersToExisting(self, model, layout, start_row): + """向现有面板添加球形参数""" + current_row = start_row + + radius_label = QLabel("半径:") + radius_label.setVisible(True) + layout.addWidget(radius_label, current_row, 0) + + self.collision_radius = self._createCollisionSpinBox(0.1, 100, 2) + self.collision_radius.setVisible(True) + self.collision_radius.valueChanged.connect(lambda v: self._updateSphereRadius(model, v)) + layout.addWidget(self.collision_radius, current_row, 1) + current_row += 1 + + return current_row + + def _addBoxParametersToExisting(self, model, layout, start_row): + """向现有面板添加盒型参数""" + current_row = start_row + + size_label = QLabel("尺寸:") + size_label.setVisible(True) + layout.addWidget(size_label, current_row, 0) + current_row += 1 + + self.collision_width = self._createCollisionSpinBox(0.1, 100, 2) + self.collision_length = self._createCollisionSpinBox(0.1, 100, 2) + self.collision_height = self._createCollisionSpinBox(0.1, 100, 2) + + width_label = QLabel("宽度:") + width_label.setVisible(True) + layout.addWidget(width_label, current_row, 0) + self.collision_width.setVisible(True) + layout.addWidget(self.collision_width, current_row, 1) + current_row += 1 + + length_label = QLabel("长度:") + length_label.setVisible(True) + layout.addWidget(length_label, current_row, 0) + self.collision_length.setVisible(True) + layout.addWidget(self.collision_length, current_row, 1) + current_row += 1 + + height_label = QLabel("高度:") + height_label.setVisible(True) + layout.addWidget(height_label, current_row, 0) + self.collision_height.setVisible(True) + layout.addWidget(self.collision_height, current_row, 1) + current_row += 1 + + # 连接信号 + self.collision_width.valueChanged.connect(lambda v: self._updateBoxSize(model, 'width', v)) + self.collision_length.valueChanged.connect(lambda v: self._updateBoxSize(model, 'length', v)) + self.collision_height.valueChanged.connect(lambda v: self._updateBoxSize(model, 'height', v)) + + return current_row + + def _addCapsuleParametersToExisting(self, model, layout, start_row): + """向现有面板添加胶囊体参数""" + current_row = start_row + + radius_label = QLabel("半径:") + layout.addWidget(radius_label, current_row, 0) + + self.collision_capsule_radius = self._createCollisionSpinBox(0.1, 100, 2) + self.collision_capsule_radius.valueChanged.connect(lambda v: self._updateCapsuleRadius(model, v)) + layout.addWidget(self.collision_capsule_radius, current_row, 1) + current_row += 1 + + height_label = QLabel("高度:") + layout.addWidget(height_label, current_row, 0) + + self.collision_capsule_height = self._createCollisionSpinBox(0.1, 100, 2) + self.collision_capsule_height.valueChanged.connect(lambda v: self._updateCapsuleHeight(model, v)) + layout.addWidget(self.collision_capsule_height, current_row, 1) + current_row += 1 + + return current_row + + def _addPlaneParametersToExisting(self, model, layout, start_row): + """向现有面板添加平面参数""" + current_row = start_row + + normal_label = QLabel("法向量:") + layout.addWidget(normal_label, current_row, 0) + current_row += 1 + + self.collision_normal_x = self._createCollisionSpinBox(-1, 1, 2) + self.collision_normal_y = self._createCollisionSpinBox(-1, 1, 2) + self.collision_normal_z = self._createCollisionSpinBox(-1, 1, 2) + + layout.addWidget(QLabel("Nx:"), current_row, 0) + layout.addWidget(self.collision_normal_x, current_row, 1) + current_row += 1 + + layout.addWidget(QLabel("Ny:"), current_row, 0) + layout.addWidget(self.collision_normal_y, current_row, 1) + current_row += 1 + + layout.addWidget(QLabel("Nz:"), current_row, 0) + layout.addWidget(self.collision_normal_z, current_row, 1) + current_row += 1 + + # 连接信号 + self.collision_normal_x.valueChanged.connect(lambda v: self._updatePlaneNormal(model, 'x', v)) + self.collision_normal_y.valueChanged.connect(lambda v: self._updatePlaneNormal(model, 'y', v)) + self.collision_normal_z.valueChanged.connect(lambda v: self._updatePlaneNormal(model, 'z', v)) + + return current_row + + def _addVisibilityButtonToExistingPanel(self, model): + """向现有面板添加可见性按钮""" + try: + if hasattr(self, 'collision_layout'): + layout = self.collision_layout + is_collision_visible = self._isCollisionVisible(model) + + # 找到合适的行位置 + current_row = layout.rowCount() + + self.collision_visibility_button = QPushButton("隐藏碰撞" if is_collision_visible else "显示碰撞") + self.collision_visibility_button.clicked.connect(lambda: self._toggleCollisionVisibility(model)) + layout.addWidget(self.collision_visibility_button, current_row - 1, 0, 1, 2) + + except Exception as e: + print(f"添加可见性按钮失败: {e}") + + def _repositionButtons(self, new_row): + """重新定位按钮位置""" + try: + if hasattr(self, 'collision_layout'): + layout = self.collision_layout + + # 移动可见性按钮 + if hasattr(self, 'collision_visibility_button'): + layout.addWidget(self.collision_visibility_button, new_row, 0, 1, 2) + new_row += 1 + + # 移动主按钮 + if hasattr(self, 'collision_button'): + layout.addWidget(self.collision_button, new_row, 0, 1, 2) + + except Exception as e: + print(f"重新定位按钮失败: {e}") + + def _hideCollisionParameterControls(self): + """隐藏碰撞参数控件(保留按钮)""" + try: + # 清理属性引用,但保留按钮 + param_attrs = [ + 'collision_pos_x', 'collision_pos_y', 'collision_pos_z', + 'collision_radius', + 'collision_width', 'collision_length', 'collision_height', + 'collision_capsule_radius', 'collision_capsule_height', + 'collision_normal_x', 'collision_normal_y', 'collision_normal_z' + ] + + # 隐藏并删除参数控件 + for attr in param_attrs: + if hasattr(self, attr): + widget = getattr(self, attr) + if widget: + widget.setVisible(False) + widget.setParent(None) + widget.deleteLater() + delattr(self, attr) + + # 同时清理可能的标签控件 + if hasattr(self, 'collision_layout'): + layout = self.collision_layout + + # 收集需要移除的控件(不包括基本控件和按钮) + widgets_to_remove = [] + + for i in range(layout.rowCount()): + if i >= 2: # 从第3行开始检查 + for j in range(layout.columnCount()): + item = layout.itemAtPosition(i, j) + if item: + widget = item.widget() + if widget and hasattr(widget, 'text'): + # 检查是否是参数相关的标签 + text = widget.text() + if any(keyword in text for keyword in ['位置偏移', 'X:', 'Y:', 'Z:', '半径:', '尺寸:', '宽度:', '长度:', '高度:', '法向量:', 'Nx:', 'Ny:', 'Nz:']): + widgets_to_remove.append(widget) + + # 移除参数标签 + for widget in widgets_to_remove: + widget.setVisible(False) + widget.setParent(None) + widget.deleteLater() + + print("隐藏碰撞参数控件完成(保留按钮)") + + except Exception as e: + print(f"隐藏碰撞参数控件失败: {e}") + import traceback + traceback.print_exc() \ No newline at end of file