From b09dd703af65a93d8bf37cfb51bb5202996a80e3 Mon Sep 17 00:00:00 2001 From: min Date: Mon, 8 Jun 2026 21:38:08 +0300 Subject: [PATCH] =?UTF-8?q?feat(rbxl):=20GUI=20mode=20+=20=D0=BF=D1=80?= =?UTF-8?q?=D0=B5=D0=B4=D1=83=D0=BF=D1=80=D0=B5=D0=B6=D0=B4=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D0=B5=20=D0=BF=D1=80=D0=BE=20=D0=B1=D0=BE=D0=BB=D1=8C?= =?UTF-8?q?=D1=88=D0=B8=D0=B5=20=D0=BA=D0=B0=D1=80=D1=82=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Robloxity (20402 Part, 278 скриптов, 295 BillboardGui, 0.1 FPS) показал: 1. Большие карты могут зависнуть студию навсегда. 2. BillboardGui/SurfaceGui (вывески, табло) рендерятся в 3D-сцене и при 200+ штук убивают FPS. Фиксы: 1. Предупреждение в модалке если parts > 5000 (жёлтое) или > 15000 (красное "может зависнуть"). Подсказка про режимы. 2. Новая опция guiMode (показывается если GUI > 50 элементов): - 'all' — все, как было. - 'screen-only' (рекомендуется) — только ScreenGui HUD, BillboardGui/SurfaceGui удаляются. - 'skip' — без GUI совсем. 3. converter.py: маркирует элемент полем gui_container_kind: 'screen' / 'billboard' / 'surface'. 4. app.py: _apply_gui_mode() фильтрует scene.gui[] по режиму. Deploy app.py + converter.py на VM 130. Robloxity рекомендуем импортировать со screen-only — Карта Robloxity будет работать в 5-10× быстрее без вывесок города. --- .../src/__pycache__/converter.cpython-314.pyc | Bin 61603 -> 62031 bytes rbxl-importer/src/app.py | 28 +++++++ rbxl-importer/src/converter.py | 10 +++ src/api/rbxlImporterApi.js | 2 + src/components/RbxlImportModal.jsx | 70 +++++++++++++++++- 5 files changed, 109 insertions(+), 1 deletion(-) diff --git a/rbxl-importer/src/__pycache__/converter.cpython-314.pyc b/rbxl-importer/src/__pycache__/converter.cpython-314.pyc index 85731e35eb01785f877cf9c839d011f6b6a0ddc1..052a1c96b4820fbb1f85834126a1eef78c5af2c9 100644 GIT binary patch delta 2243 zcmZuzdr(wW7(ZuUEXzLbzL8~FK$c|{S`kXc7nnJwk#KUZY3pNQS0j1cE;3WuG$}UO zRFnBRfiulOuW9Thc&+TA;DaJ3l!bB)%^<~cdbrd~+DvNaTv)1^&L6+?`(F2a-#O=g z_r5lwJbg)NoNhGe5FHm%d=Je((P_NuG-ZfYkJJ{_)CmpZmWUXTZaSZ9S(HtEu0M&c zA)~r@jKMx_oJ}9uT%N=%Xg##S{=xVh>MOeiV6;TrnNg`U398glXtIhV^ zK`T7>LW)wgsuZ@pYlBZ)9n4ollv9yk2RA$11)>%sG^A;)79){*F_{U;RW-G>RSh9A zEb5q?)Yv3$3{^+`Dd6t(=X2}Fo!4(_s#(9eranBR428pD8FdxU(Q3q{41!EN50!V@ zLFiPOhE&Y6QB6&`xw)C}cxM0~gm*ggl5%o)F8)fN1%sW-=SQ`HVb0ZhfBO?1Tf6V+ zClBI#FP!!X;qgr=sxUFt)-JmufvJPy=i{bSH z2XP;)mMSTKTRMuDOLYgak~_yLQHqg`qMxPrEj;mI8MSqPn1^vtx^n0c)?8pIkHDKB zSK=W2`SDNqL22}p0bJO}^ov+j4TT}K*w9oT7G>{JYVxbar>szSOupf0Q#mchg%_3oTt4njOgqg&Z)l+68C2(r_7! zb@}ijX-fA#jPHcwM>nd7MKD6oF^^Y5C>^cT3#N5Q;CNHyZ6@>Fx|y>r2!&r~(yBPT za<6Uo8){97Y~0=KfV2}fgNbm(3Yr!jf&s1`cQS3jL@bk9WBHn(dE5ih`$%jF3#*2W za4wWJr$J_u4W^v1XPJs=@obc1ViwH2dD71yuWW|G9$SWNG|?P!K2;}%z%73dp`aVK z9{0H{K~F-zsyMNzU?qzM8wl-=)X8$YCd$>xh%leO&L&XTn_Bf1T6$>Fw!BXy|*%bEJSO4Fp978C8NGh4ac@ST4`XtXId4NC9!hTrseg?FB=2K6bjGWFQmHEK{tmyUN0_LGl>@r>A zHYoqxrwkAyRKA}EZAW!1lNpoIg>v-F@n~hyI_Bnr%wCV?doBrIV2lrgcyb;-1N%>U@h$1=lSSAzWMOTw zx}kncsHQ$5$_)~N$v9&RneJN#?M#&WO)q1o8T3PPU$*8f zqrX96Up97v5VOI9eT%2YsB*|S(T*eap{m+Qc*yvl#i6V39u1<`xX}sbQ>l=5Dqkb_ zq$ARrQwy>3?365MIlav8^~by=QEy4iyCCXaaHz3+>7chf`O@2Upc_64-ahS;#y?LYQJr44t>_nr1(4-9e5g#N_B(3Dm4>bIux6U7s8&{ zN~*sSYr*)2wC$T@1-=2#p8EmorN;Bq6q%k~4oyDn-=J%_6mz>rKG4yh0{NHVS|%JDnWkqSgYi$mwGD86 z UN&005oH~XW#@9)KU$-d!1{q0x5C8xG delta 1993 zcmZuySxi({7(Qoan2niX_C=PFbyzzrgF>saxKS&0yx>*a(S(9FwJPHk+ZS7{wy7qq z5`SB1V+ux1poy|vYO#ufJBT8VRRL|mrAUlA#0T4&YR|dj5Sw})zVn@b|L<9D?;Y{p zVX-DfqgEm^3MGyeBOT3}M;5H*Ro1U|WjcuT&yDB_Qihb_F##Loeqlar49p#I0XgR+ zw&h|N=8ZTJ1{&kA1)evi!>HX12lrW^V7~#ha=FBgSPbrRBOKapg6G>pp}i>->!7R2 zDVS_%R$vKiZH^QrDZtm91ez9WRFZ`If<)&=DD+a;jS*TaAuJ&w0o*N4%)!Q%L}+T6 zBlgLiFqz+k^Whi16?a2r>wLmHTRZV=e)CUQti4Kn2gKa6&V7=AnSDCeBRmFfZ?Nb9gLn}XL)p_3SqE_(Yr(ePE*iPu?wLHRI2i7mLhK$t68?4 zuGz4qnK^o*-9lFjhunIosj)--$;8PTMjMRbRTsbWpXi35W;}X=5DmG_P|#$+Rv13% zh}O7B*K`zPM(5SC+Hq^OV6BBRw-Fjoh1fKt4xCD)!Ql;iYX?G}aJX>F5vlV;2IUJ~ z!OUJgQ|L92(^KX)*@HbDf6=m7z zK*)(03u7hK?^$9@R-sJMdzi7YAp|G9s#zirXOTM=%1R6%KRYo#f|U|kBr78r#mWiV zSp~s~OfbL`w0b3HGVfUiFe94QkIh#IG?La2}eVVa7i_@o|nvOb&d{-T;UptH*%o5DL;_H<+XGh8@71^0on zCm#RJr}t!G`1nFkwv8Jkxw#t@ZqsXBMA?1H@+iAWp^h$v_u(96J@CoJ7^#oa2eAKQ z3^v2YZ%lCW;&gl&qI-=nvv-c_DN(OA6L0#8?e3i_6`qLi`QLlo{FW?|n#on?OfYw-9M6TO zAtx5W&`_sE$$d_@Z4-shC~T(C1MS0gT4CF6(7hA3`vI)Fm5=ps@YW#}{SgL!d{3;f z`gXF4zTkm(`$;M6yFDs;hGF}iTAT*XyT|Y(czAacE`c?F@VFG>N8ZLPRE>O_^Na?+ zLoyDCt17+RG@AP%h4WOgk}~=~< None: snd['url'] = asset_map[rid] +def _apply_gui_mode(project_data: dict, mode: str) -> None: + """Фильтрует scene.gui[] по режиму. + + 'all' — оставить всё (default). + 'screen-only' — оставить только ScreenGui-HUD, удалить billboard/surface. + Карты с 200+ BillboardGui (Robloxity) перестают тормозить. + 'skip' — удалить gui[] совсем. + """ + scene = project_data.get('scene', {}) + if mode == 'skip': + scene['gui'] = [] + return + if mode == 'screen-only': + gui = scene.get('gui', []) + scene['gui'] = [g for g in gui + if g.get('gui_container_kind', 'screen') == 'screen'] + return + # 'all' — без изменений + + def _apply_scripts_mode(project_data: dict, mode: str) -> None: """Применяет режим scripts_mode к проекту. diff --git a/rbxl-importer/src/converter.py b/rbxl-importer/src/converter.py index 3478cd8..ce15ca2 100644 --- a/rbxl-importer/src/converter.py +++ b/rbxl-importer/src/converter.py @@ -825,9 +825,13 @@ class Converter: if not hasattr(self, '_screen_gui_refs'): self._screen_gui_refs = set() self._screen_gui_enabled = {} + self._screen_gui_kind = {} # ref → 'screen' | 'billboard' | 'surface' self._screen_gui_refs.add(inst.referent) enabled = inst.properties.get('Enabled', True) self._screen_gui_enabled[inst.referent] = bool(enabled) if enabled is not None else True + # Сохраняем тип контейнера — потом отфильтруем 3D-GUI если выбрано screen-only + kind = {'ScreenGui': 'screen', 'BillboardGui': 'billboard', 'SurfaceGui': 'surface'}.get(inst.class_name, 'screen') + self._screen_gui_kind[inst.referent] = kind def _gui_parent_id(self, parent_ref) -> Optional[str]: if parent_ref is None: @@ -921,12 +925,14 @@ class Converter: # элемент тоже невидим. parent_ref = inst.parent_referent screen_enabled = True + container_kind = 'screen' # default if hasattr(self, '_screen_gui_refs'): cur = parent_ref depth = 0 while cur is not None and depth < 50: if cur in self._screen_gui_refs: screen_enabled = self._screen_gui_enabled.get(cur, True) + container_kind = self._screen_gui_kind.get(cur, 'screen') break # Поиск родителя cur в instances (если есть) cur_inst = self.model.by_referent.get(cur) if hasattr(self, 'model') else None @@ -979,6 +985,10 @@ class Converter: 'imageAsset': None, 'zIndex': int(props.get('ZIndex', 1) or 1), 'origin': 'roblox-' + cls.lower(), + # 'screen' — обычный HUD; 'billboard' — 3D-табличка над частью; + # 'surface' — на грани Part. Last 2 рендерятся в 3D-сцене и + # сильно тормозят если их сотни. + 'gui_container_kind': container_kind, } scene['gui'].append(element) diff --git a/src/api/rbxlImporterApi.js b/src/api/rbxlImporterApi.js index fa7b762..9fbe53a 100644 --- a/src/api/rbxlImporterApi.js +++ b/src/api/rbxlImporterApi.js @@ -63,6 +63,8 @@ export async function createRbxlProject(previewHash, title, opts = {}) { // 'enabled' — попытаться запустить (может вешать карту) // 'skip' — не импортировать совсем scripts_mode: opts.scriptsMode || 'disabled', + // 'all' (default) / 'screen-only' (только HUD) / 'skip' (без GUI) + gui_mode: opts.guiMode || 'all', }), }); if (!resp.ok) { diff --git a/src/components/RbxlImportModal.jsx b/src/components/RbxlImportModal.jsx index 9c3e5cd..2247a42 100644 --- a/src/components/RbxlImportModal.jsx +++ b/src/components/RbxlImportModal.jsx @@ -29,6 +29,10 @@ export default function RbxlImportModal({ open, onClose, currentUserId, onCreate // Режим скриптов: 'disabled' (импортнуть выключенными — для чтения), // 'enabled' (попытаться запустить — может вешать карту), 'skip' (удалить). const [scriptsMode, setScriptsMode] = useState('disabled'); + // Режим GUI: 'all' — все, 'screen-only' — только ScreenGui (HUD), + // 'skip' — не импортировать. Старые карты часто имеют 200+ BillboardGui + // (вывески города), что вешает рендер. + const [guiMode, setGuiMode] = useState('all'); const fileInputRef = useRef(null); if (!open) return null; @@ -49,6 +53,7 @@ export default function RbxlImportModal({ open, onClose, currentUserId, onCreate setFile(null); setReport(null); setPreviewHash(null); setTitle(''); setError(null); setAnalyzing(false); setCreating(false); setScriptsMode('disabled'); + setGuiMode('all'); }; const handleClose = () => { reset(); onClose?.(); }; @@ -92,7 +97,7 @@ export default function RbxlImportModal({ open, onClose, currentUserId, onCreate setCreating(true); setError(null); try { - const result = await createRbxlProject(previewHash, title, { scriptsMode }); + const result = await createRbxlProject(previewHash, title, { scriptsMode, guiMode }); onCreated?.(result); handleClose(); // редирект на редактор @@ -179,6 +184,29 @@ export default function RbxlImportModal({ open, onClose, currentUserId, onCreate + {report.primitives_created > 5000 && ( +
15000 ? '#5a1a1a' : '#4a3a1a', + borderRadius: 6, + border: '1px solid ' + (report.primitives_created > 15000 ? '#a55' : '#a85'), + }}> +
+ {report.primitives_created > 15000 + ? '🛑 Очень большая карта' + : '⚠️ Большая карта'} +
+
+ {report.primitives_created} Part'ов — это много. Студия может + {report.primitives_created > 15000 + ? ' зависнуть или работать с FPS < 1.' + : ' тормозить (FPS 10-30).'} + {' '}Рекомендуем выбрать ниже «Не импортировать скрипты» + чтобы хоть посмотреть геометрию. +
+
+ )} + {report.top_classes?.length > 0 && (
Что внутри (топ-25 классов) @@ -262,6 +290,46 @@ export default function RbxlImportModal({ open, onClose, currentUserId, onCreate )} + {(() => { + const guiCount = (report.top_classes || []) + .filter(c => /Gui|Frame|Label|Button|Image|Text/.test(c.class)) + .reduce((s, c) => s + c.count, 0); + if (guiCount < 50) return null; + return ( +
+
+ Что делать с GUI ({guiCount}+ элементов)? +
+
+ В этой карте много GUI-элементов (BillboardGui — вывески, табло). + Они сильно тормозят рендер если их сотни. +
+ {['all', 'screen-only', 'skip'].map((m) => ( + + ))} +
+ ); + })()} +