Creating Plugins with Unreal4 – 3DBuzz lesson notes

Updated notes for UE4 v4.23.0, Creating Plugins with Unreal4 videos

video 03

don’t create file folder structure, instead
Edit > Plugins > New Plugin
blank template …

edit CoolPlugin.uplugin and change “RunTime” to “Editor”

edit CoolPlugin.Build.cs and add private dependencies

Use the files created by the New Plugins process, CoolPlugin.h and CoolPlugin.cpp instead of Module.cpp and Module.h
Skip creation of PCH file? – looks like something is built in already?

video 04

Edit > Editor Preferences, General > Miscellaneous, check Display UI Extension Points – shows that “WindowLocalTabSpawners” does not exist, so use “WindowGlobalTabSpawners” instead

replace #include “Slate.h” with

include “SlateBasics.h”

include “SlateExtras.h”

LOCTEXT_NAMESPACE already exists from the New Plugins process

change all references from “Module” to “FCoolPluginModule”

place new code inside the LOCTEXT_NAMESPACE define

video 05

place CoolWindow.h before CoolPlugin.h inside the CoolWindow.cpp file

video 06 – nothing

video 07

first error is for ATriggerBox – search unreal online docs for ATriggerBox – api says #include “Engine/TriggerBox.h”
add #include “Engine/TriggerBox.h” to the CoolPlugin.h file

error – cannot convert from FString to FText
CoolWindow.h change FString GetBoundsActorName() to FText GetBoundsActorName()
CoolWindow.cpp the same change and wrap the return values with FText::FromString(xxx)

error – iterator wont work, undefined class TActorIterator, ATriggerBox already has include so should be ok
search docs for TActorIterator says #include “EngineUtils.h”
add #include “EngineUtils.h” to the CoolPlugin.h file

error – cannot convert TIndirectArray to TArray
hover on GEngine->GetWorldContexts() shows that it returns a TIndirectArray not an TArray – so change TArray to TIndirectArray

video 08

convert FString to FText error for GetAlleySpaceText, wrap return value with FText::FromString(xxx)

video 09

error – UCubeBuilder not defined, search docs shows UBrushBuilder, with a link to UEditorBrushBuilder and now can see a UCubeBuilder link, finally UCubeBuilder says #include “Builders/CubeBuilder.h”
add #include “Builders/CubeBuilder.h” to the CoolPlugin.h file

error – GetBrush is not member of UWorld, so replace GetBrush with GetDefaultBrush

strange FText conversion error const Othertype to FText::EInitToEmptyString, error from file Attribute.h
output console error points to the .Text(FString(TEXT(“Build”)))
replace with .Text(LOCTEXT(“Build”,”Build”))

and now the whole thing works with ue 4.23.0

Question Messagebox

The trueSpace scripting question messagebox can be hidden behind the main display. This is a solution to that problem.

function Question(strText)
	var nSecondsToWait = -1;
	var strTitle = "Question";
	// values are hexadecimal
	var MB_YESNO = 4;
	var MB_SYSTEMMODAL = 4096;//1000L force on top
	var MB_ICONQUESTION = 32;//20L question mark symbol

	var IDYES = 6;
	var IDNO = 7;

	var shell = new ActiveXObject("");
	var button = shell.Popup (strText, nSecondsToWait, strTitle, nType);

	if(button == IDYES) return true;
	return false;

if(!Question("Special mesh processing\nDo you wish to continue?")) {
params.ConValue("abort") = 1;

Misc trueSpace script

function MapRange(val,a1,a2,b1,b2)
	var val2 = Math.min(val,a2);
	s = Math.max(val2, a1);
	return b1 + (s-a1)*(b2-b1)/(a2-a1)

map range a1,a2 to b1,b2

RsAnim.PutFrame places a single item at a certain frame without affecting other items in the scene, useful for some “set driven key” type behaviors.

LE.ConfirmEncapsulation(”, true, ”) – first arg nodes(guessing semi colon delimited list) or blank for selection, second arg true=3d false=2d, third arg name for new node

Workspace Shell Tool

Impractical process to simulate the shell tool scaffolding action of modelspace.

Workspace Scaffolding
  1. Select all faces of the mesh
  2. Run trueBevel script with a small amount
  3. Run fix geometry tools script and choose zero area triangles to select the narrow faces
  4. Invert the selection and delete
  5. Select all faces – the fix geometry changes the selection mode so be sure face selection is active
  6. copy selection – not the copy found the bottom of the interface
  7. flip faces
  8. invert selection
  9. static sweep script with average normals unchecked
  10. scale up or move
  11. select all and merge vertices coincidence with a small value

Truespace Script Custom Event


For some special situations you may need to define custom communication between your objects.
Use RsApp.SendCustomEvent method to send an event and implement OnCustomEvent to handle the

function OnCustomEvent(params)
	var eventData = params.Param('vtEventData');
RsApp.SendCustomEvent(Space.CurrentScene() + "/target", "hello")

It should be possible to send an event to one object and have it relay to all items in a scene making interface type behaviors – todo test this theory

Custom events seem to run in the scope of the calling function shown by running System.Trace(System.ThisName()); or System.Trace(System.ThisOwner();.

It’s possible to pass Command script access to object nodes by passing things like Node and RsTime in the event arguments.

RsApp.SendCustomEvent(Space.CurrentScene() + "/target", {Node:Node, RsTime:RsTime})

Random Truespace Dev Notes

Timeline can drive scripts by adding keyframes to an object at start and end with values equal to the first and last frames. Watchdog is used to read the keyed values. Same can be done to read the modelspace timeline.

Bitmap Pixel Format

   BFMT_UNKNOWN    = 0;
   BFMT_A8R8G8B8    = 1;
   BFMT_A16B16G16R16    = 2;
   BFMT_A32fB32fG32fR32f    = 3;
   BFMT_R8    = 4;
   BFMT_R16    = 5;
   BFMT_R32f    = 6;
   BFMT_FORCE_DWORD    = -1;

Bitmap File Format


bitmap format R8 may be invalid for exported images

popup panel – can get a panel frame by dragging an item out of the panel stack view. Set the RootNode connector value to the parent of the object that will be displayed. Set the Panel Node connector of the Panel Node to the node that will be floating. Copy into Windows Manager Space and run 2 mystery commands then set the panel aspect.

	WindowsManager.CloseWindow("Project/Windows Manager Space/TestPanelFrame")
	var rpf = Space.CurrentScene() + "/TestPanelFrame";

	Node.Value(rpf, "RootNode") = Space.CurrentScene();
	Node.Value(rpf + "/Panel Node", "PanelEditorNode") = Space.CurrentScene() + "/YafaRay IES Spot";
	Node.Copy(rpf, "/Project/Windows Manager Space");
	//UserInterface.SetAspect(Space.CurrentScene() + "/YafaRay IES Spot", 0);//icon
	//UserInterface.SetAspect(Space.CurrentScene() + "/YafaRay IES Spot", 1);//???
	//UserInterface.SetAspect(Space.CurrentScene() + "/YafaRay IES Spot", 2);//Exp
	//UserInterface.SetAspect(Space.CurrentScene() + "/YafaRay IES Spot", 3);//Default
	UserInterface.SetAspect(Space.CurrentScene() + "/YafaRay IES Spot", 4);//color

some file notes – appending writes to the end of a file. TristateTrue will write utf-8 files compatible with the xml files in trueSpace.

	var filename = params.ConValue('filename');
	var forReading = 1, forWriting = 2, forAppending = 8;
	var TristateUseDefault = -2, TristateTrue = -1, TristateFalse = 0;
	var fso = new ActiveXObject("Scripting.FileSystemObject");
	var connToFile = file.OpenAsTextStream( forReading , TristateUseDefault);
var curLine = connToFile.ReadLine();
fso.CopyFile(System.GetMainDir() + "\\Scripts\\reset.js", System.GetMainDir() + "\\Scripts\\resetORIGINAL.js", true);
	var f = fso.GetFile(System.GetMainDir() + "\\Scripts\\reset.js");
	var connToFile = f.OpenAsTextStream( forReading, TristateUseDefault );
	var filecontents = connToFile.ReadAll();
fso.CopyFolder(YafaRay4tSv090 + '\\YafaRay4tS Setup', tS + 'Rs Main Libraries\\', true)

sample write utf8 xml file

function WriteRenderPath(renderLibrary)
	var forReading = 1, forWriting = 2, forAppending = 8;
	var TristateUseDefault = -2, TristateTrue = -1, TristateFalse = 0; 

	var fso = new ActiveXObject("Scripting.FileSystemObject");

	var f = fso.GetFile(System.GetMainDir() + "\\Scripts\\D3DView.RenderToFile.Settings.xml");

	if(!f) {
		System.Alert("d3dview render to file xml not found.");

	lines = [];

	connToFile = f.OpenAsTextStream( forReading, TristateTrue );//unicode

	while (!connToFile.AtEndOfStream) {



	connToFile = f.OpenAsTextStream( forWriting, TristateTrue );//unicode

	re = /FILENAME="[\w|\:|\\|\.|\s]+"/;

	for(var i=0;i<lines.length;i++) {
		curline = lines[i];
		matchArray = curline.match(re);

		if(matchArray) {
			var currentRenderFile = matchArray[0].split('"')[1];
			var basename = fso.GetBaseName(currentRenderFile);
			var ext = fso.GetExtensionName(currentRenderFile);
			curline = curline.replace(re,'FILENAME="' + renderLibrary + '\\' + basename + '.' + ext + '"')
RsFileIO.SaveObject(tS + '\\Scripts\\preobjects\\Yafaray Folder.rsobj', System.ThisOwner() + "/Yafaray Folder")
RsFileIO.LoadObject(tS + '\\Rs Main Libraries\\YafaRay4tS Setup\\YafaRay Camera.RsObj', "/Preferences/Cameras")
always close a window not delete it
WindowsManager.CloseWindow("/Project/Windows Manager Space/YafaRay Toolbar Frame")
after copy to windows manager space
open view in panel stack – 6 is the aspect
UserInterface.OpenToolPanelViewEx2("", "", Space.CurrentScene() + "/YafaRay4tS", 6, 1, 0);
Activity.Run - runs after the script returns
ScriptObject.Execute - stops current script while executing

keyframe range

keyframe range
	//RsTime.EvalAnimStart and RsTime.EvalAnimEnd seem to ignore all arguments
	// and only operate on the entire scene animation

//alters the play range anim pref values"
		try {
			animStart = RsTime.EvalAnimStart('');
			animEnd = RsTime.EvalAnimEnd('');
play range
	var playRangeStart = RsAnimPref.PlayRangeStart;
	var playRangeEnd = RsAnimPref.PlayRangeEnd;

timeline range
Node.Value("%THIS_NAME%" , "Start_Frame") = Node.Value("AnimMng", "AnimStart");
Node.Value("%THIS_NAME%", "Number_Start") = Node.Value("AnimMng", "AnimStart");
Node.Value("%THIS_NAME%", "End_Frame") = Node.Value("AnimMng", "AnimEnd");
status line
	if(Node.Exists("/Status Line")) {
		if(!Node.ConExists("Status Line", "Model"))
			Node.ConCreate("Status Line", "Model", "string", 4);
		Node.Value("/Status Line","Model") = "Rendering...";
folder enumeration and reg exp matching
var folder = fso.GetFolder(tspath); 
	var folderEnum = new Enumerator(folder.SubFolders); 
	var regexp = /YafaRay/i

	for(;!folderEnum.atEnd(); folderEnum.moveNext()) {
		var cur = folderEnum.item();
		var curArray = (cur+"").split("\\");
		var s = "" + curArray[curArray.length-1];
		if(s.match(regexp)) {
			temppath = fso.BuildPath(cur,"bin");
			temppath = fso.BuildPath(temppath,"yafaray-xml.exe");
			if(fso.FileExists(temppath)) {
				yafpath = temppath;
temp path
	var WshShell = new ActiveXObject("WScript.Shell");
	WshSysEnv = WshShell.Environment("PROCESS");
	temppath = WshSysEnv("TEMP");
time stamp
var theDate = new Date();
	var y = theDate.getYear();
	var mo = theDate.getMonth() + 1;
	var d = theDate.getDate();
	var h = theDate.getHours();
	var mi = theDate.getMinutes();
	var s = theDate.getSeconds();

	if(mo < 10) mo = "0" + mo;
	if(d < 10) d = "0" + d;
	if(h < 10) h = "0" + h;
	if(mi < 10) mi = "0" + mi;
	if(s < 10) s = "0" + s;

	var timestamp = "_" + y + mo + d + h + mi + s;
string enum
var recentPlaces = System.CreateDO("Common Data Package/String Enum Data");

	//try catch only way to check for uninitialized value
	try {
		recentPlaces = Node.Value(owner, "RecentPlaces");
	} catch(e) {
		Node.Value(owner, "RecentPlaces") = recentPlaces;

	if(recentPlaces.GetSize() == 0) return;

	thedata = recentPlaces.GetSelectedString();

	var newRecentPlaces = System.CreateDO("Common Data Package/String Enum Data");

get frame rate
frameRateEnum = System.CreateDO('Common Data Package/String Enum Data');
	frameRateEnum = Node.Value("Preferences/AnimPref","Frame Rate");

	frameRateString = frameRateEnum.GetSelectedString();

	frameRate = 30;
	if(frameRateString=="15 fps")
		frameRate = 15;
	if(frameRateString=="Film 24 fps")
		frameRate = 24;
	if(frameRateString=="PAL 25 fps")
		frameRate = 25;
	if(frameRateString=="30 fps")
		frameRate = 30;
	if(frameRateString=="60 fps")
		frameRate = 60;
sel = Node.Selection(); //list of selected nodes
	numsel = Node.SelectionLength(sel); // number of selected nodes
	theParent = Node.SelectionGetAt(sel, numsel-1); //last selected item will be the parent
	var mybitmap = System.CreateDO("Common Data Package/Bitmap Data");

	BFMT_A8R8G8B8	= 1;

	var color = System.CreateDO('Common Data Package/Color Data');
if(!DebugViewFound("Project/Windows Manager Space"))
        CmdPrompt.DebugView('Windows Manager Space', 0);

function NodeSubObject(root, index)
    return root + "/" + Node.SubObject(root, index);

function DebugViewFound()
    var WMS = "Project/Windows Manager Space";
    var numwindows = Node.SubObjectCount(WMS);

    for(var winIndex=0; winIndex < numwindows; winIndex++)
        var currentWindow = NodeSubObject(WMS, winIndex);
        if(Node.SubObjectCount(currentWindow) < 1)

        var shortname = Node.ShortName(NodeSubObject(currentWindow, 0));
        if( shortname == "LogOutput")
            return true;
    return false;

Widgets.Rotate == rotate about origin (0,0,0) then offset back to original position

Node.Selection() – returns a selection list with a semicolon delimination, but has leading spaces in each nodes path so needs to be adjusted to work with Space.Select

sel = Node.Selection();
selArray = sel.split(";");
re = /^\s/; // whitespace in first character position
sel = selArray[0].replace(re,"");
if(selArray.length > 1)
                sel = sel + ";" + selArray[i].replace(re,"");

Node.Select – select one node



%THIS_BUTTON_ITEM% – enables a toolbar button to see it’s full path

LE. – (L E dot) undocumented LE commands

Activity.Run(scene + “/second”);//runs after the calling script is done

ScriptObject.Execute(scene + “/second”);//runs immediately pausing the calling script

If you add a new connector to a script used inside the Widgets it won’t work right away. Restarting truespace will unstick it.

//functions for generating random guid for marker display
function S4() {
return (((1+Math.random())*0x10000)|0).toString(16).substring(1);
function guid() {
return (“{“ + S4()+S4()+“-“+S4()+“-“+S4()+“-“+S4()+“-“+S4()+S4()+S4() + “}”);
WshShell = new ActiveXObject(“WScript.Shell”);

matrix multiplication

   myMatrix.SetPitch(-90);// myMatrix = -90 deg pitch => last rotation
   myMatrix.Mult(rollMatrix);// rollMatrix (this is the second rotation) * myMatrix => myMatrix
   myMatrix.Mult(yawMatrix);// yawMatrix (this is the first rotation) * myMatrix => myMatrix
   final result in myMatrix is yaw then roll then pitch
Local matrix from world matrices
theMatrix = Node.Value(bindgroup + "/" + subNode, "WldMatrix");
parentMatrix = Node.Value(bindParent, "WldMatrix");
parInvert = parentMatrix.Invert();
//now theMatrix contains local instead of world

Space.Select() – select multiple nodes from text string separated by semi colons, node paths in the list must not have leading spaces

sel = Node.Selection();
selArray = sel.split(";");
re = /^\s/; // whitespace in first character position
sel = selArray[0].replace(re,"");
if(selArray.length > 1)
                sel = sel + ";" + selArray[i].replace(re,"");
Node.Select – select one node
	var strEnum = System.CreateDO("Common Data Package/String Enum Data");
	try {
		strEnum = Node.Value("/Offline renderers", "Renderer");
	} catch(e) {
	var selString = strEnum.GetSelectedString();

Node.Selection() – returns a selection list with a semicolon delimination, but has leading spaces in each nodes path so needs to be adjusted to work with Space.Select


Cardboard (GoogleVR) Quickstart for UE4

These are my notes for an alternate Quick Start Guide for Google Cardboard using Unreal Engine 4. I’m using a custom viewer made for the Nexus 7 ( running Marshmallow 6.0 The original guide is here…eVR/QuickStart and my changes appear below.

1 – Google VR Project Creation
step 3 – use First Person instead of the Blank Project

This change is so there is something more than just an empty scene to start with.

2 – Google VR Project Setup
step 11 – minimum sdk version of 19 and target sdk of 24 instead of setting both to 24
step 12 – arm7 instead of arm64

These changes are needed to use a device running something older than Nougat. I think the arm64 and sdk 24 settings are mainly for Daydream phones

3 – Google VR Project Packaging & Deployment
step 1 – ETC1 instead of ASTC…roid/Reference
ASTC “Available on some devices at this point…”
ETC1 “Supported by all Android based devices…”

That’s it. All other instructions are the same as in the guide.

Feb 12, 2019 – the latest unreal editor 4.21.x does not work with cardboard. It builds without errors but does not run/crashes immediately. UE4.20.x is the last working version.

Ubuntu 18.04 Blu-ray

based on information found here:

Don’t install VLC using the Ubuntu Software button, it doesn’t seem to work for Blu-ray discs. The commands below will install a slightly older VLC version 3.0.4 Vetinari and the needed extras. The only difference from the link above is to use libbluray2 instead of libbluray1. The instructions also say ‘ Leave the “No disc menus” option checked. ‘, but it seems to work as expected with it checked.

sudo apt-get install vlc libaacs0 libbluray-bdj libbluray2
mkdir -p ~/.config/aacs/
cd ~/.config/aacs/ && wget

I tried installing from the Ubuntu Software button then running the command without the vlc, “sudo apt-get install libaacs0 libbluray-bdj libbluray2”, but it did not work.