4150 lines
81 KiB
GDScript
4150 lines
81 KiB
GDScript
@tool
|
||
extends VBoxContainer
|
||
|
||
|
||
enum HeightOffsetMode {
|
||
WORLD_Y,
|
||
SURFACE_NORMAL,
|
||
}
|
||
|
||
|
||
signal selected_scene_changed(scene: PackedScene)
|
||
signal scene_palette_changed(scenes: Array)
|
||
|
||
signal brush_enabled_changed(enabled: bool)
|
||
signal brush_radius_changed(radius: float)
|
||
signal instances_per_click_changed(count: int)
|
||
signal minimum_spacing_changed(spacing: float)
|
||
signal random_y_rotation_changed(enabled: bool)
|
||
signal align_to_surface_changed(enabled: bool)
|
||
|
||
signal maximum_slope_changed(value: float)
|
||
|
||
signal random_scale_enabled_changed(enabled: bool)
|
||
signal minimum_scale_changed(value: float)
|
||
signal maximum_scale_changed(value: float)
|
||
|
||
signal height_offset_changed(value: float)
|
||
signal height_offset_mode_changed(mode: int)
|
||
|
||
signal lod_settings_changed(
|
||
enabled: bool,
|
||
lod1_scene: PackedScene,
|
||
lod2_scene: PackedScene,
|
||
lod1_distance: float,
|
||
lod2_distance: float,
|
||
cull_distance: float
|
||
)
|
||
|
||
signal auto_lod_settings_changed(
|
||
enabled: bool,
|
||
lod_bias: float,
|
||
cull_distance: float
|
||
)
|
||
signal auto_lod_import_requested(
|
||
scene_path: String,
|
||
enabled: bool
|
||
)
|
||
|
||
|
||
const PALETTE_CONFIG_PATH := "user://world_brush_palette.cfg"
|
||
|
||
const PALETTE_SECTION := "scene_palette"
|
||
const CONFIG_SCENE_PATHS := "scene_paths"
|
||
const CONFIG_ACTIVE_SCENE_PATH := "active_scene_path"
|
||
|
||
const SETTINGS_SECTION := "brush_settings"
|
||
|
||
const SCENE_SETTINGS_SECTION := "scene_brush_settings"
|
||
const CONFIG_SCENE_SETTINGS := "profiles"
|
||
|
||
const CONFIG_BRUSH_RADIUS := "brush_radius"
|
||
const CONFIG_INSTANCES_PER_STAMP := "instances_per_stamp"
|
||
const CONFIG_MINIMUM_SPACING := "minimum_spacing"
|
||
|
||
const CONFIG_RANDOM_Y_ROTATION := "random_y_rotation"
|
||
const CONFIG_ALIGN_TO_SURFACE := "align_to_surface"
|
||
const CONFIG_MAXIMUM_SLOPE := "maximum_slope"
|
||
|
||
const CONFIG_RANDOM_SCALE_ENABLED := "random_scale_enabled"
|
||
const CONFIG_MINIMUM_SCALE := "minimum_scale"
|
||
const CONFIG_MAXIMUM_SCALE := "maximum_scale"
|
||
|
||
const CONFIG_HEIGHT_OFFSET := "height_offset"
|
||
const CONFIG_HEIGHT_OFFSET_MODE := "height_offset_mode"
|
||
|
||
const CONFIG_LOD_ENABLED := "lod_enabled"
|
||
const CONFIG_LOD1_SCENE_PATH := "lod1_scene_path"
|
||
const CONFIG_LOD2_SCENE_PATH := "lod2_scene_path"
|
||
const CONFIG_LOD1_DISTANCE := "lod1_distance"
|
||
const CONFIG_LOD2_DISTANCE := "lod2_distance"
|
||
const CONFIG_LOD_CULL_DISTANCE := "lod_cull_distance"
|
||
const CONFIG_AUTO_LOD_ENABLED := "auto_lod_enabled"
|
||
const CONFIG_AUTO_LOD_BIAS := "auto_lod_bias"
|
||
const CONFIG_AUTO_LOD_CULL_DISTANCE := "auto_lod_cull_distance"
|
||
|
||
const INTERFACE_SECTION := "interface"
|
||
const CONFIG_INTERFACE_LANGUAGE := "language"
|
||
|
||
const LANGUAGE_ENGLISH := "en"
|
||
const LANGUAGE_ARABIC := "ar"
|
||
|
||
|
||
@onready var title_label: Label = $TitleLabel
|
||
@onready var status_label: Label = $StatusLabel
|
||
|
||
|
||
var settings_scroll: ScrollContainer
|
||
var controls_container: VBoxContainer
|
||
var language_button: Button
|
||
|
||
var scenes_list: VBoxContainer
|
||
var palette_title_label: Label
|
||
var active_scene_label: Label
|
||
|
||
var previous_scene_button: Button
|
||
var next_scene_button: Button
|
||
var add_scene_button: Button
|
||
|
||
var brush_toggle: CheckButton
|
||
|
||
var brush_radius_label: Label
|
||
var radius_spin_box: SpinBox
|
||
|
||
var instances_label: Label
|
||
var instances_spin_box: SpinBox
|
||
|
||
var spacing_label: Label
|
||
var spacing_spin_box: SpinBox
|
||
|
||
var random_rotation_toggle: CheckButton
|
||
var align_surface_toggle: CheckButton
|
||
|
||
var maximum_slope_label: Label
|
||
var maximum_slope_spin_box: SpinBox
|
||
|
||
var height_offset_label: Label
|
||
var height_offset_spin_box: SpinBox
|
||
|
||
var height_offset_mode_label: Label
|
||
var height_offset_mode_option: OptionButton
|
||
var height_offset_help_label: Label
|
||
|
||
var random_scale_toggle: CheckButton
|
||
|
||
var minimum_scale_label: Label
|
||
var minimum_scale_spin_box: SpinBox
|
||
|
||
var maximum_scale_label: Label
|
||
var maximum_scale_spin_box: SpinBox
|
||
|
||
var lod_toggle: CheckButton
|
||
var lod_controls_container: VBoxContainer
|
||
var lod1_scene_label: Label
|
||
var lod1_picker: EditorResourcePicker
|
||
var lod2_scene_label: Label
|
||
var lod2_picker: EditorResourcePicker
|
||
var lod1_distance_label: Label
|
||
var lod1_distance_spin_box: SpinBox
|
||
var lod2_distance_label: Label
|
||
var lod2_distance_spin_box: SpinBox
|
||
var lod_cull_distance_label: Label
|
||
var lod_cull_distance_spin_box: SpinBox
|
||
var lod_help_label: Label
|
||
var auto_lod_toggle: CheckButton
|
||
var auto_lod_controls_container: VBoxContainer
|
||
var auto_lod_bias_label: Label
|
||
var auto_lod_bias_spin_box: SpinBox
|
||
var auto_lod_cull_label: Label
|
||
var auto_lod_cull_spin_box: SpinBox
|
||
var auto_lod_reimport_button: Button
|
||
var auto_lod_help_label: Label
|
||
|
||
|
||
var scene_rows: Array[Dictionary] = []
|
||
|
||
var active_picker: EditorResourcePicker
|
||
var selected_scene: PackedScene
|
||
|
||
|
||
var brush_enabled: bool = false
|
||
|
||
var brush_radius: float = 5.0
|
||
var instances_per_click: int = 5
|
||
var minimum_spacing: float = 1.0
|
||
|
||
var random_y_rotation: bool = true
|
||
var align_to_surface: bool = false
|
||
|
||
var maximum_slope_degrees: float = 85.0
|
||
|
||
var height_offset: float = 0.0
|
||
var height_offset_mode: int = HeightOffsetMode.SURFACE_NORMAL
|
||
|
||
var random_scale_enabled: bool = true
|
||
var minimum_scale: float = 0.8
|
||
var maximum_scale: float = 1.3
|
||
|
||
var lod_enabled: bool = false
|
||
var lod1_scene: PackedScene
|
||
var lod2_scene: PackedScene
|
||
var lod1_distance: float = 25.0
|
||
var lod2_distance: float = 60.0
|
||
var lod_cull_distance: float = 0.0
|
||
var auto_lod_enabled: bool = false
|
||
var auto_lod_bias: float = 1.0
|
||
var auto_lod_cull_distance: float = 0.0
|
||
|
||
|
||
var current_language: String = LANGUAGE_ENGLISH
|
||
var is_loading_configuration: bool = false
|
||
var is_applying_scene_settings: bool = false
|
||
|
||
var scene_brush_settings: Dictionary = {}
|
||
|
||
var last_status_source: String = ""
|
||
var last_status_display: String = ""
|
||
var is_applying_status_translation: bool = false
|
||
|
||
|
||
func _ready() -> void:
|
||
set_process(true)
|
||
|
||
_create_interface()
|
||
_load_saved_configuration()
|
||
|
||
_apply_language()
|
||
_update_scene_interface()
|
||
_update_controls()
|
||
|
||
call_deferred(
|
||
"_emit_loaded_state"
|
||
)
|
||
|
||
|
||
func _process(
|
||
_delta: float
|
||
) -> void:
|
||
_watch_external_status_message()
|
||
|
||
|
||
# =========================================================
|
||
# إنشاء الواجهة
|
||
# =========================================================
|
||
|
||
func _create_interface() -> void:
|
||
if is_instance_valid(
|
||
settings_scroll
|
||
):
|
||
return
|
||
|
||
settings_scroll = ScrollContainer.new()
|
||
settings_scroll.name = "BrushSettingsScroll"
|
||
|
||
settings_scroll.size_flags_horizontal = (
|
||
Control.SIZE_EXPAND_FILL
|
||
)
|
||
|
||
settings_scroll.size_flags_vertical = (
|
||
Control.SIZE_EXPAND_FILL
|
||
)
|
||
|
||
settings_scroll.horizontal_scroll_mode = (
|
||
ScrollContainer.SCROLL_MODE_DISABLED
|
||
)
|
||
|
||
settings_scroll.vertical_scroll_mode = (
|
||
ScrollContainer.SCROLL_MODE_AUTO
|
||
)
|
||
|
||
settings_scroll.follow_focus = true
|
||
settings_scroll.clip_contents = true
|
||
|
||
_insert_before_status(
|
||
settings_scroll
|
||
)
|
||
|
||
controls_container = VBoxContainer.new()
|
||
controls_container.name = "BrushControls"
|
||
|
||
controls_container.size_flags_horizontal = (
|
||
Control.SIZE_EXPAND_FILL
|
||
)
|
||
|
||
controls_container.size_flags_vertical = (
|
||
Control.SIZE_SHRINK_BEGIN
|
||
)
|
||
|
||
settings_scroll.add_child(
|
||
controls_container
|
||
)
|
||
|
||
_create_language_selector()
|
||
_create_scene_palette()
|
||
_create_auto_lod_controls()
|
||
_create_lod_controls()
|
||
_create_brush_toggle()
|
||
_create_radius_control()
|
||
_create_instances_control()
|
||
_create_spacing_control()
|
||
_create_random_rotation_control()
|
||
_create_align_surface_control()
|
||
_create_maximum_slope_control()
|
||
_create_height_offset_controls()
|
||
_create_random_scale_controls()
|
||
|
||
|
||
func _insert_before_status(
|
||
control: Control
|
||
) -> void:
|
||
var status_index := status_label.get_index()
|
||
|
||
add_child(
|
||
control
|
||
)
|
||
|
||
move_child(
|
||
control,
|
||
status_index
|
||
)
|
||
|
||
|
||
# =========================================================
|
||
# اللغة
|
||
# =========================================================
|
||
|
||
func _create_language_selector() -> void:
|
||
language_button = Button.new()
|
||
language_button.name = "LanguageButton"
|
||
|
||
language_button.size_flags_horizontal = (
|
||
Control.SIZE_EXPAND_FILL
|
||
)
|
||
|
||
language_button.pressed.connect(
|
||
_on_language_button_pressed
|
||
)
|
||
|
||
controls_container.add_child(
|
||
language_button
|
||
)
|
||
|
||
var separator := HSeparator.new()
|
||
separator.name = "LanguageSeparator"
|
||
|
||
controls_container.add_child(
|
||
separator
|
||
)
|
||
|
||
|
||
func _on_language_button_pressed() -> void:
|
||
if current_language == LANGUAGE_ENGLISH:
|
||
current_language = LANGUAGE_ARABIC
|
||
else:
|
||
current_language = LANGUAGE_ENGLISH
|
||
|
||
_apply_language()
|
||
_save_configuration()
|
||
|
||
if current_language == LANGUAGE_ARABIC:
|
||
_set_status(
|
||
"Interface language changed to Arabic."
|
||
)
|
||
else:
|
||
_set_status(
|
||
"Interface language changed to English."
|
||
)
|
||
|
||
|
||
func _apply_language() -> void:
|
||
var using_arabic := (
|
||
current_language == LANGUAGE_ARABIC
|
||
)
|
||
|
||
if using_arabic:
|
||
layout_direction = (
|
||
Control.LAYOUT_DIRECTION_RTL
|
||
)
|
||
|
||
status_label.horizontal_alignment = (
|
||
HORIZONTAL_ALIGNMENT_RIGHT
|
||
)
|
||
else:
|
||
layout_direction = (
|
||
Control.LAYOUT_DIRECTION_LTR
|
||
)
|
||
|
||
status_label.horizontal_alignment = (
|
||
HORIZONTAL_ALIGNMENT_LEFT
|
||
)
|
||
|
||
if is_instance_valid(title_label):
|
||
title_label.text = _tr(
|
||
"title"
|
||
)
|
||
|
||
if is_instance_valid(language_button):
|
||
if using_arabic:
|
||
language_button.text = "English"
|
||
|
||
language_button.tooltip_text = (
|
||
"Switch the World Brush interface to English."
|
||
)
|
||
else:
|
||
language_button.text = "العربية"
|
||
|
||
language_button.tooltip_text = (
|
||
"تغيير واجهة فرشاة العالم إلى العربية."
|
||
)
|
||
|
||
if is_instance_valid(palette_title_label):
|
||
palette_title_label.text = _tr(
|
||
"scene_palette"
|
||
)
|
||
|
||
if is_instance_valid(previous_scene_button):
|
||
previous_scene_button.text = _tr(
|
||
"previous"
|
||
)
|
||
|
||
previous_scene_button.tooltip_text = _tr(
|
||
"previous_tooltip"
|
||
)
|
||
|
||
if is_instance_valid(next_scene_button):
|
||
next_scene_button.text = _tr(
|
||
"next"
|
||
)
|
||
|
||
next_scene_button.tooltip_text = _tr(
|
||
"next_tooltip"
|
||
)
|
||
|
||
if is_instance_valid(add_scene_button):
|
||
add_scene_button.text = _tr(
|
||
"add_scene"
|
||
)
|
||
|
||
if is_instance_valid(lod_toggle):
|
||
lod_toggle.text = _tr("add_distant_scene")
|
||
|
||
if is_instance_valid(lod1_scene_label):
|
||
lod1_scene_label.text = _tr("medium_scene")
|
||
|
||
if is_instance_valid(lod2_scene_label):
|
||
lod2_scene_label.text = _tr("distant_scene")
|
||
|
||
if is_instance_valid(lod1_distance_label):
|
||
lod1_distance_label.text = _tr("medium_starts")
|
||
|
||
if is_instance_valid(lod2_distance_label):
|
||
lod2_distance_label.text = _tr("distant_starts")
|
||
|
||
if is_instance_valid(lod_cull_distance_label):
|
||
lod_cull_distance_label.text = _tr("hide_after")
|
||
|
||
if is_instance_valid(lod_help_label):
|
||
lod_help_label.text = _tr("lod_help")
|
||
|
||
if is_instance_valid(auto_lod_toggle):
|
||
auto_lod_toggle.text = _tr("auto_lod")
|
||
|
||
if is_instance_valid(auto_lod_bias_label):
|
||
auto_lod_bias_label.text = _tr("auto_lod_bias")
|
||
|
||
if is_instance_valid(auto_lod_cull_label):
|
||
auto_lod_cull_label.text = _tr("auto_lod_hide_after")
|
||
|
||
if is_instance_valid(auto_lod_reimport_button):
|
||
auto_lod_reimport_button.text = _tr("auto_lod_reimport")
|
||
|
||
if is_instance_valid(auto_lod_help_label):
|
||
auto_lod_help_label.text = _tr("auto_lod_help")
|
||
|
||
if is_instance_valid(brush_toggle):
|
||
brush_toggle.text = _tr(
|
||
"enable_brush"
|
||
)
|
||
|
||
if is_instance_valid(brush_radius_label):
|
||
brush_radius_label.text = _tr(
|
||
"brush_radius"
|
||
)
|
||
|
||
if is_instance_valid(instances_label):
|
||
instances_label.text = _tr(
|
||
"instances_per_stamp"
|
||
)
|
||
|
||
if is_instance_valid(spacing_label):
|
||
spacing_label.text = _tr(
|
||
"minimum_spacing"
|
||
)
|
||
|
||
if is_instance_valid(random_rotation_toggle):
|
||
random_rotation_toggle.text = _tr(
|
||
"random_y_rotation"
|
||
)
|
||
|
||
if is_instance_valid(align_surface_toggle):
|
||
align_surface_toggle.text = _tr(
|
||
"align_to_surface"
|
||
)
|
||
|
||
if is_instance_valid(maximum_slope_label):
|
||
maximum_slope_label.text = _tr(
|
||
"maximum_slope"
|
||
)
|
||
|
||
if is_instance_valid(height_offset_label):
|
||
height_offset_label.text = _tr(
|
||
"height_offset"
|
||
)
|
||
|
||
if is_instance_valid(height_offset_mode_label):
|
||
height_offset_mode_label.text = _tr(
|
||
"offset_direction"
|
||
)
|
||
|
||
if is_instance_valid(height_offset_mode_option):
|
||
height_offset_mode_option.set_item_text(
|
||
HeightOffsetMode.WORLD_Y,
|
||
_tr("world_y")
|
||
)
|
||
|
||
height_offset_mode_option.set_item_text(
|
||
HeightOffsetMode.SURFACE_NORMAL,
|
||
_tr("surface_normal")
|
||
)
|
||
|
||
height_offset_mode_option.tooltip_text = _tr(
|
||
"offset_mode_tooltip"
|
||
)
|
||
|
||
height_offset_mode_option.set_item_tooltip(
|
||
HeightOffsetMode.WORLD_Y,
|
||
_tr("world_y_tooltip")
|
||
)
|
||
|
||
height_offset_mode_option.set_item_tooltip(
|
||
HeightOffsetMode.SURFACE_NORMAL,
|
||
_tr("surface_normal_tooltip")
|
||
)
|
||
|
||
if is_instance_valid(height_offset_help_label):
|
||
height_offset_help_label.text = _tr(
|
||
"offset_help"
|
||
)
|
||
|
||
if is_instance_valid(align_surface_toggle):
|
||
align_surface_toggle.tooltip_text = _tr(
|
||
"align_surface_tooltip"
|
||
)
|
||
|
||
if is_instance_valid(random_scale_toggle):
|
||
random_scale_toggle.text = _tr(
|
||
"random_scale"
|
||
)
|
||
|
||
if is_instance_valid(minimum_scale_label):
|
||
minimum_scale_label.text = _tr(
|
||
"minimum_scale"
|
||
)
|
||
|
||
if is_instance_valid(maximum_scale_label):
|
||
maximum_scale_label.text = _tr(
|
||
"maximum_scale"
|
||
)
|
||
|
||
_update_scene_interface()
|
||
_refresh_status_language()
|
||
|
||
|
||
func _tr(
|
||
key: String
|
||
) -> String:
|
||
if current_language == LANGUAGE_ARABIC:
|
||
match key:
|
||
"title":
|
||
return "فرشاة العالم"
|
||
|
||
"scene_palette":
|
||
return "مكتبة المشاهد"
|
||
|
||
"active_scene_none":
|
||
return "المشهد النشط: لا يوجد"
|
||
|
||
"previous":
|
||
return "◀ السابق"
|
||
|
||
"next":
|
||
return "التالي ▶"
|
||
|
||
"previous_tooltip":
|
||
return "اختيار المشهد السابق."
|
||
|
||
"next_tooltip":
|
||
return "اختيار المشهد التالي."
|
||
|
||
"add_scene":
|
||
return "+ إضافة مشهد"
|
||
|
||
"add_distant_scene":
|
||
return "إضافة مشهد بعيد"
|
||
|
||
"medium_scene":
|
||
return "المشهد المتوسط"
|
||
|
||
"distant_scene":
|
||
return "المشهد البعيد"
|
||
|
||
"medium_starts":
|
||
return "بداية المتوسط"
|
||
|
||
"distant_starts":
|
||
return "بداية البعيد"
|
||
|
||
"hide_after":
|
||
return "الإخفاء بعد"
|
||
|
||
"lod_help":
|
||
return "يُستخدم المشهد الأصلي عند القرب، ثم يبدّل Godot تلقائيًا إلى المتوسط والبعيد حسب مسافة الكاميرا. القيمة 0 لمسافة الإخفاء تعني عدم الإخفاء."
|
||
|
||
"auto_lod":
|
||
return "Godot Auto LOD — نموذج واحد"
|
||
|
||
"auto_lod_bias":
|
||
return "دقة الانتقال"
|
||
|
||
"auto_lod_hide_after":
|
||
return "الإخفاء بعد"
|
||
|
||
"auto_lod_reimport":
|
||
return "تطبيق وإعادة استيراد هذا المجسم"
|
||
|
||
"auto_lod_help":
|
||
return "خاص بالمجسم النشط فقط. يولّد Godot مستويات أخف من النموذج الواحد عند الاستيراد. دقة 0 تفرض أخف مستوى للاختبار، والقيمة الأكبر تحفظ التفاصيل لمسافة أطول. الإخفاء 0 يعني عدم الإخفاء."
|
||
|
||
"use":
|
||
return "استخدام"
|
||
|
||
"active":
|
||
return "نشط"
|
||
|
||
"select_scene_tooltip":
|
||
return "جعل هذا المشهد هو المستخدم في الرسم."
|
||
|
||
"remove_scene_tooltip":
|
||
return "حذف المشهد من المكتبة."
|
||
|
||
"enable_brush":
|
||
return "تفعيل الفرشاة"
|
||
|
||
"brush_radius":
|
||
return "حجم الفرشاة"
|
||
|
||
"instances_per_stamp":
|
||
return "عدد العناصر في الضربة"
|
||
|
||
"minimum_spacing":
|
||
return "أقل مسافة"
|
||
|
||
"random_y_rotation":
|
||
return "دوران عشوائي حول Y"
|
||
|
||
"align_to_surface":
|
||
return "تدوير المجسم مع السطح"
|
||
|
||
"align_surface_tooltip":
|
||
return "يدير المجسم ليتبع ميل السطح. هذا الخيار مستقل عن اتجاه إزاحة الموضع."
|
||
|
||
"maximum_slope":
|
||
return "أقصى ميل مسموح"
|
||
|
||
"height_offset":
|
||
return "إزاحة الارتفاع"
|
||
|
||
"offset_direction":
|
||
return "اتجاه تحريك الموضع"
|
||
|
||
"world_y":
|
||
return "على محور Y"
|
||
|
||
"surface_normal":
|
||
return "بعيدًا عن السطح"
|
||
|
||
"offset_help":
|
||
return "اتجاه الإزاحة يحرك موضع المجسم فقط. لتغيير ميلانه فعّل خيار تدوير المجسم مع السطح."
|
||
|
||
"offset_mode_tooltip":
|
||
return "يحدد اتجاه تحريك موضع المجسم، ولا يغير دورانه."
|
||
|
||
"world_y_tooltip":
|
||
return "يحرك المجسم للأعلى أو الأسفل على محور Y العالمي."
|
||
|
||
"surface_normal_tooltip":
|
||
return "يحرك المجسم بعيدًا عن السطح أو داخله باتجاه Normal السطح."
|
||
|
||
"random_scale":
|
||
return "حجم عشوائي"
|
||
|
||
"minimum_scale":
|
||
return "أقل حجم"
|
||
|
||
"maximum_scale":
|
||
return "أكبر حجم"
|
||
|
||
"unsaved_scene":
|
||
return "مشهد غير محفوظ"
|
||
|
||
_:
|
||
return key
|
||
|
||
match key:
|
||
"title":
|
||
return "World Brush"
|
||
|
||
"scene_palette":
|
||
return "Scene Palette"
|
||
|
||
"active_scene_none":
|
||
return "Active Scene: None"
|
||
|
||
"previous":
|
||
return "◀ Previous"
|
||
|
||
"next":
|
||
return "Next ▶"
|
||
|
||
"previous_tooltip":
|
||
return "Select the previous scene."
|
||
|
||
"next_tooltip":
|
||
return "Select the next scene."
|
||
|
||
"add_scene":
|
||
return "+ Add Scene"
|
||
|
||
"add_distant_scene":
|
||
return "Add Distant Scene"
|
||
|
||
"medium_scene":
|
||
return "Medium Scene"
|
||
|
||
"distant_scene":
|
||
return "Distant Scene"
|
||
|
||
"medium_starts":
|
||
return "Medium Starts"
|
||
|
||
"distant_starts":
|
||
return "Distant Starts"
|
||
|
||
"hide_after":
|
||
return "Hide After"
|
||
|
||
"lod_help":
|
||
return "The original scene is used nearby. Godot switches to the medium and distant scenes using camera distance. A hide distance of 0 disables distance culling."
|
||
|
||
"auto_lod":
|
||
return "Godot Auto LOD — Single Model"
|
||
|
||
"auto_lod_bias":
|
||
return "Transition Detail"
|
||
|
||
"auto_lod_hide_after":
|
||
return "Hide After"
|
||
|
||
"auto_lod_reimport":
|
||
return "Apply & Reimport This Model"
|
||
|
||
"auto_lod_help":
|
||
return "Applies only to the active model. Detail 0 forces the lightest generated LOD for testing; higher values preserve detail farther away. Hide distance 0 disables culling."
|
||
|
||
"use":
|
||
return "Use"
|
||
|
||
"active":
|
||
return "Active"
|
||
|
||
"select_scene_tooltip":
|
||
return "Make this the active painting scene."
|
||
|
||
"remove_scene_tooltip":
|
||
return "Remove Scene"
|
||
|
||
"enable_brush":
|
||
return "Enable Brush"
|
||
|
||
"brush_radius":
|
||
return "Brush Radius"
|
||
|
||
"instances_per_stamp":
|
||
return "Instances Per Stamp"
|
||
|
||
"minimum_spacing":
|
||
return "Minimum Spacing"
|
||
|
||
"random_y_rotation":
|
||
return "Random Y Rotation"
|
||
|
||
"align_to_surface":
|
||
return "Rotate With Surface"
|
||
|
||
"align_surface_tooltip":
|
||
return "Rotates the instance to follow the surface slope. This is independent from the position offset direction."
|
||
|
||
"maximum_slope":
|
||
return "Maximum Slope"
|
||
|
||
"height_offset":
|
||
return "Height Offset"
|
||
|
||
"offset_direction":
|
||
return "Position Offset Direction"
|
||
|
||
"world_y":
|
||
return "Along World Y"
|
||
|
||
"surface_normal":
|
||
return "Away From Surface"
|
||
|
||
"offset_help":
|
||
return "The offset direction moves the instance position only. Enable Rotate With Surface to change its tilt."
|
||
|
||
"offset_mode_tooltip":
|
||
return "Controls the direction used to move the instance position. It does not change rotation."
|
||
|
||
"world_y_tooltip":
|
||
return "Moves the instance up or down along the global Y axis."
|
||
|
||
"surface_normal_tooltip":
|
||
return "Moves the instance away from or into the surface along its normal."
|
||
|
||
"random_scale":
|
||
return "Random Scale"
|
||
|
||
"minimum_scale":
|
||
return "Minimum Scale"
|
||
|
||
"maximum_scale":
|
||
return "Maximum Scale"
|
||
|
||
"unsaved_scene":
|
||
return "Unsaved PackedScene"
|
||
|
||
_:
|
||
return key
|
||
|
||
|
||
# =========================================================
|
||
# رسائل الحالة
|
||
# =========================================================
|
||
|
||
func _set_status(
|
||
source_message: String
|
||
) -> void:
|
||
last_status_source = source_message
|
||
|
||
var translated_message := (
|
||
_translate_status_message(
|
||
source_message
|
||
)
|
||
)
|
||
|
||
is_applying_status_translation = true
|
||
status_label.text = translated_message
|
||
is_applying_status_translation = false
|
||
|
||
last_status_display = translated_message
|
||
|
||
|
||
func _refresh_status_language() -> void:
|
||
if last_status_source.is_empty():
|
||
last_status_source = status_label.text
|
||
|
||
_set_status(
|
||
last_status_source
|
||
)
|
||
|
||
|
||
func _watch_external_status_message() -> void:
|
||
if not is_instance_valid(
|
||
status_label
|
||
):
|
||
return
|
||
|
||
if is_applying_status_translation:
|
||
return
|
||
|
||
var current_text := status_label.text
|
||
|
||
if current_text == last_status_display:
|
||
return
|
||
|
||
last_status_source = current_text
|
||
|
||
var translated_text := (
|
||
_translate_status_message(
|
||
last_status_source
|
||
)
|
||
)
|
||
|
||
is_applying_status_translation = true
|
||
status_label.text = translated_text
|
||
is_applying_status_translation = false
|
||
|
||
last_status_display = translated_text
|
||
|
||
|
||
func _translate_status_message(
|
||
message: String
|
||
) -> String:
|
||
if current_language != LANGUAGE_ARABIC:
|
||
return message
|
||
|
||
var exact_translations := {
|
||
"No Scenes Selected":
|
||
"لم يتم اختيار أي مشهد",
|
||
|
||
"New Scene Slot Added":
|
||
"تمت إضافة خانة مشهد جديدة",
|
||
|
||
"Choose A Scene In This Slot":
|
||
"اختر مشهدًا في هذه الخانة",
|
||
|
||
"At Least One Scene Slot Must Remain":
|
||
"يجب أن تبقى خانة مشهد واحدة على الأقل",
|
||
|
||
"Select An Active Scene First":
|
||
"اختر مشهدًا نشطًا أولًا",
|
||
|
||
"Select A Saved Scene First":
|
||
"اختر مشهدًا محفوظًا أولًا",
|
||
|
||
"Auto LOD: Active scene is missing or unsaved":
|
||
"LOD التلقائي: المشهد النشط مفقود أو غير محفوظ",
|
||
|
||
"Auto LOD: No supported imported model found in active scene":
|
||
"LOD التلقائي: لم يُعثر على نموذج مستورد مدعوم داخل المشهد النشط",
|
||
|
||
"Auto LOD: Import settings could not be updated":
|
||
"LOD التلقائي: تعذر تحديث إعدادات الاستيراد",
|
||
|
||
"Auto LOD: Reimporting active model only...":
|
||
"LOD التلقائي: جارٍ إعادة استيراد المجسم النشط فقط...",
|
||
|
||
"Auto LOD enabled for active model":
|
||
"تم تفعيل LOD التلقائي للمجسم النشط",
|
||
|
||
"Auto LOD disabled for active model":
|
||
"تم تعطيل LOD التلقائي للمجسم النشط",
|
||
|
||
"Brush Disabled":
|
||
"الفرشاة متوقفة",
|
||
|
||
"No Valid Surface":
|
||
"لا يوجد سطح صالح",
|
||
|
||
"Selected Scene Cannot Be Instantiated":
|
||
"لا يمكن إنشاء نسخة من المشهد المختار",
|
||
|
||
"Scene Root Must Be Node3D":
|
||
"يجب أن يكون جذر المشهد من نوع Node3D",
|
||
|
||
"3D World Not Available":
|
||
"العالم ثلاثي الأبعاد غير متاح",
|
||
|
||
"Edited Scene Changed During Stroke":
|
||
"تم تغيير المشهد أثناء استخدام الفرشاة",
|
||
|
||
"Failed To Create Container":
|
||
"تعذر إنشاء حاوية عناصر الفرشاة",
|
||
|
||
"No Valid Space Inside Brush":
|
||
"لا توجد مساحة صالحة داخل الفرشاة",
|
||
|
||
"Selected Scene Root Must Inherit Node3D":
|
||
"يجب أن يرث جذر المشهد المختار من Node3D",
|
||
|
||
"No World Brush Instances":
|
||
"لا توجد عناصر منشورة بواسطة الفرشاة",
|
||
|
||
"Open A 3D Scene First":
|
||
"افتح مشهدًا ثلاثي الأبعاد أولًا",
|
||
|
||
"No Surface Under Cursor":
|
||
"لا يوجد سطح أسفل المؤشر",
|
||
|
||
"Invalid Surface Position":
|
||
"موضع السطح غير صالح",
|
||
|
||
"Erase Brush — No Instances Here":
|
||
"فرشاة الحذف — لا توجد عناصر هنا",
|
||
|
||
"Interface language changed to Arabic.":
|
||
"تم تغيير لغة واجهة الفرشاة إلى العربية.",
|
||
|
||
"Interface language changed to English.":
|
||
"تم تغيير لغة واجهة الفرشاة إلى الإنجليزية.",
|
||
}
|
||
|
||
if exact_translations.has(
|
||
message
|
||
):
|
||
return str(
|
||
exact_translations[message]
|
||
)
|
||
|
||
var prefix_translations := {
|
||
"Active: ":
|
||
"المشهد النشط: ",
|
||
|
||
"Brush Enabled — ":
|
||
"الفرشاة مفعّلة — ",
|
||
|
||
"Palette Restored: ":
|
||
"تمت استعادة المكتبة: ",
|
||
|
||
"Painted In Stroke: ":
|
||
"العناصر المرسومة في السحبة: ",
|
||
|
||
"Erased In Stroke: ":
|
||
"العناصر المحذوفة في السحبة: ",
|
||
|
||
"Paint Stroke Saved: ":
|
||
"تم حفظ سحبة الرسم: ",
|
||
|
||
"Erase Stroke Saved: ":
|
||
"تم حفظ سحبة الحذف: ",
|
||
}
|
||
|
||
for english_prefix in prefix_translations:
|
||
if not message.begins_with(
|
||
english_prefix
|
||
):
|
||
continue
|
||
|
||
return (
|
||
str(
|
||
prefix_translations[
|
||
english_prefix
|
||
]
|
||
)
|
||
+ message.substr(
|
||
english_prefix.length()
|
||
)
|
||
)
|
||
|
||
if message.begins_with(
|
||
"Physics | "
|
||
):
|
||
return (
|
||
"سطح فيزيائي | "
|
||
+ message.substr(
|
||
"Physics | ".length()
|
||
)
|
||
)
|
||
|
||
if message.begins_with(
|
||
"Surface | "
|
||
):
|
||
return (
|
||
"سطح | "
|
||
+ message.substr(
|
||
"Surface | ".length()
|
||
)
|
||
)
|
||
|
||
return message
|
||
|
||
|
||
# =========================================================
|
||
# مكتبة المشاهد
|
||
# =========================================================
|
||
|
||
func _create_scene_palette() -> void:
|
||
palette_title_label = Label.new()
|
||
palette_title_label.name = "ScenePaletteTitle"
|
||
palette_title_label.text = "Scene Palette"
|
||
|
||
controls_container.add_child(
|
||
palette_title_label
|
||
)
|
||
|
||
active_scene_label = Label.new()
|
||
active_scene_label.name = "ActiveSceneLabel"
|
||
active_scene_label.text = "Active Scene: None"
|
||
|
||
controls_container.add_child(
|
||
active_scene_label
|
||
)
|
||
|
||
_create_scene_navigation()
|
||
|
||
var separator := HSeparator.new()
|
||
separator.name = "PaletteSeparator"
|
||
|
||
controls_container.add_child(
|
||
separator
|
||
)
|
||
|
||
scenes_list = VBoxContainer.new()
|
||
scenes_list.name = "ScenesList"
|
||
|
||
scenes_list.size_flags_horizontal = (
|
||
Control.SIZE_EXPAND_FILL
|
||
)
|
||
|
||
controls_container.add_child(
|
||
scenes_list
|
||
)
|
||
|
||
add_scene_button = Button.new()
|
||
add_scene_button.name = "AddSceneButton"
|
||
add_scene_button.text = "+ Add Scene"
|
||
|
||
add_scene_button.size_flags_horizontal = (
|
||
Control.SIZE_EXPAND_FILL
|
||
)
|
||
|
||
add_scene_button.pressed.connect(
|
||
_on_add_scene_pressed
|
||
)
|
||
|
||
controls_container.add_child(
|
||
add_scene_button
|
||
)
|
||
|
||
|
||
func _create_scene_navigation() -> void:
|
||
var navigation_row := HBoxContainer.new()
|
||
navigation_row.name = "SceneNavigation"
|
||
|
||
navigation_row.size_flags_horizontal = (
|
||
Control.SIZE_EXPAND_FILL
|
||
)
|
||
|
||
previous_scene_button = Button.new()
|
||
previous_scene_button.name = (
|
||
"PreviousSceneButton"
|
||
)
|
||
|
||
previous_scene_button.text = "◀ Previous"
|
||
|
||
previous_scene_button.size_flags_horizontal = (
|
||
Control.SIZE_EXPAND_FILL
|
||
)
|
||
|
||
previous_scene_button.pressed.connect(
|
||
_on_previous_scene_pressed
|
||
)
|
||
|
||
next_scene_button = Button.new()
|
||
next_scene_button.name = "NextSceneButton"
|
||
next_scene_button.text = "Next ▶"
|
||
|
||
next_scene_button.size_flags_horizontal = (
|
||
Control.SIZE_EXPAND_FILL
|
||
)
|
||
|
||
next_scene_button.pressed.connect(
|
||
_on_next_scene_pressed
|
||
)
|
||
|
||
navigation_row.add_child(
|
||
previous_scene_button
|
||
)
|
||
|
||
navigation_row.add_child(
|
||
next_scene_button
|
||
)
|
||
|
||
controls_container.add_child(
|
||
navigation_row
|
||
)
|
||
|
||
|
||
func _create_auto_lod_controls() -> void:
|
||
var separator := HSeparator.new()
|
||
separator.name = "AutoLODSeparator"
|
||
controls_container.add_child(separator)
|
||
|
||
auto_lod_toggle = CheckButton.new()
|
||
auto_lod_toggle.name = "AutoLODToggle"
|
||
auto_lod_toggle.text = "Godot Auto LOD — Single Model"
|
||
auto_lod_toggle.button_pressed = auto_lod_enabled
|
||
auto_lod_toggle.tooltip_text = "Independent setting for the active palette model only."
|
||
auto_lod_toggle.toggled.connect(_on_auto_lod_toggled)
|
||
controls_container.add_child(auto_lod_toggle)
|
||
|
||
auto_lod_controls_container = VBoxContainer.new()
|
||
auto_lod_controls_container.name = "AutoLODControls"
|
||
auto_lod_controls_container.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||
controls_container.add_child(auto_lod_controls_container)
|
||
|
||
var bias_row := HBoxContainer.new()
|
||
auto_lod_bias_label = Label.new()
|
||
auto_lod_bias_label.text = "Transition Detail"
|
||
auto_lod_bias_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||
auto_lod_bias_spin_box = SpinBox.new()
|
||
auto_lod_bias_spin_box.name = "AutoLODBiasSpinBox"
|
||
auto_lod_bias_spin_box.min_value = 0.0
|
||
auto_lod_bias_spin_box.max_value = 4.0
|
||
auto_lod_bias_spin_box.step = 0.05
|
||
auto_lod_bias_spin_box.value = auto_lod_bias
|
||
auto_lod_bias_spin_box.suffix = " ×"
|
||
auto_lod_bias_spin_box.custom_minimum_size.x = 105.0
|
||
auto_lod_bias_spin_box.value_changed.connect(_on_auto_lod_bias_changed)
|
||
bias_row.add_child(auto_lod_bias_label)
|
||
bias_row.add_child(auto_lod_bias_spin_box)
|
||
auto_lod_controls_container.add_child(bias_row)
|
||
|
||
var cull_row := HBoxContainer.new()
|
||
auto_lod_cull_label = Label.new()
|
||
auto_lod_cull_label.text = "Hide After"
|
||
auto_lod_cull_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||
auto_lod_cull_spin_box = _create_lod_distance_spin_box(
|
||
"AutoLODCullDistanceSpinBox",
|
||
auto_lod_cull_distance
|
||
)
|
||
auto_lod_cull_spin_box.min_value = 0.0
|
||
auto_lod_cull_spin_box.value_changed.connect(_on_auto_lod_cull_changed)
|
||
cull_row.add_child(auto_lod_cull_label)
|
||
cull_row.add_child(auto_lod_cull_spin_box)
|
||
auto_lod_controls_container.add_child(cull_row)
|
||
|
||
auto_lod_reimport_button = Button.new()
|
||
auto_lod_reimport_button.name = "AutoLODReimportButton"
|
||
auto_lod_reimport_button.text = "Apply & Reimport This Model"
|
||
auto_lod_reimport_button.tooltip_text = "Changes Generate LODs only for the active model source, then reimports it."
|
||
auto_lod_reimport_button.pressed.connect(_on_auto_lod_reimport_pressed)
|
||
auto_lod_controls_container.add_child(auto_lod_reimport_button)
|
||
|
||
auto_lod_help_label = Label.new()
|
||
auto_lod_help_label.name = "AutoLODHelpLabel"
|
||
auto_lod_help_label.text = "Godot generates lighter mesh levels from the active single model during import."
|
||
auto_lod_help_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||
auto_lod_help_label.modulate = Color(0.82, 0.90, 1.0, 0.78)
|
||
auto_lod_controls_container.add_child(auto_lod_help_label)
|
||
_update_auto_lod_controls()
|
||
|
||
|
||
func _create_lod_controls() -> void:
|
||
var separator := HSeparator.new()
|
||
separator.name = "DistanceLODSeparator"
|
||
controls_container.add_child(separator)
|
||
|
||
lod_toggle = CheckButton.new()
|
||
lod_toggle.name = "DistanceLODToggle"
|
||
lod_toggle.text = "Add Distant Scene"
|
||
lod_toggle.button_pressed = lod_enabled
|
||
lod_toggle.toggled.connect(_on_lod_toggled)
|
||
controls_container.add_child(lod_toggle)
|
||
|
||
lod_controls_container = VBoxContainer.new()
|
||
lod_controls_container.name = "DistanceLODControls"
|
||
lod_controls_container.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||
controls_container.add_child(lod_controls_container)
|
||
|
||
var lod1_row := HBoxContainer.new()
|
||
lod1_row.name = "LOD1SceneContainer"
|
||
lod1_scene_label = Label.new()
|
||
lod1_scene_label.text = "Medium Scene"
|
||
lod1_scene_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||
lod1_picker = EditorResourcePicker.new()
|
||
lod1_picker.name = "LOD1ScenePicker"
|
||
lod1_picker.base_type = "PackedScene"
|
||
lod1_picker.editable = true
|
||
lod1_picker.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||
lod1_picker.resource_changed.connect(_on_lod1_scene_changed)
|
||
lod1_row.add_child(lod1_scene_label)
|
||
lod1_row.add_child(lod1_picker)
|
||
lod_controls_container.add_child(lod1_row)
|
||
|
||
var lod2_row := HBoxContainer.new()
|
||
lod2_row.name = "LOD2SceneContainer"
|
||
lod2_scene_label = Label.new()
|
||
lod2_scene_label.text = "Distant Scene"
|
||
lod2_scene_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||
lod2_picker = EditorResourcePicker.new()
|
||
lod2_picker.name = "LOD2ScenePicker"
|
||
lod2_picker.base_type = "PackedScene"
|
||
lod2_picker.editable = true
|
||
lod2_picker.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||
lod2_picker.resource_changed.connect(_on_lod2_scene_changed)
|
||
lod2_row.add_child(lod2_scene_label)
|
||
lod2_row.add_child(lod2_picker)
|
||
lod_controls_container.add_child(lod2_row)
|
||
|
||
var lod1_distance_row := HBoxContainer.new()
|
||
lod1_distance_row.name = "LOD1DistanceContainer"
|
||
lod1_distance_label = Label.new()
|
||
lod1_distance_label.text = "Medium Starts"
|
||
lod1_distance_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||
lod1_distance_spin_box = _create_lod_distance_spin_box(
|
||
"LOD1DistanceSpinBox",
|
||
lod1_distance
|
||
)
|
||
lod1_distance_spin_box.value_changed.connect(_on_lod1_distance_changed)
|
||
lod1_distance_row.add_child(lod1_distance_label)
|
||
lod1_distance_row.add_child(lod1_distance_spin_box)
|
||
lod_controls_container.add_child(lod1_distance_row)
|
||
|
||
var lod2_distance_row := HBoxContainer.new()
|
||
lod2_distance_row.name = "LOD2DistanceContainer"
|
||
lod2_distance_label = Label.new()
|
||
lod2_distance_label.text = "Distant Starts"
|
||
lod2_distance_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||
lod2_distance_spin_box = _create_lod_distance_spin_box(
|
||
"LOD2DistanceSpinBox",
|
||
lod2_distance
|
||
)
|
||
lod2_distance_spin_box.value_changed.connect(_on_lod2_distance_changed)
|
||
lod2_distance_row.add_child(lod2_distance_label)
|
||
lod2_distance_row.add_child(lod2_distance_spin_box)
|
||
lod_controls_container.add_child(lod2_distance_row)
|
||
|
||
var cull_row := HBoxContainer.new()
|
||
cull_row.name = "LODCullDistanceContainer"
|
||
lod_cull_distance_label = Label.new()
|
||
lod_cull_distance_label.text = "Hide After"
|
||
lod_cull_distance_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||
lod_cull_distance_spin_box = _create_lod_distance_spin_box(
|
||
"LODCullDistanceSpinBox",
|
||
lod_cull_distance
|
||
)
|
||
lod_cull_distance_spin_box.min_value = 0.0
|
||
lod_cull_distance_spin_box.tooltip_text = "0 keeps the distant scene visible without a maximum distance."
|
||
lod_cull_distance_spin_box.value_changed.connect(_on_lod_cull_distance_changed)
|
||
cull_row.add_child(lod_cull_distance_label)
|
||
cull_row.add_child(lod_cull_distance_spin_box)
|
||
lod_controls_container.add_child(cull_row)
|
||
|
||
lod_help_label = Label.new()
|
||
lod_help_label.name = "DistanceLODHelpLabel"
|
||
lod_help_label.text = "The original scene is used nearby. Godot switches to the medium and distant scenes using camera distance."
|
||
lod_help_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||
lod_help_label.modulate = Color(1.0, 1.0, 1.0, 0.72)
|
||
lod_controls_container.add_child(lod_help_label)
|
||
|
||
_update_lod_controls()
|
||
|
||
|
||
func _create_lod_distance_spin_box(
|
||
control_name: String,
|
||
value: float
|
||
) -> SpinBox:
|
||
var spin_box := SpinBox.new()
|
||
spin_box.name = control_name
|
||
spin_box.min_value = 1.0
|
||
spin_box.max_value = 100000.0
|
||
spin_box.step = 1.0
|
||
spin_box.value = value
|
||
spin_box.allow_greater = true
|
||
spin_box.suffix = " m"
|
||
spin_box.custom_minimum_size.x = 105.0
|
||
return spin_box
|
||
|
||
|
||
func _on_lod_toggled(enabled: bool) -> void:
|
||
lod_enabled = enabled
|
||
_update_lod_controls()
|
||
_emit_lod_settings_changed()
|
||
_save_configuration()
|
||
|
||
|
||
func _on_auto_lod_toggled(enabled: bool) -> void:
|
||
auto_lod_enabled = enabled
|
||
_update_auto_lod_controls()
|
||
_emit_auto_lod_settings_changed()
|
||
_save_configuration()
|
||
_request_auto_lod_reimport()
|
||
|
||
|
||
func _on_auto_lod_bias_changed(value: float) -> void:
|
||
auto_lod_bias = clampf(value, 0.0, 4.0)
|
||
auto_lod_bias_spin_box.set_value_no_signal(auto_lod_bias)
|
||
_emit_auto_lod_settings_changed()
|
||
_save_configuration()
|
||
|
||
|
||
func _on_auto_lod_cull_changed(value: float) -> void:
|
||
auto_lod_cull_distance = maxf(value, 0.0)
|
||
auto_lod_cull_spin_box.set_value_no_signal(auto_lod_cull_distance)
|
||
_emit_auto_lod_settings_changed()
|
||
_save_configuration()
|
||
|
||
|
||
func _on_auto_lod_reimport_pressed() -> void:
|
||
_request_auto_lod_reimport()
|
||
|
||
|
||
func _request_auto_lod_reimport() -> void:
|
||
if selected_scene == null or selected_scene.resource_path.is_empty():
|
||
_set_status("Select A Saved Scene First")
|
||
return
|
||
auto_lod_import_requested.emit(
|
||
selected_scene.resource_path,
|
||
auto_lod_enabled
|
||
)
|
||
|
||
|
||
func _emit_auto_lod_settings_changed() -> void:
|
||
auto_lod_settings_changed.emit(
|
||
auto_lod_enabled,
|
||
auto_lod_bias,
|
||
auto_lod_cull_distance
|
||
)
|
||
|
||
|
||
func _update_auto_lod_controls() -> void:
|
||
var has_active_scene := selected_scene != null
|
||
if is_instance_valid(auto_lod_toggle):
|
||
auto_lod_toggle.visible = has_active_scene
|
||
auto_lod_toggle.disabled = not has_active_scene
|
||
if is_instance_valid(auto_lod_controls_container):
|
||
auto_lod_controls_container.visible = has_active_scene and auto_lod_enabled
|
||
var details_enabled := has_active_scene and auto_lod_enabled
|
||
if is_instance_valid(auto_lod_bias_spin_box):
|
||
auto_lod_bias_spin_box.editable = details_enabled
|
||
if is_instance_valid(auto_lod_cull_spin_box):
|
||
auto_lod_cull_spin_box.editable = details_enabled
|
||
if is_instance_valid(auto_lod_reimport_button):
|
||
auto_lod_reimport_button.disabled = not details_enabled
|
||
|
||
|
||
func _on_lod1_scene_changed(resource: Resource) -> void:
|
||
lod1_scene = resource as PackedScene
|
||
_emit_lod_settings_changed()
|
||
_save_configuration()
|
||
|
||
|
||
func _on_lod2_scene_changed(resource: Resource) -> void:
|
||
lod2_scene = resource as PackedScene
|
||
_emit_lod_settings_changed()
|
||
_save_configuration()
|
||
|
||
|
||
func _on_lod1_distance_changed(value: float) -> void:
|
||
lod1_distance = maxf(value, 1.0)
|
||
if lod2_distance <= lod1_distance:
|
||
lod2_distance = lod1_distance + 1.0
|
||
lod2_distance_spin_box.set_value_no_signal(lod2_distance)
|
||
_validate_lod_cull_distance()
|
||
_emit_lod_settings_changed()
|
||
_save_configuration()
|
||
|
||
|
||
func _on_lod2_distance_changed(value: float) -> void:
|
||
lod2_distance = maxf(value, lod1_distance + 1.0)
|
||
lod2_distance_spin_box.set_value_no_signal(lod2_distance)
|
||
_validate_lod_cull_distance()
|
||
_emit_lod_settings_changed()
|
||
_save_configuration()
|
||
|
||
|
||
func _on_lod_cull_distance_changed(value: float) -> void:
|
||
lod_cull_distance = maxf(value, 0.0)
|
||
_validate_lod_cull_distance()
|
||
_emit_lod_settings_changed()
|
||
_save_configuration()
|
||
|
||
|
||
func _validate_lod_cull_distance() -> void:
|
||
lod_cull_distance = maxf(lod_cull_distance, 0.0)
|
||
if is_instance_valid(lod_cull_distance_spin_box):
|
||
lod_cull_distance_spin_box.set_value_no_signal(lod_cull_distance)
|
||
|
||
|
||
func _emit_lod_settings_changed() -> void:
|
||
lod_settings_changed.emit(
|
||
lod_enabled,
|
||
lod1_scene,
|
||
lod2_scene,
|
||
lod1_distance,
|
||
lod2_distance,
|
||
lod_cull_distance
|
||
)
|
||
|
||
|
||
func _update_lod_controls() -> void:
|
||
var has_active_scene := selected_scene != null
|
||
if is_instance_valid(lod_toggle):
|
||
lod_toggle.visible = has_active_scene
|
||
lod_toggle.disabled = not has_active_scene
|
||
if is_instance_valid(lod_controls_container):
|
||
lod_controls_container.visible = has_active_scene and lod_enabled
|
||
var details_enabled := has_active_scene and lod_enabled
|
||
if is_instance_valid(lod1_picker):
|
||
lod1_picker.editable = details_enabled
|
||
if is_instance_valid(lod2_picker):
|
||
lod2_picker.editable = details_enabled
|
||
if is_instance_valid(lod1_distance_spin_box):
|
||
lod1_distance_spin_box.editable = details_enabled
|
||
if is_instance_valid(lod2_distance_spin_box):
|
||
lod2_distance_spin_box.editable = details_enabled
|
||
if is_instance_valid(lod_cull_distance_spin_box):
|
||
lod_cull_distance_spin_box.editable = details_enabled
|
||
|
||
|
||
func _add_scene_row(
|
||
initial_scene: PackedScene = null,
|
||
update_after_creation: bool = true
|
||
) -> EditorResourcePicker:
|
||
var row := HBoxContainer.new()
|
||
|
||
row.name = "SceneRow_%d" % (
|
||
scene_rows.size() + 1
|
||
)
|
||
|
||
row.size_flags_horizontal = (
|
||
Control.SIZE_EXPAND_FILL
|
||
)
|
||
|
||
var picker := EditorResourcePicker.new()
|
||
picker.name = "ScenePicker"
|
||
picker.base_type = "PackedScene"
|
||
picker.editable = true
|
||
|
||
picker.size_flags_horizontal = (
|
||
Control.SIZE_EXPAND_FILL
|
||
)
|
||
|
||
if initial_scene != null:
|
||
picker.edited_resource = initial_scene
|
||
|
||
var select_button := Button.new()
|
||
select_button.name = "SelectSceneButton"
|
||
select_button.text = "Use"
|
||
|
||
select_button.custom_minimum_size = Vector2(
|
||
62.0,
|
||
0.0
|
||
)
|
||
|
||
var remove_button := Button.new()
|
||
remove_button.name = "RemoveSceneButton"
|
||
remove_button.text = "×"
|
||
|
||
remove_button.custom_minimum_size = Vector2(
|
||
38.0,
|
||
0.0
|
||
)
|
||
|
||
row.add_child(
|
||
picker
|
||
)
|
||
|
||
row.add_child(
|
||
select_button
|
||
)
|
||
|
||
row.add_child(
|
||
remove_button
|
||
)
|
||
|
||
scenes_list.add_child(
|
||
row
|
||
)
|
||
|
||
scene_rows.append(
|
||
{
|
||
"row": row,
|
||
"picker": picker,
|
||
"select_button": select_button,
|
||
"remove_button": remove_button,
|
||
}
|
||
)
|
||
|
||
picker.resource_changed.connect(
|
||
_on_scene_resource_changed.bind(
|
||
picker
|
||
)
|
||
)
|
||
|
||
select_button.pressed.connect(
|
||
_on_select_scene_pressed.bind(
|
||
picker
|
||
)
|
||
)
|
||
|
||
remove_button.pressed.connect(
|
||
_on_remove_scene_pressed.bind(
|
||
picker
|
||
)
|
||
)
|
||
|
||
if update_after_creation:
|
||
_update_scene_interface()
|
||
|
||
return picker
|
||
|
||
|
||
func _on_add_scene_pressed() -> void:
|
||
_add_scene_row()
|
||
|
||
_set_status(
|
||
"New Scene Slot Added"
|
||
)
|
||
|
||
|
||
func _on_scene_resource_changed(
|
||
resource: Resource,
|
||
picker: EditorResourcePicker
|
||
) -> void:
|
||
var packed_scene := (
|
||
resource as PackedScene
|
||
)
|
||
|
||
var picker_was_active := (
|
||
picker == active_picker
|
||
)
|
||
|
||
if picker_was_active:
|
||
_store_current_scene_settings()
|
||
|
||
if (
|
||
packed_scene != null
|
||
and not is_instance_valid(
|
||
active_picker
|
||
)
|
||
):
|
||
active_picker = picker
|
||
selected_scene = packed_scene
|
||
|
||
elif picker_was_active:
|
||
if packed_scene == null:
|
||
active_picker = null
|
||
selected_scene = null
|
||
|
||
_select_first_available_scene()
|
||
else:
|
||
selected_scene = packed_scene
|
||
|
||
if (
|
||
picker == active_picker
|
||
and selected_scene != null
|
||
):
|
||
_load_active_scene_settings(
|
||
false
|
||
)
|
||
|
||
_update_scene_interface()
|
||
_emit_palette_changed()
|
||
_emit_active_scene_changed()
|
||
_emit_all_brush_settings_changed()
|
||
_save_configuration()
|
||
|
||
|
||
func _on_select_scene_pressed(
|
||
picker: EditorResourcePicker
|
||
) -> void:
|
||
if not is_instance_valid(
|
||
picker
|
||
):
|
||
return
|
||
|
||
var packed_scene := (
|
||
picker.edited_resource
|
||
as PackedScene
|
||
)
|
||
|
||
if packed_scene == null:
|
||
_set_status(
|
||
"Choose A Scene In This Slot"
|
||
)
|
||
|
||
return
|
||
|
||
_set_active_picker(
|
||
picker
|
||
)
|
||
|
||
|
||
func _on_remove_scene_pressed(
|
||
picker: EditorResourcePicker
|
||
) -> void:
|
||
if scene_rows.size() <= 1:
|
||
_set_status(
|
||
"At Least One Scene Slot Must Remain"
|
||
)
|
||
|
||
return
|
||
|
||
var row_to_remove: Control = null
|
||
var index_to_remove: int = -1
|
||
|
||
var removed_active_scene := (
|
||
picker == active_picker
|
||
)
|
||
|
||
if removed_active_scene:
|
||
_store_current_scene_settings()
|
||
|
||
for index in range(
|
||
scene_rows.size()
|
||
):
|
||
var entry_picker := (
|
||
scene_rows[index].get(
|
||
"picker"
|
||
)
|
||
as EditorResourcePicker
|
||
)
|
||
|
||
if entry_picker != picker:
|
||
continue
|
||
|
||
row_to_remove = (
|
||
scene_rows[index].get(
|
||
"row"
|
||
)
|
||
as Control
|
||
)
|
||
|
||
index_to_remove = index
|
||
break
|
||
|
||
if index_to_remove < 0:
|
||
return
|
||
|
||
scene_rows.remove_at(
|
||
index_to_remove
|
||
)
|
||
|
||
if removed_active_scene:
|
||
active_picker = null
|
||
selected_scene = null
|
||
|
||
if is_instance_valid(
|
||
row_to_remove
|
||
):
|
||
row_to_remove.queue_free()
|
||
|
||
if removed_active_scene:
|
||
_select_available_scene_near_index(
|
||
index_to_remove
|
||
)
|
||
|
||
_load_active_scene_settings(
|
||
false
|
||
)
|
||
|
||
_update_scene_interface()
|
||
_emit_palette_changed()
|
||
_emit_active_scene_changed()
|
||
|
||
if removed_active_scene:
|
||
_emit_all_brush_settings_changed()
|
||
|
||
_save_configuration()
|
||
|
||
|
||
func _on_previous_scene_pressed() -> void:
|
||
_change_active_scene(
|
||
-1
|
||
)
|
||
|
||
|
||
func _on_next_scene_pressed() -> void:
|
||
_change_active_scene(
|
||
1
|
||
)
|
||
|
||
|
||
func _change_active_scene(
|
||
direction: int
|
||
) -> void:
|
||
var valid_pickers := (
|
||
_get_valid_scene_pickers()
|
||
)
|
||
|
||
if valid_pickers.is_empty():
|
||
active_picker = null
|
||
selected_scene = null
|
||
|
||
_update_scene_interface()
|
||
_emit_active_scene_changed()
|
||
_save_configuration()
|
||
|
||
return
|
||
|
||
var current_index := valid_pickers.find(
|
||
active_picker
|
||
)
|
||
|
||
if current_index < 0:
|
||
current_index = 0
|
||
else:
|
||
current_index = posmod(
|
||
current_index + direction,
|
||
valid_pickers.size()
|
||
)
|
||
|
||
_set_active_picker(
|
||
valid_pickers[current_index]
|
||
)
|
||
|
||
|
||
func _set_active_picker(
|
||
picker: EditorResourcePicker
|
||
) -> void:
|
||
if not is_instance_valid(
|
||
picker
|
||
):
|
||
return
|
||
|
||
var packed_scene := (
|
||
picker.edited_resource
|
||
as PackedScene
|
||
)
|
||
|
||
if packed_scene == null:
|
||
return
|
||
|
||
_store_current_scene_settings()
|
||
|
||
active_picker = picker
|
||
selected_scene = packed_scene
|
||
|
||
_load_active_scene_settings(
|
||
false
|
||
)
|
||
|
||
_update_scene_interface()
|
||
_emit_active_scene_changed()
|
||
_emit_all_brush_settings_changed()
|
||
_save_configuration()
|
||
|
||
_set_status(
|
||
"Active: %s"
|
||
% _get_scene_display_name(
|
||
selected_scene
|
||
)
|
||
)
|
||
|
||
|
||
func _select_first_available_scene() -> void:
|
||
var valid_pickers := (
|
||
_get_valid_scene_pickers()
|
||
)
|
||
|
||
if valid_pickers.is_empty():
|
||
active_picker = null
|
||
selected_scene = null
|
||
|
||
return
|
||
|
||
active_picker = valid_pickers[0]
|
||
|
||
selected_scene = (
|
||
active_picker.edited_resource
|
||
as PackedScene
|
||
)
|
||
|
||
|
||
func _select_available_scene_near_index(
|
||
preferred_index: int
|
||
) -> void:
|
||
if scene_rows.is_empty():
|
||
active_picker = null
|
||
selected_scene = null
|
||
|
||
return
|
||
|
||
var clamped_index := clampi(
|
||
preferred_index,
|
||
0,
|
||
scene_rows.size() - 1
|
||
)
|
||
|
||
for index in range(
|
||
clamped_index,
|
||
scene_rows.size()
|
||
):
|
||
var picker := _get_picker_from_row(
|
||
scene_rows[index]
|
||
)
|
||
|
||
if not _picker_has_scene(
|
||
picker
|
||
):
|
||
continue
|
||
|
||
active_picker = picker
|
||
|
||
selected_scene = (
|
||
picker.edited_resource
|
||
as PackedScene
|
||
)
|
||
|
||
return
|
||
|
||
for index in range(
|
||
clamped_index - 1,
|
||
-1,
|
||
-1
|
||
):
|
||
var picker := _get_picker_from_row(
|
||
scene_rows[index]
|
||
)
|
||
|
||
if not _picker_has_scene(
|
||
picker
|
||
):
|
||
continue
|
||
|
||
active_picker = picker
|
||
|
||
selected_scene = (
|
||
picker.edited_resource
|
||
as PackedScene
|
||
)
|
||
|
||
return
|
||
|
||
active_picker = null
|
||
selected_scene = null
|
||
|
||
|
||
# =========================================================
|
||
# حفظ وتحميل الإعدادات
|
||
# =========================================================
|
||
|
||
func _save_configuration() -> void:
|
||
if is_loading_configuration:
|
||
return
|
||
|
||
_store_current_scene_settings()
|
||
|
||
var scene_paths := PackedStringArray()
|
||
|
||
for picker in _get_valid_scene_pickers():
|
||
var packed_scene := (
|
||
picker.edited_resource
|
||
as PackedScene
|
||
)
|
||
|
||
if packed_scene == null:
|
||
continue
|
||
|
||
if packed_scene.resource_path.is_empty():
|
||
continue
|
||
|
||
scene_paths.append(
|
||
packed_scene.resource_path
|
||
)
|
||
|
||
var active_scene_path := ""
|
||
|
||
if is_instance_valid(
|
||
active_picker
|
||
):
|
||
var active_scene := (
|
||
active_picker.edited_resource
|
||
as PackedScene
|
||
)
|
||
|
||
if active_scene != null:
|
||
active_scene_path = (
|
||
active_scene.resource_path
|
||
)
|
||
|
||
var config := ConfigFile.new()
|
||
|
||
config.set_value(
|
||
PALETTE_SECTION,
|
||
CONFIG_SCENE_PATHS,
|
||
scene_paths
|
||
)
|
||
|
||
config.set_value(
|
||
PALETTE_SECTION,
|
||
CONFIG_ACTIVE_SCENE_PATH,
|
||
active_scene_path
|
||
)
|
||
|
||
config.set_value(
|
||
SETTINGS_SECTION,
|
||
CONFIG_BRUSH_RADIUS,
|
||
brush_radius
|
||
)
|
||
|
||
config.set_value(
|
||
SETTINGS_SECTION,
|
||
CONFIG_INSTANCES_PER_STAMP,
|
||
instances_per_click
|
||
)
|
||
|
||
config.set_value(
|
||
SETTINGS_SECTION,
|
||
CONFIG_MINIMUM_SPACING,
|
||
minimum_spacing
|
||
)
|
||
|
||
config.set_value(
|
||
SETTINGS_SECTION,
|
||
CONFIG_RANDOM_Y_ROTATION,
|
||
random_y_rotation
|
||
)
|
||
|
||
config.set_value(
|
||
SETTINGS_SECTION,
|
||
CONFIG_ALIGN_TO_SURFACE,
|
||
align_to_surface
|
||
)
|
||
|
||
config.set_value(
|
||
SETTINGS_SECTION,
|
||
CONFIG_MAXIMUM_SLOPE,
|
||
maximum_slope_degrees
|
||
)
|
||
|
||
config.set_value(
|
||
SETTINGS_SECTION,
|
||
CONFIG_HEIGHT_OFFSET,
|
||
height_offset
|
||
)
|
||
|
||
config.set_value(
|
||
SETTINGS_SECTION,
|
||
CONFIG_HEIGHT_OFFSET_MODE,
|
||
height_offset_mode
|
||
)
|
||
|
||
config.set_value(
|
||
SETTINGS_SECTION,
|
||
CONFIG_RANDOM_SCALE_ENABLED,
|
||
random_scale_enabled
|
||
)
|
||
|
||
config.set_value(
|
||
SETTINGS_SECTION,
|
||
CONFIG_MINIMUM_SCALE,
|
||
minimum_scale
|
||
)
|
||
|
||
config.set_value(
|
||
SETTINGS_SECTION,
|
||
CONFIG_MAXIMUM_SCALE,
|
||
maximum_scale
|
||
)
|
||
|
||
config.set_value(
|
||
SETTINGS_SECTION,
|
||
CONFIG_LOD_ENABLED,
|
||
lod_enabled
|
||
)
|
||
|
||
config.set_value(
|
||
SETTINGS_SECTION,
|
||
CONFIG_LOD1_SCENE_PATH,
|
||
_get_packed_scene_path(lod1_scene)
|
||
)
|
||
|
||
config.set_value(
|
||
SETTINGS_SECTION,
|
||
CONFIG_LOD2_SCENE_PATH,
|
||
_get_packed_scene_path(lod2_scene)
|
||
)
|
||
|
||
config.set_value(
|
||
SETTINGS_SECTION,
|
||
CONFIG_LOD1_DISTANCE,
|
||
lod1_distance
|
||
)
|
||
|
||
config.set_value(
|
||
SETTINGS_SECTION,
|
||
CONFIG_LOD2_DISTANCE,
|
||
lod2_distance
|
||
)
|
||
|
||
config.set_value(
|
||
SETTINGS_SECTION,
|
||
CONFIG_LOD_CULL_DISTANCE,
|
||
lod_cull_distance
|
||
)
|
||
|
||
config.set_value(
|
||
SETTINGS_SECTION,
|
||
CONFIG_AUTO_LOD_ENABLED,
|
||
auto_lod_enabled
|
||
)
|
||
|
||
config.set_value(
|
||
SETTINGS_SECTION,
|
||
CONFIG_AUTO_LOD_BIAS,
|
||
auto_lod_bias
|
||
)
|
||
|
||
config.set_value(
|
||
SETTINGS_SECTION,
|
||
CONFIG_AUTO_LOD_CULL_DISTANCE,
|
||
auto_lod_cull_distance
|
||
)
|
||
|
||
config.set_value(
|
||
INTERFACE_SECTION,
|
||
CONFIG_INTERFACE_LANGUAGE,
|
||
current_language
|
||
)
|
||
|
||
config.set_value(
|
||
SCENE_SETTINGS_SECTION,
|
||
CONFIG_SCENE_SETTINGS,
|
||
scene_brush_settings
|
||
)
|
||
|
||
var save_error := config.save(
|
||
PALETTE_CONFIG_PATH
|
||
)
|
||
|
||
if save_error != OK:
|
||
push_warning(
|
||
"World Brush: Failed to save configuration. Error: %s"
|
||
% error_string(save_error)
|
||
)
|
||
|
||
|
||
func _load_saved_configuration() -> void:
|
||
is_loading_configuration = true
|
||
|
||
var config := ConfigFile.new()
|
||
|
||
var load_error := config.load(
|
||
PALETTE_CONFIG_PATH
|
||
)
|
||
|
||
if load_error != OK:
|
||
_add_scene_row(
|
||
null,
|
||
false
|
||
)
|
||
|
||
is_loading_configuration = false
|
||
return
|
||
|
||
_load_interface_settings(
|
||
config
|
||
)
|
||
|
||
_load_brush_settings(
|
||
config
|
||
)
|
||
|
||
_load_scene_brush_settings(
|
||
config
|
||
)
|
||
|
||
var stored_paths: Variant = config.get_value(
|
||
PALETTE_SECTION,
|
||
CONFIG_SCENE_PATHS,
|
||
PackedStringArray()
|
||
)
|
||
|
||
var active_scene_path := str(
|
||
config.get_value(
|
||
PALETTE_SECTION,
|
||
CONFIG_ACTIVE_SCENE_PATH,
|
||
""
|
||
)
|
||
)
|
||
|
||
var scene_paths: Array[String] = []
|
||
|
||
if typeof(
|
||
stored_paths
|
||
) == TYPE_PACKED_STRING_ARRAY:
|
||
for path_value in stored_paths:
|
||
scene_paths.append(
|
||
str(path_value)
|
||
)
|
||
|
||
elif typeof(
|
||
stored_paths
|
||
) == TYPE_ARRAY:
|
||
for path_value in stored_paths:
|
||
scene_paths.append(
|
||
str(path_value)
|
||
)
|
||
|
||
for scene_path in scene_paths:
|
||
if scene_path.is_empty():
|
||
continue
|
||
|
||
if not ResourceLoader.exists(
|
||
scene_path,
|
||
"PackedScene"
|
||
):
|
||
push_warning(
|
||
"World Brush: Saved scene no longer exists -> %s"
|
||
% scene_path
|
||
)
|
||
|
||
continue
|
||
|
||
var loaded_scene := (
|
||
ResourceLoader.load(
|
||
scene_path,
|
||
"PackedScene"
|
||
)
|
||
as PackedScene
|
||
)
|
||
|
||
if loaded_scene == null:
|
||
continue
|
||
|
||
var picker := _add_scene_row(
|
||
loaded_scene,
|
||
false
|
||
)
|
||
|
||
if scene_path == active_scene_path:
|
||
active_picker = picker
|
||
selected_scene = loaded_scene
|
||
|
||
if scene_rows.is_empty():
|
||
_add_scene_row(
|
||
null,
|
||
false
|
||
)
|
||
|
||
if not is_instance_valid(
|
||
active_picker
|
||
):
|
||
_select_first_available_scene()
|
||
|
||
_load_active_scene_settings(
|
||
false
|
||
)
|
||
|
||
is_loading_configuration = false
|
||
|
||
|
||
func _load_interface_settings(
|
||
config: ConfigFile
|
||
) -> void:
|
||
var stored_language := str(
|
||
config.get_value(
|
||
INTERFACE_SECTION,
|
||
CONFIG_INTERFACE_LANGUAGE,
|
||
LANGUAGE_ENGLISH
|
||
)
|
||
)
|
||
|
||
if (
|
||
stored_language != LANGUAGE_ENGLISH
|
||
and stored_language != LANGUAGE_ARABIC
|
||
):
|
||
stored_language = LANGUAGE_ENGLISH
|
||
|
||
current_language = stored_language
|
||
|
||
|
||
func _get_packed_scene_path(scene: PackedScene) -> String:
|
||
if scene == null:
|
||
return ""
|
||
return scene.resource_path
|
||
|
||
|
||
func _load_packed_scene(scene_path: String) -> PackedScene:
|
||
if scene_path.is_empty():
|
||
return null
|
||
if not ResourceLoader.exists(scene_path, "PackedScene"):
|
||
return null
|
||
return ResourceLoader.load(scene_path, "PackedScene") as PackedScene
|
||
|
||
|
||
func _load_brush_settings(
|
||
config: ConfigFile
|
||
) -> void:
|
||
brush_radius = maxf(
|
||
float(
|
||
config.get_value(
|
||
SETTINGS_SECTION,
|
||
CONFIG_BRUSH_RADIUS,
|
||
brush_radius
|
||
)
|
||
),
|
||
0.5
|
||
)
|
||
|
||
instances_per_click = maxi(
|
||
int(
|
||
config.get_value(
|
||
SETTINGS_SECTION,
|
||
CONFIG_INSTANCES_PER_STAMP,
|
||
instances_per_click
|
||
)
|
||
),
|
||
1
|
||
)
|
||
|
||
minimum_spacing = maxf(
|
||
float(
|
||
config.get_value(
|
||
SETTINGS_SECTION,
|
||
CONFIG_MINIMUM_SPACING,
|
||
minimum_spacing
|
||
)
|
||
),
|
||
0.0
|
||
)
|
||
|
||
random_y_rotation = bool(
|
||
config.get_value(
|
||
SETTINGS_SECTION,
|
||
CONFIG_RANDOM_Y_ROTATION,
|
||
random_y_rotation
|
||
)
|
||
)
|
||
|
||
align_to_surface = bool(
|
||
config.get_value(
|
||
SETTINGS_SECTION,
|
||
CONFIG_ALIGN_TO_SURFACE,
|
||
align_to_surface
|
||
)
|
||
)
|
||
|
||
maximum_slope_degrees = clampf(
|
||
float(
|
||
config.get_value(
|
||
SETTINGS_SECTION,
|
||
CONFIG_MAXIMUM_SLOPE,
|
||
maximum_slope_degrees
|
||
)
|
||
),
|
||
0.0,
|
||
90.0
|
||
)
|
||
|
||
height_offset = float(
|
||
config.get_value(
|
||
SETTINGS_SECTION,
|
||
CONFIG_HEIGHT_OFFSET,
|
||
height_offset
|
||
)
|
||
)
|
||
|
||
height_offset_mode = int(
|
||
config.get_value(
|
||
SETTINGS_SECTION,
|
||
CONFIG_HEIGHT_OFFSET_MODE,
|
||
HeightOffsetMode.SURFACE_NORMAL
|
||
)
|
||
)
|
||
|
||
height_offset_mode = clampi(
|
||
height_offset_mode,
|
||
HeightOffsetMode.WORLD_Y,
|
||
HeightOffsetMode.SURFACE_NORMAL
|
||
)
|
||
|
||
random_scale_enabled = bool(
|
||
config.get_value(
|
||
SETTINGS_SECTION,
|
||
CONFIG_RANDOM_SCALE_ENABLED,
|
||
random_scale_enabled
|
||
)
|
||
)
|
||
|
||
minimum_scale = maxf(
|
||
float(
|
||
config.get_value(
|
||
SETTINGS_SECTION,
|
||
CONFIG_MINIMUM_SCALE,
|
||
minimum_scale
|
||
)
|
||
),
|
||
0.01
|
||
)
|
||
|
||
maximum_scale = maxf(
|
||
float(
|
||
config.get_value(
|
||
SETTINGS_SECTION,
|
||
CONFIG_MAXIMUM_SCALE,
|
||
maximum_scale
|
||
)
|
||
),
|
||
0.01
|
||
)
|
||
|
||
if minimum_scale > maximum_scale:
|
||
maximum_scale = minimum_scale
|
||
|
||
lod_enabled = bool(
|
||
config.get_value(
|
||
SETTINGS_SECTION,
|
||
CONFIG_LOD_ENABLED,
|
||
lod_enabled
|
||
)
|
||
)
|
||
|
||
lod1_scene = _load_packed_scene(
|
||
str(config.get_value(
|
||
SETTINGS_SECTION,
|
||
CONFIG_LOD1_SCENE_PATH,
|
||
""
|
||
))
|
||
)
|
||
|
||
lod2_scene = _load_packed_scene(
|
||
str(config.get_value(
|
||
SETTINGS_SECTION,
|
||
CONFIG_LOD2_SCENE_PATH,
|
||
""
|
||
))
|
||
)
|
||
|
||
lod1_distance = maxf(
|
||
float(config.get_value(
|
||
SETTINGS_SECTION,
|
||
CONFIG_LOD1_DISTANCE,
|
||
lod1_distance
|
||
)),
|
||
1.0
|
||
)
|
||
|
||
lod2_distance = maxf(
|
||
float(config.get_value(
|
||
SETTINGS_SECTION,
|
||
CONFIG_LOD2_DISTANCE,
|
||
lod2_distance
|
||
)),
|
||
lod1_distance + 1.0
|
||
)
|
||
|
||
lod_cull_distance = maxf(
|
||
float(config.get_value(
|
||
SETTINGS_SECTION,
|
||
CONFIG_LOD_CULL_DISTANCE,
|
||
lod_cull_distance
|
||
)),
|
||
0.0
|
||
)
|
||
_validate_lod_cull_distance()
|
||
|
||
auto_lod_enabled = bool(
|
||
config.get_value(
|
||
SETTINGS_SECTION,
|
||
CONFIG_AUTO_LOD_ENABLED,
|
||
auto_lod_enabled
|
||
)
|
||
)
|
||
auto_lod_bias = clampf(
|
||
float(config.get_value(
|
||
SETTINGS_SECTION,
|
||
CONFIG_AUTO_LOD_BIAS,
|
||
auto_lod_bias
|
||
)),
|
||
0.0,
|
||
4.0
|
||
)
|
||
auto_lod_cull_distance = maxf(
|
||
float(config.get_value(
|
||
SETTINGS_SECTION,
|
||
CONFIG_AUTO_LOD_CULL_DISTANCE,
|
||
auto_lod_cull_distance
|
||
)),
|
||
0.0
|
||
)
|
||
|
||
_apply_settings_to_controls()
|
||
|
||
|
||
func _load_scene_brush_settings(
|
||
config: ConfigFile
|
||
) -> void:
|
||
scene_brush_settings.clear()
|
||
|
||
var stored_profiles: Variant = config.get_value(
|
||
SCENE_SETTINGS_SECTION,
|
||
CONFIG_SCENE_SETTINGS,
|
||
{}
|
||
)
|
||
|
||
if typeof(
|
||
stored_profiles
|
||
) != TYPE_DICTIONARY:
|
||
return
|
||
|
||
var profiles_dictionary: Dictionary = (
|
||
stored_profiles
|
||
)
|
||
|
||
for stored_scene_path in profiles_dictionary:
|
||
var scene_path := str(
|
||
stored_scene_path
|
||
)
|
||
|
||
var stored_profile: Variant = (
|
||
profiles_dictionary[
|
||
stored_scene_path
|
||
]
|
||
)
|
||
|
||
if typeof(
|
||
stored_profile
|
||
) != TYPE_DICTIONARY:
|
||
continue
|
||
|
||
var profile_dictionary: Dictionary = (
|
||
stored_profile
|
||
)
|
||
|
||
scene_brush_settings[
|
||
scene_path
|
||
] = profile_dictionary.duplicate(
|
||
true
|
||
)
|
||
|
||
|
||
func _get_active_scene_settings_key() -> String:
|
||
if selected_scene == null:
|
||
return ""
|
||
|
||
return selected_scene.resource_path
|
||
|
||
|
||
func _capture_current_brush_settings() -> Dictionary:
|
||
return {
|
||
CONFIG_BRUSH_RADIUS:
|
||
brush_radius,
|
||
|
||
CONFIG_INSTANCES_PER_STAMP:
|
||
instances_per_click,
|
||
|
||
CONFIG_MINIMUM_SPACING:
|
||
minimum_spacing,
|
||
|
||
CONFIG_RANDOM_Y_ROTATION:
|
||
random_y_rotation,
|
||
|
||
CONFIG_ALIGN_TO_SURFACE:
|
||
align_to_surface,
|
||
|
||
CONFIG_MAXIMUM_SLOPE:
|
||
maximum_slope_degrees,
|
||
|
||
CONFIG_HEIGHT_OFFSET:
|
||
height_offset,
|
||
|
||
CONFIG_HEIGHT_OFFSET_MODE:
|
||
height_offset_mode,
|
||
|
||
CONFIG_RANDOM_SCALE_ENABLED:
|
||
random_scale_enabled,
|
||
|
||
CONFIG_MINIMUM_SCALE:
|
||
minimum_scale,
|
||
|
||
CONFIG_MAXIMUM_SCALE:
|
||
maximum_scale,
|
||
|
||
CONFIG_LOD_ENABLED:
|
||
lod_enabled,
|
||
|
||
CONFIG_LOD1_SCENE_PATH:
|
||
_get_packed_scene_path(lod1_scene),
|
||
|
||
CONFIG_LOD2_SCENE_PATH:
|
||
_get_packed_scene_path(lod2_scene),
|
||
|
||
CONFIG_LOD1_DISTANCE:
|
||
lod1_distance,
|
||
|
||
CONFIG_LOD2_DISTANCE:
|
||
lod2_distance,
|
||
|
||
CONFIG_LOD_CULL_DISTANCE:
|
||
lod_cull_distance,
|
||
|
||
CONFIG_AUTO_LOD_ENABLED:
|
||
auto_lod_enabled,
|
||
|
||
CONFIG_AUTO_LOD_BIAS:
|
||
auto_lod_bias,
|
||
|
||
CONFIG_AUTO_LOD_CULL_DISTANCE:
|
||
auto_lod_cull_distance,
|
||
}
|
||
|
||
|
||
func _create_default_scene_settings() -> Dictionary:
|
||
return {
|
||
CONFIG_BRUSH_RADIUS: 5.0,
|
||
CONFIG_INSTANCES_PER_STAMP: 5,
|
||
CONFIG_MINIMUM_SPACING: 1.0,
|
||
CONFIG_RANDOM_Y_ROTATION: true,
|
||
CONFIG_ALIGN_TO_SURFACE: false,
|
||
CONFIG_MAXIMUM_SLOPE: 85.0,
|
||
CONFIG_HEIGHT_OFFSET: 0.0,
|
||
CONFIG_HEIGHT_OFFSET_MODE:
|
||
HeightOffsetMode.SURFACE_NORMAL,
|
||
CONFIG_RANDOM_SCALE_ENABLED: true,
|
||
CONFIG_MINIMUM_SCALE: 0.8,
|
||
CONFIG_MAXIMUM_SCALE: 1.3,
|
||
CONFIG_LOD_ENABLED: false,
|
||
CONFIG_LOD1_SCENE_PATH: "",
|
||
CONFIG_LOD2_SCENE_PATH: "",
|
||
CONFIG_LOD1_DISTANCE: 25.0,
|
||
CONFIG_LOD2_DISTANCE: 60.0,
|
||
CONFIG_LOD_CULL_DISTANCE: 0.0,
|
||
CONFIG_AUTO_LOD_ENABLED: false,
|
||
CONFIG_AUTO_LOD_BIAS: 1.0,
|
||
CONFIG_AUTO_LOD_CULL_DISTANCE: 0.0,
|
||
}
|
||
|
||
|
||
func _store_current_scene_settings() -> void:
|
||
if (
|
||
is_loading_configuration
|
||
or is_applying_scene_settings
|
||
):
|
||
return
|
||
|
||
var scene_key := (
|
||
_get_active_scene_settings_key()
|
||
)
|
||
|
||
if scene_key.is_empty():
|
||
return
|
||
|
||
scene_brush_settings[
|
||
scene_key
|
||
] = _capture_current_brush_settings()
|
||
|
||
|
||
func _load_active_scene_settings(
|
||
emit_changes: bool = true
|
||
) -> void:
|
||
var scene_key := (
|
||
_get_active_scene_settings_key()
|
||
)
|
||
|
||
if scene_key.is_empty():
|
||
return
|
||
|
||
if not scene_brush_settings.has(
|
||
scene_key
|
||
):
|
||
scene_brush_settings[
|
||
scene_key
|
||
] = _create_default_scene_settings()
|
||
_apply_brush_settings_profile(
|
||
scene_brush_settings[scene_key]
|
||
)
|
||
|
||
if emit_changes:
|
||
_emit_all_brush_settings_changed()
|
||
|
||
return
|
||
|
||
var stored_profile: Variant = (
|
||
scene_brush_settings[
|
||
scene_key
|
||
]
|
||
)
|
||
|
||
if typeof(
|
||
stored_profile
|
||
) != TYPE_DICTIONARY:
|
||
scene_brush_settings[
|
||
scene_key
|
||
] = _create_default_scene_settings()
|
||
_apply_brush_settings_profile(
|
||
scene_brush_settings[scene_key]
|
||
)
|
||
|
||
if emit_changes:
|
||
_emit_all_brush_settings_changed()
|
||
|
||
return
|
||
|
||
var profile: Dictionary = stored_profile
|
||
|
||
_apply_brush_settings_profile(
|
||
profile
|
||
)
|
||
|
||
if emit_changes:
|
||
_emit_all_brush_settings_changed()
|
||
|
||
|
||
func _apply_brush_settings_profile(
|
||
profile: Dictionary
|
||
) -> void:
|
||
is_applying_scene_settings = true
|
||
|
||
brush_radius = maxf(
|
||
float(
|
||
profile.get(
|
||
CONFIG_BRUSH_RADIUS,
|
||
brush_radius
|
||
)
|
||
),
|
||
0.5
|
||
)
|
||
|
||
instances_per_click = maxi(
|
||
int(
|
||
profile.get(
|
||
CONFIG_INSTANCES_PER_STAMP,
|
||
instances_per_click
|
||
)
|
||
),
|
||
1
|
||
)
|
||
|
||
minimum_spacing = maxf(
|
||
float(
|
||
profile.get(
|
||
CONFIG_MINIMUM_SPACING,
|
||
minimum_spacing
|
||
)
|
||
),
|
||
0.0
|
||
)
|
||
|
||
random_y_rotation = bool(
|
||
profile.get(
|
||
CONFIG_RANDOM_Y_ROTATION,
|
||
random_y_rotation
|
||
)
|
||
)
|
||
|
||
align_to_surface = bool(
|
||
profile.get(
|
||
CONFIG_ALIGN_TO_SURFACE,
|
||
align_to_surface
|
||
)
|
||
)
|
||
|
||
maximum_slope_degrees = clampf(
|
||
float(
|
||
profile.get(
|
||
CONFIG_MAXIMUM_SLOPE,
|
||
maximum_slope_degrees
|
||
)
|
||
),
|
||
0.0,
|
||
90.0
|
||
)
|
||
|
||
height_offset = float(
|
||
profile.get(
|
||
CONFIG_HEIGHT_OFFSET,
|
||
height_offset
|
||
)
|
||
)
|
||
|
||
height_offset_mode = clampi(
|
||
int(
|
||
profile.get(
|
||
CONFIG_HEIGHT_OFFSET_MODE,
|
||
height_offset_mode
|
||
)
|
||
),
|
||
HeightOffsetMode.WORLD_Y,
|
||
HeightOffsetMode.SURFACE_NORMAL
|
||
)
|
||
|
||
random_scale_enabled = bool(
|
||
profile.get(
|
||
CONFIG_RANDOM_SCALE_ENABLED,
|
||
random_scale_enabled
|
||
)
|
||
)
|
||
|
||
minimum_scale = maxf(
|
||
float(
|
||
profile.get(
|
||
CONFIG_MINIMUM_SCALE,
|
||
minimum_scale
|
||
)
|
||
),
|
||
0.01
|
||
)
|
||
|
||
maximum_scale = maxf(
|
||
float(
|
||
profile.get(
|
||
CONFIG_MAXIMUM_SCALE,
|
||
maximum_scale
|
||
)
|
||
),
|
||
0.01
|
||
)
|
||
|
||
lod_enabled = bool(
|
||
profile.get(
|
||
CONFIG_LOD_ENABLED,
|
||
lod_enabled
|
||
)
|
||
)
|
||
|
||
lod1_scene = _load_packed_scene(
|
||
str(profile.get(
|
||
CONFIG_LOD1_SCENE_PATH,
|
||
_get_packed_scene_path(lod1_scene)
|
||
))
|
||
)
|
||
|
||
lod2_scene = _load_packed_scene(
|
||
str(profile.get(
|
||
CONFIG_LOD2_SCENE_PATH,
|
||
_get_packed_scene_path(lod2_scene)
|
||
))
|
||
)
|
||
|
||
lod1_distance = maxf(
|
||
float(profile.get(
|
||
CONFIG_LOD1_DISTANCE,
|
||
lod1_distance
|
||
)),
|
||
1.0
|
||
)
|
||
|
||
lod2_distance = maxf(
|
||
float(profile.get(
|
||
CONFIG_LOD2_DISTANCE,
|
||
lod2_distance
|
||
)),
|
||
lod1_distance + 1.0
|
||
)
|
||
|
||
lod_cull_distance = maxf(
|
||
float(profile.get(
|
||
CONFIG_LOD_CULL_DISTANCE,
|
||
lod_cull_distance
|
||
)),
|
||
0.0
|
||
)
|
||
_validate_lod_cull_distance()
|
||
|
||
auto_lod_enabled = bool(
|
||
profile.get(
|
||
CONFIG_AUTO_LOD_ENABLED,
|
||
false
|
||
)
|
||
)
|
||
auto_lod_bias = clampf(
|
||
float(profile.get(
|
||
CONFIG_AUTO_LOD_BIAS,
|
||
1.0
|
||
)),
|
||
0.0,
|
||
4.0
|
||
)
|
||
auto_lod_cull_distance = maxf(
|
||
float(profile.get(
|
||
CONFIG_AUTO_LOD_CULL_DISTANCE,
|
||
0.0
|
||
)),
|
||
0.0
|
||
)
|
||
|
||
if minimum_scale > maximum_scale:
|
||
maximum_scale = minimum_scale
|
||
|
||
_apply_settings_to_controls()
|
||
_update_controls()
|
||
|
||
is_applying_scene_settings = false
|
||
|
||
|
||
func _emit_all_brush_settings_changed() -> void:
|
||
brush_radius_changed.emit(
|
||
brush_radius
|
||
)
|
||
|
||
instances_per_click_changed.emit(
|
||
instances_per_click
|
||
)
|
||
|
||
minimum_spacing_changed.emit(
|
||
minimum_spacing
|
||
)
|
||
|
||
random_y_rotation_changed.emit(
|
||
random_y_rotation
|
||
)
|
||
|
||
align_to_surface_changed.emit(
|
||
align_to_surface
|
||
)
|
||
|
||
maximum_slope_changed.emit(
|
||
maximum_slope_degrees
|
||
)
|
||
|
||
height_offset_changed.emit(
|
||
height_offset
|
||
)
|
||
|
||
height_offset_mode_changed.emit(
|
||
height_offset_mode
|
||
)
|
||
|
||
random_scale_enabled_changed.emit(
|
||
random_scale_enabled
|
||
)
|
||
|
||
minimum_scale_changed.emit(
|
||
minimum_scale
|
||
)
|
||
|
||
maximum_scale_changed.emit(
|
||
maximum_scale
|
||
)
|
||
|
||
_emit_lod_settings_changed()
|
||
_emit_auto_lod_settings_changed()
|
||
|
||
|
||
func _apply_settings_to_controls() -> void:
|
||
if is_instance_valid(radius_spin_box):
|
||
radius_spin_box.set_value_no_signal(
|
||
brush_radius
|
||
)
|
||
|
||
if is_instance_valid(instances_spin_box):
|
||
instances_spin_box.set_value_no_signal(
|
||
float(instances_per_click)
|
||
)
|
||
|
||
if is_instance_valid(spacing_spin_box):
|
||
spacing_spin_box.set_value_no_signal(
|
||
minimum_spacing
|
||
)
|
||
|
||
if is_instance_valid(random_rotation_toggle):
|
||
random_rotation_toggle.set_pressed_no_signal(
|
||
random_y_rotation
|
||
)
|
||
|
||
if is_instance_valid(align_surface_toggle):
|
||
align_surface_toggle.set_pressed_no_signal(
|
||
align_to_surface
|
||
)
|
||
|
||
if is_instance_valid(maximum_slope_spin_box):
|
||
maximum_slope_spin_box.set_value_no_signal(
|
||
maximum_slope_degrees
|
||
)
|
||
|
||
if is_instance_valid(height_offset_spin_box):
|
||
height_offset_spin_box.set_value_no_signal(
|
||
height_offset
|
||
)
|
||
|
||
if is_instance_valid(height_offset_mode_option):
|
||
height_offset_mode_option.select(
|
||
height_offset_mode
|
||
)
|
||
|
||
if is_instance_valid(random_scale_toggle):
|
||
random_scale_toggle.set_pressed_no_signal(
|
||
random_scale_enabled
|
||
)
|
||
|
||
if is_instance_valid(minimum_scale_spin_box):
|
||
minimum_scale_spin_box.set_value_no_signal(
|
||
minimum_scale
|
||
)
|
||
|
||
if is_instance_valid(maximum_scale_spin_box):
|
||
maximum_scale_spin_box.set_value_no_signal(
|
||
maximum_scale
|
||
)
|
||
|
||
if is_instance_valid(lod_toggle):
|
||
lod_toggle.set_pressed_no_signal(lod_enabled)
|
||
|
||
if is_instance_valid(lod1_picker):
|
||
lod1_picker.edited_resource = lod1_scene
|
||
|
||
if is_instance_valid(lod2_picker):
|
||
lod2_picker.edited_resource = lod2_scene
|
||
|
||
if is_instance_valid(lod1_distance_spin_box):
|
||
lod1_distance_spin_box.set_value_no_signal(lod1_distance)
|
||
|
||
if is_instance_valid(lod2_distance_spin_box):
|
||
lod2_distance_spin_box.set_value_no_signal(lod2_distance)
|
||
|
||
if is_instance_valid(lod_cull_distance_spin_box):
|
||
lod_cull_distance_spin_box.set_value_no_signal(lod_cull_distance)
|
||
|
||
if is_instance_valid(auto_lod_toggle):
|
||
auto_lod_toggle.set_pressed_no_signal(auto_lod_enabled)
|
||
|
||
if is_instance_valid(auto_lod_bias_spin_box):
|
||
auto_lod_bias_spin_box.set_value_no_signal(auto_lod_bias)
|
||
|
||
if is_instance_valid(auto_lod_cull_spin_box):
|
||
auto_lod_cull_spin_box.set_value_no_signal(auto_lod_cull_distance)
|
||
|
||
_update_lod_controls()
|
||
_update_auto_lod_controls()
|
||
|
||
|
||
func _emit_loaded_state() -> void:
|
||
_apply_language()
|
||
_update_scene_interface()
|
||
|
||
_emit_palette_changed()
|
||
_emit_active_scene_changed()
|
||
|
||
_emit_all_brush_settings_changed()
|
||
|
||
var valid_scene_count := (
|
||
_get_valid_scene_pickers().size()
|
||
)
|
||
|
||
if valid_scene_count <= 0:
|
||
_set_status(
|
||
"No Scenes Selected"
|
||
)
|
||
|
||
return
|
||
|
||
_set_status(
|
||
"Palette Restored: %d Scene(s)"
|
||
% valid_scene_count
|
||
)
|
||
|
||
|
||
# =========================================================
|
||
# تحديث قائمة المشاهد
|
||
# =========================================================
|
||
|
||
func _update_scene_interface() -> void:
|
||
_validate_active_picker()
|
||
|
||
var valid_pickers := (
|
||
_get_valid_scene_pickers()
|
||
)
|
||
|
||
var valid_scene_count := (
|
||
valid_pickers.size()
|
||
)
|
||
|
||
var active_valid_index := valid_pickers.find(
|
||
active_picker
|
||
)
|
||
|
||
for entry in scene_rows:
|
||
var picker := _get_picker_from_row(
|
||
entry
|
||
)
|
||
|
||
var select_button := (
|
||
entry.get(
|
||
"select_button"
|
||
)
|
||
as Button
|
||
)
|
||
|
||
var remove_button := (
|
||
entry.get(
|
||
"remove_button"
|
||
)
|
||
as Button
|
||
)
|
||
|
||
var has_scene := _picker_has_scene(
|
||
picker
|
||
)
|
||
|
||
var is_active := (
|
||
has_scene
|
||
and picker == active_picker
|
||
)
|
||
|
||
if is_instance_valid(
|
||
select_button
|
||
):
|
||
select_button.disabled = (
|
||
not has_scene
|
||
or is_active
|
||
)
|
||
|
||
select_button.tooltip_text = _tr(
|
||
"select_scene_tooltip"
|
||
)
|
||
|
||
if is_active:
|
||
select_button.text = _tr(
|
||
"active"
|
||
)
|
||
else:
|
||
select_button.text = _tr(
|
||
"use"
|
||
)
|
||
|
||
if is_instance_valid(
|
||
remove_button
|
||
):
|
||
remove_button.disabled = (
|
||
scene_rows.size() <= 1
|
||
)
|
||
|
||
remove_button.tooltip_text = _tr(
|
||
"remove_scene_tooltip"
|
||
)
|
||
|
||
if is_instance_valid(
|
||
previous_scene_button
|
||
):
|
||
previous_scene_button.disabled = (
|
||
valid_scene_count <= 1
|
||
)
|
||
|
||
if is_instance_valid(
|
||
next_scene_button
|
||
):
|
||
next_scene_button.disabled = (
|
||
valid_scene_count <= 1
|
||
)
|
||
|
||
if (
|
||
is_instance_valid(active_picker)
|
||
and active_valid_index >= 0
|
||
):
|
||
selected_scene = (
|
||
active_picker.edited_resource
|
||
as PackedScene
|
||
)
|
||
|
||
if current_language == LANGUAGE_ARABIC:
|
||
active_scene_label.text = (
|
||
"المشهد النشط: %d/%d — %s"
|
||
% [
|
||
active_valid_index + 1,
|
||
valid_scene_count,
|
||
_get_scene_display_name(
|
||
selected_scene
|
||
),
|
||
]
|
||
)
|
||
else:
|
||
active_scene_label.text = (
|
||
"Active Scene: %d/%d — %s"
|
||
% [
|
||
active_valid_index + 1,
|
||
valid_scene_count,
|
||
_get_scene_display_name(
|
||
selected_scene
|
||
),
|
||
]
|
||
)
|
||
|
||
active_scene_label.tooltip_text = (
|
||
selected_scene.resource_path
|
||
)
|
||
|
||
else:
|
||
selected_scene = null
|
||
|
||
active_scene_label.text = _tr(
|
||
"active_scene_none"
|
||
)
|
||
|
||
active_scene_label.tooltip_text = ""
|
||
|
||
_update_controls()
|
||
|
||
|
||
func _validate_active_picker() -> void:
|
||
if (
|
||
is_instance_valid(active_picker)
|
||
and _picker_belongs_to_palette(
|
||
active_picker
|
||
)
|
||
and _picker_has_scene(
|
||
active_picker
|
||
)
|
||
):
|
||
return
|
||
|
||
active_picker = null
|
||
selected_scene = null
|
||
|
||
_select_first_available_scene()
|
||
|
||
|
||
func _picker_belongs_to_palette(
|
||
picker: EditorResourcePicker
|
||
) -> bool:
|
||
for entry in scene_rows:
|
||
if _get_picker_from_row(
|
||
entry
|
||
) == picker:
|
||
return true
|
||
|
||
return false
|
||
|
||
|
||
func _get_valid_scene_pickers() -> Array[EditorResourcePicker]:
|
||
var result: Array[EditorResourcePicker] = []
|
||
|
||
for entry in scene_rows:
|
||
var picker := _get_picker_from_row(
|
||
entry
|
||
)
|
||
|
||
if _picker_has_scene(
|
||
picker
|
||
):
|
||
result.append(
|
||
picker
|
||
)
|
||
|
||
return result
|
||
|
||
|
||
func _get_picker_from_row(
|
||
entry: Dictionary
|
||
) -> EditorResourcePicker:
|
||
return (
|
||
entry.get("picker")
|
||
as EditorResourcePicker
|
||
)
|
||
|
||
|
||
func _picker_has_scene(
|
||
picker: EditorResourcePicker
|
||
) -> bool:
|
||
return (
|
||
is_instance_valid(picker)
|
||
and picker.edited_resource is PackedScene
|
||
)
|
||
|
||
|
||
func _get_scene_display_name(
|
||
scene: PackedScene
|
||
) -> String:
|
||
if scene == null:
|
||
return "None"
|
||
|
||
if scene.resource_path.is_empty():
|
||
return _tr(
|
||
"unsaved_scene"
|
||
)
|
||
|
||
return scene.resource_path.get_file()
|
||
|
||
|
||
func _emit_active_scene_changed() -> void:
|
||
_validate_active_picker()
|
||
|
||
if not is_instance_valid(
|
||
active_picker
|
||
):
|
||
selected_scene = null
|
||
|
||
selected_scene_changed.emit(
|
||
null
|
||
)
|
||
|
||
_disable_brush_if_no_scene()
|
||
return
|
||
|
||
selected_scene = (
|
||
active_picker.edited_resource
|
||
as PackedScene
|
||
)
|
||
|
||
selected_scene_changed.emit(
|
||
selected_scene
|
||
)
|
||
|
||
_disable_brush_if_no_scene()
|
||
|
||
|
||
func _emit_palette_changed() -> void:
|
||
scene_palette_changed.emit(
|
||
get_scene_palette()
|
||
)
|
||
|
||
|
||
func get_scene_palette() -> Array:
|
||
var scenes: Array = []
|
||
|
||
for picker in _get_valid_scene_pickers():
|
||
var packed_scene := (
|
||
picker.edited_resource
|
||
as PackedScene
|
||
)
|
||
|
||
if packed_scene != null:
|
||
scenes.append(
|
||
packed_scene
|
||
)
|
||
|
||
return scenes
|
||
|
||
|
||
# =========================================================
|
||
# إعدادات الفرشاة
|
||
# =========================================================
|
||
|
||
func _create_brush_toggle() -> void:
|
||
var separator := HSeparator.new()
|
||
separator.name = "BrushSeparator"
|
||
|
||
controls_container.add_child(
|
||
separator
|
||
)
|
||
|
||
brush_toggle = CheckButton.new()
|
||
brush_toggle.name = "BrushToggle"
|
||
brush_toggle.text = "Enable Brush"
|
||
brush_toggle.button_pressed = false
|
||
|
||
brush_toggle.toggled.connect(
|
||
_on_brush_toggled
|
||
)
|
||
|
||
controls_container.add_child(
|
||
brush_toggle
|
||
)
|
||
|
||
|
||
func _on_brush_toggled(
|
||
enabled: bool
|
||
) -> void:
|
||
if enabled and selected_scene == null:
|
||
_disable_brush()
|
||
|
||
_set_status(
|
||
"Select An Active Scene First"
|
||
)
|
||
|
||
return
|
||
|
||
brush_enabled = enabled
|
||
|
||
if brush_enabled:
|
||
_set_status(
|
||
"Brush Enabled — %s"
|
||
% _get_scene_display_name(
|
||
selected_scene
|
||
)
|
||
)
|
||
else:
|
||
_set_status(
|
||
"Brush Disabled"
|
||
)
|
||
|
||
brush_enabled_changed.emit(
|
||
brush_enabled
|
||
)
|
||
|
||
|
||
func _disable_brush_if_no_scene() -> void:
|
||
if selected_scene == null:
|
||
_disable_brush()
|
||
|
||
|
||
func _disable_brush() -> void:
|
||
brush_enabled = false
|
||
|
||
if is_instance_valid(
|
||
brush_toggle
|
||
):
|
||
brush_toggle.set_pressed_no_signal(
|
||
false
|
||
)
|
||
|
||
brush_enabled_changed.emit(
|
||
false
|
||
)
|
||
|
||
|
||
func _create_radius_control() -> void:
|
||
var row := _create_setting_row(
|
||
"RadiusContainer",
|
||
"Brush Radius"
|
||
)
|
||
|
||
brush_radius_label = (
|
||
row.get_child(0)
|
||
as Label
|
||
)
|
||
|
||
radius_spin_box = SpinBox.new()
|
||
radius_spin_box.name = "RadiusSpinBox"
|
||
radius_spin_box.min_value = 0.5
|
||
radius_spin_box.max_value = 100.0
|
||
radius_spin_box.step = 0.5
|
||
radius_spin_box.value = brush_radius
|
||
radius_spin_box.allow_greater = true
|
||
radius_spin_box.suffix = " m"
|
||
|
||
radius_spin_box.custom_minimum_size.x = (
|
||
105.0
|
||
)
|
||
|
||
radius_spin_box.value_changed.connect(
|
||
_on_radius_changed
|
||
)
|
||
|
||
row.add_child(
|
||
radius_spin_box
|
||
)
|
||
|
||
|
||
func _on_radius_changed(
|
||
value: float
|
||
) -> void:
|
||
brush_radius = maxf(
|
||
value,
|
||
0.5
|
||
)
|
||
|
||
brush_radius_changed.emit(
|
||
brush_radius
|
||
)
|
||
|
||
_save_configuration()
|
||
|
||
|
||
func _create_instances_control() -> void:
|
||
var row := _create_setting_row(
|
||
"InstancesContainer",
|
||
"Instances Per Stamp"
|
||
)
|
||
|
||
instances_label = (
|
||
row.get_child(0)
|
||
as Label
|
||
)
|
||
|
||
instances_spin_box = SpinBox.new()
|
||
instances_spin_box.name = "InstancesSpinBox"
|
||
instances_spin_box.min_value = 1.0
|
||
instances_spin_box.max_value = 100.0
|
||
instances_spin_box.step = 1.0
|
||
instances_spin_box.value = instances_per_click
|
||
instances_spin_box.rounded = true
|
||
instances_spin_box.allow_greater = true
|
||
|
||
instances_spin_box.custom_minimum_size.x = (
|
||
105.0
|
||
)
|
||
|
||
instances_spin_box.value_changed.connect(
|
||
_on_instances_changed
|
||
)
|
||
|
||
row.add_child(
|
||
instances_spin_box
|
||
)
|
||
|
||
|
||
func _on_instances_changed(
|
||
value: float
|
||
) -> void:
|
||
instances_per_click = maxi(
|
||
roundi(value),
|
||
1
|
||
)
|
||
|
||
instances_per_click_changed.emit(
|
||
instances_per_click
|
||
)
|
||
|
||
_save_configuration()
|
||
|
||
|
||
func _create_spacing_control() -> void:
|
||
var row := _create_setting_row(
|
||
"SpacingContainer",
|
||
"Minimum Spacing"
|
||
)
|
||
|
||
spacing_label = (
|
||
row.get_child(0)
|
||
as Label
|
||
)
|
||
|
||
spacing_spin_box = SpinBox.new()
|
||
spacing_spin_box.name = "SpacingSpinBox"
|
||
spacing_spin_box.min_value = 0.0
|
||
spacing_spin_box.max_value = 100.0
|
||
spacing_spin_box.step = 0.25
|
||
spacing_spin_box.value = minimum_spacing
|
||
spacing_spin_box.allow_greater = true
|
||
spacing_spin_box.suffix = " m"
|
||
|
||
spacing_spin_box.custom_minimum_size.x = (
|
||
105.0
|
||
)
|
||
|
||
spacing_spin_box.value_changed.connect(
|
||
_on_spacing_changed
|
||
)
|
||
|
||
row.add_child(
|
||
spacing_spin_box
|
||
)
|
||
|
||
|
||
func _on_spacing_changed(
|
||
value: float
|
||
) -> void:
|
||
minimum_spacing = maxf(
|
||
value,
|
||
0.0
|
||
)
|
||
|
||
minimum_spacing_changed.emit(
|
||
minimum_spacing
|
||
)
|
||
|
||
_save_configuration()
|
||
|
||
|
||
func _create_random_rotation_control() -> void:
|
||
random_rotation_toggle = CheckButton.new()
|
||
random_rotation_toggle.name = "RandomRotationToggle"
|
||
random_rotation_toggle.text = "Random Y Rotation"
|
||
|
||
random_rotation_toggle.button_pressed = (
|
||
random_y_rotation
|
||
)
|
||
|
||
random_rotation_toggle.toggled.connect(
|
||
_on_random_rotation_toggled
|
||
)
|
||
|
||
controls_container.add_child(
|
||
random_rotation_toggle
|
||
)
|
||
|
||
|
||
func _on_random_rotation_toggled(
|
||
enabled: bool
|
||
) -> void:
|
||
random_y_rotation = enabled
|
||
|
||
random_y_rotation_changed.emit(
|
||
random_y_rotation
|
||
)
|
||
|
||
_save_configuration()
|
||
|
||
|
||
func _create_align_surface_control() -> void:
|
||
align_surface_toggle = CheckButton.new()
|
||
align_surface_toggle.name = "AlignSurfaceToggle"
|
||
align_surface_toggle.text = "Rotate With Surface"
|
||
align_surface_toggle.tooltip_text = (
|
||
"Rotates the instance to follow the surface slope."
|
||
)
|
||
|
||
align_surface_toggle.button_pressed = (
|
||
align_to_surface
|
||
)
|
||
|
||
align_surface_toggle.toggled.connect(
|
||
_on_align_surface_toggled
|
||
)
|
||
|
||
controls_container.add_child(
|
||
align_surface_toggle
|
||
)
|
||
|
||
|
||
func _on_align_surface_toggled(
|
||
enabled: bool
|
||
) -> void:
|
||
align_to_surface = enabled
|
||
|
||
align_to_surface_changed.emit(
|
||
align_to_surface
|
||
)
|
||
|
||
_save_configuration()
|
||
|
||
|
||
# =========================================================
|
||
# أقصى ميل مسموح
|
||
# =========================================================
|
||
|
||
func _create_maximum_slope_control() -> void:
|
||
var separator := HSeparator.new()
|
||
separator.name = "MaximumSlopeSeparator"
|
||
|
||
controls_container.add_child(
|
||
separator
|
||
)
|
||
|
||
var row := _create_setting_row(
|
||
"MaximumSlopeContainer",
|
||
"Maximum Slope"
|
||
)
|
||
|
||
maximum_slope_label = (
|
||
row.get_child(0)
|
||
as Label
|
||
)
|
||
|
||
maximum_slope_spin_box = SpinBox.new()
|
||
maximum_slope_spin_box.name = "MaximumSlopeSpinBox"
|
||
|
||
maximum_slope_spin_box.min_value = 0.0
|
||
maximum_slope_spin_box.max_value = 90.0
|
||
maximum_slope_spin_box.step = 1.0
|
||
|
||
maximum_slope_spin_box.value = (
|
||
maximum_slope_degrees
|
||
)
|
||
|
||
maximum_slope_spin_box.suffix = "°"
|
||
|
||
maximum_slope_spin_box.custom_minimum_size.x = (
|
||
105.0
|
||
)
|
||
|
||
maximum_slope_spin_box.tooltip_text = (
|
||
"0° allows flat surfaces only. "
|
||
+ "90° allows nearly all surface slopes."
|
||
)
|
||
|
||
maximum_slope_spin_box.value_changed.connect(
|
||
_on_maximum_slope_changed
|
||
)
|
||
|
||
row.add_child(
|
||
maximum_slope_spin_box
|
||
)
|
||
|
||
|
||
func _on_maximum_slope_changed(
|
||
value: float
|
||
) -> void:
|
||
maximum_slope_degrees = clampf(
|
||
value,
|
||
0.0,
|
||
90.0
|
||
)
|
||
|
||
maximum_slope_changed.emit(
|
||
maximum_slope_degrees
|
||
)
|
||
|
||
_save_configuration()
|
||
|
||
|
||
# =========================================================
|
||
# إزاحة الارتفاع
|
||
# =========================================================
|
||
|
||
func _create_height_offset_controls() -> void:
|
||
var separator := HSeparator.new()
|
||
separator.name = "HeightOffsetSeparator"
|
||
|
||
controls_container.add_child(
|
||
separator
|
||
)
|
||
|
||
var offset_row := _create_setting_row(
|
||
"HeightOffsetContainer",
|
||
"Height Offset"
|
||
)
|
||
|
||
height_offset_label = (
|
||
offset_row.get_child(0)
|
||
as Label
|
||
)
|
||
|
||
height_offset_spin_box = SpinBox.new()
|
||
height_offset_spin_box.name = "HeightOffsetSpinBox"
|
||
|
||
height_offset_spin_box.min_value = -100.0
|
||
height_offset_spin_box.max_value = 100.0
|
||
height_offset_spin_box.step = 0.05
|
||
height_offset_spin_box.value = height_offset
|
||
|
||
height_offset_spin_box.allow_lesser = true
|
||
height_offset_spin_box.allow_greater = true
|
||
height_offset_spin_box.suffix = " m"
|
||
|
||
height_offset_spin_box.custom_minimum_size.x = (
|
||
105.0
|
||
)
|
||
|
||
height_offset_spin_box.value_changed.connect(
|
||
_on_height_offset_changed
|
||
)
|
||
|
||
offset_row.add_child(
|
||
height_offset_spin_box
|
||
)
|
||
|
||
var direction_row := _create_setting_row(
|
||
"HeightOffsetModeContainer",
|
||
"Offset Direction"
|
||
)
|
||
|
||
height_offset_mode_label = (
|
||
direction_row.get_child(0)
|
||
as Label
|
||
)
|
||
|
||
height_offset_mode_option = OptionButton.new()
|
||
|
||
height_offset_mode_option.name = (
|
||
"HeightOffsetModeOption"
|
||
)
|
||
|
||
height_offset_mode_option.custom_minimum_size.x = (
|
||
150.0
|
||
)
|
||
|
||
height_offset_mode_option.add_item(
|
||
"World Y",
|
||
HeightOffsetMode.WORLD_Y
|
||
)
|
||
|
||
height_offset_mode_option.add_item(
|
||
"Surface Normal",
|
||
HeightOffsetMode.SURFACE_NORMAL
|
||
)
|
||
|
||
height_offset_mode_option.select(
|
||
height_offset_mode
|
||
)
|
||
|
||
height_offset_mode_option.item_selected.connect(
|
||
_on_height_offset_mode_selected
|
||
)
|
||
|
||
direction_row.add_child(
|
||
height_offset_mode_option
|
||
)
|
||
|
||
height_offset_help_label = Label.new()
|
||
height_offset_help_label.name = "HeightOffsetHelpLabel"
|
||
height_offset_help_label.text = (
|
||
"The offset direction moves the instance position only. "
|
||
+ "Enable Rotate With Surface to change its tilt."
|
||
)
|
||
|
||
height_offset_help_label.autowrap_mode = (
|
||
TextServer.AUTOWRAP_WORD_SMART
|
||
)
|
||
|
||
height_offset_help_label.size_flags_horizontal = (
|
||
Control.SIZE_EXPAND_FILL
|
||
)
|
||
|
||
height_offset_help_label.modulate = Color(
|
||
1.0,
|
||
1.0,
|
||
1.0,
|
||
0.72
|
||
)
|
||
|
||
controls_container.add_child(
|
||
height_offset_help_label
|
||
)
|
||
|
||
|
||
func _on_height_offset_changed(
|
||
value: float
|
||
) -> void:
|
||
height_offset = value
|
||
|
||
height_offset_changed.emit(
|
||
height_offset
|
||
)
|
||
|
||
_save_configuration()
|
||
|
||
|
||
func _on_height_offset_mode_selected(
|
||
index: int
|
||
) -> void:
|
||
height_offset_mode = clampi(
|
||
index,
|
||
HeightOffsetMode.WORLD_Y,
|
||
HeightOffsetMode.SURFACE_NORMAL
|
||
)
|
||
|
||
height_offset_mode_changed.emit(
|
||
height_offset_mode
|
||
)
|
||
|
||
_save_configuration()
|
||
|
||
|
||
# =========================================================
|
||
# الحجم العشوائي
|
||
# =========================================================
|
||
|
||
func _create_random_scale_controls() -> void:
|
||
var separator := HSeparator.new()
|
||
separator.name = "RandomScaleSeparator"
|
||
|
||
controls_container.add_child(
|
||
separator
|
||
)
|
||
|
||
random_scale_toggle = CheckButton.new()
|
||
random_scale_toggle.name = "RandomScaleToggle"
|
||
random_scale_toggle.text = "Random Scale"
|
||
|
||
random_scale_toggle.button_pressed = (
|
||
random_scale_enabled
|
||
)
|
||
|
||
random_scale_toggle.toggled.connect(
|
||
_on_random_scale_toggled
|
||
)
|
||
|
||
controls_container.add_child(
|
||
random_scale_toggle
|
||
)
|
||
|
||
var minimum_row := _create_setting_row(
|
||
"MinimumScaleContainer",
|
||
"Minimum Scale"
|
||
)
|
||
|
||
minimum_scale_label = (
|
||
minimum_row.get_child(0)
|
||
as Label
|
||
)
|
||
|
||
minimum_scale_spin_box = SpinBox.new()
|
||
|
||
minimum_scale_spin_box.name = (
|
||
"MinimumScaleSpinBox"
|
||
)
|
||
|
||
minimum_scale_spin_box.min_value = 0.01
|
||
minimum_scale_spin_box.max_value = 100.0
|
||
minimum_scale_spin_box.step = 0.05
|
||
minimum_scale_spin_box.value = minimum_scale
|
||
minimum_scale_spin_box.allow_greater = true
|
||
|
||
minimum_scale_spin_box.custom_minimum_size.x = (
|
||
105.0
|
||
)
|
||
|
||
minimum_scale_spin_box.value_changed.connect(
|
||
_on_minimum_scale_changed
|
||
)
|
||
|
||
minimum_row.add_child(
|
||
minimum_scale_spin_box
|
||
)
|
||
|
||
var maximum_row := _create_setting_row(
|
||
"MaximumScaleContainer",
|
||
"Maximum Scale"
|
||
)
|
||
|
||
maximum_scale_label = (
|
||
maximum_row.get_child(0)
|
||
as Label
|
||
)
|
||
|
||
maximum_scale_spin_box = SpinBox.new()
|
||
|
||
maximum_scale_spin_box.name = (
|
||
"MaximumScaleSpinBox"
|
||
)
|
||
|
||
maximum_scale_spin_box.min_value = 0.01
|
||
maximum_scale_spin_box.max_value = 100.0
|
||
maximum_scale_spin_box.step = 0.05
|
||
maximum_scale_spin_box.value = maximum_scale
|
||
maximum_scale_spin_box.allow_greater = true
|
||
|
||
maximum_scale_spin_box.custom_minimum_size.x = (
|
||
105.0
|
||
)
|
||
|
||
maximum_scale_spin_box.value_changed.connect(
|
||
_on_maximum_scale_changed
|
||
)
|
||
|
||
maximum_row.add_child(
|
||
maximum_scale_spin_box
|
||
)
|
||
|
||
|
||
func _on_random_scale_toggled(
|
||
enabled: bool
|
||
) -> void:
|
||
random_scale_enabled = enabled
|
||
|
||
random_scale_enabled_changed.emit(
|
||
random_scale_enabled
|
||
)
|
||
|
||
_update_controls()
|
||
_save_configuration()
|
||
|
||
|
||
func _on_minimum_scale_changed(
|
||
value: float
|
||
) -> void:
|
||
minimum_scale = maxf(
|
||
value,
|
||
0.01
|
||
)
|
||
|
||
if minimum_scale > maximum_scale:
|
||
maximum_scale = minimum_scale
|
||
|
||
if is_instance_valid(
|
||
maximum_scale_spin_box
|
||
):
|
||
maximum_scale_spin_box.set_value_no_signal(
|
||
maximum_scale
|
||
)
|
||
|
||
maximum_scale_changed.emit(
|
||
maximum_scale
|
||
)
|
||
|
||
minimum_scale_changed.emit(
|
||
minimum_scale
|
||
)
|
||
|
||
_save_configuration()
|
||
|
||
|
||
func _on_maximum_scale_changed(
|
||
value: float
|
||
) -> void:
|
||
maximum_scale = maxf(
|
||
value,
|
||
0.01
|
||
)
|
||
|
||
if maximum_scale < minimum_scale:
|
||
minimum_scale = maximum_scale
|
||
|
||
if is_instance_valid(
|
||
minimum_scale_spin_box
|
||
):
|
||
minimum_scale_spin_box.set_value_no_signal(
|
||
minimum_scale
|
||
)
|
||
|
||
minimum_scale_changed.emit(
|
||
minimum_scale
|
||
)
|
||
|
||
maximum_scale_changed.emit(
|
||
maximum_scale
|
||
)
|
||
|
||
_save_configuration()
|
||
|
||
|
||
func _create_setting_row(
|
||
row_name: String,
|
||
label_text: String
|
||
) -> HBoxContainer:
|
||
var row := HBoxContainer.new()
|
||
row.name = row_name
|
||
|
||
row.size_flags_horizontal = (
|
||
Control.SIZE_EXPAND_FILL
|
||
)
|
||
|
||
var label := Label.new()
|
||
label.name = "%sLabel" % row_name
|
||
label.text = label_text
|
||
|
||
label.size_flags_horizontal = (
|
||
Control.SIZE_EXPAND_FILL
|
||
)
|
||
|
||
row.add_child(
|
||
label
|
||
)
|
||
|
||
controls_container.add_child(
|
||
row
|
||
)
|
||
|
||
return row
|
||
|
||
|
||
func _update_controls() -> void:
|
||
var has_active_scene := (
|
||
selected_scene != null
|
||
)
|
||
|
||
_update_lod_controls()
|
||
_update_auto_lod_controls()
|
||
|
||
if is_instance_valid(
|
||
brush_toggle
|
||
):
|
||
brush_toggle.disabled = (
|
||
not has_active_scene
|
||
)
|
||
|
||
if is_instance_valid(
|
||
radius_spin_box
|
||
):
|
||
radius_spin_box.editable = (
|
||
has_active_scene
|
||
)
|
||
|
||
if is_instance_valid(
|
||
instances_spin_box
|
||
):
|
||
instances_spin_box.editable = (
|
||
has_active_scene
|
||
)
|
||
|
||
if is_instance_valid(
|
||
spacing_spin_box
|
||
):
|
||
spacing_spin_box.editable = (
|
||
has_active_scene
|
||
)
|
||
|
||
if is_instance_valid(
|
||
random_rotation_toggle
|
||
):
|
||
random_rotation_toggle.disabled = (
|
||
not has_active_scene
|
||
)
|
||
|
||
if is_instance_valid(
|
||
align_surface_toggle
|
||
):
|
||
align_surface_toggle.disabled = (
|
||
not has_active_scene
|
||
)
|
||
|
||
if is_instance_valid(
|
||
maximum_slope_spin_box
|
||
):
|
||
maximum_slope_spin_box.editable = (
|
||
has_active_scene
|
||
)
|
||
|
||
if is_instance_valid(
|
||
height_offset_spin_box
|
||
):
|
||
height_offset_spin_box.editable = (
|
||
has_active_scene
|
||
)
|
||
|
||
if is_instance_valid(
|
||
height_offset_mode_option
|
||
):
|
||
height_offset_mode_option.disabled = (
|
||
not has_active_scene
|
||
)
|
||
|
||
if is_instance_valid(
|
||
random_scale_toggle
|
||
):
|
||
random_scale_toggle.disabled = (
|
||
not has_active_scene
|
||
)
|
||
|
||
var scale_controls_enabled := (
|
||
has_active_scene
|
||
and random_scale_enabled
|
||
)
|
||
|
||
if is_instance_valid(
|
||
minimum_scale_spin_box
|
||
):
|
||
minimum_scale_spin_box.editable = (
|
||
scale_controls_enabled
|
||
)
|
||
|
||
if is_instance_valid(
|
||
maximum_scale_spin_box
|
||
):
|
||
maximum_scale_spin_box.editable = (
|
||
scale_controls_enabled
|
||
)
|
||
|
||
|
||
# =========================================================
|
||
# دوال القراءة
|
||
# =========================================================
|
||
|
||
func get_selected_scene() -> PackedScene:
|
||
return selected_scene
|
||
|
||
|
||
func get_active_scene_index() -> int:
|
||
return (
|
||
_get_valid_scene_pickers().find(
|
||
active_picker
|
||
)
|
||
)
|
||
|
||
|
||
func is_brush_enabled() -> bool:
|
||
return brush_enabled
|
||
|
||
|
||
func get_brush_radius() -> float:
|
||
return brush_radius
|
||
|
||
|
||
func get_instances_per_click() -> int:
|
||
return instances_per_click
|
||
|
||
|
||
func get_minimum_spacing() -> float:
|
||
return minimum_spacing
|
||
|
||
|
||
func is_random_y_rotation_enabled() -> bool:
|
||
return random_y_rotation
|
||
|
||
|
||
func is_align_to_surface_enabled() -> bool:
|
||
return align_to_surface
|
||
|
||
|
||
func get_maximum_slope() -> float:
|
||
return maximum_slope_degrees
|
||
|
||
|
||
func get_height_offset() -> float:
|
||
return height_offset
|
||
|
||
|
||
func get_height_offset_mode() -> int:
|
||
return height_offset_mode
|
||
|
||
|
||
func is_random_scale_enabled() -> bool:
|
||
return random_scale_enabled
|
||
|
||
|
||
func get_minimum_scale() -> float:
|
||
return minimum_scale
|
||
|
||
|
||
func get_maximum_scale() -> float:
|
||
return maximum_scale
|
||
|
||
|
||
func get_interface_language() -> String:
|
||
return current_language
|