Complete M11 Creator Studio and M12 Trust & Safety for PC5
checkpoint / checkpoint (push) Failing after 3h14m9s

- Creator Studio: Visual behavior & property inspector, GLB model importer, binary persistence
- Trust & Safety: 18+ TOS confirmation (DOB removed), creator guidelines & briefing card on join, in-game reporting HUD
- Test Harness: Add --filter, --skip-backend, --skip-godot to run_checkpoint_tests.py
This commit is contained in:
2026-09-17 23:13:57 +00:00
parent 920354705a
commit 7ecf0d53bd
30 changed files with 2099 additions and 157 deletions
+189 -1
View File
@@ -4,13 +4,26 @@ extends VBoxContainer
## `PalettePanel`). Lists every id in an `EditorObjectCatalog` and emits
## which one the player picked; `InGameEditor` remembers it as the pending
## `catalog_id` for the next `ObjectPlaceTool` placement.
##
## Creator Studio feature: In-game .glb/.gltf 3D Model Importer.
## Creators can import custom 3D models from disk, validate them via GLTFDocument,
## and place them directly into their worlds.
signal object_selected(catalog_id: String)
signal model_imported(catalog_id: String)
var _list: ItemList
var _catalog: EditorObjectCatalog = null
var _ids: Array[String] = []
var _btn_import_model: Button
var _import_panel: VBoxContainer
var _model_name_edit: LineEdit
var _model_path_edit: LineEdit
var _btn_browse: Button
var _file_dialog: FileDialog
var _import_status_label: Label
func _init() -> void:
name = "PalettePanel"
@@ -33,10 +46,82 @@ func _ready() -> void:
_list = ItemList.new()
_list.size_flags_vertical = Control.SIZE_EXPAND_FILL
_list.custom_minimum_size = Vector2(0, 180)
_list.custom_minimum_size = Vector2(0, 160)
_list.item_selected.connect(_on_item_selected)
add_child(_list)
_setup_import_ui()
func _setup_import_ui() -> void:
_btn_import_model = Button.new()
_btn_import_model.text = "+ Import .glb Model"
_btn_import_model.add_theme_color_override("font_color", Color(0.35, 0.85, 1.0))
_btn_import_model.pressed.connect(_on_import_button_pressed)
add_child(_btn_import_model)
_import_panel = VBoxContainer.new()
_import_panel.visible = false
add_child(_import_panel)
var title := Label.new()
title.text = "Import Custom Model (.glb)"
title.add_theme_font_size_override("font_size", 12)
title.add_theme_color_override("font_color", Color(1.0, 0.9, 0.4))
_import_panel.add_child(title)
var name_row := HBoxContainer.new()
var name_lbl := Label.new()
name_lbl.text = "Name:"
name_lbl.custom_minimum_size = Vector2(50, 0)
name_row.add_child(name_lbl)
_model_name_edit = LineEdit.new()
_model_name_edit.placeholder_text = "e.g. Vintage Chair"
_model_name_edit.size_flags_horizontal = Control.SIZE_EXPAND_FILL
name_row.add_child(_model_name_edit)
_import_panel.add_child(name_row)
var path_row := HBoxContainer.new()
var path_lbl := Label.new()
path_lbl.text = "Path:"
path_lbl.custom_minimum_size = Vector2(50, 0)
path_row.add_child(path_lbl)
_model_path_edit = LineEdit.new()
_model_path_edit.placeholder_text = "Path to .glb or .gltf"
_model_path_edit.size_flags_horizontal = Control.SIZE_EXPAND_FILL
path_row.add_child(_model_path_edit)
_btn_browse = Button.new()
_btn_browse.text = "..."
_btn_browse.pressed.connect(_on_browse_pressed)
path_row.add_child(_btn_browse)
_import_panel.add_child(path_row)
_import_status_label = Label.new()
_import_status_label.text = ""
_import_status_label.add_theme_font_size_override("font_size", 10)
_import_panel.add_child(_import_status_label)
var btn_row := HBoxContainer.new()
var do_import_btn := Button.new()
do_import_btn.text = "Import"
do_import_btn.size_flags_horizontal = Control.SIZE_EXPAND_FILL
do_import_btn.pressed.connect(_on_do_import_pressed)
btn_row.add_child(do_import_btn)
var cancel_btn := Button.new()
cancel_btn.text = "Cancel"
cancel_btn.pressed.connect(_on_cancel_import_pressed)
btn_row.add_child(cancel_btn)
_import_panel.add_child(btn_row)
_file_dialog = FileDialog.new()
_file_dialog.file_mode = FileDialog.FILE_MODE_OPEN_FILE
_file_dialog.access = FileDialog.ACCESS_FILESYSTEM
_file_dialog.filters = ["*.glb ; GLTF Binary Files", "*.gltf ; GLTF Scene Files"]
_file_dialog.file_selected.connect(_on_file_selected)
add_child(_file_dialog)
func set_catalog(catalog: EditorObjectCatalog) -> void:
_catalog = catalog
@@ -51,6 +136,8 @@ func _refresh() -> void:
for id in _catalog.get_all_ids():
var entry := _catalog.resolve(id)
var label: String = entry.get("display_name", id)
if entry.get("category", "") == "custom":
label = "★ " + label
_list.add_item(label)
_ids.append(id)
@@ -66,3 +153,104 @@ func get_selected_catalog_id() -> String:
if selected.is_empty():
return ""
return _ids[selected[0]]
func _on_import_button_pressed() -> void:
_import_panel.visible = true
_btn_import_model.visible = false
_import_status_label.text = ""
func _on_cancel_import_pressed() -> void:
_import_panel.visible = false
_btn_import_model.visible = true
func _on_browse_pressed() -> void:
_file_dialog.popup_centered(Vector2i(600, 400))
func _on_file_selected(path: String) -> void:
_model_path_edit.text = path
if _model_name_edit.text.strip_edges().is_empty():
_model_name_edit.text = path.get_file().get_basename().capitalize()
func _on_do_import_pressed() -> void:
var path := _model_path_edit.text.strip_edges()
var display_name := _model_name_edit.text.strip_edges()
if path.is_empty():
_import_status_label.text = "Error: Please select a file path."
_import_status_label.add_theme_color_override("font_color", Color(1.0, 0.4, 0.4))
return
if display_name.is_empty():
display_name = path.get_file().get_basename().capitalize()
var cat_id := import_model_file(path, display_name)
if cat_id.is_empty():
_import_status_label.text = "Error: Failed to import/validate model."
_import_status_label.add_theme_color_override("font_color", Color(1.0, 0.4, 0.4))
return
_import_panel.visible = false
_btn_import_model.visible = true
_model_name_edit.text = ""
_model_path_edit.text = ""
## Import a 3D model (.glb/.gltf), validate it, copy to user custom assets,
## and register it into the active EditorObjectCatalog.
func import_model_file(file_path: String, display_name: String, category: String = "custom") -> String:
if not FileAccess.file_exists(file_path):
push_error("PalettePanel: File does not exist: %s" % file_path)
return ""
# Validate model using GLTFDocument
var gltf := GLTFDocument.new()
var state := GLTFState.new()
var err := gltf.append_from_file(file_path, state)
if err != OK:
push_error("PalettePanel: GLTFDocument failed to parse file (%d): %s" % [err, file_path])
return ""
# Copy to user custom assets directory
var dir := DirAccess.open("user://")
if dir != null:
if not dir.dir_exists("custom_assets"):
dir.make_dir("custom_assets")
var filename := file_path.get_file()
var target_path := "user://custom_assets/%s" % filename
var copy_err := DirAccess.copy_absolute(file_path, target_path)
if copy_err != OK:
# If direct copy failed, use original path
target_path = file_path
# Generate clean unique catalog id
var clean_slug := display_name.to_lower().replace(" ", "_").replace("-", "_")
var regex := RegEx.new()
regex.compile("[^a-z0-9_]")
clean_slug = regex.sub(clean_slug, "", true)
if clean_slug.is_empty():
clean_slug = "model"
var cat_id := "custom_%s" % clean_slug
var catalog_entry: Dictionary = {
"asset_id": target_path,
"type": "prop",
"display_name": display_name,
"category": category
}
if _catalog != null:
_catalog.register_custom_object(cat_id, catalog_entry)
_refresh()
# Select the newly imported model
for i in range(_ids.size()):
if _ids[i] == cat_id:
_list.select(i)
object_selected.emit(cat_id)
break
model_imported.emit(cat_id)
return cat_id