feature/builder-export (b3fcb35) in main (e2954a3) gemerged; Doppel-Vendor aufgelöst: addons/modular_builder_en gelöscht, Export-Kern nach addons/modular_builder portiert (Pfad-Referenzen angepasst): - scopedvr_export.gd (Kern: slugify, normalisiere, trimesh_vertices, glb_bytes) - scopedvr_export_ui.gd (Name-Dialog + Objekt-Pipeline + Thumbnail) - editor_actions/UI: Export-Modus 'In ScopedVR-Bibliothek (Objekt-Pipeline)' - tests/test_builder_export.gd (21 Checks, Pitfall ARRAY_INDEX=NULL) - gen_thumbs: Trimesh-Objekte mit vorhandenem echten Thumbnail überspringen (harmlos kombiniert mit dem GLB-Echt-Mesh-Rendering aus t_7cf80244) - project.godot: Plugin 'res://addons/modular_builder/plugin.cfg'
544 lines
27 KiB
GDScript
544 lines
27 KiB
GDScript
@tool
|
||
extends EditorPlugin
|
||
|
||
const PIECE_SCRIPT = preload("res://addons/modular_builder/modular_piece.gd")
|
||
const DETAIL_SCRIPT = preload("res://addons/modular_builder/modular_building_detail.gd")
|
||
const MESH_BAKER = preload("res://addons/modular_builder/modular_mesh_baker.gd")
|
||
const TOOL_NONE := ""
|
||
const TOOL_FOUNDATION := "foundation"
|
||
const TOOL_FOUNDATION_SQUARE := "foundation_square"
|
||
const TOOL_FOUNDATION_TRIANGLE := "foundation_triangle"
|
||
const TOOL_WALL := "wall"
|
||
const TOOL_GABLE_WALL := "gable_wall"
|
||
const TOOL_GABLE_WALL_RIGHT := "gable_wall_right"
|
||
const TOOL_FLOOR_SQUARE := "floor_square"
|
||
const TOOL_FLOOR_TRIANGLE := "floor_triangle"
|
||
const TOOL_ROOF_GABLE := "roof_gable"
|
||
const TOOL_ROOF_SHED := "roof_shed"
|
||
const TOOL_ROOF_PYRAMID := "roof_pyramid"
|
||
const TOOL_STAIRS := "stairs"
|
||
const TOOL_FENCE := "fence"
|
||
const TOOL_PARAPET := "parapet"
|
||
const TOOL_MERGE_VERTICES := "merge_vertices"
|
||
const TOOL_CREATE_VERTEX := "create_vertex"
|
||
const TOOL_MOVE_VERTEX := "move_vertex"
|
||
const TOOL_REMOVE_VERTEX := "remove_vertex"
|
||
const MERGED_VERTEX_GROUPS_META := &"modular_builder_merged_vertex_groups"
|
||
const CUSTOM_VERTICES_META := &"modular_builder_custom_vertices"
|
||
const TOOL_WINDOW := "detail_window"
|
||
const TOOL_DOOR := "detail_door"
|
||
const TOOL_HATCH := "detail_hatch"
|
||
const TOOL_PILLAR := "detail_pillar"
|
||
const EXPORT_NEW_SCENE := 0
|
||
const EXPORT_SCENE_COPY := 1
|
||
const EXPORT_LIBRARY := 2
|
||
const METERS_TO_GODOT_UNITS := 1.0
|
||
const DOOR_DEFAULT_SCALE := 0.8
|
||
const DOOR_DEFAULT_WIDTH_METERS := 0.87 * DOOR_DEFAULT_SCALE
|
||
const DOOR_DEFAULT_HEIGHT_METERS := 2.15 * DOOR_DEFAULT_SCALE
|
||
const MARKER_RADIUS := 8.0
|
||
const MARKER_HIT_RADIUS := 18.0
|
||
|
||
var dock: VBoxContainer
|
||
var _creation_row: HFlowContainer
|
||
var _export_row: HFlowContainer
|
||
var _tool_row: HFlowContainer
|
||
var _vertex_edit_row: HFlowContainer
|
||
var _marker_scope_check: CheckBox
|
||
var _simplify_vertices_check: CheckBox
|
||
var _rise_options_row: HFlowContainer
|
||
var _linear_options_row: HFlowContainer
|
||
var _stair_count_label: Label
|
||
var _stair_width_label: Label
|
||
var _fence_divisions_label: Label
|
||
var _detail_options_row: HFlowContainer
|
||
var _foundation_type_selected := ModularBuilderPiece.PieceType.FOUNDATION_SQUARE
|
||
var size_spin: SpinBox
|
||
var rise_spin: SpinBox
|
||
var stair_count_spin: SpinBox
|
||
var stair_width_spin: SpinBox
|
||
var fence_divisions_spin: SpinBox
|
||
var fence_height_spin: SpinBox
|
||
var _fence_height_label: Label
|
||
var _rise_label: Label
|
||
var _gable_centralize_check: CheckBox
|
||
var _gable_centralize_default := false
|
||
var detail_width_spin: SpinBox
|
||
var detail_height_spin: SpinBox
|
||
var detail_height_offset_spin: SpinBox
|
||
var _detail_height_offset_label: Label
|
||
var _detail_width_label: Label
|
||
var _detail_height_label: Label
|
||
var detail_mode_option: OptionButton
|
||
var detail_style_option: OptionButton
|
||
var status_label: Label
|
||
var active_tool := TOOL_NONE
|
||
var pending_points: Array[Vector3] = []
|
||
var _redirecting_selection := false
|
||
var _marker_container: Node3D
|
||
var _marker_root: ModularBuilderPiece
|
||
var _marker_point_nodes: Array[Label3D] = []
|
||
var _side_button_nodes: Array[Label3D] = []
|
||
var _side_material: StandardMaterial3D
|
||
var _tool_button_group: ButtonGroup
|
||
var _tool_buttons: Dictionary = {}
|
||
var _pillar_centralize_check: CheckBox
|
||
var _pillar_centralize_default := false
|
||
var _active_button_key := ""
|
||
var _last_created_piece: ModularBuilderPiece
|
||
var _detail_preview: ModularBuilderDetail
|
||
var _preview_material: StandardMaterial3D
|
||
var _preview_camera: Camera3D
|
||
var _preview_screen_position := Vector2.ZERO
|
||
var _save_module_dialog: FileDialog
|
||
var _load_module_dialog: FileDialog
|
||
var _export_scene_dialog: FileDialog
|
||
var _export_mode_option: OptionButton
|
||
var _pending_export_mesh: ArrayMesh
|
||
var _pending_export_name := "BakedBuilding"
|
||
var _pending_export_basis := Basis.IDENTITY
|
||
var _selected_window_style := ModularBuilderDetail.WindowStyle.SINGLE_PANE
|
||
var _selected_door_style := ModularBuilderDetail.DoorStyle.FLAT
|
||
var _editor_sync_elapsed := 0.0
|
||
|
||
func _ready() -> void:
|
||
set_process(true)
|
||
_side_material = StandardMaterial3D.new()
|
||
_side_material.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
|
||
_side_material.albedo_color = Color(0.18, 0.95, 0.52)
|
||
_side_material.no_depth_test = true
|
||
_side_material.cull_mode = BaseMaterial3D.CULL_DISABLED
|
||
|
||
func _process(delta: float) -> void:
|
||
_editor_sync_elapsed += delta
|
||
if _editor_sync_elapsed < 1.0 / 15.0:
|
||
return
|
||
_editor_sync_elapsed = 0.0
|
||
call("_sync_vertex_markers")
|
||
_sync_pillar_centralize_control()
|
||
_sync_gable_centralize_control()
|
||
|
||
func _enter_tree() -> void:
|
||
dock = VBoxContainer.new()
|
||
dock.name = "Modular Builder"
|
||
dock.custom_minimum_size = Vector2(0, 110)
|
||
_creation_row = HFlowContainer.new()
|
||
dock.add_child(_creation_row)
|
||
_creation_row.add_child(_label("Size:"))
|
||
size_spin = SpinBox.new()
|
||
size_spin.min_value = 0.5
|
||
size_spin.max_value = 8.0
|
||
size_spin.step = 0.001
|
||
size_spin.value = 2.0
|
||
size_spin.suffix = " m"
|
||
size_spin.custom_minimum_size = Vector2(100, 0)
|
||
_creation_row.add_child(size_spin)
|
||
var create_button := _make_icon_button("res://addons/modular_builder/icons/add_building.svg", "Add ModularBuilderPiece", Color(0.22, 0.30, 0.38))
|
||
create_button.pressed.connect(Callable(self, "_add_piece"))
|
||
_creation_row.add_child(create_button)
|
||
var rotate_button := _make_icon_button("res://addons/modular_builder/icons/rotate.svg", "Rotate selected piece 90°", Color(0.22, 0.30, 0.38))
|
||
rotate_button.pressed.connect(Callable(self, "_rotate_selected_piece"))
|
||
_creation_row.add_child(rotate_button)
|
||
var collision_button := _make_icon_button("res://addons/modular_builder/icons/collision.svg", "Add general collision to selected structure", Color(0.22, 0.30, 0.38))
|
||
collision_button.pressed.connect(Callable(self, "_add_general_collision"))
|
||
_creation_row.add_child(collision_button)
|
||
var save_module_button := _make_icon_button("res://addons/modular_builder/icons/save_module.svg", "Save selected structure as a .tscn module", Color(0.22, 0.30, 0.38))
|
||
save_module_button.pressed.connect(Callable(self, "_show_save_module_dialog"))
|
||
_creation_row.add_child(save_module_button)
|
||
var load_module_button := _make_icon_button("res://addons/modular_builder/icons/load_module.svg", "Insert saved module into the scene", Color(0.22, 0.30, 0.38))
|
||
load_module_button.pressed.connect(Callable(self, "_show_load_module_dialog"))
|
||
_creation_row.add_child(load_module_button)
|
||
_export_row = HFlowContainer.new()
|
||
dock.add_child(_export_row)
|
||
_export_row.add_child(_label("Export structure as mesh:") )
|
||
_export_mode_option = OptionButton.new()
|
||
_export_mode_option.add_item("New scene", EXPORT_NEW_SCENE)
|
||
_export_mode_option.add_item("Copy in this scene", EXPORT_SCENE_COPY)
|
||
_export_mode_option.add_item("In ScopedVR-Bibliothek (Objekt-Pipeline)", EXPORT_LIBRARY)
|
||
_export_mode_option.tooltip_text = "Choose whether to save the combined mesh as a new scene, add a copy to the current scene, or export it into the ScopedVR object library (svmap-ready with trimesh collision)."
|
||
_export_row.add_child(_export_mode_option)
|
||
var export_button := Button.new()
|
||
export_button.text = "Export"
|
||
export_button.tooltip_text = "Generate one static mesh with materials and UVs from the selected structure."
|
||
export_button.pressed.connect(Callable(self, "_on_export_pressed"))
|
||
_export_row.add_child(export_button)
|
||
_tool_row = HFlowContainer.new()
|
||
dock.add_child(_tool_row)
|
||
_tool_button_group = ButtonGroup.new()
|
||
_tool_button_group.allow_unpress = true
|
||
_add_tool_button("res://addons/modular_builder/icons/foundation_square.svg", "Square foundation", TOOL_FOUNDATION_SQUARE, Color(0.08, 0.48, 0.22))
|
||
_add_tool_button("res://addons/modular_builder/icons/foundation_triangle.svg", "Triangular foundation", TOOL_FOUNDATION_TRIANGLE, Color(0.08, 0.48, 0.22))
|
||
_add_tool_button("res://addons/modular_builder/icons/wall.svg", "Wall", TOOL_WALL, Color(0.65, 0.12, 0.12))
|
||
_add_tool_button("res://addons/modular_builder/icons/gable_wall.svg", "Gable wall", TOOL_GABLE_WALL, Color(0.65, 0.12, 0.12))
|
||
_add_tool_button("res://addons/modular_builder/icons/gable_wall_right.svg", "Right triangular gable wall", TOOL_GABLE_WALL_RIGHT, Color(0.65, 0.12, 0.12))
|
||
_add_tool_button("res://addons/modular_builder/icons/floor_square.svg", "Square floor", TOOL_FLOOR_SQUARE, Color(0.68, 0.48, 0.06))
|
||
_add_tool_button("res://addons/modular_builder/icons/floor_triangle.svg", "Triangular floor", TOOL_FLOOR_TRIANGLE, Color(0.68, 0.48, 0.06))
|
||
_add_tool_button("res://addons/modular_builder/icons/roof_gable.svg", "Gable roof", TOOL_ROOF_GABLE, Color(0.10, 0.38, 0.62))
|
||
_add_tool_button("res://addons/modular_builder/icons/roof_shed.svg", "Shed roof", TOOL_ROOF_SHED, Color(0.10, 0.38, 0.62))
|
||
_add_tool_button("res://addons/modular_builder/icons/roof_pyramid.svg", "Pyramid roof", TOOL_ROOF_PYRAMID, Color(0.10, 0.38, 0.62))
|
||
_add_tool_button("res://addons/modular_builder/icons/stairs.svg", "Stairs", TOOL_STAIRS, Color(0.10, 0.38, 0.62))
|
||
_add_tool_button("res://addons/modular_builder/icons/fence.svg", "Decorative fence", TOOL_FENCE, Color(0.88, 0.22, 0.55))
|
||
_add_tool_button("res://addons/modular_builder/icons/parapet.svg", "Parapet: single rail and posts", TOOL_PARAPET, Color(0.88, 0.22, 0.55))
|
||
_add_tool_button("res://addons/modular_builder/icons/window.svg", "Detail: window", TOOL_WINDOW, Color(0.88, 0.22, 0.55))
|
||
_add_tool_button("res://addons/modular_builder/icons/door.svg", "Detail: door", TOOL_DOOR, Color(0.88, 0.22, 0.55))
|
||
_add_tool_button("res://addons/modular_builder/icons/hatch.svg", "Detail: hatch", TOOL_HATCH, Color(0.88, 0.22, 0.55))
|
||
_add_tool_button("res://addons/modular_builder/icons/pillar.svg", "Plain pillar: two vertices or Alt+click for a vertical pillar", TOOL_PILLAR, Color(0.88, 0.22, 0.55))
|
||
var cancel_button := _make_icon_button("res://addons/modular_builder/icons/cancel.svg", "Deactivate tool", Color(0.34, 0.20, 0.20))
|
||
cancel_button.pressed.connect(Callable(self, "_set_tool").bind(TOOL_NONE))
|
||
_tool_row.add_child(cancel_button)
|
||
_vertex_edit_row = HFlowContainer.new()
|
||
dock.add_child(_vertex_edit_row)
|
||
_vertex_edit_row.add_child(_label("Vertices:"))
|
||
_add_tool_button("res://addons/modular_builder/icons/merge_vertices.svg", "Merge vertices to reduce markers", TOOL_MERGE_VERTICES, Color(0.38, 0.32, 0.52), _vertex_edit_row)
|
||
_add_tool_button("res://addons/modular_builder/icons/create_vertex.svg", "Create a connection vertex on the clicked surface", TOOL_CREATE_VERTEX, Color(0.38, 0.32, 0.52), _vertex_edit_row)
|
||
_add_tool_button("res://addons/modular_builder/icons/move_vertex.svg", "Move a manual connection vertex", TOOL_MOVE_VERTEX, Color(0.38, 0.32, 0.52), _vertex_edit_row)
|
||
_add_tool_button("res://addons/modular_builder/icons/remove_vertex.svg", "Remove a manual connection vertex", TOOL_REMOVE_VERTEX, Color(0.38, 0.32, 0.52), _vertex_edit_row)
|
||
var marker_options_row := HFlowContainer.new()
|
||
dock.add_child(marker_options_row)
|
||
_marker_scope_check = CheckBox.new()
|
||
_marker_scope_check.text = "Only show points on selected pieces"
|
||
_marker_scope_check.tooltip_text = "Limit markers to pieces selected in the Scene tree. Selecting the structure container shows all its points."
|
||
_marker_scope_check.toggled.connect(Callable(self, "_on_marker_scope_toggled"))
|
||
marker_options_row.add_child(_marker_scope_check)
|
||
_simplify_vertices_check = CheckBox.new()
|
||
_simplify_vertices_check.text = "Simplify vertices"
|
||
_simplify_vertices_check.tooltip_text = "Merge front and back points at the middle of walls and pillars; floors and foundations show their top outline. Vertex editing tools show the exact points."
|
||
_simplify_vertices_check.toggled.connect(Callable(self, "_on_simplify_vertices_toggled"))
|
||
marker_options_row.add_child(_simplify_vertices_check)
|
||
_rise_options_row = HFlowContainer.new()
|
||
dock.add_child(_rise_options_row)
|
||
_rise_label = _label("Peak rise:")
|
||
_rise_options_row.add_child(_rise_label)
|
||
rise_spin = _detail_spin(1.0, 0.1, 8.0, 0.001)
|
||
_rise_options_row.add_child(rise_spin)
|
||
_linear_options_row = HFlowContainer.new()
|
||
dock.add_child(_linear_options_row)
|
||
_stair_count_label = _label("Steps:")
|
||
_linear_options_row.add_child(_stair_count_label)
|
||
stair_count_spin = SpinBox.new()
|
||
stair_count_spin.min_value = 1
|
||
stair_count_spin.max_value = 100
|
||
stair_count_spin.step = 1
|
||
stair_count_spin.value = 10
|
||
stair_count_spin.custom_minimum_size = Vector2(72, 0)
|
||
_linear_options_row.add_child(stair_count_spin)
|
||
_stair_width_label = _label("Width:")
|
||
_linear_options_row.add_child(_stair_width_label)
|
||
stair_width_spin = _detail_spin(1.0, 0.01, 8.0, 0.001)
|
||
_linear_options_row.add_child(stair_width_spin)
|
||
_fence_divisions_label = _label("Intermediate posts:")
|
||
_linear_options_row.add_child(_fence_divisions_label)
|
||
fence_divisions_spin = SpinBox.new()
|
||
fence_divisions_spin.min_value = 0
|
||
fence_divisions_spin.max_value = 100
|
||
fence_divisions_spin.step = 1
|
||
fence_divisions_spin.value = 1
|
||
fence_divisions_spin.custom_minimum_size = Vector2(72, 0)
|
||
_linear_options_row.add_child(fence_divisions_spin)
|
||
_fence_height_label = _label("Height:")
|
||
_linear_options_row.add_child(_fence_height_label)
|
||
fence_height_spin = _detail_spin(0.7, 0.1, 8.0, 0.001)
|
||
_linear_options_row.add_child(fence_height_spin)
|
||
_linear_options_row.visible = false
|
||
_gable_centralize_check = CheckBox.new()
|
||
_gable_centralize_check.text = "Center"
|
||
_gable_centralize_check.tooltip_text = "Align the gable with the center of the supporting wall thickness."
|
||
_gable_centralize_check.visible = false
|
||
_gable_centralize_check.toggled.connect(_on_gable_centralize_toggled)
|
||
_rise_options_row.add_child(_gable_centralize_check)
|
||
_rise_options_row.visible = false
|
||
_detail_options_row = HFlowContainer.new()
|
||
dock.add_child(_detail_options_row)
|
||
_detail_width_label = _label("Width:")
|
||
_detail_options_row.add_child(_detail_width_label)
|
||
detail_width_spin = _detail_spin(1.0, 0.01, 5.0, 0.001)
|
||
_detail_options_row.add_child(detail_width_spin)
|
||
_detail_height_label = _label("Height:")
|
||
_detail_options_row.add_child(_detail_height_label)
|
||
detail_height_spin = _detail_spin(1.0, 0.2, 5.0, 0.001)
|
||
_detail_options_row.add_child(detail_height_spin)
|
||
_pillar_centralize_check = CheckBox.new()
|
||
_pillar_centralize_check.text = "Center"
|
||
_pillar_centralize_check.tooltip_text = "Align the pillar with the nearest axis of the first vertex’s piece while keeping it centered between the points."
|
||
_pillar_centralize_check.visible = false
|
||
_pillar_centralize_check.toggled.connect(_on_pillar_centralize_toggled)
|
||
_detail_options_row.add_child(_pillar_centralize_check)
|
||
_detail_height_offset_label = _label("Starting height:")
|
||
_detail_options_row.add_child(_detail_height_offset_label)
|
||
detail_height_offset_spin = _detail_spin(0.0, -2.0, 2.0, 0.001)
|
||
detail_height_offset_spin.suffix = " m"
|
||
_detail_options_row.add_child(detail_height_offset_spin)
|
||
detail_mode_option = OptionButton.new()
|
||
detail_mode_option.add_item("Opening", ModularBuilderDetail.DetailMode.OPENING)
|
||
detail_mode_option.add_item("Opening + object", ModularBuilderDetail.DetailMode.SEPARATE_OBJECT)
|
||
detail_mode_option.select(0)
|
||
detail_mode_option.tooltip_text = "Choose an opening alone or an opening with a separately textured object."
|
||
_detail_options_row.add_child(detail_mode_option)
|
||
detail_style_option = OptionButton.new()
|
||
detail_style_option.tooltip_text = "Window or door style."
|
||
detail_style_option.item_selected.connect(_on_detail_style_selected)
|
||
_detail_options_row.add_child(detail_style_option)
|
||
detail_width_spin.value_changed.connect(Callable(self, "_refresh_detail_preview"))
|
||
detail_height_spin.value_changed.connect(Callable(self, "_refresh_detail_preview"))
|
||
detail_height_offset_spin.value_changed.connect(Callable(self, "_refresh_detail_preview"))
|
||
detail_mode_option.item_selected.connect(_on_detail_mode_selected)
|
||
_detail_options_row.visible = false
|
||
_preview_material = StandardMaterial3D.new()
|
||
_preview_material.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
|
||
_preview_material.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
|
||
_preview_material.albedo_color = Color(1.0, 0.35, 0.72, 0.48)
|
||
_save_module_dialog = FileDialog.new()
|
||
_save_module_dialog.file_mode = FileDialog.FILE_MODE_SAVE_FILE
|
||
_save_module_dialog.access = FileDialog.ACCESS_RESOURCES
|
||
_save_module_dialog.add_filter("*.tscn", "Godot Scene")
|
||
_save_module_dialog.file_selected.connect(Callable(self, "_save_module_to_path"))
|
||
add_child(_save_module_dialog)
|
||
_load_module_dialog = FileDialog.new()
|
||
_load_module_dialog.file_mode = FileDialog.FILE_MODE_OPEN_FILE
|
||
_load_module_dialog.access = FileDialog.ACCESS_RESOURCES
|
||
_load_module_dialog.add_filter("*.tscn", "Godot Scene")
|
||
_load_module_dialog.file_selected.connect(Callable(self, "_load_module_from_path"))
|
||
add_child(_load_module_dialog)
|
||
_export_scene_dialog = FileDialog.new()
|
||
_export_scene_dialog.file_mode = FileDialog.FILE_MODE_SAVE_FILE
|
||
_export_scene_dialog.access = FileDialog.ACCESS_RESOURCES
|
||
_export_scene_dialog.add_filter("*.tscn", "Godot Scene")
|
||
_export_scene_dialog.file_selected.connect(Callable(self, "_export_baked_scene_to_path"))
|
||
add_child(_export_scene_dialog)
|
||
status_label = _label("Select a tool to show the vertices.")
|
||
status_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||
dock.add_child(status_label)
|
||
add_control_to_bottom_panel(dock, "Modular Builder")
|
||
set_input_event_forwarding_always_enabled()
|
||
get_editor_interface().get_selection().selection_changed.connect(Callable(self, "_on_editor_selection_changed"))
|
||
var inspector := get_editor_interface().get_inspector()
|
||
if not inspector.property_edited.is_connected(_on_inspector_property_edited):
|
||
inspector.property_edited.connect(_on_inspector_property_edited)
|
||
|
||
func _exit_tree() -> void:
|
||
set_process(false)
|
||
var editor_selection := get_editor_interface().get_selection()
|
||
if editor_selection.selection_changed.is_connected(Callable(self, "_on_editor_selection_changed")):
|
||
editor_selection.selection_changed.disconnect(Callable(self, "_on_editor_selection_changed"))
|
||
var inspector := get_editor_interface().get_inspector()
|
||
if inspector.property_edited.is_connected(_on_inspector_property_edited):
|
||
inspector.property_edited.disconnect(_on_inspector_property_edited)
|
||
if dock:
|
||
remove_control_from_bottom_panel(dock)
|
||
dock.queue_free()
|
||
call("_clear_marker_container")
|
||
call("_clear_detail_preview")
|
||
update_overlays()
|
||
|
||
func _handles(object: Object) -> bool:
|
||
return object is ModularBuilderPiece
|
||
|
||
func _label(text: String) -> Label:
|
||
var label := Label.new()
|
||
label.text = text
|
||
return label
|
||
|
||
func _detail_spin(value: float, minimum: float, maximum: float, step: float) -> SpinBox:
|
||
var spin := SpinBox.new()
|
||
spin.min_value = minimum
|
||
spin.max_value = maximum
|
||
spin.step = step
|
||
spin.value = value
|
||
spin.suffix = " m"
|
||
spin.custom_minimum_size = Vector2(84, 0)
|
||
return spin
|
||
|
||
func _make_icon_button(icon_path: String, tooltip: String, color: Color) -> Button:
|
||
var button := Button.new()
|
||
button.icon = load(icon_path) as Texture2D
|
||
button.tooltip_text = tooltip
|
||
button.custom_minimum_size = Vector2(48, 48)
|
||
button.icon_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||
button.expand_icon = true
|
||
button.add_theme_constant_override("icon_max_width", 30)
|
||
var normal := StyleBoxFlat.new()
|
||
normal.bg_color = color
|
||
normal.set_corner_radius_all(4)
|
||
normal.set_border_width_all(1)
|
||
normal.border_color = Color(0.02, 0.02, 0.02)
|
||
button.add_theme_stylebox_override("normal", normal)
|
||
var hover := normal.duplicate() as StyleBoxFlat
|
||
hover.bg_color = color.lightened(0.16)
|
||
button.add_theme_stylebox_override("hover", hover)
|
||
var pressed := normal.duplicate() as StyleBoxFlat
|
||
pressed.bg_color = color.lightened(0.28)
|
||
pressed.border_width_bottom = 3
|
||
button.add_theme_stylebox_override("pressed", pressed)
|
||
return button
|
||
|
||
func _add_tool_button(icon_path: String, tooltip: String, tool: String, color: Color, target_row: Container = null) -> void:
|
||
var button := _make_icon_button(icon_path, tooltip, color)
|
||
button.toggle_mode = true
|
||
button.button_group = _tool_button_group
|
||
button.toggled.connect(_on_tool_button_toggled.bind(tool))
|
||
(_tool_row if target_row == null else target_row).add_child(button)
|
||
_tool_buttons[tool] = button
|
||
|
||
func _on_tool_button_toggled(pressed: bool, tool: String) -> void:
|
||
var is_foundation := tool == TOOL_FOUNDATION_SQUARE or tool == TOOL_FOUNDATION_TRIANGLE
|
||
if pressed:
|
||
_active_button_key = tool
|
||
if is_foundation:
|
||
_foundation_type_selected = ModularBuilderPiece.PieceType.FOUNDATION_TRIANGLE if tool == TOOL_FOUNDATION_TRIANGLE else ModularBuilderPiece.PieceType.FOUNDATION_SQUARE
|
||
call("_set_tool", TOOL_FOUNDATION)
|
||
else:
|
||
call("_set_tool", tool)
|
||
_update_detail_options(tool)
|
||
else:
|
||
call_deferred("_finish_tool_button_toggle_off", tool)
|
||
|
||
func _finish_tool_button_toggle_off(tool: String) -> void:
|
||
var button := _tool_buttons.get(tool) as Button
|
||
if button != null and not button.button_pressed and _active_button_key == tool:
|
||
call("_set_tool", TOOL_NONE)
|
||
|
||
func _update_detail_options(tool: String) -> void:
|
||
var is_detail := _is_detail_tool(tool)
|
||
var is_fence := tool == TOOL_FENCE or tool == TOOL_PARAPET
|
||
_detail_options_row.visible = is_detail
|
||
detail_width_spin.visible = is_detail and not is_fence
|
||
_detail_width_label.visible = is_detail and not is_fence
|
||
detail_height_spin.visible = is_detail and not is_fence
|
||
_detail_height_label.visible = is_detail and not is_fence
|
||
detail_height_offset_spin.visible = tool != TOOL_HATCH and tool != TOOL_PILLAR and not is_fence
|
||
_detail_height_offset_label.visible = detail_height_offset_spin.visible
|
||
detail_mode_option.visible = tool != TOOL_PILLAR and not is_fence
|
||
_detail_width_label.text = "Side:" if tool == TOOL_PILLAR else "Width:"
|
||
_detail_height_label.text = "Length:" if tool == TOOL_PILLAR else "Height:"
|
||
_linear_options_row.visible = tool == TOOL_STAIRS or is_fence
|
||
_stair_count_label.visible = tool == TOOL_STAIRS
|
||
stair_count_spin.visible = tool == TOOL_STAIRS
|
||
_stair_width_label.visible = tool == TOOL_STAIRS
|
||
stair_width_spin.visible = tool == TOOL_STAIRS
|
||
_fence_divisions_label.visible = is_fence
|
||
fence_divisions_spin.visible = is_fence
|
||
_fence_height_label.visible = is_fence
|
||
fence_height_spin.visible = is_fence
|
||
if not is_detail:
|
||
return
|
||
var width := 1.0
|
||
var height := 1.0
|
||
var bottom_offset := 0.5
|
||
if tool == TOOL_PILLAR:
|
||
width = 0.2
|
||
height = 2.0
|
||
bottom_offset = 0.0
|
||
elif tool == TOOL_DOOR:
|
||
width = DOOR_DEFAULT_WIDTH_METERS
|
||
height = DOOR_DEFAULT_HEIGHT_METERS
|
||
bottom_offset = 0.0
|
||
elif tool == TOOL_HATCH:
|
||
bottom_offset = 0.0
|
||
detail_width_spin.set_value_no_signal(width)
|
||
detail_height_spin.set_value_no_signal(height)
|
||
detail_height_offset_spin.set_value_no_signal(bottom_offset)
|
||
detail_style_option.clear()
|
||
detail_style_option.visible = tool == TOOL_WINDOW or tool == TOOL_DOOR
|
||
if tool == TOOL_WINDOW:
|
||
detail_style_option.add_item("Single pane", ModularBuilderDetail.WindowStyle.SINGLE_PANE)
|
||
detail_style_option.add_item("Cross pane", ModularBuilderDetail.WindowStyle.CROSS_PANE)
|
||
detail_style_option.select(_selected_window_style)
|
||
elif tool == TOOL_DOOR:
|
||
detail_style_option.add_item("Flat", ModularBuilderDetail.DoorStyle.FLAT)
|
||
detail_style_option.add_item("Paneled", ModularBuilderDetail.DoorStyle.PANELED)
|
||
detail_style_option.select(_selected_door_style)
|
||
call("_refresh_detail_preview")
|
||
|
||
func _on_detail_style_selected(index: int) -> void:
|
||
if active_tool == TOOL_WINDOW:
|
||
_selected_window_style = index
|
||
if is_instance_valid(_detail_preview):
|
||
_detail_preview.window_style = index
|
||
elif active_tool == TOOL_DOOR:
|
||
_selected_door_style = index
|
||
if is_instance_valid(_detail_preview):
|
||
_detail_preview.door_style = index
|
||
call("_refresh_detail_preview")
|
||
|
||
func _on_detail_mode_selected(_index: int) -> void:
|
||
call("_refresh_detail_preview")
|
||
|
||
func _on_pillar_centralize_toggled(enabled: bool) -> void:
|
||
_pillar_centralize_default = enabled
|
||
var selected_pillar := _get_selected_pillar_detail()
|
||
if selected_pillar != null:
|
||
selected_pillar.center_align = enabled
|
||
if is_instance_valid(_detail_preview) and _detail_preview.detail_type == ModularBuilderDetail.DetailType.PILLAR:
|
||
_detail_preview.center_align = enabled
|
||
call("_refresh_detail_preview")
|
||
|
||
func _on_gable_centralize_toggled(enabled: bool) -> void:
|
||
_gable_centralize_default = enabled
|
||
var selected := _get_selected_gable_wall()
|
||
if selected != null:
|
||
selected.center_align = enabled
|
||
|
||
func _sync_gable_centralize_control() -> void:
|
||
if _gable_centralize_check == null:
|
||
return
|
||
var selected := _get_selected_gable_wall()
|
||
var show_option := active_tool == TOOL_WALL or active_tool == TOOL_GABLE_WALL or active_tool == TOOL_GABLE_WALL_RIGHT or selected != null
|
||
var show_rise := active_tool == TOOL_GABLE_WALL or active_tool == TOOL_GABLE_WALL_RIGHT or active_tool == TOOL_ROOF_GABLE or active_tool == TOOL_ROOF_PYRAMID
|
||
_rise_options_row.visible = show_rise or selected != null
|
||
_rise_label.visible = show_rise
|
||
rise_spin.visible = show_rise
|
||
_gable_centralize_check.visible = show_option
|
||
_gable_centralize_check.text = "Center walls" if active_tool == TOOL_WALL or (selected != null and selected.piece_type == ModularBuilderPiece.PieceType.WALL) else "Center"
|
||
_gable_centralize_check.tooltip_text = "Align wall endpoints with the centerlines of supporting walls." if active_tool == TOOL_WALL or (selected != null and selected.piece_type == ModularBuilderPiece.PieceType.WALL) else "Align the gable with the center of the supporting wall thickness."
|
||
if selected != null:
|
||
_gable_centralize_default = selected.center_align
|
||
_gable_centralize_check.set_pressed_no_signal(selected.center_align)
|
||
else:
|
||
_gable_centralize_check.set_pressed_no_signal(_gable_centralize_default)
|
||
|
||
func _get_selected_gable_wall() -> ModularBuilderPiece:
|
||
var selected := get_editor_interface().get_selection().get_selected_nodes()
|
||
if selected.size() == 1 and selected[0] is ModularBuilderPiece and (selected[0].piece_type == ModularBuilderPiece.PieceType.WALL or selected[0].piece_type == ModularBuilderPiece.PieceType.GABLE_WALL or selected[0].piece_type == ModularBuilderPiece.PieceType.GABLE_WALL_RIGHT):
|
||
return selected[0] as ModularBuilderPiece
|
||
return null
|
||
|
||
func _sync_pillar_centralize_control() -> void:
|
||
if _pillar_centralize_check == null:
|
||
return
|
||
var selected_pillar := _get_selected_pillar_detail()
|
||
var show_pillar_options := active_tool == TOOL_PILLAR or selected_pillar != null
|
||
_detail_options_row.visible = _is_detail_tool(active_tool) or show_pillar_options
|
||
_pillar_centralize_check.visible = show_pillar_options
|
||
if selected_pillar != null and active_tool != TOOL_PILLAR and not _is_detail_tool(active_tool):
|
||
_detail_width_label.visible = false
|
||
detail_width_spin.visible = false
|
||
_detail_height_label.visible = false
|
||
detail_height_spin.visible = false
|
||
_detail_height_offset_label.visible = false
|
||
detail_height_offset_spin.visible = false
|
||
detail_mode_option.visible = false
|
||
detail_style_option.visible = false
|
||
elif active_tool == TOOL_PILLAR:
|
||
_detail_width_label.visible = true
|
||
detail_width_spin.visible = true
|
||
_detail_height_label.visible = true
|
||
detail_height_spin.visible = true
|
||
if selected_pillar != null:
|
||
_pillar_centralize_default = selected_pillar.center_align
|
||
_pillar_centralize_check.set_pressed_no_signal(selected_pillar.center_align)
|
||
else:
|
||
_pillar_centralize_check.set_pressed_no_signal(_pillar_centralize_default)
|
||
|
||
func _get_selected_pillar_detail() -> ModularBuilderDetail:
|
||
var selected := get_editor_interface().get_selection().get_selected_nodes()
|
||
if selected.size() != 1 or not selected[0] is ModularBuilderDetail:
|
||
return null
|
||
var detail := selected[0] as ModularBuilderDetail
|
||
return detail if detail.detail_type == ModularBuilderDetail.DetailType.PILLAR else null
|
||
|
||
func _on_inspector_property_edited(property: String) -> void:
|
||
if property == "center_align":
|
||
_sync_pillar_centralize_control()
|
||
|
||
func _is_detail_tool(tool: String) -> bool:
|
||
return tool == TOOL_WINDOW or tool == TOOL_DOOR or tool == TOOL_HATCH or tool == TOOL_PILLAR or tool == TOOL_FENCE or tool == TOOL_PARAPET
|