# Create an Editor Tool Extension¶

This tutorial will help you to create your own tool for the world editor, following some paradigms and editor APIs usage that will allow the tool to seamlessly integrate in the editor workflow.

When you have questions, make sure you check the World Editor Coding documentation first.

## Step 1 - Create the extension’s Lua file¶

Lets call our tool just MyCoolTool, then we’ll create a new file in the /lua/ge/extensions/editor folder, as myCoolTool.lua. If you have some low level API for this tool’s operations you can create a Lua file in the /lua/ge/extensions/editor/api folder, where all the low level non-UI APIs for editor operations reside.

## Step 2 - Writing the base code¶

The current code will be written in your new editor extension Lua file:

Base code
  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 -- This Source Code Form is subject to the terms of the bCDDL, v. 1.1. -- If a copy of the bCDDL was not distributed with this -- file, You can obtain one at http://beamng.com/bCDDL-1.1.txt local M = {} local logTag = 'editor_myCoolTool' -- this is used for logging as a tag local imgui = ui_imgui -- shortcut for imgui, please do not use just 'im' -- here you can place your imgui calls -- this function is called every frame when the world editor is active local function onEditorGui() -- if our tool window is not shown, just return if not editor.isWindowVisible("myWindow") then return end -- setup the window, will set the default size and position editor.setupWindow("myWindow") -- begin our window, providing the visible bool ptr to be set in case of pressing X to close window if imgui.Begin("My Window", editor.getWindowVisibleBoolPtr("myWindow")) then -- your widgets here end imgui.End() end -- this function is a callback which we add below and called when the tools menu item is clicked -- usually used to just show our tool window local function onToolsMenuItem() -- show our window, this will set the visible bool to true editor.showWindow("myWindow") end -- this function is called when the editor is first initialized -- it will not be called everytime after that when we toggle editor visibility -- so just once, per game session, or when Ctrl+L pressed to reload Lua scripts local function onEditorInitialized() -- this will add our tool s aa menu item in the Tools menu -- the first parameter is the text of the item -- the second parameter is a callback to be called when item is clicked local editorExtensionInfo = { experimental = false -- this extension is not experimental/wip } editor.addToolsMenuItem("My Cool Tool", onToolsMenuItem, editorExtensionInfo) -- register our window by name and a default size editor.registerWindow("myWindow", imgui.ImVec2(500, 600)) end M.onEditorInitialized = onEditorInitialized M.onEditorGui = onEditorGui return M 

## Step 3 - Add an edit mode¶

If you want your tool to have a toolbar with actions, then you can use the edit modes system which lets you add an edit mode to the editor. An edit mode is a state in which the editor enters and all the actions are focused on a specific task. For example the object tool has 3 actions for move, rotate and scale which appear on the toolbar when this mode is selected. Likewise you can add your own custom edit mode. Edit modes are usually for viewport actions like scene operations (editing or creating specific objects, like road/rivers, paths, special AI tools, etc.) To add an edit mode, you will do the following:

Adding an edit mode
  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 -- this callback will be called when your edit mode is activated, -- be it by toolbar button click or via editor api (editor.selectEditMode) local function testEditModeActivate() -- here you can prepare your tool editing mode by activating various gizmos or -- setting various edit state for objects/caching etc. end -- this will be called when the edit mode is deactivated, either by choosing another -- edit mode in the toolbar or via code select a new edit mode local function testEditModeDeactivate() -- here you can disable the edit gizmos or delete any temporary data end -- this will be called every frame when your edit mode is active local function testEditModeUpdate() -- here you can check for example mouse ray cast scene object intersection -- or any other operations you may want to perform on the scene end local function onEditorInitialized() -- this basically adds your new edit mode to the editor's system editor.editModes.myCoolTool = { onActivate = testEditModeActivate, -- called when our edit mode is activated onDeactivate = testEditModeDeactivate, -- called when our edit mode is deactivated onUpdate = testEditModeUpdate, -- called every frame when our edit mode is active icon = editor.icons.some_icon_here, -- this icon will appear in the edit modes toolbar iconTooltip = "My Tool" } -- your tool can have many edit modes, but usually there is one per tool -- we'll learn below how to add a toolbar to our edit mode, in which we can place more actions end 

## Step 4 - Add a toolbar for the edit mode¶

When the edit mode is active, we can add some UI, respectively toolbar buttons for our editing operations. To do that we set the onToolbar callback member of our edit mode and draw toolbar buttons there:

  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 local function myEditModeToolbar() if editor.uiIconImageButton(editor.icons.some_icon_here) then -- do something here, set some edit mode variable, do some action end -- this is for the tooltip of the toolbar button if imgui.IsItemHovered() then imgui.BeginTooltip() imgui.Text("My Tool 1") imgui.EndTooltip() end imgui.SameLine() if editor.uiIconImageButton(editor.icons.some_icon_here) then -- do something here, set some edit mode variable, do some action end -- this is for the tooltip of the toolbar button if imgui.IsItemHovered() then imgui.BeginTooltip() imgui.Text("My Tool 2") imgui.EndTooltip() end imgui.SameLine() end local function onEditorInitialized() -- this basically adds your new edit mode to the editor's system editor.editModes.myCoolTool = { --.................... onToolbar = myEditModeToolbar -- set our toolbar callback, called when the edit mode's toolbar must be rendered } end 

## Step 5 - Add edit mode undo/redo capabilities¶

When doing various operations in the editor, you can add undo and redo steps which is a must do, so the user experience is aided by the fact operations can be reverted. This doesnt apply to edit modes only, but also to various editing operations in general, in the following example we’ll explore both scenarios.

NOTE: Most editor API functions, especially the ones in api/object.lua have undo enabled, so for example when doing object operations they already have undo/redo. This step is mostly for your own editor operations.

  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 -- here lets define some simple variables used as test for the undo steps local testVariable1 = 0 local testVariable2 = 0 -- the undo history action handlers local MySampleUndoAction1 = {} local MySampleUndoAction2 = {} -- this will be executed when the user presses Ctrl+Z or the Undo toolbar button or the Edit->Undo menu item was clicked -- it must set the old values which were modified -- of course, this can get as involved as you can see it fit -- NOTE: do not make this function local function MySampleUndoAction1:undo(actionData) testVariable1 = actionData.oldValue end -- this will be execute when the user presses Ctrl+Y or the Redo toolbar button or the Edit->Redo menu item was clicked -- NOTE: do not make this function local function MySampleUndoAction1:redo(actionData) testVariable1 = actionData.newValue end -- same as in MySampleUndoAction1 -- NOTE: do not make this function local function MySampleUndoAction2:undo(actionData) testVariable2 = actionData.oldValue end -- same as in MySampleUndoAction1 -- NOTE: do not make this function local function MySampleUndoAction2:redo(actionData) testVariable2 = actionData.newValue end -- this will be called by our tool to just set the value of the test variable and handle undo local function doAction1(value) -- we add the history action, name it, set a table with the action data, with old and new values, and the undo action class type local action = editor.history:addAction("MySampleAction1", {oldValue = testVariable1, newValue = value}, MySampleUndoAction1) -- this will bascally call the redo function of this history action, efectivelly executing the action of new value variable assignment editor.history:executeAction(action) end -- this will be called by our tool to just set the value of the test variable and handle undo local function doAction2(value) -- we add the history action, name it, set a table with the action data, with old and new values, and the undo action class type local action = editor.history:addAction("MySampleAction2", {oldValue = testVariable2, newValue = value}, MySampleUndoAction2) -- this will bascally call the redo function of this history action, efectivelly executing the action of new value variable assignment editor.history:executeAction(action) end -- so in your edit mode toolbar add some action call when a button pressed local function myEditModeToolbar() if editor.uiIconImageButton(editor.icons.some_icon_here) then -- lets just execute the action, which will also add the undo history item -- just set the value to something doAction1(123) end -- this is for the tooltip of the toolbar button if imgui.IsItemHovered() then imgui.BeginTooltip() imgui.Text("My Tool 1") imgui.EndTooltip() end imgui.SameLine() --................................................. end local function onEditorGui() -- if our window is not open, just return if not windowIsOpen[0] then return end --................................................. -- lets say we have a button inside our tool window that will call doAction2(...) -- remember we declared the windowIsOpen above in the previous steps imgui.Begin("My Tool", windowIsOpen, 0) -- if button pressed, it will execute the undo enabled function doAction2(...), storing a history item also -- if then Ctrl+Z will be pressed, the action will be undone if imgui.Button("Action 2") then -- just set the value to something doAction2(321) end imgui.End() --................................................. end local function onEditorInitialized() --................................................. end M.onEditorGui = onEditorGui M.onEditorInitialized = onEditorInitialized 

## Step 6 - Add custom clipboard operations¶

When your edit mode is active the cut/copy/paste etc., are redirected to your edit mode callbacks, if present. This way you can implement custom clipboard operations for your needs. When the shortcuts Ctrl+V, Ctrl+C, etc. are issued, the edit mode callbacks are called. Same for when the menu items in Edit are clicked.

NOTE: this step is not necessary if your edit mode or tool does not require custom clipboard operations.

  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 local function onCut() -- cut objects/things to clipboard -- you can check if your tool window is focused and cut some selection -- for example the object tool is using this to cut objects to clipboard -- the asset browser is using it to cut assets to clipboard end local function onCopy() -- copy selected things to clipboard end local function onPaste() -- paste selected things from clipboard end local function onDuplicate() -- duplicate selected things end local function onSelectAll() -- select all things in the current container, be it scene or some list end local function onDeleteSelection() -- delete the things in the current selection end local function onDeselect() -- deselect the things in the current selection end local function onEditorInitialized() editor.editModes.myTool = { --........................................... onCut = onCut, onCopy = onCopy, onPaste = onPaste, onDeleteSelection = onDeleteSelection, onDuplicate = onDuplicate, onDeselect = onDeselect --........................................... } end --........................................... M.onEditorInitialized = onEditorInitialized 

## Step 7 - Add a preferences page in the Editor Preferences¶

This step is valid when you want to have some settings for your tool that users can customize. To keep things tidy, we have an API to add a preferences page in the Preferences window of the editor.

  1 2 3 4 5 6 7 8 9 10 11 12 13 local function myToolPreferencesUI() -- here you can directly call imgui widget function and manage their logic for the preferences page -- you can find your page in the Editor Preferences end local function onEditorInitialized() --........................................... editor.addPreferencesPage("My Tool", myToolPreferencesUI) end M.onEditorGui = onEditorGui M.onEditorInitialized = onEditorInitialized M.onExtensionLoaded = onExtensionLoaded 
  1 2 3 4 5 6 7 8 9 10 local function onEditorInitialized() --........................................... editor.editModes.myTool = { --........................................... -- here we set the action map name for this edit mode actionMap = "MyToolActionMap" --........................................... } end