New pipeline widget framework
This commit is contained in:
@ -6,13 +6,35 @@ from .event_bus import EventBus
|
||||
class ImagePipeline:
|
||||
def __init__(self, bus: EventBus):
|
||||
self.bus = bus
|
||||
self.stages = {}
|
||||
self.stages = []
|
||||
self.stagedata = {}
|
||||
|
||||
def add_stage(self, name: str, img: np.ndarray):
|
||||
self.stages[name] = img.astype(np.float32)
|
||||
# notify widgets of updated stage list and data
|
||||
self.bus.publish_deferred("pipeline_stages", list(self.stages.keys()))
|
||||
self.bus.publish_deferred("pipeline_stage", (name, self.stages[name]))
|
||||
def register_stage(self, name: str):
|
||||
self.stages.append(name)
|
||||
self.stagedata[name] = None
|
||||
self.bus.publish_deferred("pipeline_stages", self.stages)
|
||||
return len(self.stages) - 1
|
||||
|
||||
def get_stage(self, name: str):
|
||||
return self.stages.get(name)
|
||||
def rename_stage(self, id: int, name: str):
|
||||
if id >= 0 and id < len(self.stages):
|
||||
self.stages[id] = name
|
||||
self.bus.publish_deferred("pipeline_stages", self.stages)
|
||||
|
||||
def publish(self, id: int, img: np.ndarray):
|
||||
self.stagedata[id] = img.astype(np.float32)
|
||||
self.bus.publish_deferred("pipeline_stage", (id, self.stagedata[id]))
|
||||
|
||||
def get_stage_data(self, id: int):
|
||||
if id >= 0 and id < len(self.stages):
|
||||
return self.stagedata[id]
|
||||
else:
|
||||
return None
|
||||
|
||||
def get_stage_name(self, id:int):
|
||||
if id >= 0 and id < len(self.stages):
|
||||
return self.stages[id]
|
||||
else:
|
||||
return None
|
||||
|
||||
def republish_stages(self):
|
||||
self.bus.publish_deferred("pipeline_stages", self.stages)
|
@ -20,6 +20,7 @@ logger = logging.getLogger(__name__)
|
||||
class EditorManager:
|
||||
def __init__(self):
|
||||
dpg.create_context()
|
||||
self.texture_registry = dpg.add_texture_registry()
|
||||
self.bus = EventBus(logger)
|
||||
self.pipeline = ImagePipeline(self.bus)
|
||||
self.layout_manager = LayoutManager(self, logger)
|
||||
|
@ -11,24 +11,68 @@ class BaseWidget:
|
||||
name: str = "BaseWidget"
|
||||
register: bool = False
|
||||
|
||||
def __init__(self, manager: "EditorManager", logger: logging.Logger):
|
||||
def __init__(
|
||||
self,
|
||||
manager: "EditorManager",
|
||||
logger: logging.Logger,
|
||||
window_width: int = 300,
|
||||
window_height: int = 200,
|
||||
):
|
||||
self.manager = manager
|
||||
self.logger = logger
|
||||
self.window_width = window_width
|
||||
self.window_height = window_height
|
||||
self.window_offset_x = 0
|
||||
self.window_offset_y = 0
|
||||
self.window_tag = dpg.generate_uuid()
|
||||
self.config = {}
|
||||
|
||||
def create(self):
|
||||
"""Called by negstation itself, creates the window"""
|
||||
with dpg.window(
|
||||
label=self.name,
|
||||
tag=self.window_tag,
|
||||
width=self.window_width,
|
||||
height=self.window_height,
|
||||
on_close=self._on_window_close,
|
||||
):
|
||||
self.window_handler = dpg.add_item_handler_registry()
|
||||
dpg.add_item_resize_handler(
|
||||
callback=self._on_window_resize, parent=self.window_handler
|
||||
)
|
||||
dpg.bind_item_handler_registry(self.window_tag, self.window_handler)
|
||||
|
||||
self.create_content()
|
||||
|
||||
def create_content(self):
|
||||
"""Must be implemented by the widget, creates the content of the window"""
|
||||
raise NotImplementedError
|
||||
|
||||
def update(self):
|
||||
"""Must be implemented by the widget, is called in the render loop every frame"""
|
||||
pass
|
||||
|
||||
def on_resize(self, width: int, height: int):
|
||||
"""Must be implemented by the widget, is called after a resize"""
|
||||
pass
|
||||
|
||||
# Internal but public funtions
|
||||
|
||||
def get_config(self):
|
||||
"""Caled by negstation itself, returns the saved widget config"""
|
||||
return self.config
|
||||
|
||||
# Callbacks
|
||||
|
||||
def _on_window_close(self):
|
||||
try:
|
||||
dpg.delete_item(self.window_tag)
|
||||
self.manager.widgets.remove(self)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
def _on_window_resize(self, data):
|
||||
win_w, win_h = dpg.get_item_rect_size(self.window_tag)
|
||||
self.window_height = win_h
|
||||
self.window_width = win_w
|
||||
self.on_resize(win_w, win_h)
|
||||
|
@ -1,25 +1,24 @@
|
||||
import dearpygui.dearpygui as dpg
|
||||
import numpy as np
|
||||
|
||||
from .pipeline_stage_widget import PipelineStageWidget
|
||||
|
||||
|
||||
class InvertStage(PipelineStageWidget):
|
||||
name = "Invert Image"
|
||||
register = True
|
||||
has_pipeline_in = True
|
||||
has_pipeline_out = True
|
||||
|
||||
def __init__(self, manager, logger):
|
||||
super().__init__(manager, logger)
|
||||
self.stage_out = "inverted_image"
|
||||
super().__init__(manager, logger, default_stage_out="inverted_image")
|
||||
|
||||
def create_pipeline_stage_content(self):
|
||||
dpg.add_text("Inversion is happening here")
|
||||
|
||||
def create_content(self):
|
||||
dpg.add_button(label="Invert", callback=lambda s, a, u: self._do_invert())
|
||||
|
||||
def on_stage(self, img):
|
||||
self._do_invert()
|
||||
|
||||
def _do_invert(self):
|
||||
if self.img is None:
|
||||
def on_pipeline_data(self, img):
|
||||
if img is None:
|
||||
return
|
||||
inverted = self.img.copy()
|
||||
inverted = img.copy()
|
||||
inverted[...,:3] = 1.0 - inverted[...,:3]
|
||||
self.publish(inverted)
|
||||
self.publish_stage(inverted)
|
||||
|
@ -2,20 +2,21 @@ import dearpygui.dearpygui as dpg
|
||||
from PIL import Image
|
||||
import numpy as np
|
||||
|
||||
from .base_widget import BaseWidget
|
||||
from .pipeline_stage_widget import PipelineStageWidget
|
||||
|
||||
|
||||
class OpenImageWidget(BaseWidget):
|
||||
name: str = "Open Image"
|
||||
class OpenImageWidget(PipelineStageWidget):
|
||||
name = "Open Image"
|
||||
register = True
|
||||
has_pipeline_in = False
|
||||
has_pipeline_out = True
|
||||
|
||||
def __init__(self, manager, logger, stage_out="loaded_image"):
|
||||
super().__init__(manager, logger)
|
||||
self.stage_out = stage_out
|
||||
def __init__(self, manager, logger):
|
||||
super().__init__(manager, logger, default_stage_out="opened_image")
|
||||
self.dialog_tag = dpg.generate_uuid()
|
||||
self.output_tag = dpg.generate_uuid()
|
||||
|
||||
def create(self):
|
||||
def create_pipeline_stage_content(self):
|
||||
with dpg.file_dialog(
|
||||
directory_selector=False,
|
||||
show=False,
|
||||
@ -25,24 +26,12 @@ class OpenImageWidget(BaseWidget):
|
||||
width=400,
|
||||
):
|
||||
dpg.add_file_extension(".*")
|
||||
|
||||
with dpg.window(
|
||||
label="Open Image File",
|
||||
tag=self.window_tag,
|
||||
width=300,
|
||||
height=150,
|
||||
on_close=self._on_window_close,
|
||||
):
|
||||
dpg.add_input_text(label="Stage Output Name", tag=self.output_tag)
|
||||
dpg.add_button(label="Open File...", callback=self._on_open_file)
|
||||
|
||||
dpg.set_value(self.output_tag, self.stage_out)
|
||||
dpg.add_button(label="Open File...", callback=self._on_open_file)
|
||||
|
||||
def _on_open_file(self):
|
||||
dpg.configure_item(self.dialog_tag, show=True)
|
||||
|
||||
def _on_file_selected(self, sender, app_data):
|
||||
# app_data[0] is dict with selected file paths
|
||||
selection = (
|
||||
f"{app_data['current_path']}/{list(app_data['selections'].keys())[0]}"
|
||||
if isinstance(app_data, dict)
|
||||
@ -55,6 +44,6 @@ class OpenImageWidget(BaseWidget):
|
||||
img = Image.open(selection).convert("RGBA")
|
||||
arr = np.asarray(img).astype(np.float32) / 255.0 # normalize to [0,1]
|
||||
# Publish into pipeline
|
||||
self.manager.pipeline.add_stage(dpg.get_value(self.output_tag), arr)
|
||||
self.manager.pipeline.publish(self.pipeline_stage_out_id, arr)
|
||||
except Exception as e:
|
||||
self.logger.error(f"Failed to load image {selection}: {e}")
|
||||
|
@ -4,60 +4,100 @@ from .base_widget import BaseWidget
|
||||
|
||||
|
||||
class PipelineStageWidget(BaseWidget):
|
||||
name = "Pipeline Stage Widget"
|
||||
register = False
|
||||
has_pipeline_in: bool = False
|
||||
has_pipeline_out: bool = False
|
||||
|
||||
def __init__(self, manager, logger):
|
||||
super().__init__(manager, logger)
|
||||
self.stage_in = ""
|
||||
self.stage_out = ""
|
||||
self.img = None
|
||||
def __init__(
|
||||
self,
|
||||
manager,
|
||||
logger,
|
||||
default_stage_in: str = "pipeline_in",
|
||||
default_stage_out: str = "pipeline_out",
|
||||
window_width: int = 300,
|
||||
window_height: int = 200,
|
||||
):
|
||||
super().__init__(manager, logger, window_width, window_height)
|
||||
self.pipeline_stage_in_id = None
|
||||
self.pipeline_stage_out_id = None
|
||||
self.pipeline_config_group_tag = dpg.generate_uuid()
|
||||
|
||||
self.manager.bus.subscribe(
|
||||
"pipeline_stage", self._on_pipeline, main_thread=True
|
||||
)
|
||||
self.manager.bus.subscribe(
|
||||
"pipeline_stages", self._on_stage_list, main_thread=True
|
||||
)
|
||||
|
||||
def create(self):
|
||||
with dpg.window(label=self.name, tag=self.window_tag, width=400, height=300):
|
||||
# top‐row: input / output
|
||||
dpg.add_text("Configuration:")
|
||||
self.combo = dpg.add_combo(
|
||||
label="Stage In", items=[], callback=self._on_select
|
||||
if self.has_pipeline_out:
|
||||
self.pipeline_stage_out_id = self.manager.pipeline.register_stage(
|
||||
default_stage_out
|
||||
)
|
||||
dpg.add_input_text(
|
||||
label="Stage Out",
|
||||
default_value=self.stage_out,
|
||||
callback=lambda s, a, u: setattr(self, "stage_out", a),
|
||||
)
|
||||
dpg.add_separator()
|
||||
# now let subclasses populate the rest
|
||||
self.create_content()
|
||||
|
||||
def _on_select(self, sender, selected_stage):
|
||||
self.stage_in = selected_stage
|
||||
self.img = self.manager.pipeline.get_stage(selected_stage)
|
||||
self.on_stage(self.img)
|
||||
|
||||
def update(self):
|
||||
pass
|
||||
self.manager.bus.subscribe("pipeline_stages", self._on_stage_list, True)
|
||||
if self.has_pipeline_in:
|
||||
self.pipeline_stage_in_id = 0
|
||||
self.manager.bus.subscribe("pipeline_stage", self._on_stage_data, True)
|
||||
# force getting all available pipeline stages
|
||||
self.manager.pipeline.republish_stages()
|
||||
|
||||
def create_content(self):
|
||||
with dpg.group(tag=self.pipeline_config_group_tag):
|
||||
if self.has_pipeline_in:
|
||||
self.stage_in_combo = dpg.add_combo(
|
||||
label="Stage In",
|
||||
items=[],
|
||||
callback=self._on_stage_in_select,
|
||||
default_value=f"{self.manager.pipeline.get_stage_name(0)} : 0",
|
||||
)
|
||||
if self.has_pipeline_out:
|
||||
dpg.add_input_text(
|
||||
label="Stage Out",
|
||||
default_value=self.manager.pipeline.get_stage_name(
|
||||
self.pipeline_stage_out_id
|
||||
),
|
||||
callback=lambda s, a, u: self.manager.pipeline.rename_stage(
|
||||
self.pipeline_stage_out_id, a
|
||||
),
|
||||
)
|
||||
dpg.add_separator()
|
||||
self.create_pipeline_stage_content()
|
||||
|
||||
def publish_stage(self, img: np.ndarray):
|
||||
"""Publishes an image to output stage"""
|
||||
if self.has_pipeline_out:
|
||||
self.manager.pipeline.publish(self.pipeline_stage_out_id, img)
|
||||
|
||||
def create_pipeline_stage_content(self):
|
||||
"""Must be implemented by the widget, creates the content of the window"""
|
||||
raise NotImplementedError
|
||||
|
||||
def _on_pipeline(self, data):
|
||||
name, img = data
|
||||
if name == self.stage_in:
|
||||
self.img = img
|
||||
self.on_stage(img)
|
||||
|
||||
def _on_stage_list(self, stages):
|
||||
self.stages = stages
|
||||
dpg.configure_item(self.combo, items=stages)
|
||||
|
||||
def on_stage(self, img: np.ndarray):
|
||||
def on_pipeline_data(self, img: np.ndarray):
|
||||
"""Must be implemented by the widget, is called when there is a new image published on the in stage"""
|
||||
pass
|
||||
|
||||
def publish(self, img: np.ndarray):
|
||||
self.manager.pipeline.add_stage(self.stage_out, img)
|
||||
# Callbacks
|
||||
|
||||
def _on_stage_list(self, stagelist):
|
||||
if self.has_pipeline_in:
|
||||
stages = [f"{stage} : {id}" for id, stage in enumerate(stagelist)]
|
||||
dpg.configure_item(self.stage_in_combo, items=stages)
|
||||
|
||||
def _on_stage_in_select(self, sender, selected_stage: str):
|
||||
d = selected_stage.split(" : ")
|
||||
name = d[0]
|
||||
id = int(d[1])
|
||||
self.pipeline_stage_in_id = id
|
||||
if self.has_pipeline_in:
|
||||
img = self.manager.pipeline.get_stage_data(id)
|
||||
self.on_pipeline_data(img)
|
||||
|
||||
def _on_stage_data(self, data):
|
||||
pipeline_id = data[0]
|
||||
img = data[1]
|
||||
if self.has_pipeline_in and pipeline_id == self.pipeline_stage_in_id:
|
||||
self.on_pipeline_data(img)
|
||||
|
||||
# Override the window resize callback
|
||||
def _on_window_resize(self, data):
|
||||
win_w, win_h = dpg.get_item_rect_size(self.window_tag)
|
||||
group_w, group_h = dpg.get_item_rect_size(self.pipeline_config_group_tag)
|
||||
group_x, group_y = dpg.get_item_pos(self.pipeline_config_group_tag)
|
||||
self.window_height = win_h - group_h - group_y - 12
|
||||
self.window_width = win_w - 7
|
||||
self.window_offset_y = group_h + group_y + 3
|
||||
self.on_resize(win_w, win_h)
|
||||
|
@ -1,114 +1,72 @@
|
||||
import dearpygui.dearpygui as dpg
|
||||
import numpy as np
|
||||
|
||||
from .base_widget import BaseWidget
|
||||
from .pipeline_stage_widget import PipelineStageWidget
|
||||
|
||||
|
||||
class StageViewerWidget(BaseWidget):
|
||||
name: str = "Image Stage Viewer"
|
||||
class PipelineStageViewer(PipelineStageWidget):
|
||||
name = "View Image"
|
||||
register = True
|
||||
has_pipeline_in = True
|
||||
has_pipeline_out = False
|
||||
|
||||
def __init__(self, manager, logger):
|
||||
super().__init__(manager, logger)
|
||||
# Ensure shared texture registry
|
||||
if not hasattr(manager, "texture_registry"):
|
||||
manager.texture_registry = dpg.add_texture_registry(
|
||||
tag=dpg.generate_uuid())
|
||||
self.registry = manager.texture_registry
|
||||
|
||||
self.stages = []
|
||||
self.current = None
|
||||
super().__init__(manager, logger, default_stage_in="pipeline_out")
|
||||
self.texture_tag = dpg.generate_uuid()
|
||||
self.img = None
|
||||
self.needs_update = False
|
||||
self.registry = manager.texture_registry
|
||||
|
||||
# Subscribe only to stage list updates
|
||||
self.manager.bus.subscribe(
|
||||
"pipeline_stages", self.on_stage_list, main_thread=True)
|
||||
self.manager.bus.subscribe(
|
||||
"pipeline_stage", self.on_stage, main_thread=True)
|
||||
def create_pipeline_stage_content(self):
|
||||
dpg.add_dynamic_texture(
|
||||
1, 1, [0, 0, 0, 0], tag=self.texture_tag, parent=self.registry
|
||||
)
|
||||
self.image_item = dpg.add_image(self.texture_tag)
|
||||
|
||||
def create(self):
|
||||
with dpg.window(label="Stage Viewer", tag=self.window_tag, width=400, height=400):
|
||||
# Dropdown for selecting a stage
|
||||
self.combo = dpg.add_combo(
|
||||
label="Stage", items=[], callback=self.on_select)
|
||||
# Placeholder 1×1 texture in registry
|
||||
dpg.add_dynamic_texture(
|
||||
1, 1, [0, 0, 0, 0], tag=self.texture_tag, parent=self.registry)
|
||||
# Image widget that will display the texture
|
||||
self.image_item = dpg.add_image(self.texture_tag)
|
||||
def on_resize(self, width, height):
|
||||
self.needs_update = True
|
||||
|
||||
with dpg.item_handler_registry() as handler:
|
||||
dpg.add_item_resize_handler(
|
||||
callback=self.on_resize)
|
||||
dpg.bind_item_handler_registry(self.window_tag, handler)
|
||||
|
||||
def on_resize(self, app_data):
|
||||
if self.img is not None:
|
||||
self.needs_update = True
|
||||
# self.update_texture(self.img)
|
||||
|
||||
def on_stage_list(self, stages):
|
||||
# Update dropdown items
|
||||
self.stages = stages
|
||||
dpg.configure_item(self.combo, items=stages)
|
||||
|
||||
def on_stage(self, stage):
|
||||
name, img = stage
|
||||
if name == self.current:
|
||||
if img is not None:
|
||||
self.img = img
|
||||
self.needs_update = True
|
||||
# self.update_texture(img)
|
||||
|
||||
def on_select(self, sender, selected_stage):
|
||||
# User-picked stage: fetch and render
|
||||
self.current = selected_stage
|
||||
img = self.manager.pipeline.get_stage(selected_stage)
|
||||
if img is not None:
|
||||
self.img = img
|
||||
self.needs_update = True
|
||||
# self.update_texture(img)
|
||||
def on_pipeline_data(self, img):
|
||||
self.img = img
|
||||
self.needs_update = True
|
||||
|
||||
def update_texture(self, img: np.ndarray):
|
||||
# img is a NumPy array with shape (h, w, 4)
|
||||
"""Only call from update function"""
|
||||
if img is None:
|
||||
dpg.configure_item(self.image_item, show=False)
|
||||
return
|
||||
|
||||
h, w, _ = img.shape
|
||||
flat = img.flatten().tolist()
|
||||
|
||||
# 1) Recreate the dynamic texture at the correct size
|
||||
if dpg.does_item_exist(self.texture_tag):
|
||||
dpg.delete_item(self.texture_tag)
|
||||
dpg.add_dynamic_texture(
|
||||
width=w, height=h,
|
||||
width=w,
|
||||
height=h,
|
||||
default_value=flat,
|
||||
tag=self.texture_tag,
|
||||
parent=self.registry
|
||||
parent=self.registry,
|
||||
)
|
||||
|
||||
# 2) Compute available space: full window width, from just below combo to bottom
|
||||
win_w, win_h = dpg.get_item_rect_size(self.window_tag)
|
||||
combo_w, combo_h = dpg.get_item_rect_size(self.combo)
|
||||
combo_x, combo_y = dpg.get_item_pos(self.combo)
|
||||
avail_w = win_w - 15
|
||||
avail_h = win_h - combo_h - combo_y - 15
|
||||
win_w, win_h = self.window_width, self.window_height
|
||||
avail_w = win_w
|
||||
avail_h = win_h
|
||||
|
||||
# 3) Compute scale to fit the available rectangle
|
||||
scale = min(avail_w / w, avail_h / h, 1.0)
|
||||
disp_w = int(w * scale)
|
||||
disp_h = int(h * scale)
|
||||
|
||||
# 4) Center horizontally, start exactly below the combo
|
||||
x_off = (avail_w - disp_w) / 2 + 7
|
||||
y_off = combo_h + combo_y + 7 # flush immediately below the dropdown
|
||||
x_off = (avail_w - disp_w) / 2
|
||||
y_off = self.window_offset_y
|
||||
|
||||
# 5) Apply to the image widget
|
||||
dpg.configure_item(
|
||||
self.image_item,
|
||||
texture_tag=self.texture_tag,
|
||||
pos=(x_off, y_off),
|
||||
width=disp_w,
|
||||
height=disp_h
|
||||
height=disp_h,
|
||||
show=True
|
||||
)
|
||||
|
||||
def update(self):
|
||||
|
@ -1,43 +1,103 @@
|
||||
[Window][WindowOverViewport_11111111]
|
||||
Pos=0,19
|
||||
Size=814,581
|
||||
Size=1109,809
|
||||
Collapsed=0
|
||||
|
||||
[Window][###22]
|
||||
Pos=0,19
|
||||
Size=291,244
|
||||
Collapsed=0
|
||||
DockId=0x00000005,0
|
||||
|
||||
[Window][###27]
|
||||
Pos=293,19
|
||||
Size=521,581
|
||||
Collapsed=0
|
||||
DockId=0x00000002,0
|
||||
|
||||
[Window][Debug##Default]
|
||||
Pos=60,60
|
||||
Size=400,400
|
||||
Collapsed=0
|
||||
|
||||
[Window][###41]
|
||||
Pos=0,312
|
||||
Size=299,288
|
||||
[Window][###28]
|
||||
Pos=0,19
|
||||
Size=299,379
|
||||
Collapsed=0
|
||||
DockId=0x00000003,0
|
||||
|
||||
[Window][###27]
|
||||
Pos=0,19
|
||||
Size=300,200
|
||||
Collapsed=0
|
||||
|
||||
[Window][###34]
|
||||
Pos=434,65
|
||||
Size=300,200
|
||||
Collapsed=0
|
||||
|
||||
[Window][###33]
|
||||
Pos=0,445
|
||||
Size=250,383
|
||||
Collapsed=0
|
||||
DockId=0x0000000E,0
|
||||
|
||||
[Window][###37]
|
||||
Pos=0,400
|
||||
Size=299,200
|
||||
Collapsed=0
|
||||
DockId=0x00000004,0
|
||||
|
||||
[Window][###34]
|
||||
Pos=0,265
|
||||
Size=291,335
|
||||
[Window][###22]
|
||||
Pos=0,19
|
||||
Size=288,427
|
||||
Collapsed=0
|
||||
DockId=0x00000006,0
|
||||
DockId=0x00000007,0
|
||||
|
||||
[Window][###31]
|
||||
Pos=0,448
|
||||
Size=288,152
|
||||
Collapsed=0
|
||||
DockId=0x00000008,0
|
||||
|
||||
[Window][###23]
|
||||
Pos=0,19
|
||||
Size=250,424
|
||||
Collapsed=0
|
||||
DockId=0x0000000D,0
|
||||
|
||||
[Window][###32]
|
||||
Pos=0,376
|
||||
Size=250,224
|
||||
Collapsed=0
|
||||
DockId=0x0000000C,0
|
||||
|
||||
[Window][###46]
|
||||
Pos=252,19
|
||||
Size=569,605
|
||||
Collapsed=0
|
||||
DockId=0x00000002,0
|
||||
|
||||
[Window][###39]
|
||||
Pos=252,19
|
||||
Size=663,581
|
||||
Collapsed=0
|
||||
DockId=0x00000002,0
|
||||
|
||||
[Window][###53]
|
||||
Pos=252,19
|
||||
Size=548,581
|
||||
Collapsed=0
|
||||
DockId=0x00000002,0
|
||||
|
||||
[Window][###41]
|
||||
Pos=252,19
|
||||
Size=857,809
|
||||
Collapsed=0
|
||||
DockId=0x00000002,0
|
||||
|
||||
[Docking][Data]
|
||||
DockSpace ID=0x7C6B3D9B Window=0xA87D555D Pos=0,19 Size=814,581 Split=X Selected=0x26E8F608
|
||||
DockNode ID=0x00000001 Parent=0x7C6B3D9B SizeRef=291,581 Split=Y Selected=0xEE087978
|
||||
DockNode ID=0x00000003 Parent=0x00000001 SizeRef=299,291 Split=Y Selected=0xEE087978
|
||||
DockNode ID=0x00000005 Parent=0x00000003 SizeRef=299,244 Selected=0xEE087978
|
||||
DockNode ID=0x00000006 Parent=0x00000003 SizeRef=299,335 Selected=0xAA145F7D
|
||||
DockNode ID=0x00000004 Parent=0x00000001 SizeRef=299,288 Selected=0x7FF1E0B5
|
||||
DockNode ID=0x00000002 Parent=0x7C6B3D9B SizeRef=521,581 CentralNode=1 Selected=0x26E8F608
|
||||
DockSpace ID=0x7C6B3D9B Window=0xA87D555D Pos=0,19 Size=1109,809 Split=X
|
||||
DockNode ID=0x00000009 Parent=0x7C6B3D9B SizeRef=250,581 Split=Y Selected=0xD36850C8
|
||||
DockNode ID=0x0000000B Parent=0x00000009 SizeRef=147,355 Split=Y Selected=0xD36850C8
|
||||
DockNode ID=0x0000000D Parent=0x0000000B SizeRef=250,304 Selected=0xD36850C8
|
||||
DockNode ID=0x0000000E Parent=0x0000000B SizeRef=250,275 Selected=0x1834836D
|
||||
DockNode ID=0x0000000C Parent=0x00000009 SizeRef=147,224 Selected=0x2554AADD
|
||||
DockNode ID=0x0000000A Parent=0x7C6B3D9B SizeRef=548,581 Split=X
|
||||
DockNode ID=0x00000005 Parent=0x0000000A SizeRef=288,581 Split=Y Selected=0xEE087978
|
||||
DockNode ID=0x00000007 Parent=0x00000005 SizeRef=147,427 Selected=0xEE087978
|
||||
DockNode ID=0x00000008 Parent=0x00000005 SizeRef=147,152 Selected=0x62F4D00D
|
||||
DockNode ID=0x00000006 Parent=0x0000000A SizeRef=510,581 Split=X
|
||||
DockNode ID=0x00000001 Parent=0x00000006 SizeRef=299,581 Split=Y Selected=0xA4B861D9
|
||||
DockNode ID=0x00000003 Parent=0x00000001 SizeRef=299,379 Selected=0xA4B861D9
|
||||
DockNode ID=0x00000004 Parent=0x00000001 SizeRef=299,200 Selected=0xEDB425AD
|
||||
DockNode ID=0x00000002 Parent=0x00000006 SizeRef=499,581 CentralNode=1 Selected=0x7FF1E0B5
|
||||
|
||||
|
@ -4,11 +4,11 @@
|
||||
"config": {}
|
||||
},
|
||||
{
|
||||
"widget_type": "StageViewerWidget",
|
||||
"widget_type": "InvertStage",
|
||||
"config": {}
|
||||
},
|
||||
{
|
||||
"widget_type": "InvertStage",
|
||||
"widget_type": "PipelineStageViewer",
|
||||
"config": {}
|
||||
}
|
||||
]
|
Reference in New Issue
Block a user