https://planetside.co.uk/wiki/api.php?action=feedcontributions&user=Redmaw&feedformat=atomTerragen Documentation from Planetside Software - User contributions [en]2024-03-29T00:57:19ZUser contributionsMediaWiki 1.31.0https://planetside.co.uk/wiki/index.php?title=Micro_Exporter&diff=16679Micro Exporter2023-11-16T23:18:53Z<p>Redmaw: Updated descriptions based on TG 4.7 release features.</p>
<hr />
<div>[[File:00_MicroExport_GUI.PNG|none|507 px|Micro Exporter]]<br />
<br />
<br />
__TOC__<br />
<br />
<br />
== Overview ==<br />
The Micro exporter feature is only available in the Professional version of Terragen. It allows you to export procedural terrains as geometry, including displacement and overhangs. Exporting a terrain as a heightfield would not allow this. The Micro exporter can export geometry (polygons), normals and texture coordinates. It supports TGO, FBX, OBJ and Lightwave LW02 formats.<br />
<br />
Beginning with Terragen 4.7.15 the Micro exporter will take advantage of multi-threaded renders and produces cleaner geometry and smaller files, by merging duplicate vertices, normals, UVWs and faces.<br />
<br />
[[File:03_MicroExport_RenderVsGeometry.jpg|none | 800px | Mesh generated by Micro exporter from procedural terrain.]]<br />
<br />
<br />
The Micro exporter is used in conjunction with a Render node to export the micropolygons generated during a render as geometry. For this reason the Render node’s Micropoly detail setting controls the size of the polygons generated. Lower values generate a coarser mesh, and as polygons get further away from the camera they also get larger. <br><br />
You can control the level of detail in the exported geometry by understanding the relationship between the image resolution and the “Micropoly detail” setting. For example, a mesh generated with the Renderer’s “Image width” and “Image height” values equal to “1024” and the “Micropoly detail” value at “1.0” would be practically identical to a mesh generated with the “Image width” and “Image height” values at “2048” and the “Micropoly detail” value at “0.5”, as would a mesh generated with the “Image width” and “Image height” values at “512” and the “Micropoly detail” value at “2.0”.<br />
<br />
[[File:04_MicroExport_MeshDetailComparision_0001.jpg|none | 800px | Comparison of the geometry exported from the procedural terrain at different Detail values.]]<br />
<br />
<br />
The camera assigned to the Render node, along with the Near distance and Far distance parameters, define the area of terrain to export. Only geometry within the camera’s field of view is exported, including Backfacing polygons, which are those facing away from the camera.<br />
<br />
[[File:05_MicroExport_FarDistanceComparision_0001.jpg|none | 800px|The Near and Far distance parameters can limit the area of the terrain to export. ]]<br />
<br />
<br />
The Micro exporter works only when using the Renderer’s “Render To Disk”, “Render Sequence” or command line rendering. It does not work when using “Render Image”. <br />
<br />
Information about the kinds of data to be output by “Render To Disk” is shown to the right of the button, such as “Images”, “Micro export” or “Images + micro export”. To export only geometry, uncheck the checkbox to the left of the “Output image file” setting. This will disable image output by the renderer.<br />
<br />
<br />
'''Settings:'''<br />
<br />
*'''Name:''' This setting allows you to apply a descriptive name to the node, which can be helpful when using multiple Micro Exporter nodes in a project.<br />
<br />
*'''Normals (from Compute Terrain or Compute Normal)''': When checked, the terrain’s normal values calculated by the upstream Compute Terrain node or Compute Normal node are exported to the OBJ file, and are available to other 3D software packages that load the OBJ file. When unchecked, the terrain's normal values are not saved with the object, resulting in a slightly smaller file size for the exported mesh.<br />
<ul><br />
[[File:06_MicroExport_NormalsOnOff_0001.jpg|none | 800px | When checked, the terrain normals are saved om the OBJ file and can be read by other 3D software packages.]]<br />
</ul><br />
<br />
<br />
*'''Texture coords (UVs)''': This pop-up menu has two options for exporting the uv data.<br />
<ul><br />
[[File:09_MicroExport_GUI_TextureCoords.PNG|None | 705 px| Texture coords options.]]<br />
<br />
<br />
*'''Image UV (render Projection)''': When selected, the uv coordinates are mapped into the 0 to 1 uv space from the render camera’s point of view. <br />
*'''UVW from Compute Terrain or Tex Coords:''' When selected the uv coordinates are mapped into the world space coordinates as determined by the last Compute terrain node or Tex Coords node These coordinate values can exceed 0 to 1.<br />
<ul><br />
[[File:07_MicroExport_TextureCoords_PerspectiveCamera.jpg|none|800px|Examples of the terrain geometry mapped into the UV texture coordinate space from a perspective camera’s point of view.]] <br /n><br />
<br />
[[File:08_MicroExport_TextureCoords_OrthoCamera.jpg|none| 800px | Examples of the terrain geometry mappedinto the UV texture coordinate space from an orthographic camera’s point of view.]]<br />
</ul><br />
</ul><br />
<br />
<br />
*'''Nearest distance:''' This sets the distance from the camera where geometry starts to be exported. When set to 0 geometry is exported right from the camera position. If you set this to 1000, then only parts of the terrain 1000 metres and more from the camera will be exported.<br />
<br />
<br />
*'''Farthest distance:''' This sets the furthest distance from the camera that geometry will be exported. Parts of the terrain beyond this distance are not exported.<br />
<br />
<br />
*'''Filename:''' This setting specifies the file that the exported geometry will be saved to. Wavefront OBJ, Autodesk FBX, Terragen TGO, and Lightwave LWO (format v2) are supported.<br />
<br />
<br />
== Fun with the Micro Exporter ==<br />
<br />
Large amounts of geometry can be created by the Micro exporter, which can be managed by using the Near distance and Far distance settings to limit the area exported, and using detail levels of 0.5 or below in the Render node.<br />
<br />
To export geometry from a procedural terrain follow these steps:<br />
# Go to the Sequence/Output tab of the Render node.<br />
# Check the '''Micro exporter''' checkbox.<br />
# Click the Assign button (green plus icon) to the right of the '''Micro exporter''' parameter.<br />
# Choose "Create new micro handler" from the menu which pops up.<br />
# Choose "Micro exporter" from the submenu.<br />
<br />
Note that the Lwo micro exporter which is also available in the "Create new micro handler" submenu is an older node which remains for compatibility reasons and is deprecated. You can export LWO files from the Micro exporter itself, and should use it instead.<br />
<br />
<br /n><br />
<br />
<br />
<br />
[[Category:Other Nodes]]</div>Redmawhttps://planetside.co.uk/wiki/index.php?title=Terragen_Sky_Early_Access&diff=16678Terragen Sky Early Access2023-10-20T19:46:08Z<p>Redmaw: Update online docs to TG Sky 2023-09-15 release build features.</p>
<hr />
<div>=Overview==<br />
This is the online documentation for the Early Release version of Terragen Sky. <br />
<br />
As features are being added or updated on a daily basis, please consider this documentation to be a work-in-progress.<br />
<br />
<br />
__TOC__<br />
<br />
==Menu Bar==<br />
'''File menu button'''<br />
<br />
[[File:53_FileMenu.jpg|none|468px|File menu.]]<br />
<br />
<br />
*'''New:''' Starts a new Terragen Sky project with the default values.<br />
*'''Open:''' Opens a previously saved Terragen Sky project.<br />
*'''Save As:''' Saves the current project as a Terragen Sky project.<br />
<br />
<br />
'''Edit menu button'''<br />
<br />
[[File:54_UndoMenu.jpg|none|468px|Edit menu]]<br />
<br />
<br />
* '''Undo:''' Undo the last operation.<br />
*'''Redo:''' Redo the last operation<br />
<br />
<br />
'''UI menu button'''<br />
<ul><br />
<span style="color:#0000ff"><br />
[[File:00_UI_Options.jpg|none|469px|UI menu. ]]<br/n><br />
[[File:01_UI_Scale90.jpg|none|800px|UI at 90% ]]<br/n><br />
[[File:02_UI_Scale300.jpg|none|800px|UI at 300% ]]<br/n><br />
</ul><br />
The presets under this menu scale the user interface components accordingly, so that you can adjust the layout to best fit your display device or visual preference.<br />
<br />
<br />
'''SysInfo button'''<br />
<br />
[[File:14_SysInfo.jpg|none|800px|SysInfo display.]]<br />
<br />
<br />
When clicked, the system information is displayed including OpenGL Renderer and OpenGL Version. Click anywhere outside the information window to close it and return to the main interface.<br />
<br />
<br />
'''License button'''<br />
<br />
[[File:15_LicenseInfo.JPG|none|800px|LicenseInfo display.]]<br />
<br />
<br />
When clicked, the license information is displayed. One year of maintenance is included with the purchase of a perpetual license of Terragen. Click anywhere outside the information window to close it and return to the main interface.<br />
<br />
<br />
==Sun Direction Panel==<br />
[[File:03_Sundial.jpg|none|468px|The Sundial.]]<br />
<br />
<br />
This panel provides control over the direction of the sun by means of a Sundial and/or the use of numeric sliders for heading and elevation parameters.<br />
<br />
*'''Sunlight dial:''' Clicking or dragging with the mouse within the Sunlight dial automatically sets the sun heading and elevation values.<br />
<br />
*'''Sun heading:''' This parameter sets the heading of the sun in degrees. You can think of 0° as North, +90° as East, +-180° as South, and -90° as West. The parameter can be used as a slider with a range of -180 to +180 degrees, or by clicking on the numerical value to directly input an amount.<br />
<br />
*'''Sun elevation:''' This parameter sets the elevation of the sun in degrees. You can think of 0° being directly on the horizon, +90° being directly overhead, and -90° being on the underside of the planet. When negative values are used to simulate a night sky, you can compensate for the darkness by increasing the Exposure value. The parameter can be used as a slider with a range of -90 to +90 degrees, or by clicking on the numerical value to directly input an amount.<br />
<br />
<br />
==Sky Viewport Tools==<br />
[[File:04_SkyViewport.jpg|none|245px|Sky Viewport Tools.]]<br />
<br />
<br />
*'''Sun Position:''' When active, clicking in the main viewport repositions the sun to that location, updating the Sun heading and elevation values.<br />
<br />
*'''Exposure:''' When active, clicking and dragging in the main viewport changes the camera’s exposure value, simulating the response to light a real camera might have. Dragging to the right increases the exposure value and brightens up the image as more light reaches the film. Dragging to the left decreases the exposure value and darkens the image, much like stopping down the camera lens aperture from f/2 to f/4. Right-click the exposure button to open a context menu which has the option to reset the exposure value to default.<br />
<ul><br />
[[File:16_SkyViewportTools_ExposureReset.jpg|none|450px|Exposure reset.]]<br />
</ul><br />
<br />
<br />
*'''Camera Zoom:''' When active, clicking and dragging in the main viewport changes the camera’s focal length, which can also be seen in the Camera Focal length parameter.<br />
<ul><br />
[[File:19_CameraZoom_Reset.JPG|none|549px|Right click on the Camera Zoom button to reset the camera zoom. ]] <br /n><br />
[[File:20_CameraZoomFocalLengths0001.jpg|none|800px|Various camera focal lengths.]]<br />
</ul><br />
<br />
<br />
==Cloud Height Diagram==<br />
The Cloud Height Diagram provides a visual and alternate way of positioning the cloud and haze layers, and camera. Moving an item within the diagram adjusts its altitude. When one item’s altitude overlaps with another their indicators appear side by side.<br />
<br />
{|<br />
|-<br />
| [[File:21_CloudHeightDiagram_Default.jpg|none|266px|Default Cloud heights. ]] || [[File:22_CloudHeightDiagram_Overlap.jpg|none|266px|Overlapping Cloud heights. ]] || [[File:23_CloudHeightDiagram_Staggered.jpg|none|266px|Staggard Cloud heights. ]]<br />
|}<br />
<br />
<br />
==Camera==<br />
[[File:05_UI_CameraeLens.jpg|none|470px|The Camera.]]<br />
<br />
<br />
*'''Altitude:''' This parameter controls the height of the virtual camera. Dragging the slider to the right increases the altitude of the camera and updates the preview window and Cloud Height Diagram, while dragging to the left decreases the camera’s altitude.<br />
<br />
*'''Position:''' This parameter controls the position of the virtual camera in the scene. XYZ position values may be modified by dragging the sliders to the left or right, or by directly clicking on a value and entering a new numeric value.<br />
<br />
*'''Rotation phb:''' This parameter controls the rotation of the virtual camera in the scene. Pitch, heading, and bank values may be modified by dragging the sliders to the left or right, or by directly clicking on a value and entering a new numeric value.<br />
<br />
*'''Focal length:''' This parameter controls the focal length of the virtual camera lens and is the equivalent to the focal length of a real camera lens. The parameter includes a slider ranging from 0 mm to 300 mm, and a numerical input field in which you can enter any value.<br />
<br />
<br />
==Panorama and Panorama Roller==<br />
<br />
The Panorama displays a “lat-long” (spherical) image or a cube map overview of the entire environment. The overlaid text provides information about the type and resolution of the environment map, whose resolution depends on the Terragen Sky mode selected. Dragging the grey roller below the Panorama rotates the environment from -180° to +180°. Clicking with the RMB resets the rotation value to 0. .<br />
<br />
<br />
[[File:23_Panoramic_LatLong.jpg|none|800px|The Panorama view displaying LatLong information. ]]<br /n><br />
[[File:24_Panoramic_CubeMap.jpg|none|800px|The Panorama view displaying CubeMap information.]]<br />
<br />
<br />
<br />
==Paint Tab==<br />
This is a teaser of things to come.<br />
<br />
<br />
==Viewport==<br />
The viewport is the main display of the UI. The Alt key + mouse buttons and dragging in the viewport will change the view. These are similar to the default bindings in Terragen 4.<br />
<br />
Translate forwards/backwards (dolly): Alt+MMB, or Alt+Shift+LMB<br />
Translate up/down/left right: Alt+RMB, or Alt+Ctrl+LMB<br />
<br />
Pressing the Esc key resets the camera’s position and rotation to the values they had when the project was loaded, or created in the case of using New Project.<br />
<br />
Below the viewport is the status bar, which displays helpful information, such as the name of the UI component that the mouse is currently pointed at.<br />
<br />
[[File:26_MainViewport.JPG|none|800px|The Viewport.]]<br />
<br />
<br />
<br />
*'''Filmic Tonemap:''' When checked, a tone map is applied to the viewport in much the same way as the Renderers Tonemap settings in Terragen 4. When unchecked, no tone mapping is applied and you may see clipping in the bright areas of the sky.<br />
<ul><br />
{|<br />
|-<br />
| [[File:27_ToneMapOn.JPG|none|400px|Tonemap checked.]] || [[File:28_ToneMapOff.JPG|none|400px|Tonemap unchecked.]]<br />
|}<br />
</ul><br />
<br />
<br />
*'''Enviro map/sphere:''' Inset into the main viewport is an environment shader object. <!---When the Panorama displays a “lat-long” spherical image this will appear as a flat 2d plane with the environment map applied to it. When the Panorama displays a cube-map, the object changes to a reflective sphere, which can be useful when rotating the environment map in order to see what’s behind the camera position. --> This chrome sphere can be useful when rotating the environment map in order to see what's behind the camera position. You can drag the object around the viewport by clicking on it with the LMB, or resize it using the MMB.<br />
<br />
<ul><br />
<!--33_EnviroPlane.jpg --><br />
[[File:32_EnviroBall.JPG|none|627px|The chrome sphere.]]<br />
</ul><br />
<br />
<br />
==Preview Modes (Camera, Camera+, 360+, 360)==<br />
[[File:52_PreviewMode.jpg|none|602px|Preview modes.]]<br />
<br />
<br />
'''Camera-Priority Modes:'''<br />
<br />
*'''Camera:''' Use this mode when you want to preview the area you’re looking at as quickly as possible. In this mode, whatever you’re looking at is progressively refined up to an internal target resolution and quality. <br />
<br />
*'''Camera+:''' In this mode, whatever you’re looking at is progressively refined up to an internal target resolution and quality, then the rest of the 360° view is updated and refined until everything is at the internal target resolution and quality.<br />
<br />
<br />
'''360/Lighting-Priority Modes:'''<br />
<br />
*'''360 +:''' Use this mode when you want an approximate 360° render as quickly as possible (e.g. for lighting) but you also want to refine the area within the camera’s view. In this mode, the entire 360° view is first updated to a minimum resolution, then whatever you’re looking at is refined to the internal target resolution and quality, and finally the rest of the 360° view is updated and refined until everything is at the internal target resolution and quality.<br />
<br />
*'''360:''' Use this mode when you want a 360° view as quickly as possible (e.g. for lighting). In this mode, the entire 360° view is updated at a uniform resolution.<br />
<br />
<br />
==Sky Parameters==<br />
The Sky parameters consist of three cloud layers, a Haze layer and an Atmosphere control. <br />
<br />
'''Cloud Layers'''<br />
<br />
<!-- [[File:30_SkyParameters_CloudLayers.JPG|602px|Cloud Layer parameters.]] --><br />
[[File:48_CloudLayer.jpg|597px|Cloud Layer parameters.]]<br />
<br />
*'''Layer # (checkbox):''' When checked, the layer is active and will appear in the viewport and in renders.<br />
<br />
*'''Presets menu (down chevron):''' When clicked, a list of cloud presets is displayed, allowing you to choose the type of clouds to assign to this cloud layer. See below.<br />
<br />
*'''Advanced Settings (right chevron):''' When clicked, the advanced settings for the selected cloud, haze or atmosphere is displayed. This feature is not currently implemented.<br />
<br />
*'''Seed:''' This value generates the random aspects of the cloud layer.<br />
<br />
*'''Coverage:''' This setting controls the amount of clouds within the cloud layer. Low values reduce the amount of individual clouds and increase the amount of empty space between them. Increasing the value fills up the area with more individual clouds.<br />
<br />
*'''Density:''' This setting controls the transparency or opaqueness of the clouds within the cloud layer. Low values make the clouds look thin and let more light pass through them. Higher values make the clouds look thicker and denser, as well as darker because less light passes through them. <br />
<br />
*'''Thickness:''' This setting controls the height range of the cloud layer from top to bottom. This corresponds to “Cloud depth” in Terragen 4.<br />
<br />
*'''Altitude:''' This setting controls the height of the cloud in metres above sea level. For some cloud types this corresponds to the base of the cloud and for others it corresponds to the mid-point between the base and the top.<br />
<br />
<br />
'''Presets menu'''<br />
<br />
[[File:61_SkyPresets.jpg|none|464px|Presets menu]]<br />
<br />
<br />
Through the presets menu for Cloud and Haze layers, you have access to the cloud type presets available in Terragen, including Cloud Layer V2, Cloud Layer V3, and Easy Clouds.<br />
<br />
*'''High-level:''' These cloud layers default to an altitude of 8,000 metres or more, and are based on Terragen’s Cloud Layer V2 or Easy Clouds presets. When an Easy Cloud preset is selected, you can further choose between Cumulus, Altocumulus Castellanus, and Stratocumulus cloud types under the cloud layer’s advanced settings.<br />
<br />
*'''Mid-level:''' These cloud layers default to an altitude around 4,000 metres, and are based on Terragen’s Easy Cloud presets. Under the cloud layer’s advanced settings you can further choose between Cumulus, Altocumulus Castellanus, and Stratocumulus cloud types.<br />
<br />
*'''Low-level:''' These cloud layers default to an altitude around 1,500 - 2,000 metres and are based on Terragen’s Easy Cloud presets. Under the cloud layer’s advanced settings you can further choose between Cumulus, Altocumulus Castellanus, and Stratocumulus cloud types.<br />
<br />
*'''Haze:''' High-level, Mid-level, and Low-level haze layers are available based on Terragen’s Cloud Layer V2 without a Density shader, tapering or falloff. It provides a volumetric layer in which you can control the density, thickness and altitude.<br />
<br />
<br />
'''Haze Layer'''<br />
<br />
[[File:07_UI_SkyParams_Haze.JPG|none|567px|The Haze layer.]]<br />
<br />
<br />
Haze is the appearance of moisture and particulates in the atmosphere. It can be used to simulate a broad range of phenomena such as fog, mist, or dry haze caused by dust and smoke. The Haze layer is a layer of uniform haze with a prescribed height range, whereas the Atmosphere is a layer of haze with exponential fall-off with respect to height above sea level.<br />
<br />
*'''Haze layer:''' When checked, the haze layer is visible.<br />
<br />
*'''Haze density:''' This is the opacity of the haze layer.<br />
<br />
*'''Thickness:''' This is the height range of the Haze layer from top to bottom, measured in metres.<br />
<br />
*'''Altitude:''' This is the altitude, specified in metres, at which the bottom of the Haze layer begins.<br />
<br />
<br />
'''Atmosphere'''<br />
<br />
[[File:08_UI_SkyParams_Atmo.JPG|none|567px|Atmosphere.]]<br />
<br />
<br />
*'''Haze density:''' The exponential fall-off of haze in the atmosphere with respect to height above sea level.<br />
<br />
<br />
'''Other buttons'''<br />
<br />
[[File:31_SavePreset.JPG|none|602pox|Save presets button.]]<br />
<br />
<br />
*'''Save Preset button:''' Clicking this button saves the current settings as a preset, which becomes available in the Presets tab.<br />
<br />
<br />
==Presets Tab==<br />
[[File:62_PresetsTab.jpg|none|599px|The Presets Tab.]]<br />
<br />
<br />
The presets tab is a convenient way to access your favorite Terragen Sky settings. <br />
<br />
To load a preset, simply click on the thumbnail image in the Presets tab, or right click it and select “Load Preset”.<br />
<br />
To save a preset, simply press the Save Preset button located at the bottom of the Sky Parameters, and the new preset will be added to the Preset tab. <br />
<br />
User presets and presets from third party sources show up under the User Presets or Unregistered categories.<br />
<br />
The Planetside Software Presets category displays the presets that ship with Terragen Sky. You can download them by clicking on the Download button as shown in the image below.<br />
<br />
[[File:63_PresetsTabDownloadBrowser.jpg|none|559px|Presets download button.]]<br />
<br />
You can also navigate to their installation directory by clicking on the ellipsis button.<br />
<br />
[[File:64_PresetsTabOpenFile.jpg|none|599px|Installation directory button]]<br />
<br />
<br />
== Render Tab ==<br />
[[File:65_RenderTab.jpg|none|601px|The Render Tab.]]<br />
<br />
<br />
The render tab provides controls for the rendered output, including size and quality settings.<br />
<br />
*'''Current View & Spherical:''' These settings provide a dropdown menu of standard image resolutions to choose from. The width and height of the rendered image will be set to the chosen resolution.<br />
<ul><br />
[[File:66_RenderTabResolutions.jpg|none|599px|Composit image of Current and Spherical view resolutions and aspect ratios.]]<br />
</ul><br />
<br />
*'''Quality:''' The quality of the rendered image is governed by the four Q buttons. The higher the Q button value the better the quality of the rendered image, at the expense of a longer render time.<br />
<br />
*'''Render Image button:''' Click this button to start the render process. When the process is complete, a notification is displayed below the button indicating how long the render took.<br />
<br />
*'''Save button:''' Click this button to save the rendered image.<br />
<br />
*'''Save with sun:''' When checked, the sun disc is saved in the rendered image. Note that to turn the sun disc on or off, the image does not need to be re-rendered.<br />
<br />
*'''Auto-save when finished:''' When checked, the image will automatically be saved upon render.<br />
<br />
<br />
== Export Tab ==<br />
<br />
[[File:67_ExportTab-JSON.jpg|none|599px|The Export Tab]]<br />
<br />
<br />
The items on the Export tab allow you to save the project as a Terragen clip file or scene file for further use in Terragen 4. Additionally, with Live Send 360, you can immediately save the environment map to a file for access by third party software.<br />
<br />
'''Export to Terragen 4'''<br />
*'''Export Clip File:''' Saves the Sky Parameter settings as a clip file which can then be imported into Terragen 4 for further manipulation.<br />
<br />
*'''Export Scene file:''' Saves the project, including Sky Parameters, sun and camera, as a scene file which can be opened in Terragen 4 for further manipulation.<br />
<br />
'''Live Send 360:''' This feature allows you to export a 2k spherical image to a specific location which is based on the current render state of the viewport, eliminating the need to wait for a final rendered image. <br />
<br />
*'''Set Output File:''' Clicking this button brings up a standard file requestor in which to set the filename and path for the Live Send 360 output.<br />
<br />
*'''Send Now:''' Clicking this button immediately updates the image defined in Set Output File parameter.<br />
<br />
*'''Send Image after loading a preset:''' When checked, the image is immediately updated as soon as a preset is loaded. When unchecked, you need to click the Send Now button.<br />
<br />
*'''Send image every “n” seconds:''' When checked, the image is saved to the output file every “n” seconds that you specify.<br />
<br />
*'''Send JSON when Sun moves or Env rotates:''' When checked, a JSON file located in the same folder as the output file, is updated with project information such as the Sun’s heading and elevation. Third party applications supporting dynamic updating, like Unreal Engine, can access the information and their project assets can be synced with Terragen Sky.</div>Redmawhttps://planetside.co.uk/wiki/index.php?title=File:67_ExportTab-JSON.jpg&diff=16677File:67 ExportTab-JSON.jpg2023-10-20T19:40:47Z<p>Redmaw: File uploaded with MsUpload</p>
<hr />
<div>File uploaded with MsUpload</div>Redmawhttps://planetside.co.uk/wiki/index.php?title=File:66_RenderTabResolutions.jpg&diff=16676File:66 RenderTabResolutions.jpg2023-10-20T19:39:32Z<p>Redmaw: File uploaded with MsUpload</p>
<hr />
<div>File uploaded with MsUpload</div>Redmawhttps://planetside.co.uk/wiki/index.php?title=File:65_RenderTab.jpg&diff=16675File:65 RenderTab.jpg2023-10-20T19:37:44Z<p>Redmaw: File uploaded with MsUpload</p>
<hr />
<div>File uploaded with MsUpload</div>Redmawhttps://planetside.co.uk/wiki/index.php?title=File:64_PresetsTabOpenFile.jpg&diff=16674File:64 PresetsTabOpenFile.jpg2023-10-20T19:32:59Z<p>Redmaw: File uploaded with MsUpload</p>
<hr />
<div>File uploaded with MsUpload</div>Redmawhttps://planetside.co.uk/wiki/index.php?title=File:63_PresetsTabDownloadBrowser.jpg&diff=16673File:63 PresetsTabDownloadBrowser.jpg2023-10-20T19:32:58Z<p>Redmaw: File uploaded with MsUpload</p>
<hr />
<div>File uploaded with MsUpload</div>Redmawhttps://planetside.co.uk/wiki/index.php?title=File:62_PresetsTab.jpg&diff=16672File:62 PresetsTab.jpg2023-10-20T19:29:46Z<p>Redmaw: File uploaded with MsUpload</p>
<hr />
<div>File uploaded with MsUpload</div>Redmawhttps://planetside.co.uk/wiki/index.php?title=File:61_SkyPresets.jpg&diff=16671File:61 SkyPresets.jpg2023-10-20T19:26:21Z<p>Redmaw: File uploaded with MsUpload</p>
<hr />
<div>File uploaded with MsUpload</div>Redmawhttps://planetside.co.uk/wiki/index.php?title=File:53_FileMenu.jpg&diff=16670File:53 FileMenu.jpg2023-10-20T19:23:55Z<p>Redmaw: File uploaded with MsUpload</p>
<hr />
<div>File uploaded with MsUpload</div>Redmawhttps://planetside.co.uk/wiki/index.php?title=File:54_UndoMenu.jpg&diff=16669File:54 UndoMenu.jpg2023-10-20T19:20:33Z<p>Redmaw: File uploaded with MsUpload</p>
<hr />
<div>File uploaded with MsUpload</div>Redmawhttps://planetside.co.uk/wiki/index.php?title=Terragen_RPC:_Time_of_Day_Example_Script&diff=16668Terragen RPC: Time of Day Example Script2023-06-28T18:34:34Z<p>Redmaw: Test</p>
<hr />
<div>Terragen RPC: Time of Day Example Script<br />
<br />
== Overview ==<br />
Terragen 4.6.30 Professional introduces a new feature for remote procedure calling, or RPC. With a running instance of Terragen acting as a server, other programs can make remote procedure calls to query and modify a Terragen project. <br />
<br />
The goal of this tutorial is to walk you through a step-by-step procedure using the Python programming language to create a script that changes the time of day in a Terragen project; in other words, provide the ability to modify a Sunlight node’s heading and elevation parameters outside of the Terragen program.<br />
<br />
__TOC__<br />
<br />
<br />
== Part 01: Set up ==<br />
For this tutorial you’ll first need to download and install Terragen 4.6.30 Professional as well as [https://www.python.org/ Python]. Once you’ve installed the new build of Terragen be sure to launch it.<br />
<br />
On a Windows computer you can verify that Python is installed by querying the operating system via the Command Prompt window.<br />
<br />
Type “command prompt” or “cmd” in the Windows search box and press “enter” to open the Command Prompt window.<br />
Type “python” on the command line of the Command Prompt window, and press “enter”.<br />
If installed, the version of Python will be displayed.<br />
<br />
[[File:RPCTOD_03_CmdPromptPython.jpg|none|800px|Command prompt windows displaying the installed version of Python.]]<br />
<br />
<br />
Next, create a folder for this project that is accessible to your computer network. We’ll be saving and executing the python script from this location, as well as other files and resources.<br />
<br />
This project uses the Python script “sunpos.py” created by John Clark Craig to calculate the sun’s heading and elevation values based on the time of day and a given location. You’ll need to download the script and save it in your project folder.<br />
<br />
* Download the “[https://levelup.gitconnected.com/python-sun-position-for-solar-energy-and-research-7a4ead801777 sunpos.py]” script and save it in your project folder as sunpos.py. <br /n><br />
* https://levelup.gitconnected.com/python-sun-position-for-solar-energy-and-research-7a4ead801777<br />
<br />
<br />
== Part 02: Coding the basic functionality ==<br />
In this section our goal is to simply pass the minimum requirements needed by the sunpos.py in order for it to calculate the sun’s heading and elevation; then pass along these results to the default Sun node in Terragen. <br />
<br />
We’ll use Python’s Integrated Development and Learning Environment, or IDLE, to write the code for our script.<br />
<br />
Type “IDLE” in the Windows search box and press “enter” to open the IDLE Shell window.<br />
<br />
[[File:RPCTOD_04_IDLEShell.jpg|None| 800px|IDLE Shell.]]<br />
<br />
<br />
While we can type and execute python commands directly in the IDLE shell, we’ll use the built-in file editor to create our script file so we can run it multiple times as we refine it. <br />
<br />
Select “New File” from the IDLE shell menu to open the file editor window.<br />
<br />
[[File:RPCTOD_05_IDLEShell_NewFile.jpg|none|800px|Select New File to open the file editor.]]<br />
<br />
<br />
Right now our script is completely blank, and in order for it to do something we first need to instruct Python to load certain modules or libraries when the script is run. Not surprisingly, we need to tell Python to load the Terragen RPC library and the sunpos.py script we previously downloaded. Additionally, you might need to tell Python where the Terragen RPC module was installed, as well as have the ability to query the operating system in order to access the date and time.<br />
[[File:RPCTOD_06_IDLEEditor.jpg|none|800px|Editor window.]]<br />
<br />
<br />
We’ll use the “import” keyword to insert a module into our script at run time. For some modules we’ll also use the “as” keyword to provide the module with an alias that we reference within the script.<br />
<br />
Type the following lines of code into the file editor window. You can also copy and paste them. <span style="color:red">Note that your Terragen RPC installation path may vary.</span><br />
<br />
<br />
<ul><br />
import sys <br/n><br />
sys.path.insert(0,'P:/PlanetsideSoftware/RPC/terragen_rpc-0.9.0/terragen_rpc-0.9.0') <br/n><br />
import terragen_rpc as tg <br/n><br />
import sunpos as sp<br />
</ul><br />
<br />
<br />
In order for the sunpos module to determine the sun’s heading and elevation, it needs to know a date, time, and a location. Initially we can supply this information via Python’s datetime module, so we’ll load that as well, this time using the “from” keyword instead of the “import” keyword in order to load just the portion of the module we want.<br />
<br />
<br />
<ul><br />
from datetime import datetime<br />
</ul><br />
<br />
<br />
Our script should look like this:<br />
[[File:RPCTOD_07_IDLEEditor_ImportModules.jpg|none|800px|Import the modules into the script.]]<br />
<br />
<br />
With the library modules imported into the script, our next step is to define the values which the sunpos module requires to make its calculations. Variables are used to store information that can be referenced or manipulated within a computer program, such as text and numbers. In Python we can define the variable and its value in the same statement.<br />
<br />
Let’s create four variables to hold this information, one for the computer system’s date and time, one for the location on Earth utilizing latitude and longitude coordinates, one for a timezone, and one last variable to format the date, time and timezone information the way that the sunpos module expects it to be.<br />
<br />
For this example, we’ll use the timezone, latitude, and longitude for Los Angeles, CA, USA. Notice that the location variable and the lookupTime variable consist of multiple components. This is referred to as a list in Python, and is somewhat similar to an array in other programming languages.<br />
<br />
<br />
<ul><br />
sysTime = datetime.now() <br /n><br />
timezone = -7 <br /n><br />
location = [34.052235, -104.741667] <br /n><br />
lookupTime = [sysTime.year, sysTime.month, sysTime.day, sysTime.hour, sysTime.minute, sysTime.second, timezone] <br /n><br />
</ul><br />
<br />
<br />
Our script now looks like this:<br />
[[File:RPCTOD_08_IDLEEditor_DefineVariables.jpg|none|800px|Define the variables.]]<br />
<br />
<br />
Now that the variables have been defined that will be passed to the sunpos module, we can call the module and save its results. All of this can be done with one Python statement. We’ll define a new variable on the left of the equals sign called “results” to store the sun’s heading and elevation information which will be returned by the call to the sunpos module.<br />
<br />
The statement to the right of the equals sign is the call to the sunpos module. The “sp” is the alias we defined at the start of the script to reference the sunpos module, and the “sunpos()” is the function within the module that gets run and calculates the sun’s heading and elevation. All the data needed to perform the calculations are passed to the module by listing the variables between the two parenthesis <br />
<br />
<br />
<ul><br />
results = sp.sunpos(lookupTime, location, True)<br />
</ul><br />
<br />
<br />
[[File:RPCTOD_09_IDLEEditor_ScriptResults.jpg|None|800px|Define a variable for the results from the sunpos module.]]<br />
<br />
<br />
The last step is to apply the results to our Terragen project via RPC. We’ll do that by telling Terragen which item we want to act upon, the default Sun node, then setting the new values for its heading and elevation parameters.<br />
<br />
The code below creates an instance of the item “Sunlight 01” called “node” which can be manipulated by our script. The “set_param” function then changes the heading and elevation to the values in the results[] list.<br />
<br />
<br />
<ul><br />
node = tg.node_by_path("Sunlight 01")<br />
node.set_param('heading',results[0])<br />
node.set_param('elevation',results[1])<br />
</ul><br />
<br />
<br />
Our script now looks like this:<br />
[[File:RPCTOD_10_IDLEEditor_CallTGPRC.jpg|none|800px|Call the Terragen RPC module]]<br />
<br />
<br />
Save the script in the project folder by selecting “File Save As” from the IDLE main menu and giving the script a name. Then press “F5” or select “Run > Run Module” from the IDLE main menu to run the script. The 3D Preview window in Terragen will update to show the new direction of the sun.<br />
<br />
[[File:RPCTOD_51_3DPreview.jpg|none|800px|The Sunlight 01 node's heading and elevation changed by the script.]]<br />
<br />
<br />
== Part 03: Coding a basic GUI ==<br />
<br /n><br />
[[File:RPCTOD_11_GUI.jpg|none|452px|A very simple GUI in order to manipulate the parameter values easier.]]<br />
<br />
<br />
In the previous section we showed how a simple script can be created to manipulate the sun’s heading and position. In this section we’ll make the simple graphical interface, or GUI, seen below in order to manipulate the values of each variable much easier.<br />
<br />
To build the GUI for our script we’ll take advantage of the “tk interface” package, or “tkinter”, included in Python. Note that in some cases, tkinter’s syntax may appear slightly different than the coding in the section above, for example in defining a variable.<br />
<br />
Just as in the previous section, the first step is to import the tkinter package into our script. When creating a script that includes a GUI these are typically the first lines of code in the script. So we’ll prepend our existing script with these two lines of code.<br />
<br />
The first line of code imports all the functions and built-in modules in the tkinter package into the script, while the second line of code imports the ttk package which is responsible for the style of the widgets (buttons, etc.)<br />
<br />
<br />
<ul><br />
From tkinter import * <br /n><br />
From tkinter import ttk<br />
</ul><br />
<br />
<br />
[[File:RPCTOD_12_IDLEEditor_ImportTkinter.jpg|none|800px|Import the tkinter package into the script.]]<br />
<br />
<br />
Next we’ll build the window for the GUI, starting with its size and title. We’ll insert these lines of code after the import statements and before the variables. It’s also useful to add notes or comments in a script as well. The “#” symbol instructs Python to ignore anything in the line of code that follows it. We’ll make use of this symbol to set reminders and stay organized in our script. <br />
<br />
In Python scripts the basic window is often defined as “root”, but for this example we’ll use the word “gui”.<br />
<br />
<br />
In the “title” attribute we can define text to display in the windows title bar. The “geometry” attribute sets the width and height of the window in pixels.<br />
<br />
<br />
<ul><br />
gui = Tk() <br /n><br />
gui.title("RPC - Time of day") <br /n><br />
gui.geometry("300x250") <br /n><br />
</ul><br />
<br />
<br />
Whenever you make a GUI, you want to include a looping function for that window at the end of the script, which in effect causes the script to wait for user input. This will remain the last line of code in our script. Add the following line of code at the bottom of the script.<br />
<br />
<br />
<ul><br />
gui.mainloop()<br />
</ul><br />
<br />
<br />
Here's the script so far:<br />
[[File:RPCTOD_50_IDLEEditor_DefineWindowMainLoop.jpg|none|800px|Create the GUI window.]]<br />
<br />
<br />
In order to individually adjust each component that makes up the date and time information, we need to define a variable for each part. We’ll create these variables using tkinter functions, such as IntVar(). These functions accept many arguments such as a window name or a value. In our script, “gui” is the window name, and the value comes from the sysTime variable’s attributes we previously defined.<br />
<br />
Insert the lines of code below, following the sysTime variable statement.<br />
<br />
<br />
<ul><br />
year = IntVar(gui,value = sysTime.year) <br /n><br />
month = IntVar(gui,value = sysTime.month) <br /n><br />
day = IntVar(gui,value = sysTime.day) <br /n><br />
hour = IntVar(gui,value = sysTime.hour) <br /n><br />
minute = IntVar(gui,value = sysTime.minute) <br /n><br />
second = IntVar(gui,value = sysTime.second) <br /n><br />
</ul><br />
<br />
<br />
We need to rewrite the timezone variable definition in the tkinter syntax too.<br />
<br />
<br />
<ul><br />
timezone = IntVar(gui,value = -7) <br />
</ul><br />
<br />
<br />
Just as with the date and time information, we need to split the location data into latitude and longitude, which use decimal precision and therefore require a different tkinter function, DoubleVar(). As these variables will replace the existing location variable, we can comment out that line of code so that Python ignores it.<br />
<br />
The “#” at the beginning of the line of code defining the location variable, so Python ignores it.<br />
<br />
<br />
<ul><br />
<nowiki>#</nowiki> location = [34.052235, -104.741667] <br /n><br />
latitude = DoubleVar(gui,value = 34.052235) <br /n><br />
longitude = DoubleVar(gui,value = -104.741667) <br /n><br />
</ul><br />
<br />
<br />
[[File:RPCTOD_14_IDLEEditor_DefineVariables.jpg|none|800px|Defining the variables for each part of the time, date and location data.]]<br />
<br />
<br />
Now that we’ve accounted for all the individual variables we can add them to the GUI. There are several ways to do this in Python, and for this tutorial we’ll use the grid method, which will allow us to align each variable with a corresponding text label to describe it.<br />
<br />
Python refers to the GUI components, such as labels and buttons, as widgets. We’ll begin by creating a widget for each label. Each widget needs a unique name, so for the label widgets we’ll use “l_” followed by the name of the data it contains, for example “l_year”. The labels themselves will include information for the window it belongs on, the text to display, its position within the window, and how it is aligned. All the labels will belong to the “gui” window for now, and the text to be displayed is simply their name. The grid method uses rows and columns to position the label, and a “sticky” attribute to align the label.<br />
<br />
<br />
The last widget allows us to insert a blank line that spans both columns.<br />
<br />
<br />
<ul><br />
l_year = Label(gui,text = 'Year').grid(row=0,column=0,sticky='w') <br /n><br />
l_month = Label(gui,text = 'Month').grid(row=1, column=0,sticky='w') <br /n><br />
l_day = Label(gui,text = 'Day').grid(row=2, column=0,sticky='w') <br /n><br />
l_hour = Label(gui,text = 'Hour').grid(row=3, column=0,sticky='w') <br /n><br />
l_minute = Label(gui,text = 'Minute').grid(row=4, column=0,sticky='w') <br /n><br />
l_second = Label(gui,text = 'Second').grid(row=5, column=0,sticky='w') <br /n><br />
l_timezone = Label(gui,text = 'Timezone').grid(row=6, column=0,sticky='w') <br /n><br />
l_latitude = Label(gui,text='Latitude (N)').grid(row=7,column=0,sticky='w') <br /n><br />
l_longitude = Label(gui,text='Longitude (W)').grid(row=8,column=0,sticky='w') <br /n><br />
l_null = Label(gui,text=" ").grid(row=9,columnspan=2) <br /n><br />
</ul><br />
<br />
<br />
The code for the labels should look like this:<br />
<br />
[[File:RPCTOD_15_IDLEEditor_DefineLabels.jpg|none|800px|Defining the label widgets.]]<br />
<br />
<br />
While the Label widget is used to display text and can not be changed by the user, the Entry widget accepts a starting value and user input. We’ll set each Entry’s “textvariable” to the corresponding initial value we assigned to the variables. Later, the user can change these values by simply entering a different value. To describe the widget for each Entry, we’ll use the prefix “e_” and the name of the Entry, for example “e_year”.<br />
<br />
<br />
<ul><br />
e_year = Entry(gui, textvariable = year,width=6).grid(row=0,column=2, sticky='e') <br /n><br />
e_month = Entry(gui, textvariable = month,width=6).grid(row=1,column=2, sticky='e') <br /n><br />
e_day = Entry(gui, textvariable = day,width=6).grid(row=2,column=2, sticky='e') <br /n><br />
e_hour = Entry(gui, textvariable = hour,width=6).grid(row=3,column=2, sticky='e') <br /n><br />
e_minute = Entry(gui, textvariable = minute,width=6).grid(row=4,column=2, sticky='e') <br /n><br />
e_second = Entry(gui, textvariable = second,width=6).grid(row=5,column=2, sticky='e') <br /n><br />
e_timezone = Entry(gui, textvariable = timezone,width=6).grid(row=6, column = 2, sticky='e') <br /n><br />
e_latitude = Entry(gui, textvariable = latitude,width=16).grid(row=7, column =2) <br /n><br />
e_longitude = Entry(gui, textvariable = longitude,width=16).grid (row=8,column =2) <br /n><br />
</ul><br />
<br />
<br />
The code for the Entry section should look like this:<br />
[[File:RPCTOD_16_IDLEEditor_DefineEntry.jpg|none|800px|Defining the entry widgets.]]<br />
<br />
<br />
Now that we can change the initial values for each parameter, we need a way to tell the script when to calculate our changes. For this we’ll use a button widget. Like the other widgets, the button can contain several bits of information, such as the text to display on the button. Most importantly it contains a command attribute, telling it what to do when the button has been clicked. In this example, the script will run the function “whenWhere”, which is yet to be defined. <br />
<br />
<br />
<ul><br />
b_ApplySun = Button(gui,text='Apply to Sun',bg='yellow',command=whenWhere).grid(row=11,column=2)<br />
</ul><br />
<br />
<br />
This is the code for the button section of the script.<br />
[[File:RPCTOD_17_IDLEEditor_DefineButton.jpg|none|800px|Defining the button widget.]]<br />
<br />
<br />
To define the whenWhere() function, insert the code below into the script following the variables section and before the labels section. Note that it’s important to indent the lines of code after defining the function. <br />
<br />
Using the get() function the “lookupTime” variable is updated with the current date and time values and the “location” variable is updated with the current latitude and longitude values. Note, some of these variables will be defined immediately after this step.<br />
<br />
The next statement first passes three values to the sunpos module, then it accepts the returned data in the “results” variable as a list containing the heading and elevation.<br />
<br />
Then the function “setTerragenSunHeadingAndElevation” is called, passing along the text “Sunlight 01” and the results variable containing the sun heading and elevation.<br />
<br />
<br />
<ul><br />
def whenWhere(): <br /n><br />
: lookupTime = [year.get(), month.get(), day.get(), hour.get(), minute.get(), second.get(),timezone.get()] <br /n><br />
: location = [latitude.get(),longitude.get()] <br /n><br />
: results = sp.sunpos(lookupTime,location,True) <br /n><br />
: setTerragenSunHeadingAndElevation(“Sunlight 01”,results) <br /n><br />
</ul><br />
<br />
<br />
This is the code defining the whenWhere() function.<br />
[[File:RPCTOD_18_IDLEEditor_whenWhereFunction.jpg|none|800px|Create the lookupTime function.]]<br />
<br />
<br />
<br />
Since we’ve included the “lookupTime” variable within the whenWhere() function, and it is updated by the get() functions whenever the whenWhere() function is called, we no longer need it in the variables section. <br />
<br />
<span style="color:red">Delete the line of code in the variables section that defines the “lookupTime” variable.</span><br />
<br />
Also included in the whenWhere() function was the call to a function that performs the actual RPC commands and updates Terragen. The function will accept two arguments, one for the item to modify in Terragen which is the Sunlight 01 node, and the other, a list, containing the heading and elevation values. Let’s code that function directly beneath the previous function in the script. <br />
<br />
Once you’ve completed coding the function below you can <span style="color:red">remove the previous lines of code created in Part 02 of this tutorial</span> that did the same thing.<br />
<br />
This function includes error checking commands such as “try”, “except”, and “raise”. Checking for potential errors or exceptions allows the program to handle them before they cause a problem, like crashing the application. For example, if a user entered text data in a numeric field, and the program expected numeric data, then an error would occur and the application might crash. With error handling, the application could display a message indicating the wrong type of data was entered and giving the user an opportunity to change the data. You can read more about the error checking command in the Terragen RPC documentation. <br />
<br />
<br />
<ul><br />
def setTerragenSunHeadingAndElevation(name,values): <br /n><br />
: try: <br /n><br />
:: node = tg.node_by_path(name) <br /n><br />
:: try: <br /n><br />
::: node.set_param('heading',values[0]) <br /n><br />
::: node.set_param('elevation',values[1]) <br /n><br />
:: except AttributeError as err: <br /n><br />
::: showError("Sunlight not in project. Refresh list.") <br /n><br />
:: except ConnectionError as e: <br /n><br />
::: showError("Terragen RPC connection error: " + str(e)) <br /n><br />
:: except TimeoutError as e: <br /n><br />
::: showError("Terragen RPC timeout error: " + str(e)) <br /n><br />
:: except tg.ReplyError as e: <br /n><br />
::: showError("Terragen RPC server reply error: " + str(e)) <br /n><br />
:: except tg.ApiError: <br /n><br />
::: showError("Terragen RPC API error") <br /n><br />
::: raise <br />
</ul><br />
<br />
<br />
Here is the code for the setTerragenSunHeadingAndElevation function.<br />
[[File:RPCTOD_19_IDLEEditor_SetTGSunHeadElevFunction.jpg|none|800px|The setTerragenSunHeadingAndElevation function.]]<br />
<br />
<br />
The last step in this section is to create another function which displays any message passed to it by another part of the script. This function receives one input, the message to be displayed. <br />
<br />
<br />
<ul><br />
def showError(text): <br/n><br />
: errMsg.set(text)<br />
</ul><br />
<br />
<br />
This is the code for the showError() function.<br />
[[File:RPCTOD_20_IDLEEditor_showErrorFunction.jpg|none|800px|The showError function.]]<br />
<br />
<br />
We need to create one last variable to store any messages set by the error checking processes. We can define the new variable at the end of the variables section of the script.<br />
<br />
<br />
<ul><br />
errMsg = StringVar()<br />
</ul><br />
<br />
<br />
Here is the variable section of the script.<br />
[[File:RPCTOD_21_IDLEEditor_Variables.jpg|none|800px|The defined variables.]]<br />
<br />
<br />
Save and run the script. Try entering new values, especially for the hour of the day, and applying them to the Terragen sun.<br />
<br />
<br />
== Part 04: Modifying parameters more easily ==<br />
<br /n><br />
[[File:RPCTOD_23_IDLEEditor_GUI_Part04.jpg|none|452px|The GUI with sliders added.]]<br />
<br />
<br />
<br />
Now that we have a GUI and can modify the values for the time of day and location, let’s make it easier to make those modifications by adding sliders to the interface for the date and time parameters.<br />
<br />
A slider can be created with Tkinter’s Scale widget. We’ll use the prefix “s_” and the name of each parameter to define each slider. Scale widgets can also accept arguments, such as which window to be positioned in, a range of values to slide between, and the orientation of the slider. We’ll set an appropriate range for each slider, for example: 1 - 12, for the Month parameter, and orient the sliders horizontally so they best fit the layout of the GUI we’ve already created.<br />
<br />
Insert the following lines of code into the script, between the Labels section and the Entry section. <br />
<br />
<br />
<ul><br />
s_year = Scale(gui,from_= 1900, to = 2099,variable=year,orient=HORIZONTAL,showvalue=0).grid(row=0,column=1) <br /n><br />
s_month = Scale(gui,from_= 1, to = 12,variable=month,orient=HORIZONTAL,showvalue=0).grid(row=1,column=1) <br /n><br />
s_day = Scale(gui,from_= 1, to =31,variable=day,orient=HORIZONTAL,showvalue=0).grid(row=2,column=1) <br /n><br />
s_hour = Scale(gui,from_= 0, to = 23,variable=hour,orient=HORIZONTAL,showvalue=0).grid(row=3,column=1) <br /n><br />
s_minute = Scale(gui,from_= 0,to = 59,variable=minute,orient=HORIZONTAL,showvalue=0).grid(row=4,column=1) <br /n><br />
s_second = Scale(gui,from_= 0, to = 59,variable=second,orient=HORIZONTAL,showvalue=0).grid(row=5,column=1) <br /n><br />
s_timezone = Scale(gui,from_= -13, to = 13,variable=timezone,orient=HORIZONTAL,showvalue=0).grid(row=6,column=1) <br /n><br />
</ul><br />
<br />
<br />
Here’s the sliders section of the script:<br />
[[File:RPCTOD_24_IDLEEditor_Sliders.jpg|none|800px|Sliders section.]]<br />
<br />
<br />
You may have noticed in the lines of code above, that the slider’s grid position is located in column 1, and in the previous step the label’s grid position was located in column 0, while the entry’s grid position was located in column 2. This allows our slider to drop in between the label and the entry columns of the GUI. <br />
<br />
Visually, the GUI will look better if the latitude and longitude entry fields align with the sliders because of their width; and that the Apply button is centered within the GUI, so modify all their grid positions to column 1 as well.<br />
<br />
<br />
<ul><br />
e_latitude = Entry(gui, textvariable = latitude,width=16).grid(row=7, column =1) <br /n><br />
e_longitude = Entry(gui, textvariable = longitude,width=16).grid (row=8,column =1) <br /n><br />
b_ApplySun = Button(gui,text='Apply to Sun',bg='yellow',command=whenWhere).grid(row=11,column=1) <br /n><br />
</ul><br />
<br />
<br />
If you save the script and run it, you’ll notice how much easier it is to adjust the time and date parameters with the sliders, while still being able to enter a precise numerical value in the Entry field.<br />
<br />
<br />
== Part 05: Multiple sunlight nodes ==<br />
<br /n><br />
[[File:RPCTOD_27_IDLEEditor_GUI_Part05.jpg|none|800px|The final GUI for Part 05.]]<br />
<br />
<br />
<i>“But, what if there is more than one sunlight node in the Terragen project, or it’s named something else, or it’s missing entirely?” </i> Good questions, and in this section we’ll address them while expanding the functionality of the script and GUI.<br />
<br />
<br />
So far all of the GUI components have been located in the “gui” window. Tkinter allows you to divide the window into smaller frames. You can then tell each component which frame to be displayed in. <br />
<br />
To accommodate the features we’ll be adding to the script we can start by making the GUI larger. Edit the existing line of code that sets the width and height of the GUI as indicated below.<br />
<br />
<br />
<ul><br />
gui.geometry("900x600")<br />
</ul><br />
<br />
<br />
We define a frame using the LabelFrame widget. Just like the other widgets, it accepts options for which window to be located in, and what text to display, if any. For starters we’ll create two frames. The first frame will be used to select the available Sunlight nodes in the project, and the second frame will contain the parameters we’ve already coded. <br />
<br />
To define the frames, enter the following lines of code right below the existing code that defines the window.<br />
<br />
The first frame, frame0, will reside in the “gui” window and display the text “Select sunlight node / Click drop-down arrow to refresh list:”. It has 20 pixels of padding on its left and right, and 10 pixels of padding above and below it. No border will be drawn around the frame because “relief” set to FLAT.<br />
<br />
<br />
<ul><br />
frame0 = LabelFrame(gui,text="Select sunlight node / Click drop-down arrow to refresh list:",relief=FLAT, padx=20,pady=10)<br />
</ul><br />
<br />
<br />
The second frame, frame1, will aslo reside in the “gui” window. It will display the text “Enter time and location:”. It has 10 pixels of padding all around it and will display a border.<br />
<br />
<br />
<ul><br />
frame1 = LabelFrame(gui,text="Enter time and location: ",padx=10,pady=10)<br />
</ul><br />
<br />
<br />
Then skip a line and enter the following lines of code which define where the frames are positioned within the gui window.<br />
<br />
<br />
<ul><br />
frame0.grid(row=0,column=0,padx=5,pady=5) <br /><br />
frame1.grid(row=10,column=0,padx=25,pady=0,sticky='w')<br />
</ul><br />
<br />
<br />
The code should look like this:<br />
[[File:RPCTOD_25_IDLEEditor_DefineFrames.jpg|none|800px|Defining the frame.]]<br />
<br />
<br />
In order for the frames to have any effect, the display option for the labels, sliders, entry, and buttons needs to be changed from “gui” to the one of the frames. <span style="color:red">Edit the lines of code for each labels, sliders, entry, and button which are currently coded to “gui” to “frame1”. </span><br />
<br />
The code for the labels, sliders, entry and button now look like this:<br />
[[File:RPCTOD_26_IDLEEditor_LabelsSlidersEntryButtonFrames.jpg|none|800px|The labels, sliders, entry and buttons edited to reflect the new frame location.]]<br />
<br />
<br />
So far the design of our script has been to send certain information to Terragen in order to change something in the project, but now we want Terragen to provide some information about the project first. Specifically, we want to know what sunlight type nodes are in the project as soon as the script is run. Our GUI needs to reflect this information and provide us with the ability to select among the sunlight nodes.<br />
<br />
Coding the query to Terragen consists of two parts. Part one will be a way in which to store the sunlight node names and IDs returned from Terragen. Part two will be a function to make the request to Terragen for the sunlight node information.<br />
<br />
Enter the following line of code into the script, after the functions already defined and before the labels section.<br />
<br />
This creates the variable “sunNodes” which will store the returned information from the getSunlightInProject() function. <br />
<br />
<br />
<ul><br />
sunNodes = getSunlightInProject()<br />
</ul><br />
<br />
<br />
Next, create the new function getSunlightInProject() just below the last function currently in the script by entering the lines of code below. Note, that this function also includes the same kind of error checking as in Part 3; please see that section for more explanation on capturing errors and exceptions. This function initializes the variables “nodeIDs” and “nodeNames” so that they’re empty each time this function is run, then populates them with the current information from the Terragen project. <br />
<br />
<br />
<ul><br />
def getSunlightInProject(): <br /n><br />
: try: <br /n><br />
:: project = tg.root() <br /n><br />
:: nodeIDs = [] <br /n><br />
:: nodeNames = [] <br /n><br />
<!-- :: nodeIDs = tg.children_filtered_by_class(project,'sunlight') <br /n> --><br />
:: nodeIDs = project.children_filtered_by_class(‘sunlight’) <br /n><br />
:: for nodes in nodeIDs: <br /n><br />
<!-- ::: nodeNames.append(tg.name(nodes)) <br /n> --><br />
::: nodeNames.append(nodes.name()) <br /n><br />
:: return nodeIDs,nodeNames <br /n><br />
: except ConnectionError as e: <br /n><br />
:: showError("Terragen RPC connection error: " + str(e)) <br /n><br />
: except TimeoutError as e: <br /n><br />
:: showError("Terragen RPC timeout error: " + str(e)) <br /n><br />
: except tg.ReplyError as e: <br /n><br />
:: showError("Terragen RPC server reply error: " + str(e)) <br /n><br />
: except tg.ApiError: <br /n><br />
:: showError("Terragen RPC API error") <br /n><br />
:: raise<br />
</ul><br />
<br />
<br />
The code for the function call looks like this:<br />
<!-- [[File:RPCTOD_28_IDLEEditor_GetSunlightInProjectFunction.jpg|none|800px|The getSunlightInProject() function.]] --><br />
[[File:RPCTOD_52_IDLEEditor_GetSunlightInProjectFunction.jpg|none|800px|The getSunlightInProject() function.]]<br />
<br />
<br />
Now that we have the names of the sunlight nodes in the project, we can add them to our GUI. Currently, the labels, sliders, entries and Apply button reside in the “frame1” portion of the GUI, so we’ll place the names of the sunlight nodes in the “frame0” portion of the GUI and use a widget known as a Combobox to do so. As we’ve seen with other widgets, Comboboxes also accept options such as which part of the window to be displayed in. <br />
<br />
Enter the following lines of code right after the statement to call the getSunlightInProject() function and before the Labels section.<br />
<br />
The postcommand option is run each time a change is made to the Combobox. In this example, the updateCBList function is run, ensuring that the list of Sunlight nodes is always up to date. The textvariable “num” acts as a pointer to which item in the list is acted upon.<br />
<br />
<br />
<ul><br />
cb_suns = ttk.Combobox(frame0,textvariable=num,postcommand=updateCbList)<br />
</ul><br />
<br />
<br />
Here's the code for the Combobox:<br />
[[File:RPCTOD_29_IDLEEditor_ComboBox.jpg|none|800px|Combobox.]]<br />
<br />
<br />
Next we need to define the new variable and function that we referenced in the above line of code.<br />
<br />
In the variables section of our script, add the following to define the pointer variable “num”. By using tkinter’s StringVar() function we can later retrieve and store new values to the variable with the get() and set() functions.<br />
<br />
<br />
<ul><br />
num = StringVar()<br />
</ul><br />
<br />
<br />
Here is the variables section of the script after adding the num variable:<br />
[[File:RPCTOD_30_IDLEEditor_VariablesNum.jpg|none|800px|Add the num variable in the variables section.]]<br />
<br />
<br />
Following the last function we defined in our script, add the following lines of code. Note there is error checking in this function because it’s possible that a sunlight node is no longer in the Terragen project or the names have been changed since the last time this query was made.<br />
<br />
<br />
<ul><br />
def updateCbList(): <br /n><br />
: updateSuns = getSunlightInProject() <br /n><br />
: try: <br /n><br />
:: updateSunID.set(updateSuns[0]) <br /n><br />
:: updateSunNames.set(updateSuns[1]) <br /n><br />
:: cb_suns["values"] = updateSuns[1] <br /n><br />
:: showError(" ") <br /n><br />
: except TypeError as e: <br /n><br />
:: showError("Sunlight nodes not found. " + str(e))<br />
</ul><br />
<br />
<br />
The code for this function looks like this:<br />
[[File:RPCTOD_31_IDLEEditor_updateCbListFunction.jpg|none|800px|The updateCbList() function.]]<br />
<br />
<br />
We need to create the two variables we just reference in the above function so they can store the sunlight node ID numbers and names, and get updated whenever the updateCbList() function is called. At the end of the variables section in the script add these lines of code:<br />
<br />
<br />
<ul><br />
updateSunID = StringVar() <br /n><br />
updateSunNames = StringVar()”<br />
</ul><br />
<br />
<br />
The code for the variable section now looks like this:<br />
[[File:RPCTOD_32_IDLEEditor_Variables.jpg|none|800px|The variables section.]]<br />
<br />
<br />
Return to the script and directly beneath the line of code that creates the Combobox add these lines of code, in order to display the updated list of Sunlight nodes and to catch any errors or exceptions. <br />
<br />
<br />
<ul><br />
Try: <br /n><br />
: cb_suns["values"]=r[1]<br />
: cb_suns.current(0)<br />
except TypeError as e:<br />
: showError("No sunlight nodes in project or Terragen not running." + str(e))<br />
</ul><br />
<br />
<br />
To position the Combobox within frame0 add this line of code.<br />
<br />
<br />
<ul><br />
cb_suns.grid(row=0,column=0)<br />
</ul><br />
<br />
<br />
When dealing with multiple sunlight nodes in the project, it would be helpful if there was a way within the script to enable or disable each sunlight node. Since we’ve already created a Combobox to select a sunlight node, we can easily add a bit of code to enable or disable the selected node. We’ll use a button widget for this.<br />
<br />
Just after the Combobox code and before the Labels code, add this line of code to create the button to create a button to the right of the Combobox position that will execute the ckboxSun command when clicked.<br />
<br />
<br />
<ul><br />
b_SunOnOff = Button(frame0,text="Toggle sun on/off",bg='pink',command=ckboxSun).grid(row=0,column=1,padx=10)<br />
</ul><br />
<br />
<br />
The code for the Combobox should look like this:<br />
[[File:RPCTOD_33_IDLEEditor_MainAndComboBox.jpg|none|800px|The Combobox and button.]]<br />
<br />
<br />
Now we’ll create the ckboxSun() function that gets called when the button is clicked. Just below the lines of code for the last function we wrote add the following to call the toggleTerragenSun() function, passing it the index number for the currently selected item in the Combobox.<br />
<br />
<br />
<ul><br />
def ckboxSun(): <br /n><br />
: toggleTerragenSun(num.get())<br />
</ul><br />
<br />
<br />
You might wonder why we call one function when the button gets clicked and then another function. The reason is to keep the functions themselves as simple as possible. That way they can be used by other functions within the script. Let’s create the toggleTerragenSun() function so we can turn the sun on and off. Just below the last function type the following lines of code, which also contain some error checking.<br />
<br />
<br />
<ul><br />
def toggleTerragenSun(name): <br /n><br />
: try: <br /n><br />
:: node = tg.node_by_path(name) <br /n><br />
:: try: <br /n><br />
::: x = node.get_param_as_int('enable') <br /n><br />
:: except AttributeError as err: <br /n><br />
::: showError("Sunlight not in project. Refresh list.") <br /n><br />
:: else: <br /n><br />
::: if x == 1: <br /n><br />
:::: node.set_param('enable',0) <br /n><br />
::: else: <br /n><br />
:::: node.set_param('enable',1) <br /n><br />
: except ConnectionError as e: <br /n><br />
:: showError("Terragen RPC connection error: " + str(e)) <br /n><br />
: except TimeoutError as e: <br /n><br />
:: showError("Terragen RPC timeout error: " + str(e)) <br /n><br />
: except tg.ReplyError as e: <br /n><br />
:: showError("Terragen RPC server reply error: " + str(e)) <br /n><br />
: except tg.ApiError: <br /n><br />
:: showError("Terragen RPC API error") <br /n><br />
:: raise <br />
</ul><br />
<br />
<br />
Here is the code for the above functions.<br />
[[File:RPCTOD_34_IDLEEditor_ckBoxSunAndToggleTGSunFunctions.jpg|none|800px|Code for the ckboxSun() function and toggleTerragenSun() function.]]<br />
<br />
<br />
Save the script and run it. In Terragen, duplicate the Sunlight object a few times, then click in the Combobox and enable or disable the lights, and change their heading and elevation.<br />
<br />
<br />
== Part 06: Presets ==<br />
<br /n><br />
[[File:RPCTOD_43_GUI_Part06.jpg|none|800px|GUI for Part 06.]]<br />
<br />
<br />
Let’s add one more section to our GUI which contains presets for selected locations and timezones around the world. This way we can jump from the Los Angeles timezone to any other timezone around the world with a click of a button. <br />
<br />
[[File:RPCTOD_44_GUI_PresetsDropDownList.jpg|none|417px|Presets drop down list.]]<br />
<br />
<br />
We’ll create an external text file to hold the data so that it can be easily customized in any text editor, such as Notepad, and save the file in the CSV format which stands for “comma separated values”. As the format name implies each piece of data is separated from the next by a comma. Each row in the file should be formatted as follows: Timezone City/Country , longitude, latitude, timezone offset<br />
<br />
Copy and paste the following data into a text file and save it as presets_latLong.csv. As you can see from the “Timezone City/Country” data the entries are organized from timezone -11 to timezone +13. This is the order that the data will show up in the GUI.<br />
<br />
<ul><br />
-11 Liku/Niue, -169.792327,-19.053680, -11.0 <br /n><br />
-10 Honolulu/Hawaii, -157.854995, 21.306647, -10.0 <br /n><br />
-9 Adak/Adak Island, -176.660156, 51.862923, -9.0 <br /n><br />
-8 Anchorage/Alaska, -149.721679, 61.227957, -8.0 <br /n><br />
-7 Los Angeles/USA, -104.741667, 34.052235, -7.0 <br /n><br />
-7 Vancouver/Canada, -123.108673, 49.263323,-7.0 <br /n><br />
-6 Calgary/Canada, -114.071044, 51.058660, -6.0 <br /n><br />
-5 Winnipeg/Canada, -97.141113, 49.866316, -5.0 <br /n><br />
-4 Igloolik/Igloolik Island, -81.806259, 69.379587, -4.0 <br /n><br />
-4 Ontario/Canada, -84.887696, 49.315573, -4.0 <br /n><br />
-3 Salvador/Brazil, -38.480987, -12.973780, -3.0 <br /n><br />
-3 Goose Bay/Newfoundland, -60.342407, 53.304621,-3.0 <br /n><br />
-2 Nuuk/Greenland, -51.723632, 64.182464, -2.0 <br /n><br />
-1 Sao Filipe/Fogo, -24.483718, 14.890709,-1.0 <br /n><br />
0 Reykjavik/Iceland, -21.928710,64.144161, 0.0 <br /n><br />
+1 Dublin/Ireland, -6.249847, 53.350551, 1.0 <br /n><br />
+1 Greenwhich/UK, -0.193634, 51.489293, 1.0 <br /n><br />
+2 Brussels/Belgium, 4.390869, 50.824444, 2.0 <br /n><br />
+2 Berlin/Germany, 13.405151, 52.511763, 2.0 <br /n><br />
+2 Krakow/Poland, 19.929199, 50.050084, 2.0 <br /n><br />
+3 Odesa/Ukraine, 30.739746, 46.468132, 3.0 <br /n><br />
+3 Novgorodd/Russia, 43.978271, 56.307396, 3.0 <br /n><br />
+4 Baku/Azerbaijan, 49.844970, 40.390488, 4.0 <br /n><br />
+4 Dubai/UAE, 55.341795, 25.138654, 4.0 <br /n><br />
+5 Islamabad/Pakistan, 73.044433, 33.706061, 5.0 <br /n><br />
+6 Jahore/Bangladesh, 89.229125, 23.179080, 6.0 <br /n><br />
+7 Bangkok/Thailand, 100.546875, 13.496472, 7.0 <br /n><br />
+8 Beijing/China, 116.389160, 39.884450, 8.0 <br /n><br />
+8 Manilla/Philippines, 120.878899, 14.491408, 8.0 <br /n><br />
+9 Sapporo/Japan, 141.344604, 43.056847, 9.0 <br /n><br />
+10 Andersen AFB/Guam, 144.924087, 13.580586, 10.0 <br /n><br />
+11 Magadan/Russia, 150.805206, 59.568418, 11.0 <br /n><br />
+12 Queenstown/New Zealand, 168.618640, -45.104546, 12.0 <br /n><br />
+12 McMurdo Station/Antartica, 166.690063, -77.84011, 12.0 <br /n><br />
+13 Nomuka/Tonga, -174.802093, -20.251890, 13.0 <br /n><br />
</ul><br />
<br />
<br />
With the presets file saved, let’s load the data into our script. Immediately following the line of code that retrieves the Sunlight nodes in the Terragen project add the line of code:<br />
<br />
<br />
<ul><br />
presets()<br />
</ul><br />
<br />
<br />
[[File:RPCTOD_35_IDLEEditor_Presets.jpg|none|800px|The statement that calls the presets() function.]]<br />
<br />
<br />
Then create the new presets() function with the following lines of code, which will read the contents of the text file in “read only” mode, one line at a time. Each line is split into data each time the comma separator is encountered and these bits of data are appended to new variables for the Timezone City/Country, longitude, latitude and timezone offset.<br />
<br />
<br />
<ul><br />
def presets(): <br /n><br />
: file = open('presets_latlong.csv',"r") <br /n><br />
: content = file.readlines() <br /n><br />
: for line in content: <br /n><br />
:: x,y,z,a = line.strip().split(',') <br /n><br />
:: plocation.append(x) <br /n><br />
:: plat.append(z) <br /n><br />
:: plon.append(y) <br /n><br />
:: ptz.append(a) <br /n><br />
: file.close()<br />
</ul><br />
<br />
<br />
The code for the presets() function looks like this:<br />
[[File:RPCTOD_36_IDLEEditor_PresetsFunction.jpg|none|800px|The presets function.]]<br />
<br />
<br />
We referenced four new variables in the presets() function above, which store the data from the presets text file. Since these variables are created when the script is first run and their values do not change during the execution of the script, we can define them using standard Python variables, and not tkinter variables such as with the StringVar() function. Add these variables to the variables section of the script.<br />
<br />
<br />
<ul><br />
plocation = [] <br/n><br />
plat = [] <br/n><br />
plon = [] <br/n><br />
ptz = []<br />
</ul><br />
<br />
<br />
Here's the updated variables section in our script:<br />
[[File:RPCTOD_37_IDLEEditor_Variables.jpg|none|800px|The variables section of the script.]]<br />
<br />
<br />
We’ll add another frame to our main window and place the GUI components to display the presets there. We’ll need to add another Combobox to display the preset choices, labels for descriptions, and a button in order to update the variables for the timezone, latitude and longitude. Following the last line of code in the script add the lines below:<br />
<br />
<br />
<ul><br />
cb_presets = ttk.Combobox(frame3,textvariable=pnum) <br /n><br />
cb_presets["values"]=plocation <br /n><br />
cb_presets.current(16) <br /n><br />
cb_presets.grid(row=0,column=1,sticky='w') <br /n><br />
<br /n><br />
l_presetlocation = Label(frame3,text="Location: ").grid(row=0,column=0,sticky='w') <br /n><br />
l_presetLat = Label(frame3,text="Latitude:").grid(row=1,column=0,sticky='w') <br /n><br />
l_presetLat = Label(frame3,text="Longitude:").grid(row=2,column=0,sticky='w') <br /n><br />
l_presetTz = Label(frame3,text="Timezone:").grid(row=3,column=0,sticky='w') <br /n><br />
l_presetDispLat = Label(frame3,textvariable=displayLat).grid(row=1,column=1,sticky='w') <br /n><br />
l_presetDispLon = Label(frame3,textvariable=displayLon).grid(row=2,column=1,sticky='w') <br /n><br />
l_presetDispTz = Label (frame3, textvariable=displayTz).grid(row=3,column=1,sticky='w') <br /n><br />
<br /n><br />
b1 = Button(frame3,text="Apply Preset",command=setLatLon).grid(row=5,column=1,pady=5,sticky='w') <br /n><br />
</ul><br />
<br />
<br />
Here is the code for the frame3 components:<br />
[[File:RPCTOD_38_IDLEEditor_ComboboxPresets.jpg|none|800px|The Combobox for the Presets.]]<br />
<br />
<br />
We referenced a new frame and variable in the above lines of code to define the Combobox, so let’s add both those now.<br />
<br />
Append the following line of code to the section of our script where we’ve defined the window title, size and frames. Note, we will define frame2 in the last section of this tutorial.<br />
<br />
<br />
<ul><br />
frame3 = LabelFrame(gui,text="Location presets: ",padx=10,pady=10) <br /n><br />
frame3.grid(row=15,column=0,padx=25,pady=10,sticky='w')<br />
</ul><br />
<br />
<br />
Here's the updated lines of code for the window section:<br />
[[File:RPCTOD_39_IDLEEditor_Frame3Window.jpg|none|800px|Defining frame3 in the GUI window.]]<br />
<br />
<br />
Then add the following line of code the the variables section of our script to account for the new variable, which identifies the current item in the preset list.<br />
<br />
<br />
<ul><br />
pnum=StringVar()<br />
</ul><br />
<br />
<br />
Here's the updated variables section:<br />
[[File:RPCTOD_40_IDLEEditor_Variables.jpg|none|800px|Variables section.]]<br />
<br />
<br />
The last step to our preset code is to define the following function which will update and display the latitude, longitude and timezone variables to those of the selected preset.<br />
<br />
<br />
<ul><br />
def setLatLon(): <br /n><br />
: v = cb_presets.current() <br /n><br />
: displayLat.set(plat[v]) <br /n><br />
: displayLon.set(plon[v]) <br /n><br />
: displayTz.set(ptz[v]) <br /n><br />
: latitude.set(plat[v]) <br /n><br />
: longitude.set(plon[v])<br /n><br />
: timezone.set(ptz[v])<br />
</ul><br />
<br />
<br />
Here's the code for the setLatLon() function:<br />
[[File:RPCTOD_41_IDLEEditor_SetLatLongFunction.jpg|none|800px|The setLatLon() function.]]<br />
<br />
<br />
Three new variables were referenced in the setLatLon() function above, so we need to add them to the variable section, using the tkinter format so they can be updated throughout the script with the get() and set() functions. Add these lines of code following the last entry in the variable section of the script.<br />
<br />
<br />
<ul><br />
displayLat = StringVar() <br /n><br />
displayLon = StringVar() <br /n><br />
displayTz = StringVar()<br />
</ul><br />
<br />
<br />
Here's the updated variables section of our script:<br />
[[File:RPCTOD_42_IDLEEditor_Variables.jpg|none|800px|Variables section.]]<br />
<br />
<br />
== Part 07 - Displaying previous values, errors & exceptions ==<br />
<br /n><br />
[[File:RPCTOD_49_GUI_Part07.jpg|none|800px|GUI for Part 07.]]<br />
<br />
<br />
Several of the functions throughout this script have accounted for errors and exceptions, but up to now we haven’t displayed those messages. In addition, as we modify the Sunlight nodes in Terragen, it would be useful if we could see those values displayed in the GUI. We’ll use frame2, the empty right portion of our window to display any of these kinds of information.<br />
<br />
<br />
We can begin by defining the frame, which will consist of a border with a description and labels. In the windows section of the script insert the following line of code between frame1 and frame3.<br />
<br />
<br />
<ul><br />
frame2 = LabelFrame(gui,text="Last values plotted were: ",padx=10,pady=10) <br /n><br />
frame2.grid(row=10,column=1,padx=0,pady=0,sticky='nw')<br />
</ul><br />
<br />
<br />
The code for the GUI window now looks like this:<br />
[[File:RPCTOD_45_GUI_Window.jpg|none|800px|Defining the frame2 area within the main window.]]<br />
<br />
<br />
Since the display of this information is mostly in the form of Labels, let’s add the following code at the end of the Labels section and before the Sliders section of our script to display the last Sunlight node that was modified via our script, and the values used to do so.<br />
<br />
<br />
<ul><br />
l_lastSun = Label(frame2,text='Node: ').grid(row=12,column=0,sticky='w') <br /n><br />
l_lastSunName = Label(frame2,textvariable=displayLastSun).grid(row=12,column=1,sticky='w') <br /n><br />
l_dateTime = Label(frame2,text='Date & time: ').grid(row=13, column=0,sticky='w') <br /n><br />
l_dateTimeData = Label(frame2,textvariable=displayTimeData).grid(row=13, column=1,sticky='w') <br /n><br />
l_location = Label(frame2,text='Location: ').grid(row=14,column=0,sticky='w') <br /n><br />
l_locationData = Label(frame2,textvariable=displayLocation).grid(row=14,column=1,sticky='w') <br /n><br />
l_azimuth = Label(frame2,text='Azimuth:').grid(row=15,column=0,sticky='w') <br /n><br />
l_azimuthData = Label (frame2,textvariable=azimuth).grid(row=15,column=1,sticky='w') <br /n><br />
l_elevation = Label(frame2,text='Elevation:').grid(row=16,column=0,sticky='w') <br /n><br />
l_elevationData = Label(frame2,textvariable=elevation).grid(row=16,column=1,sticky='w') <br /n><br />
l_errMsg = Label(frame2,textvariable=errMsg,fg='red').grid(row=17,columnspan=2,sticky='w') <br /n><br />
</ul><br />
<br />
<br />
The labels section of the script, with frame2 components added.<br />
[[File:RPCTOD_46_IDLEEditor_Labels.jpg|none|800px|Labels section of the script.]]<br />
<br />
<br />
Now we’ll add the new variables we referenced in the lines of code above, so they can be updated by different functions in the script with the get() and set() functions. Append the following lines of code to the Variables section of the script.<br />
<br />
<br />
<ul><br />
displayLastSun = StringVar() <br /n><br />
displayTimeData=StringVar() <br /n><br />
displayLocation=StringVar() <br /n><br />
azimuth = StringVar() <br /n><br />
elevation = StringVar()<br />
</ul><br />
<br />
<br />
The updated variables section of the script:<br />
[[File:RPCTOD_47_IDLEEditor_Variables.jpg|none|800px|Variables section of the script.]]<br />
<br />
<br />
The last step is to actually display the information, whenever the “Apply to Sun” button is clicked. To do this we need to update some of the whenWhere() function we’ve already created. Insert the following lines of code into that function, taking care to position them in the script as indicated in the screen shot.<br />
<br />
<br />
<ul><br />
displayTimeData.set(lookupTime) <br /n><br />
displayLocation.set(location) <br /n><br />
azimuth.set(results[0]) <br /n><br />
elevation.set(results[1]) <br /n><br />
displayLastSun.set(num.get())<br />
</ul><br />
<br />
<br />
The updated whenWhere() function now looks like this:<br />
[[File:RPCTOD_48_IDLEEditor_whenWhereFunction.jpg|none|800px|The updated whenWhere() function will now display messages.]]<br />
<br />
<br />
<br />
== In Conclusion ==<br />
<br />
This tutorial is meant to show you the possibilities of Terragen’s new RPC feature. How might you further modify this script to add even more functionality to it? <br />
* How about adding the ability to modify the camera’s exposure when the Sunlight node is below the horizon? What if more than one camera existed in the project? <br />
* How about a button that resets the sun’s heading and elevation to their default settings? <br />
* How would you add your location and timezone to the presets?<br />
* How would you turn off all the sunlight nodes at once? Back on again?<br />
<br />
Special thanks to John Clark Craig for making his Python script available to the public. <br />
<br />
<!-- The Terragen RPC Time of Day example script may be downloaded from Planetside Software [https://www.dropbox.com/s/x7gzd06mkqe8eoi/TerragenRPC_TimeOfDayExample.zip?dl=0 here]. --><br />
The Terragen RPC Time of Day example script may be downloaded from Planetside Software [https://www.dropbox.com/s/takc4hrcp59snum/TerragenRPC_TimeOfDayExample_v2.zip?dl=0 here].<br />
<br />
<br /n></div>Redmawhttps://planetside.co.uk/wiki/index.php?title=Terragen_RPC:_Time_of_Day_Example_Script&diff=16667Terragen RPC: Time of Day Example Script2023-06-28T18:32:30Z<p>Redmaw: Test open external link in new window.</p>
<hr />
<div>Terragen RPC: Time of Day Example Script<br />
<br />
== Overview ==<br />
Terragen 4.6.30 Professional introduces a new feature for remote procedure calling, or RPC. With a running instance of Terragen acting as a server, other programs can make remote procedure calls to query and modify a Terragen project. <br />
<br />
The goal of this tutorial is to walk you through a step-by-step procedure using the Python programming language to create a script that changes the time of day in a Terragen project; in other words, provide the ability to modify a Sunlight node’s heading and elevation parameters outside of the Terragen program.<br />
<br />
__TOC__<br />
<br />
<br />
== Part 01: Set up ==<br />
For this tutorial you’ll first need to download and install Terragen 4.6.30 Professional as well as [https://www.python.org/ Python]{{#tag:nowiki|<!-- -->}}<nowiki> | </nowiki>'''target="_blank"'''. Once you’ve installed the new build of Terragen be sure to launch it.<br />
<br />
On a Windows computer you can verify that Python is installed by querying the operating system via the Command Prompt window.<br />
<br />
Type “command prompt” or “cmd” in the Windows search box and press “enter” to open the Command Prompt window.<br />
Type “python” on the command line of the Command Prompt window, and press “enter”.<br />
If installed, the version of Python will be displayed.<br />
<br />
[[File:RPCTOD_03_CmdPromptPython.jpg|none|800px|Command prompt windows displaying the installed version of Python.]]<br />
<br />
<br />
Next, create a folder for this project that is accessible to your computer network. We’ll be saving and executing the python script from this location, as well as other files and resources.<br />
<br />
This project uses the Python script “sunpos.py” created by John Clark Craig to calculate the sun’s heading and elevation values based on the time of day and a given location. You’ll need to download the script and save it in your project folder.<br />
<br />
* Download the “[https://levelup.gitconnected.com/python-sun-position-for-solar-energy-and-research-7a4ead801777 sunpos.py]” script and save it in your project folder as sunpos.py. <br /n><br />
* https://levelup.gitconnected.com/python-sun-position-for-solar-energy-and-research-7a4ead801777<br />
<br />
<br />
== Part 02: Coding the basic functionality ==<br />
In this section our goal is to simply pass the minimum requirements needed by the sunpos.py in order for it to calculate the sun’s heading and elevation; then pass along these results to the default Sun node in Terragen. <br />
<br />
We’ll use Python’s Integrated Development and Learning Environment, or IDLE, to write the code for our script.<br />
<br />
Type “IDLE” in the Windows search box and press “enter” to open the IDLE Shell window.<br />
<br />
[[File:RPCTOD_04_IDLEShell.jpg|None| 800px|IDLE Shell.]]<br />
<br />
<br />
While we can type and execute python commands directly in the IDLE shell, we’ll use the built-in file editor to create our script file so we can run it multiple times as we refine it. <br />
<br />
Select “New File” from the IDLE shell menu to open the file editor window.<br />
<br />
[[File:RPCTOD_05_IDLEShell_NewFile.jpg|none|800px|Select New File to open the file editor.]]<br />
<br />
<br />
Right now our script is completely blank, and in order for it to do something we first need to instruct Python to load certain modules or libraries when the script is run. Not surprisingly, we need to tell Python to load the Terragen RPC library and the sunpos.py script we previously downloaded. Additionally, you might need to tell Python where the Terragen RPC module was installed, as well as have the ability to query the operating system in order to access the date and time.<br />
[[File:RPCTOD_06_IDLEEditor.jpg|none|800px|Editor window.]]<br />
<br />
<br />
We’ll use the “import” keyword to insert a module into our script at run time. For some modules we’ll also use the “as” keyword to provide the module with an alias that we reference within the script.<br />
<br />
Type the following lines of code into the file editor window. You can also copy and paste them. <span style="color:red">Note that your Terragen RPC installation path may vary.</span><br />
<br />
<br />
<ul><br />
import sys <br/n><br />
sys.path.insert(0,'P:/PlanetsideSoftware/RPC/terragen_rpc-0.9.0/terragen_rpc-0.9.0') <br/n><br />
import terragen_rpc as tg <br/n><br />
import sunpos as sp<br />
</ul><br />
<br />
<br />
In order for the sunpos module to determine the sun’s heading and elevation, it needs to know a date, time, and a location. Initially we can supply this information via Python’s datetime module, so we’ll load that as well, this time using the “from” keyword instead of the “import” keyword in order to load just the portion of the module we want.<br />
<br />
<br />
<ul><br />
from datetime import datetime<br />
</ul><br />
<br />
<br />
Our script should look like this:<br />
[[File:RPCTOD_07_IDLEEditor_ImportModules.jpg|none|800px|Import the modules into the script.]]<br />
<br />
<br />
With the library modules imported into the script, our next step is to define the values which the sunpos module requires to make its calculations. Variables are used to store information that can be referenced or manipulated within a computer program, such as text and numbers. In Python we can define the variable and its value in the same statement.<br />
<br />
Let’s create four variables to hold this information, one for the computer system’s date and time, one for the location on Earth utilizing latitude and longitude coordinates, one for a timezone, and one last variable to format the date, time and timezone information the way that the sunpos module expects it to be.<br />
<br />
For this example, we’ll use the timezone, latitude, and longitude for Los Angeles, CA, USA. Notice that the location variable and the lookupTime variable consist of multiple components. This is referred to as a list in Python, and is somewhat similar to an array in other programming languages.<br />
<br />
<br />
<ul><br />
sysTime = datetime.now() <br /n><br />
timezone = -7 <br /n><br />
location = [34.052235, -104.741667] <br /n><br />
lookupTime = [sysTime.year, sysTime.month, sysTime.day, sysTime.hour, sysTime.minute, sysTime.second, timezone] <br /n><br />
</ul><br />
<br />
<br />
Our script now looks like this:<br />
[[File:RPCTOD_08_IDLEEditor_DefineVariables.jpg|none|800px|Define the variables.]]<br />
<br />
<br />
Now that the variables have been defined that will be passed to the sunpos module, we can call the module and save its results. All of this can be done with one Python statement. We’ll define a new variable on the left of the equals sign called “results” to store the sun’s heading and elevation information which will be returned by the call to the sunpos module.<br />
<br />
The statement to the right of the equals sign is the call to the sunpos module. The “sp” is the alias we defined at the start of the script to reference the sunpos module, and the “sunpos()” is the function within the module that gets run and calculates the sun’s heading and elevation. All the data needed to perform the calculations are passed to the module by listing the variables between the two parenthesis <br />
<br />
<br />
<ul><br />
results = sp.sunpos(lookupTime, location, True)<br />
</ul><br />
<br />
<br />
[[File:RPCTOD_09_IDLEEditor_ScriptResults.jpg|None|800px|Define a variable for the results from the sunpos module.]]<br />
<br />
<br />
The last step is to apply the results to our Terragen project via RPC. We’ll do that by telling Terragen which item we want to act upon, the default Sun node, then setting the new values for its heading and elevation parameters.<br />
<br />
The code below creates an instance of the item “Sunlight 01” called “node” which can be manipulated by our script. The “set_param” function then changes the heading and elevation to the values in the results[] list.<br />
<br />
<br />
<ul><br />
node = tg.node_by_path("Sunlight 01")<br />
node.set_param('heading',results[0])<br />
node.set_param('elevation',results[1])<br />
</ul><br />
<br />
<br />
Our script now looks like this:<br />
[[File:RPCTOD_10_IDLEEditor_CallTGPRC.jpg|none|800px|Call the Terragen RPC module]]<br />
<br />
<br />
Save the script in the project folder by selecting “File Save As” from the IDLE main menu and giving the script a name. Then press “F5” or select “Run > Run Module” from the IDLE main menu to run the script. The 3D Preview window in Terragen will update to show the new direction of the sun.<br />
<br />
[[File:RPCTOD_51_3DPreview.jpg|none|800px|The Sunlight 01 node's heading and elevation changed by the script.]]<br />
<br />
<br />
== Part 03: Coding a basic GUI ==<br />
<br /n><br />
[[File:RPCTOD_11_GUI.jpg|none|452px|A very simple GUI in order to manipulate the parameter values easier.]]<br />
<br />
<br />
In the previous section we showed how a simple script can be created to manipulate the sun’s heading and position. In this section we’ll make the simple graphical interface, or GUI, seen below in order to manipulate the values of each variable much easier.<br />
<br />
To build the GUI for our script we’ll take advantage of the “tk interface” package, or “tkinter”, included in Python. Note that in some cases, tkinter’s syntax may appear slightly different than the coding in the section above, for example in defining a variable.<br />
<br />
Just as in the previous section, the first step is to import the tkinter package into our script. When creating a script that includes a GUI these are typically the first lines of code in the script. So we’ll prepend our existing script with these two lines of code.<br />
<br />
The first line of code imports all the functions and built-in modules in the tkinter package into the script, while the second line of code imports the ttk package which is responsible for the style of the widgets (buttons, etc.)<br />
<br />
<br />
<ul><br />
From tkinter import * <br /n><br />
From tkinter import ttk<br />
</ul><br />
<br />
<br />
[[File:RPCTOD_12_IDLEEditor_ImportTkinter.jpg|none|800px|Import the tkinter package into the script.]]<br />
<br />
<br />
Next we’ll build the window for the GUI, starting with its size and title. We’ll insert these lines of code after the import statements and before the variables. It’s also useful to add notes or comments in a script as well. The “#” symbol instructs Python to ignore anything in the line of code that follows it. We’ll make use of this symbol to set reminders and stay organized in our script. <br />
<br />
In Python scripts the basic window is often defined as “root”, but for this example we’ll use the word “gui”.<br />
<br />
<br />
In the “title” attribute we can define text to display in the windows title bar. The “geometry” attribute sets the width and height of the window in pixels.<br />
<br />
<br />
<ul><br />
gui = Tk() <br /n><br />
gui.title("RPC - Time of day") <br /n><br />
gui.geometry("300x250") <br /n><br />
</ul><br />
<br />
<br />
Whenever you make a GUI, you want to include a looping function for that window at the end of the script, which in effect causes the script to wait for user input. This will remain the last line of code in our script. Add the following line of code at the bottom of the script.<br />
<br />
<br />
<ul><br />
gui.mainloop()<br />
</ul><br />
<br />
<br />
Here's the script so far:<br />
[[File:RPCTOD_50_IDLEEditor_DefineWindowMainLoop.jpg|none|800px|Create the GUI window.]]<br />
<br />
<br />
In order to individually adjust each component that makes up the date and time information, we need to define a variable for each part. We’ll create these variables using tkinter functions, such as IntVar(). These functions accept many arguments such as a window name or a value. In our script, “gui” is the window name, and the value comes from the sysTime variable’s attributes we previously defined.<br />
<br />
Insert the lines of code below, following the sysTime variable statement.<br />
<br />
<br />
<ul><br />
year = IntVar(gui,value = sysTime.year) <br /n><br />
month = IntVar(gui,value = sysTime.month) <br /n><br />
day = IntVar(gui,value = sysTime.day) <br /n><br />
hour = IntVar(gui,value = sysTime.hour) <br /n><br />
minute = IntVar(gui,value = sysTime.minute) <br /n><br />
second = IntVar(gui,value = sysTime.second) <br /n><br />
</ul><br />
<br />
<br />
We need to rewrite the timezone variable definition in the tkinter syntax too.<br />
<br />
<br />
<ul><br />
timezone = IntVar(gui,value = -7) <br />
</ul><br />
<br />
<br />
Just as with the date and time information, we need to split the location data into latitude and longitude, which use decimal precision and therefore require a different tkinter function, DoubleVar(). As these variables will replace the existing location variable, we can comment out that line of code so that Python ignores it.<br />
<br />
The “#” at the beginning of the line of code defining the location variable, so Python ignores it.<br />
<br />
<br />
<ul><br />
<nowiki>#</nowiki> location = [34.052235, -104.741667] <br /n><br />
latitude = DoubleVar(gui,value = 34.052235) <br /n><br />
longitude = DoubleVar(gui,value = -104.741667) <br /n><br />
</ul><br />
<br />
<br />
[[File:RPCTOD_14_IDLEEditor_DefineVariables.jpg|none|800px|Defining the variables for each part of the time, date and location data.]]<br />
<br />
<br />
Now that we’ve accounted for all the individual variables we can add them to the GUI. There are several ways to do this in Python, and for this tutorial we’ll use the grid method, which will allow us to align each variable with a corresponding text label to describe it.<br />
<br />
Python refers to the GUI components, such as labels and buttons, as widgets. We’ll begin by creating a widget for each label. Each widget needs a unique name, so for the label widgets we’ll use “l_” followed by the name of the data it contains, for example “l_year”. The labels themselves will include information for the window it belongs on, the text to display, its position within the window, and how it is aligned. All the labels will belong to the “gui” window for now, and the text to be displayed is simply their name. The grid method uses rows and columns to position the label, and a “sticky” attribute to align the label.<br />
<br />
<br />
The last widget allows us to insert a blank line that spans both columns.<br />
<br />
<br />
<ul><br />
l_year = Label(gui,text = 'Year').grid(row=0,column=0,sticky='w') <br /n><br />
l_month = Label(gui,text = 'Month').grid(row=1, column=0,sticky='w') <br /n><br />
l_day = Label(gui,text = 'Day').grid(row=2, column=0,sticky='w') <br /n><br />
l_hour = Label(gui,text = 'Hour').grid(row=3, column=0,sticky='w') <br /n><br />
l_minute = Label(gui,text = 'Minute').grid(row=4, column=0,sticky='w') <br /n><br />
l_second = Label(gui,text = 'Second').grid(row=5, column=0,sticky='w') <br /n><br />
l_timezone = Label(gui,text = 'Timezone').grid(row=6, column=0,sticky='w') <br /n><br />
l_latitude = Label(gui,text='Latitude (N)').grid(row=7,column=0,sticky='w') <br /n><br />
l_longitude = Label(gui,text='Longitude (W)').grid(row=8,column=0,sticky='w') <br /n><br />
l_null = Label(gui,text=" ").grid(row=9,columnspan=2) <br /n><br />
</ul><br />
<br />
<br />
The code for the labels should look like this:<br />
<br />
[[File:RPCTOD_15_IDLEEditor_DefineLabels.jpg|none|800px|Defining the label widgets.]]<br />
<br />
<br />
While the Label widget is used to display text and can not be changed by the user, the Entry widget accepts a starting value and user input. We’ll set each Entry’s “textvariable” to the corresponding initial value we assigned to the variables. Later, the user can change these values by simply entering a different value. To describe the widget for each Entry, we’ll use the prefix “e_” and the name of the Entry, for example “e_year”.<br />
<br />
<br />
<ul><br />
e_year = Entry(gui, textvariable = year,width=6).grid(row=0,column=2, sticky='e') <br /n><br />
e_month = Entry(gui, textvariable = month,width=6).grid(row=1,column=2, sticky='e') <br /n><br />
e_day = Entry(gui, textvariable = day,width=6).grid(row=2,column=2, sticky='e') <br /n><br />
e_hour = Entry(gui, textvariable = hour,width=6).grid(row=3,column=2, sticky='e') <br /n><br />
e_minute = Entry(gui, textvariable = minute,width=6).grid(row=4,column=2, sticky='e') <br /n><br />
e_second = Entry(gui, textvariable = second,width=6).grid(row=5,column=2, sticky='e') <br /n><br />
e_timezone = Entry(gui, textvariable = timezone,width=6).grid(row=6, column = 2, sticky='e') <br /n><br />
e_latitude = Entry(gui, textvariable = latitude,width=16).grid(row=7, column =2) <br /n><br />
e_longitude = Entry(gui, textvariable = longitude,width=16).grid (row=8,column =2) <br /n><br />
</ul><br />
<br />
<br />
The code for the Entry section should look like this:<br />
[[File:RPCTOD_16_IDLEEditor_DefineEntry.jpg|none|800px|Defining the entry widgets.]]<br />
<br />
<br />
Now that we can change the initial values for each parameter, we need a way to tell the script when to calculate our changes. For this we’ll use a button widget. Like the other widgets, the button can contain several bits of information, such as the text to display on the button. Most importantly it contains a command attribute, telling it what to do when the button has been clicked. In this example, the script will run the function “whenWhere”, which is yet to be defined. <br />
<br />
<br />
<ul><br />
b_ApplySun = Button(gui,text='Apply to Sun',bg='yellow',command=whenWhere).grid(row=11,column=2)<br />
</ul><br />
<br />
<br />
This is the code for the button section of the script.<br />
[[File:RPCTOD_17_IDLEEditor_DefineButton.jpg|none|800px|Defining the button widget.]]<br />
<br />
<br />
To define the whenWhere() function, insert the code below into the script following the variables section and before the labels section. Note that it’s important to indent the lines of code after defining the function. <br />
<br />
Using the get() function the “lookupTime” variable is updated with the current date and time values and the “location” variable is updated with the current latitude and longitude values. Note, some of these variables will be defined immediately after this step.<br />
<br />
The next statement first passes three values to the sunpos module, then it accepts the returned data in the “results” variable as a list containing the heading and elevation.<br />
<br />
Then the function “setTerragenSunHeadingAndElevation” is called, passing along the text “Sunlight 01” and the results variable containing the sun heading and elevation.<br />
<br />
<br />
<ul><br />
def whenWhere(): <br /n><br />
: lookupTime = [year.get(), month.get(), day.get(), hour.get(), minute.get(), second.get(),timezone.get()] <br /n><br />
: location = [latitude.get(),longitude.get()] <br /n><br />
: results = sp.sunpos(lookupTime,location,True) <br /n><br />
: setTerragenSunHeadingAndElevation(“Sunlight 01”,results) <br /n><br />
</ul><br />
<br />
<br />
This is the code defining the whenWhere() function.<br />
[[File:RPCTOD_18_IDLEEditor_whenWhereFunction.jpg|none|800px|Create the lookupTime function.]]<br />
<br />
<br />
<br />
Since we’ve included the “lookupTime” variable within the whenWhere() function, and it is updated by the get() functions whenever the whenWhere() function is called, we no longer need it in the variables section. <br />
<br />
<span style="color:red">Delete the line of code in the variables section that defines the “lookupTime” variable.</span><br />
<br />
Also included in the whenWhere() function was the call to a function that performs the actual RPC commands and updates Terragen. The function will accept two arguments, one for the item to modify in Terragen which is the Sunlight 01 node, and the other, a list, containing the heading and elevation values. Let’s code that function directly beneath the previous function in the script. <br />
<br />
Once you’ve completed coding the function below you can <span style="color:red">remove the previous lines of code created in Part 02 of this tutorial</span> that did the same thing.<br />
<br />
This function includes error checking commands such as “try”, “except”, and “raise”. Checking for potential errors or exceptions allows the program to handle them before they cause a problem, like crashing the application. For example, if a user entered text data in a numeric field, and the program expected numeric data, then an error would occur and the application might crash. With error handling, the application could display a message indicating the wrong type of data was entered and giving the user an opportunity to change the data. You can read more about the error checking command in the Terragen RPC documentation. <br />
<br />
<br />
<ul><br />
def setTerragenSunHeadingAndElevation(name,values): <br /n><br />
: try: <br /n><br />
:: node = tg.node_by_path(name) <br /n><br />
:: try: <br /n><br />
::: node.set_param('heading',values[0]) <br /n><br />
::: node.set_param('elevation',values[1]) <br /n><br />
:: except AttributeError as err: <br /n><br />
::: showError("Sunlight not in project. Refresh list.") <br /n><br />
:: except ConnectionError as e: <br /n><br />
::: showError("Terragen RPC connection error: " + str(e)) <br /n><br />
:: except TimeoutError as e: <br /n><br />
::: showError("Terragen RPC timeout error: " + str(e)) <br /n><br />
:: except tg.ReplyError as e: <br /n><br />
::: showError("Terragen RPC server reply error: " + str(e)) <br /n><br />
:: except tg.ApiError: <br /n><br />
::: showError("Terragen RPC API error") <br /n><br />
::: raise <br />
</ul><br />
<br />
<br />
Here is the code for the setTerragenSunHeadingAndElevation function.<br />
[[File:RPCTOD_19_IDLEEditor_SetTGSunHeadElevFunction.jpg|none|800px|The setTerragenSunHeadingAndElevation function.]]<br />
<br />
<br />
The last step in this section is to create another function which displays any message passed to it by another part of the script. This function receives one input, the message to be displayed. <br />
<br />
<br />
<ul><br />
def showError(text): <br/n><br />
: errMsg.set(text)<br />
</ul><br />
<br />
<br />
This is the code for the showError() function.<br />
[[File:RPCTOD_20_IDLEEditor_showErrorFunction.jpg|none|800px|The showError function.]]<br />
<br />
<br />
We need to create one last variable to store any messages set by the error checking processes. We can define the new variable at the end of the variables section of the script.<br />
<br />
<br />
<ul><br />
errMsg = StringVar()<br />
</ul><br />
<br />
<br />
Here is the variable section of the script.<br />
[[File:RPCTOD_21_IDLEEditor_Variables.jpg|none|800px|The defined variables.]]<br />
<br />
<br />
Save and run the script. Try entering new values, especially for the hour of the day, and applying them to the Terragen sun.<br />
<br />
<br />
== Part 04: Modifying parameters more easily ==<br />
<br /n><br />
[[File:RPCTOD_23_IDLEEditor_GUI_Part04.jpg|none|452px|The GUI with sliders added.]]<br />
<br />
<br />
<br />
Now that we have a GUI and can modify the values for the time of day and location, let’s make it easier to make those modifications by adding sliders to the interface for the date and time parameters.<br />
<br />
A slider can be created with Tkinter’s Scale widget. We’ll use the prefix “s_” and the name of each parameter to define each slider. Scale widgets can also accept arguments, such as which window to be positioned in, a range of values to slide between, and the orientation of the slider. We’ll set an appropriate range for each slider, for example: 1 - 12, for the Month parameter, and orient the sliders horizontally so they best fit the layout of the GUI we’ve already created.<br />
<br />
Insert the following lines of code into the script, between the Labels section and the Entry section. <br />
<br />
<br />
<ul><br />
s_year = Scale(gui,from_= 1900, to = 2099,variable=year,orient=HORIZONTAL,showvalue=0).grid(row=0,column=1) <br /n><br />
s_month = Scale(gui,from_= 1, to = 12,variable=month,orient=HORIZONTAL,showvalue=0).grid(row=1,column=1) <br /n><br />
s_day = Scale(gui,from_= 1, to =31,variable=day,orient=HORIZONTAL,showvalue=0).grid(row=2,column=1) <br /n><br />
s_hour = Scale(gui,from_= 0, to = 23,variable=hour,orient=HORIZONTAL,showvalue=0).grid(row=3,column=1) <br /n><br />
s_minute = Scale(gui,from_= 0,to = 59,variable=minute,orient=HORIZONTAL,showvalue=0).grid(row=4,column=1) <br /n><br />
s_second = Scale(gui,from_= 0, to = 59,variable=second,orient=HORIZONTAL,showvalue=0).grid(row=5,column=1) <br /n><br />
s_timezone = Scale(gui,from_= -13, to = 13,variable=timezone,orient=HORIZONTAL,showvalue=0).grid(row=6,column=1) <br /n><br />
</ul><br />
<br />
<br />
Here’s the sliders section of the script:<br />
[[File:RPCTOD_24_IDLEEditor_Sliders.jpg|none|800px|Sliders section.]]<br />
<br />
<br />
You may have noticed in the lines of code above, that the slider’s grid position is located in column 1, and in the previous step the label’s grid position was located in column 0, while the entry’s grid position was located in column 2. This allows our slider to drop in between the label and the entry columns of the GUI. <br />
<br />
Visually, the GUI will look better if the latitude and longitude entry fields align with the sliders because of their width; and that the Apply button is centered within the GUI, so modify all their grid positions to column 1 as well.<br />
<br />
<br />
<ul><br />
e_latitude = Entry(gui, textvariable = latitude,width=16).grid(row=7, column =1) <br /n><br />
e_longitude = Entry(gui, textvariable = longitude,width=16).grid (row=8,column =1) <br /n><br />
b_ApplySun = Button(gui,text='Apply to Sun',bg='yellow',command=whenWhere).grid(row=11,column=1) <br /n><br />
</ul><br />
<br />
<br />
If you save the script and run it, you’ll notice how much easier it is to adjust the time and date parameters with the sliders, while still being able to enter a precise numerical value in the Entry field.<br />
<br />
<br />
== Part 05: Multiple sunlight nodes ==<br />
<br /n><br />
[[File:RPCTOD_27_IDLEEditor_GUI_Part05.jpg|none|800px|The final GUI for Part 05.]]<br />
<br />
<br />
<i>“But, what if there is more than one sunlight node in the Terragen project, or it’s named something else, or it’s missing entirely?” </i> Good questions, and in this section we’ll address them while expanding the functionality of the script and GUI.<br />
<br />
<br />
So far all of the GUI components have been located in the “gui” window. Tkinter allows you to divide the window into smaller frames. You can then tell each component which frame to be displayed in. <br />
<br />
To accommodate the features we’ll be adding to the script we can start by making the GUI larger. Edit the existing line of code that sets the width and height of the GUI as indicated below.<br />
<br />
<br />
<ul><br />
gui.geometry("900x600")<br />
</ul><br />
<br />
<br />
We define a frame using the LabelFrame widget. Just like the other widgets, it accepts options for which window to be located in, and what text to display, if any. For starters we’ll create two frames. The first frame will be used to select the available Sunlight nodes in the project, and the second frame will contain the parameters we’ve already coded. <br />
<br />
To define the frames, enter the following lines of code right below the existing code that defines the window.<br />
<br />
The first frame, frame0, will reside in the “gui” window and display the text “Select sunlight node / Click drop-down arrow to refresh list:”. It has 20 pixels of padding on its left and right, and 10 pixels of padding above and below it. No border will be drawn around the frame because “relief” set to FLAT.<br />
<br />
<br />
<ul><br />
frame0 = LabelFrame(gui,text="Select sunlight node / Click drop-down arrow to refresh list:",relief=FLAT, padx=20,pady=10)<br />
</ul><br />
<br />
<br />
The second frame, frame1, will aslo reside in the “gui” window. It will display the text “Enter time and location:”. It has 10 pixels of padding all around it and will display a border.<br />
<br />
<br />
<ul><br />
frame1 = LabelFrame(gui,text="Enter time and location: ",padx=10,pady=10)<br />
</ul><br />
<br />
<br />
Then skip a line and enter the following lines of code which define where the frames are positioned within the gui window.<br />
<br />
<br />
<ul><br />
frame0.grid(row=0,column=0,padx=5,pady=5) <br /><br />
frame1.grid(row=10,column=0,padx=25,pady=0,sticky='w')<br />
</ul><br />
<br />
<br />
The code should look like this:<br />
[[File:RPCTOD_25_IDLEEditor_DefineFrames.jpg|none|800px|Defining the frame.]]<br />
<br />
<br />
In order for the frames to have any effect, the display option for the labels, sliders, entry, and buttons needs to be changed from “gui” to the one of the frames. <span style="color:red">Edit the lines of code for each labels, sliders, entry, and button which are currently coded to “gui” to “frame1”. </span><br />
<br />
The code for the labels, sliders, entry and button now look like this:<br />
[[File:RPCTOD_26_IDLEEditor_LabelsSlidersEntryButtonFrames.jpg|none|800px|The labels, sliders, entry and buttons edited to reflect the new frame location.]]<br />
<br />
<br />
So far the design of our script has been to send certain information to Terragen in order to change something in the project, but now we want Terragen to provide some information about the project first. Specifically, we want to know what sunlight type nodes are in the project as soon as the script is run. Our GUI needs to reflect this information and provide us with the ability to select among the sunlight nodes.<br />
<br />
Coding the query to Terragen consists of two parts. Part one will be a way in which to store the sunlight node names and IDs returned from Terragen. Part two will be a function to make the request to Terragen for the sunlight node information.<br />
<br />
Enter the following line of code into the script, after the functions already defined and before the labels section.<br />
<br />
This creates the variable “sunNodes” which will store the returned information from the getSunlightInProject() function. <br />
<br />
<br />
<ul><br />
sunNodes = getSunlightInProject()<br />
</ul><br />
<br />
<br />
Next, create the new function getSunlightInProject() just below the last function currently in the script by entering the lines of code below. Note, that this function also includes the same kind of error checking as in Part 3; please see that section for more explanation on capturing errors and exceptions. This function initializes the variables “nodeIDs” and “nodeNames” so that they’re empty each time this function is run, then populates them with the current information from the Terragen project. <br />
<br />
<br />
<ul><br />
def getSunlightInProject(): <br /n><br />
: try: <br /n><br />
:: project = tg.root() <br /n><br />
:: nodeIDs = [] <br /n><br />
:: nodeNames = [] <br /n><br />
<!-- :: nodeIDs = tg.children_filtered_by_class(project,'sunlight') <br /n> --><br />
:: nodeIDs = project.children_filtered_by_class(‘sunlight’) <br /n><br />
:: for nodes in nodeIDs: <br /n><br />
<!-- ::: nodeNames.append(tg.name(nodes)) <br /n> --><br />
::: nodeNames.append(nodes.name()) <br /n><br />
:: return nodeIDs,nodeNames <br /n><br />
: except ConnectionError as e: <br /n><br />
:: showError("Terragen RPC connection error: " + str(e)) <br /n><br />
: except TimeoutError as e: <br /n><br />
:: showError("Terragen RPC timeout error: " + str(e)) <br /n><br />
: except tg.ReplyError as e: <br /n><br />
:: showError("Terragen RPC server reply error: " + str(e)) <br /n><br />
: except tg.ApiError: <br /n><br />
:: showError("Terragen RPC API error") <br /n><br />
:: raise<br />
</ul><br />
<br />
<br />
The code for the function call looks like this:<br />
<!-- [[File:RPCTOD_28_IDLEEditor_GetSunlightInProjectFunction.jpg|none|800px|The getSunlightInProject() function.]] --><br />
[[File:RPCTOD_52_IDLEEditor_GetSunlightInProjectFunction.jpg|none|800px|The getSunlightInProject() function.]]<br />
<br />
<br />
Now that we have the names of the sunlight nodes in the project, we can add them to our GUI. Currently, the labels, sliders, entries and Apply button reside in the “frame1” portion of the GUI, so we’ll place the names of the sunlight nodes in the “frame0” portion of the GUI and use a widget known as a Combobox to do so. As we’ve seen with other widgets, Comboboxes also accept options such as which part of the window to be displayed in. <br />
<br />
Enter the following lines of code right after the statement to call the getSunlightInProject() function and before the Labels section.<br />
<br />
The postcommand option is run each time a change is made to the Combobox. In this example, the updateCBList function is run, ensuring that the list of Sunlight nodes is always up to date. The textvariable “num” acts as a pointer to which item in the list is acted upon.<br />
<br />
<br />
<ul><br />
cb_suns = ttk.Combobox(frame0,textvariable=num,postcommand=updateCbList)<br />
</ul><br />
<br />
<br />
Here's the code for the Combobox:<br />
[[File:RPCTOD_29_IDLEEditor_ComboBox.jpg|none|800px|Combobox.]]<br />
<br />
<br />
Next we need to define the new variable and function that we referenced in the above line of code.<br />
<br />
In the variables section of our script, add the following to define the pointer variable “num”. By using tkinter’s StringVar() function we can later retrieve and store new values to the variable with the get() and set() functions.<br />
<br />
<br />
<ul><br />
num = StringVar()<br />
</ul><br />
<br />
<br />
Here is the variables section of the script after adding the num variable:<br />
[[File:RPCTOD_30_IDLEEditor_VariablesNum.jpg|none|800px|Add the num variable in the variables section.]]<br />
<br />
<br />
Following the last function we defined in our script, add the following lines of code. Note there is error checking in this function because it’s possible that a sunlight node is no longer in the Terragen project or the names have been changed since the last time this query was made.<br />
<br />
<br />
<ul><br />
def updateCbList(): <br /n><br />
: updateSuns = getSunlightInProject() <br /n><br />
: try: <br /n><br />
:: updateSunID.set(updateSuns[0]) <br /n><br />
:: updateSunNames.set(updateSuns[1]) <br /n><br />
:: cb_suns["values"] = updateSuns[1] <br /n><br />
:: showError(" ") <br /n><br />
: except TypeError as e: <br /n><br />
:: showError("Sunlight nodes not found. " + str(e))<br />
</ul><br />
<br />
<br />
The code for this function looks like this:<br />
[[File:RPCTOD_31_IDLEEditor_updateCbListFunction.jpg|none|800px|The updateCbList() function.]]<br />
<br />
<br />
We need to create the two variables we just reference in the above function so they can store the sunlight node ID numbers and names, and get updated whenever the updateCbList() function is called. At the end of the variables section in the script add these lines of code:<br />
<br />
<br />
<ul><br />
updateSunID = StringVar() <br /n><br />
updateSunNames = StringVar()”<br />
</ul><br />
<br />
<br />
The code for the variable section now looks like this:<br />
[[File:RPCTOD_32_IDLEEditor_Variables.jpg|none|800px|The variables section.]]<br />
<br />
<br />
Return to the script and directly beneath the line of code that creates the Combobox add these lines of code, in order to display the updated list of Sunlight nodes and to catch any errors or exceptions. <br />
<br />
<br />
<ul><br />
Try: <br /n><br />
: cb_suns["values"]=r[1]<br />
: cb_suns.current(0)<br />
except TypeError as e:<br />
: showError("No sunlight nodes in project or Terragen not running." + str(e))<br />
</ul><br />
<br />
<br />
To position the Combobox within frame0 add this line of code.<br />
<br />
<br />
<ul><br />
cb_suns.grid(row=0,column=0)<br />
</ul><br />
<br />
<br />
When dealing with multiple sunlight nodes in the project, it would be helpful if there was a way within the script to enable or disable each sunlight node. Since we’ve already created a Combobox to select a sunlight node, we can easily add a bit of code to enable or disable the selected node. We’ll use a button widget for this.<br />
<br />
Just after the Combobox code and before the Labels code, add this line of code to create the button to create a button to the right of the Combobox position that will execute the ckboxSun command when clicked.<br />
<br />
<br />
<ul><br />
b_SunOnOff = Button(frame0,text="Toggle sun on/off",bg='pink',command=ckboxSun).grid(row=0,column=1,padx=10)<br />
</ul><br />
<br />
<br />
The code for the Combobox should look like this:<br />
[[File:RPCTOD_33_IDLEEditor_MainAndComboBox.jpg|none|800px|The Combobox and button.]]<br />
<br />
<br />
Now we’ll create the ckboxSun() function that gets called when the button is clicked. Just below the lines of code for the last function we wrote add the following to call the toggleTerragenSun() function, passing it the index number for the currently selected item in the Combobox.<br />
<br />
<br />
<ul><br />
def ckboxSun(): <br /n><br />
: toggleTerragenSun(num.get())<br />
</ul><br />
<br />
<br />
You might wonder why we call one function when the button gets clicked and then another function. The reason is to keep the functions themselves as simple as possible. That way they can be used by other functions within the script. Let’s create the toggleTerragenSun() function so we can turn the sun on and off. Just below the last function type the following lines of code, which also contain some error checking.<br />
<br />
<br />
<ul><br />
def toggleTerragenSun(name): <br /n><br />
: try: <br /n><br />
:: node = tg.node_by_path(name) <br /n><br />
:: try: <br /n><br />
::: x = node.get_param_as_int('enable') <br /n><br />
:: except AttributeError as err: <br /n><br />
::: showError("Sunlight not in project. Refresh list.") <br /n><br />
:: else: <br /n><br />
::: if x == 1: <br /n><br />
:::: node.set_param('enable',0) <br /n><br />
::: else: <br /n><br />
:::: node.set_param('enable',1) <br /n><br />
: except ConnectionError as e: <br /n><br />
:: showError("Terragen RPC connection error: " + str(e)) <br /n><br />
: except TimeoutError as e: <br /n><br />
:: showError("Terragen RPC timeout error: " + str(e)) <br /n><br />
: except tg.ReplyError as e: <br /n><br />
:: showError("Terragen RPC server reply error: " + str(e)) <br /n><br />
: except tg.ApiError: <br /n><br />
:: showError("Terragen RPC API error") <br /n><br />
:: raise <br />
</ul><br />
<br />
<br />
Here is the code for the above functions.<br />
[[File:RPCTOD_34_IDLEEditor_ckBoxSunAndToggleTGSunFunctions.jpg|none|800px|Code for the ckboxSun() function and toggleTerragenSun() function.]]<br />
<br />
<br />
Save the script and run it. In Terragen, duplicate the Sunlight object a few times, then click in the Combobox and enable or disable the lights, and change their heading and elevation.<br />
<br />
<br />
== Part 06: Presets ==<br />
<br /n><br />
[[File:RPCTOD_43_GUI_Part06.jpg|none|800px|GUI for Part 06.]]<br />
<br />
<br />
Let’s add one more section to our GUI which contains presets for selected locations and timezones around the world. This way we can jump from the Los Angeles timezone to any other timezone around the world with a click of a button. <br />
<br />
[[File:RPCTOD_44_GUI_PresetsDropDownList.jpg|none|417px|Presets drop down list.]]<br />
<br />
<br />
We’ll create an external text file to hold the data so that it can be easily customized in any text editor, such as Notepad, and save the file in the CSV format which stands for “comma separated values”. As the format name implies each piece of data is separated from the next by a comma. Each row in the file should be formatted as follows: Timezone City/Country , longitude, latitude, timezone offset<br />
<br />
Copy and paste the following data into a text file and save it as presets_latLong.csv. As you can see from the “Timezone City/Country” data the entries are organized from timezone -11 to timezone +13. This is the order that the data will show up in the GUI.<br />
<br />
<ul><br />
-11 Liku/Niue, -169.792327,-19.053680, -11.0 <br /n><br />
-10 Honolulu/Hawaii, -157.854995, 21.306647, -10.0 <br /n><br />
-9 Adak/Adak Island, -176.660156, 51.862923, -9.0 <br /n><br />
-8 Anchorage/Alaska, -149.721679, 61.227957, -8.0 <br /n><br />
-7 Los Angeles/USA, -104.741667, 34.052235, -7.0 <br /n><br />
-7 Vancouver/Canada, -123.108673, 49.263323,-7.0 <br /n><br />
-6 Calgary/Canada, -114.071044, 51.058660, -6.0 <br /n><br />
-5 Winnipeg/Canada, -97.141113, 49.866316, -5.0 <br /n><br />
-4 Igloolik/Igloolik Island, -81.806259, 69.379587, -4.0 <br /n><br />
-4 Ontario/Canada, -84.887696, 49.315573, -4.0 <br /n><br />
-3 Salvador/Brazil, -38.480987, -12.973780, -3.0 <br /n><br />
-3 Goose Bay/Newfoundland, -60.342407, 53.304621,-3.0 <br /n><br />
-2 Nuuk/Greenland, -51.723632, 64.182464, -2.0 <br /n><br />
-1 Sao Filipe/Fogo, -24.483718, 14.890709,-1.0 <br /n><br />
0 Reykjavik/Iceland, -21.928710,64.144161, 0.0 <br /n><br />
+1 Dublin/Ireland, -6.249847, 53.350551, 1.0 <br /n><br />
+1 Greenwhich/UK, -0.193634, 51.489293, 1.0 <br /n><br />
+2 Brussels/Belgium, 4.390869, 50.824444, 2.0 <br /n><br />
+2 Berlin/Germany, 13.405151, 52.511763, 2.0 <br /n><br />
+2 Krakow/Poland, 19.929199, 50.050084, 2.0 <br /n><br />
+3 Odesa/Ukraine, 30.739746, 46.468132, 3.0 <br /n><br />
+3 Novgorodd/Russia, 43.978271, 56.307396, 3.0 <br /n><br />
+4 Baku/Azerbaijan, 49.844970, 40.390488, 4.0 <br /n><br />
+4 Dubai/UAE, 55.341795, 25.138654, 4.0 <br /n><br />
+5 Islamabad/Pakistan, 73.044433, 33.706061, 5.0 <br /n><br />
+6 Jahore/Bangladesh, 89.229125, 23.179080, 6.0 <br /n><br />
+7 Bangkok/Thailand, 100.546875, 13.496472, 7.0 <br /n><br />
+8 Beijing/China, 116.389160, 39.884450, 8.0 <br /n><br />
+8 Manilla/Philippines, 120.878899, 14.491408, 8.0 <br /n><br />
+9 Sapporo/Japan, 141.344604, 43.056847, 9.0 <br /n><br />
+10 Andersen AFB/Guam, 144.924087, 13.580586, 10.0 <br /n><br />
+11 Magadan/Russia, 150.805206, 59.568418, 11.0 <br /n><br />
+12 Queenstown/New Zealand, 168.618640, -45.104546, 12.0 <br /n><br />
+12 McMurdo Station/Antartica, 166.690063, -77.84011, 12.0 <br /n><br />
+13 Nomuka/Tonga, -174.802093, -20.251890, 13.0 <br /n><br />
</ul><br />
<br />
<br />
With the presets file saved, let’s load the data into our script. Immediately following the line of code that retrieves the Sunlight nodes in the Terragen project add the line of code:<br />
<br />
<br />
<ul><br />
presets()<br />
</ul><br />
<br />
<br />
[[File:RPCTOD_35_IDLEEditor_Presets.jpg|none|800px|The statement that calls the presets() function.]]<br />
<br />
<br />
Then create the new presets() function with the following lines of code, which will read the contents of the text file in “read only” mode, one line at a time. Each line is split into data each time the comma separator is encountered and these bits of data are appended to new variables for the Timezone City/Country, longitude, latitude and timezone offset.<br />
<br />
<br />
<ul><br />
def presets(): <br /n><br />
: file = open('presets_latlong.csv',"r") <br /n><br />
: content = file.readlines() <br /n><br />
: for line in content: <br /n><br />
:: x,y,z,a = line.strip().split(',') <br /n><br />
:: plocation.append(x) <br /n><br />
:: plat.append(z) <br /n><br />
:: plon.append(y) <br /n><br />
:: ptz.append(a) <br /n><br />
: file.close()<br />
</ul><br />
<br />
<br />
The code for the presets() function looks like this:<br />
[[File:RPCTOD_36_IDLEEditor_PresetsFunction.jpg|none|800px|The presets function.]]<br />
<br />
<br />
We referenced four new variables in the presets() function above, which store the data from the presets text file. Since these variables are created when the script is first run and their values do not change during the execution of the script, we can define them using standard Python variables, and not tkinter variables such as with the StringVar() function. Add these variables to the variables section of the script.<br />
<br />
<br />
<ul><br />
plocation = [] <br/n><br />
plat = [] <br/n><br />
plon = [] <br/n><br />
ptz = []<br />
</ul><br />
<br />
<br />
Here's the updated variables section in our script:<br />
[[File:RPCTOD_37_IDLEEditor_Variables.jpg|none|800px|The variables section of the script.]]<br />
<br />
<br />
We’ll add another frame to our main window and place the GUI components to display the presets there. We’ll need to add another Combobox to display the preset choices, labels for descriptions, and a button in order to update the variables for the timezone, latitude and longitude. Following the last line of code in the script add the lines below:<br />
<br />
<br />
<ul><br />
cb_presets = ttk.Combobox(frame3,textvariable=pnum) <br /n><br />
cb_presets["values"]=plocation <br /n><br />
cb_presets.current(16) <br /n><br />
cb_presets.grid(row=0,column=1,sticky='w') <br /n><br />
<br /n><br />
l_presetlocation = Label(frame3,text="Location: ").grid(row=0,column=0,sticky='w') <br /n><br />
l_presetLat = Label(frame3,text="Latitude:").grid(row=1,column=0,sticky='w') <br /n><br />
l_presetLat = Label(frame3,text="Longitude:").grid(row=2,column=0,sticky='w') <br /n><br />
l_presetTz = Label(frame3,text="Timezone:").grid(row=3,column=0,sticky='w') <br /n><br />
l_presetDispLat = Label(frame3,textvariable=displayLat).grid(row=1,column=1,sticky='w') <br /n><br />
l_presetDispLon = Label(frame3,textvariable=displayLon).grid(row=2,column=1,sticky='w') <br /n><br />
l_presetDispTz = Label (frame3, textvariable=displayTz).grid(row=3,column=1,sticky='w') <br /n><br />
<br /n><br />
b1 = Button(frame3,text="Apply Preset",command=setLatLon).grid(row=5,column=1,pady=5,sticky='w') <br /n><br />
</ul><br />
<br />
<br />
Here is the code for the frame3 components:<br />
[[File:RPCTOD_38_IDLEEditor_ComboboxPresets.jpg|none|800px|The Combobox for the Presets.]]<br />
<br />
<br />
We referenced a new frame and variable in the above lines of code to define the Combobox, so let’s add both those now.<br />
<br />
Append the following line of code to the section of our script where we’ve defined the window title, size and frames. Note, we will define frame2 in the last section of this tutorial.<br />
<br />
<br />
<ul><br />
frame3 = LabelFrame(gui,text="Location presets: ",padx=10,pady=10) <br /n><br />
frame3.grid(row=15,column=0,padx=25,pady=10,sticky='w')<br />
</ul><br />
<br />
<br />
Here's the updated lines of code for the window section:<br />
[[File:RPCTOD_39_IDLEEditor_Frame3Window.jpg|none|800px|Defining frame3 in the GUI window.]]<br />
<br />
<br />
Then add the following line of code the the variables section of our script to account for the new variable, which identifies the current item in the preset list.<br />
<br />
<br />
<ul><br />
pnum=StringVar()<br />
</ul><br />
<br />
<br />
Here's the updated variables section:<br />
[[File:RPCTOD_40_IDLEEditor_Variables.jpg|none|800px|Variables section.]]<br />
<br />
<br />
The last step to our preset code is to define the following function which will update and display the latitude, longitude and timezone variables to those of the selected preset.<br />
<br />
<br />
<ul><br />
def setLatLon(): <br /n><br />
: v = cb_presets.current() <br /n><br />
: displayLat.set(plat[v]) <br /n><br />
: displayLon.set(plon[v]) <br /n><br />
: displayTz.set(ptz[v]) <br /n><br />
: latitude.set(plat[v]) <br /n><br />
: longitude.set(plon[v])<br /n><br />
: timezone.set(ptz[v])<br />
</ul><br />
<br />
<br />
Here's the code for the setLatLon() function:<br />
[[File:RPCTOD_41_IDLEEditor_SetLatLongFunction.jpg|none|800px|The setLatLon() function.]]<br />
<br />
<br />
Three new variables were referenced in the setLatLon() function above, so we need to add them to the variable section, using the tkinter format so they can be updated throughout the script with the get() and set() functions. Add these lines of code following the last entry in the variable section of the script.<br />
<br />
<br />
<ul><br />
displayLat = StringVar() <br /n><br />
displayLon = StringVar() <br /n><br />
displayTz = StringVar()<br />
</ul><br />
<br />
<br />
Here's the updated variables section of our script:<br />
[[File:RPCTOD_42_IDLEEditor_Variables.jpg|none|800px|Variables section.]]<br />
<br />
<br />
== Part 07 - Displaying previous values, errors & exceptions ==<br />
<br /n><br />
[[File:RPCTOD_49_GUI_Part07.jpg|none|800px|GUI for Part 07.]]<br />
<br />
<br />
Several of the functions throughout this script have accounted for errors and exceptions, but up to now we haven’t displayed those messages. In addition, as we modify the Sunlight nodes in Terragen, it would be useful if we could see those values displayed in the GUI. We’ll use frame2, the empty right portion of our window to display any of these kinds of information.<br />
<br />
<br />
We can begin by defining the frame, which will consist of a border with a description and labels. In the windows section of the script insert the following line of code between frame1 and frame3.<br />
<br />
<br />
<ul><br />
frame2 = LabelFrame(gui,text="Last values plotted were: ",padx=10,pady=10) <br /n><br />
frame2.grid(row=10,column=1,padx=0,pady=0,sticky='nw')<br />
</ul><br />
<br />
<br />
The code for the GUI window now looks like this:<br />
[[File:RPCTOD_45_GUI_Window.jpg|none|800px|Defining the frame2 area within the main window.]]<br />
<br />
<br />
Since the display of this information is mostly in the form of Labels, let’s add the following code at the end of the Labels section and before the Sliders section of our script to display the last Sunlight node that was modified via our script, and the values used to do so.<br />
<br />
<br />
<ul><br />
l_lastSun = Label(frame2,text='Node: ').grid(row=12,column=0,sticky='w') <br /n><br />
l_lastSunName = Label(frame2,textvariable=displayLastSun).grid(row=12,column=1,sticky='w') <br /n><br />
l_dateTime = Label(frame2,text='Date & time: ').grid(row=13, column=0,sticky='w') <br /n><br />
l_dateTimeData = Label(frame2,textvariable=displayTimeData).grid(row=13, column=1,sticky='w') <br /n><br />
l_location = Label(frame2,text='Location: ').grid(row=14,column=0,sticky='w') <br /n><br />
l_locationData = Label(frame2,textvariable=displayLocation).grid(row=14,column=1,sticky='w') <br /n><br />
l_azimuth = Label(frame2,text='Azimuth:').grid(row=15,column=0,sticky='w') <br /n><br />
l_azimuthData = Label (frame2,textvariable=azimuth).grid(row=15,column=1,sticky='w') <br /n><br />
l_elevation = Label(frame2,text='Elevation:').grid(row=16,column=0,sticky='w') <br /n><br />
l_elevationData = Label(frame2,textvariable=elevation).grid(row=16,column=1,sticky='w') <br /n><br />
l_errMsg = Label(frame2,textvariable=errMsg,fg='red').grid(row=17,columnspan=2,sticky='w') <br /n><br />
</ul><br />
<br />
<br />
The labels section of the script, with frame2 components added.<br />
[[File:RPCTOD_46_IDLEEditor_Labels.jpg|none|800px|Labels section of the script.]]<br />
<br />
<br />
Now we’ll add the new variables we referenced in the lines of code above, so they can be updated by different functions in the script with the get() and set() functions. Append the following lines of code to the Variables section of the script.<br />
<br />
<br />
<ul><br />
displayLastSun = StringVar() <br /n><br />
displayTimeData=StringVar() <br /n><br />
displayLocation=StringVar() <br /n><br />
azimuth = StringVar() <br /n><br />
elevation = StringVar()<br />
</ul><br />
<br />
<br />
The updated variables section of the script:<br />
[[File:RPCTOD_47_IDLEEditor_Variables.jpg|none|800px|Variables section of the script.]]<br />
<br />
<br />
The last step is to actually display the information, whenever the “Apply to Sun” button is clicked. To do this we need to update some of the whenWhere() function we’ve already created. Insert the following lines of code into that function, taking care to position them in the script as indicated in the screen shot.<br />
<br />
<br />
<ul><br />
displayTimeData.set(lookupTime) <br /n><br />
displayLocation.set(location) <br /n><br />
azimuth.set(results[0]) <br /n><br />
elevation.set(results[1]) <br /n><br />
displayLastSun.set(num.get())<br />
</ul><br />
<br />
<br />
The updated whenWhere() function now looks like this:<br />
[[File:RPCTOD_48_IDLEEditor_whenWhereFunction.jpg|none|800px|The updated whenWhere() function will now display messages.]]<br />
<br />
<br />
<br />
== In Conclusion ==<br />
<br />
This tutorial is meant to show you the possibilities of Terragen’s new RPC feature. How might you further modify this script to add even more functionality to it? <br />
* How about adding the ability to modify the camera’s exposure when the Sunlight node is below the horizon? What if more than one camera existed in the project? <br />
* How about a button that resets the sun’s heading and elevation to their default settings? <br />
* How would you add your location and timezone to the presets?<br />
* How would you turn off all the sunlight nodes at once? Back on again?<br />
<br />
Special thanks to John Clark Craig for making his Python script available to the public. <br />
<br />
<!-- The Terragen RPC Time of Day example script may be downloaded from Planetside Software [https://www.dropbox.com/s/x7gzd06mkqe8eoi/TerragenRPC_TimeOfDayExample.zip?dl=0 here]. --><br />
The Terragen RPC Time of Day example script may be downloaded from Planetside Software [https://www.dropbox.com/s/takc4hrcp59snum/TerragenRPC_TimeOfDayExample_v2.zip?dl=0 here].<br />
<br />
<br /n></div>Redmawhttps://planetside.co.uk/wiki/index.php?title=Terragen_RPC:_Time_of_Day_Example_Script&diff=16666Terragen RPC: Time of Day Example Script2023-06-28T18:24:55Z<p>Redmaw: Fixed link to Python</p>
<hr />
<div>Terragen RPC: Time of Day Example Script<br />
<br />
== Overview ==<br />
Terragen 4.6.30 Professional introduces a new feature for remote procedure calling, or RPC. With a running instance of Terragen acting as a server, other programs can make remote procedure calls to query and modify a Terragen project. <br />
<br />
The goal of this tutorial is to walk you through a step-by-step procedure using the Python programming language to create a script that changes the time of day in a Terragen project; in other words, provide the ability to modify a Sunlight node’s heading and elevation parameters outside of the Terragen program.<br />
<br />
__TOC__<br />
<br />
<br />
== Part 01: Set up ==<br />
For this tutorial you’ll first need to download and install Terragen 4.6.30 Professional as well as [https://www.python.org/ Python]. Once you’ve installed the new build of Terragen be sure to launch it.<br />
<br />
On a Windows computer you can verify that Python is installed by querying the operating system via the Command Prompt window.<br />
<br />
Type “command prompt” or “cmd” in the Windows search box and press “enter” to open the Command Prompt window.<br />
Type “python” on the command line of the Command Prompt window, and press “enter”.<br />
If installed, the version of Python will be displayed.<br />
<br />
[[File:RPCTOD_03_CmdPromptPython.jpg|none|800px|Command prompt windows displaying the installed version of Python.]]<br />
<br />
<br />
Next, create a folder for this project that is accessible to your computer network. We’ll be saving and executing the python script from this location, as well as other files and resources.<br />
<br />
This project uses the Python script “sunpos.py” created by John Clark Craig to calculate the sun’s heading and elevation values based on the time of day and a given location. You’ll need to download the script and save it in your project folder.<br />
<br />
* Download the “[https://levelup.gitconnected.com/python-sun-position-for-solar-energy-and-research-7a4ead801777 sunpos.py]” script and save it in your project folder as sunpos.py. <br /n><br />
* https://levelup.gitconnected.com/python-sun-position-for-solar-energy-and-research-7a4ead801777<br />
<br />
<br />
== Part 02: Coding the basic functionality ==<br />
In this section our goal is to simply pass the minimum requirements needed by the sunpos.py in order for it to calculate the sun’s heading and elevation; then pass along these results to the default Sun node in Terragen. <br />
<br />
We’ll use Python’s Integrated Development and Learning Environment, or IDLE, to write the code for our script.<br />
<br />
Type “IDLE” in the Windows search box and press “enter” to open the IDLE Shell window.<br />
<br />
[[File:RPCTOD_04_IDLEShell.jpg|None| 800px|IDLE Shell.]]<br />
<br />
<br />
While we can type and execute python commands directly in the IDLE shell, we’ll use the built-in file editor to create our script file so we can run it multiple times as we refine it. <br />
<br />
Select “New File” from the IDLE shell menu to open the file editor window.<br />
<br />
[[File:RPCTOD_05_IDLEShell_NewFile.jpg|none|800px|Select New File to open the file editor.]]<br />
<br />
<br />
Right now our script is completely blank, and in order for it to do something we first need to instruct Python to load certain modules or libraries when the script is run. Not surprisingly, we need to tell Python to load the Terragen RPC library and the sunpos.py script we previously downloaded. Additionally, you might need to tell Python where the Terragen RPC module was installed, as well as have the ability to query the operating system in order to access the date and time.<br />
[[File:RPCTOD_06_IDLEEditor.jpg|none|800px|Editor window.]]<br />
<br />
<br />
We’ll use the “import” keyword to insert a module into our script at run time. For some modules we’ll also use the “as” keyword to provide the module with an alias that we reference within the script.<br />
<br />
Type the following lines of code into the file editor window. You can also copy and paste them. <span style="color:red">Note that your Terragen RPC installation path may vary.</span><br />
<br />
<br />
<ul><br />
import sys <br/n><br />
sys.path.insert(0,'P:/PlanetsideSoftware/RPC/terragen_rpc-0.9.0/terragen_rpc-0.9.0') <br/n><br />
import terragen_rpc as tg <br/n><br />
import sunpos as sp<br />
</ul><br />
<br />
<br />
In order for the sunpos module to determine the sun’s heading and elevation, it needs to know a date, time, and a location. Initially we can supply this information via Python’s datetime module, so we’ll load that as well, this time using the “from” keyword instead of the “import” keyword in order to load just the portion of the module we want.<br />
<br />
<br />
<ul><br />
from datetime import datetime<br />
</ul><br />
<br />
<br />
Our script should look like this:<br />
[[File:RPCTOD_07_IDLEEditor_ImportModules.jpg|none|800px|Import the modules into the script.]]<br />
<br />
<br />
With the library modules imported into the script, our next step is to define the values which the sunpos module requires to make its calculations. Variables are used to store information that can be referenced or manipulated within a computer program, such as text and numbers. In Python we can define the variable and its value in the same statement.<br />
<br />
Let’s create four variables to hold this information, one for the computer system’s date and time, one for the location on Earth utilizing latitude and longitude coordinates, one for a timezone, and one last variable to format the date, time and timezone information the way that the sunpos module expects it to be.<br />
<br />
For this example, we’ll use the timezone, latitude, and longitude for Los Angeles, CA, USA. Notice that the location variable and the lookupTime variable consist of multiple components. This is referred to as a list in Python, and is somewhat similar to an array in other programming languages.<br />
<br />
<br />
<ul><br />
sysTime = datetime.now() <br /n><br />
timezone = -7 <br /n><br />
location = [34.052235, -104.741667] <br /n><br />
lookupTime = [sysTime.year, sysTime.month, sysTime.day, sysTime.hour, sysTime.minute, sysTime.second, timezone] <br /n><br />
</ul><br />
<br />
<br />
Our script now looks like this:<br />
[[File:RPCTOD_08_IDLEEditor_DefineVariables.jpg|none|800px|Define the variables.]]<br />
<br />
<br />
Now that the variables have been defined that will be passed to the sunpos module, we can call the module and save its results. All of this can be done with one Python statement. We’ll define a new variable on the left of the equals sign called “results” to store the sun’s heading and elevation information which will be returned by the call to the sunpos module.<br />
<br />
The statement to the right of the equals sign is the call to the sunpos module. The “sp” is the alias we defined at the start of the script to reference the sunpos module, and the “sunpos()” is the function within the module that gets run and calculates the sun’s heading and elevation. All the data needed to perform the calculations are passed to the module by listing the variables between the two parenthesis <br />
<br />
<br />
<ul><br />
results = sp.sunpos(lookupTime, location, True)<br />
</ul><br />
<br />
<br />
[[File:RPCTOD_09_IDLEEditor_ScriptResults.jpg|None|800px|Define a variable for the results from the sunpos module.]]<br />
<br />
<br />
The last step is to apply the results to our Terragen project via RPC. We’ll do that by telling Terragen which item we want to act upon, the default Sun node, then setting the new values for its heading and elevation parameters.<br />
<br />
The code below creates an instance of the item “Sunlight 01” called “node” which can be manipulated by our script. The “set_param” function then changes the heading and elevation to the values in the results[] list.<br />
<br />
<br />
<ul><br />
node = tg.node_by_path("Sunlight 01")<br />
node.set_param('heading',results[0])<br />
node.set_param('elevation',results[1])<br />
</ul><br />
<br />
<br />
Our script now looks like this:<br />
[[File:RPCTOD_10_IDLEEditor_CallTGPRC.jpg|none|800px|Call the Terragen RPC module]]<br />
<br />
<br />
Save the script in the project folder by selecting “File Save As” from the IDLE main menu and giving the script a name. Then press “F5” or select “Run > Run Module” from the IDLE main menu to run the script. The 3D Preview window in Terragen will update to show the new direction of the sun.<br />
<br />
[[File:RPCTOD_51_3DPreview.jpg|none|800px|The Sunlight 01 node's heading and elevation changed by the script.]]<br />
<br />
<br />
== Part 03: Coding a basic GUI ==<br />
<br /n><br />
[[File:RPCTOD_11_GUI.jpg|none|452px|A very simple GUI in order to manipulate the parameter values easier.]]<br />
<br />
<br />
In the previous section we showed how a simple script can be created to manipulate the sun’s heading and position. In this section we’ll make the simple graphical interface, or GUI, seen below in order to manipulate the values of each variable much easier.<br />
<br />
To build the GUI for our script we’ll take advantage of the “tk interface” package, or “tkinter”, included in Python. Note that in some cases, tkinter’s syntax may appear slightly different than the coding in the section above, for example in defining a variable.<br />
<br />
Just as in the previous section, the first step is to import the tkinter package into our script. When creating a script that includes a GUI these are typically the first lines of code in the script. So we’ll prepend our existing script with these two lines of code.<br />
<br />
The first line of code imports all the functions and built-in modules in the tkinter package into the script, while the second line of code imports the ttk package which is responsible for the style of the widgets (buttons, etc.)<br />
<br />
<br />
<ul><br />
From tkinter import * <br /n><br />
From tkinter import ttk<br />
</ul><br />
<br />
<br />
[[File:RPCTOD_12_IDLEEditor_ImportTkinter.jpg|none|800px|Import the tkinter package into the script.]]<br />
<br />
<br />
Next we’ll build the window for the GUI, starting with its size and title. We’ll insert these lines of code after the import statements and before the variables. It’s also useful to add notes or comments in a script as well. The “#” symbol instructs Python to ignore anything in the line of code that follows it. We’ll make use of this symbol to set reminders and stay organized in our script. <br />
<br />
In Python scripts the basic window is often defined as “root”, but for this example we’ll use the word “gui”.<br />
<br />
<br />
In the “title” attribute we can define text to display in the windows title bar. The “geometry” attribute sets the width and height of the window in pixels.<br />
<br />
<br />
<ul><br />
gui = Tk() <br /n><br />
gui.title("RPC - Time of day") <br /n><br />
gui.geometry("300x250") <br /n><br />
</ul><br />
<br />
<br />
Whenever you make a GUI, you want to include a looping function for that window at the end of the script, which in effect causes the script to wait for user input. This will remain the last line of code in our script. Add the following line of code at the bottom of the script.<br />
<br />
<br />
<ul><br />
gui.mainloop()<br />
</ul><br />
<br />
<br />
Here's the script so far:<br />
[[File:RPCTOD_50_IDLEEditor_DefineWindowMainLoop.jpg|none|800px|Create the GUI window.]]<br />
<br />
<br />
In order to individually adjust each component that makes up the date and time information, we need to define a variable for each part. We’ll create these variables using tkinter functions, such as IntVar(). These functions accept many arguments such as a window name or a value. In our script, “gui” is the window name, and the value comes from the sysTime variable’s attributes we previously defined.<br />
<br />
Insert the lines of code below, following the sysTime variable statement.<br />
<br />
<br />
<ul><br />
year = IntVar(gui,value = sysTime.year) <br /n><br />
month = IntVar(gui,value = sysTime.month) <br /n><br />
day = IntVar(gui,value = sysTime.day) <br /n><br />
hour = IntVar(gui,value = sysTime.hour) <br /n><br />
minute = IntVar(gui,value = sysTime.minute) <br /n><br />
second = IntVar(gui,value = sysTime.second) <br /n><br />
</ul><br />
<br />
<br />
We need to rewrite the timezone variable definition in the tkinter syntax too.<br />
<br />
<br />
<ul><br />
timezone = IntVar(gui,value = -7) <br />
</ul><br />
<br />
<br />
Just as with the date and time information, we need to split the location data into latitude and longitude, which use decimal precision and therefore require a different tkinter function, DoubleVar(). As these variables will replace the existing location variable, we can comment out that line of code so that Python ignores it.<br />
<br />
The “#” at the beginning of the line of code defining the location variable, so Python ignores it.<br />
<br />
<br />
<ul><br />
<nowiki>#</nowiki> location = [34.052235, -104.741667] <br /n><br />
latitude = DoubleVar(gui,value = 34.052235) <br /n><br />
longitude = DoubleVar(gui,value = -104.741667) <br /n><br />
</ul><br />
<br />
<br />
[[File:RPCTOD_14_IDLEEditor_DefineVariables.jpg|none|800px|Defining the variables for each part of the time, date and location data.]]<br />
<br />
<br />
Now that we’ve accounted for all the individual variables we can add them to the GUI. There are several ways to do this in Python, and for this tutorial we’ll use the grid method, which will allow us to align each variable with a corresponding text label to describe it.<br />
<br />
Python refers to the GUI components, such as labels and buttons, as widgets. We’ll begin by creating a widget for each label. Each widget needs a unique name, so for the label widgets we’ll use “l_” followed by the name of the data it contains, for example “l_year”. The labels themselves will include information for the window it belongs on, the text to display, its position within the window, and how it is aligned. All the labels will belong to the “gui” window for now, and the text to be displayed is simply their name. The grid method uses rows and columns to position the label, and a “sticky” attribute to align the label.<br />
<br />
<br />
The last widget allows us to insert a blank line that spans both columns.<br />
<br />
<br />
<ul><br />
l_year = Label(gui,text = 'Year').grid(row=0,column=0,sticky='w') <br /n><br />
l_month = Label(gui,text = 'Month').grid(row=1, column=0,sticky='w') <br /n><br />
l_day = Label(gui,text = 'Day').grid(row=2, column=0,sticky='w') <br /n><br />
l_hour = Label(gui,text = 'Hour').grid(row=3, column=0,sticky='w') <br /n><br />
l_minute = Label(gui,text = 'Minute').grid(row=4, column=0,sticky='w') <br /n><br />
l_second = Label(gui,text = 'Second').grid(row=5, column=0,sticky='w') <br /n><br />
l_timezone = Label(gui,text = 'Timezone').grid(row=6, column=0,sticky='w') <br /n><br />
l_latitude = Label(gui,text='Latitude (N)').grid(row=7,column=0,sticky='w') <br /n><br />
l_longitude = Label(gui,text='Longitude (W)').grid(row=8,column=0,sticky='w') <br /n><br />
l_null = Label(gui,text=" ").grid(row=9,columnspan=2) <br /n><br />
</ul><br />
<br />
<br />
The code for the labels should look like this:<br />
<br />
[[File:RPCTOD_15_IDLEEditor_DefineLabels.jpg|none|800px|Defining the label widgets.]]<br />
<br />
<br />
While the Label widget is used to display text and can not be changed by the user, the Entry widget accepts a starting value and user input. We’ll set each Entry’s “textvariable” to the corresponding initial value we assigned to the variables. Later, the user can change these values by simply entering a different value. To describe the widget for each Entry, we’ll use the prefix “e_” and the name of the Entry, for example “e_year”.<br />
<br />
<br />
<ul><br />
e_year = Entry(gui, textvariable = year,width=6).grid(row=0,column=2, sticky='e') <br /n><br />
e_month = Entry(gui, textvariable = month,width=6).grid(row=1,column=2, sticky='e') <br /n><br />
e_day = Entry(gui, textvariable = day,width=6).grid(row=2,column=2, sticky='e') <br /n><br />
e_hour = Entry(gui, textvariable = hour,width=6).grid(row=3,column=2, sticky='e') <br /n><br />
e_minute = Entry(gui, textvariable = minute,width=6).grid(row=4,column=2, sticky='e') <br /n><br />
e_second = Entry(gui, textvariable = second,width=6).grid(row=5,column=2, sticky='e') <br /n><br />
e_timezone = Entry(gui, textvariable = timezone,width=6).grid(row=6, column = 2, sticky='e') <br /n><br />
e_latitude = Entry(gui, textvariable = latitude,width=16).grid(row=7, column =2) <br /n><br />
e_longitude = Entry(gui, textvariable = longitude,width=16).grid (row=8,column =2) <br /n><br />
</ul><br />
<br />
<br />
The code for the Entry section should look like this:<br />
[[File:RPCTOD_16_IDLEEditor_DefineEntry.jpg|none|800px|Defining the entry widgets.]]<br />
<br />
<br />
Now that we can change the initial values for each parameter, we need a way to tell the script when to calculate our changes. For this we’ll use a button widget. Like the other widgets, the button can contain several bits of information, such as the text to display on the button. Most importantly it contains a command attribute, telling it what to do when the button has been clicked. In this example, the script will run the function “whenWhere”, which is yet to be defined. <br />
<br />
<br />
<ul><br />
b_ApplySun = Button(gui,text='Apply to Sun',bg='yellow',command=whenWhere).grid(row=11,column=2)<br />
</ul><br />
<br />
<br />
This is the code for the button section of the script.<br />
[[File:RPCTOD_17_IDLEEditor_DefineButton.jpg|none|800px|Defining the button widget.]]<br />
<br />
<br />
To define the whenWhere() function, insert the code below into the script following the variables section and before the labels section. Note that it’s important to indent the lines of code after defining the function. <br />
<br />
Using the get() function the “lookupTime” variable is updated with the current date and time values and the “location” variable is updated with the current latitude and longitude values. Note, some of these variables will be defined immediately after this step.<br />
<br />
The next statement first passes three values to the sunpos module, then it accepts the returned data in the “results” variable as a list containing the heading and elevation.<br />
<br />
Then the function “setTerragenSunHeadingAndElevation” is called, passing along the text “Sunlight 01” and the results variable containing the sun heading and elevation.<br />
<br />
<br />
<ul><br />
def whenWhere(): <br /n><br />
: lookupTime = [year.get(), month.get(), day.get(), hour.get(), minute.get(), second.get(),timezone.get()] <br /n><br />
: location = [latitude.get(),longitude.get()] <br /n><br />
: results = sp.sunpos(lookupTime,location,True) <br /n><br />
: setTerragenSunHeadingAndElevation(“Sunlight 01”,results) <br /n><br />
</ul><br />
<br />
<br />
This is the code defining the whenWhere() function.<br />
[[File:RPCTOD_18_IDLEEditor_whenWhereFunction.jpg|none|800px|Create the lookupTime function.]]<br />
<br />
<br />
<br />
Since we’ve included the “lookupTime” variable within the whenWhere() function, and it is updated by the get() functions whenever the whenWhere() function is called, we no longer need it in the variables section. <br />
<br />
<span style="color:red">Delete the line of code in the variables section that defines the “lookupTime” variable.</span><br />
<br />
Also included in the whenWhere() function was the call to a function that performs the actual RPC commands and updates Terragen. The function will accept two arguments, one for the item to modify in Terragen which is the Sunlight 01 node, and the other, a list, containing the heading and elevation values. Let’s code that function directly beneath the previous function in the script. <br />
<br />
Once you’ve completed coding the function below you can <span style="color:red">remove the previous lines of code created in Part 02 of this tutorial</span> that did the same thing.<br />
<br />
This function includes error checking commands such as “try”, “except”, and “raise”. Checking for potential errors or exceptions allows the program to handle them before they cause a problem, like crashing the application. For example, if a user entered text data in a numeric field, and the program expected numeric data, then an error would occur and the application might crash. With error handling, the application could display a message indicating the wrong type of data was entered and giving the user an opportunity to change the data. You can read more about the error checking command in the Terragen RPC documentation. <br />
<br />
<br />
<ul><br />
def setTerragenSunHeadingAndElevation(name,values): <br /n><br />
: try: <br /n><br />
:: node = tg.node_by_path(name) <br /n><br />
:: try: <br /n><br />
::: node.set_param('heading',values[0]) <br /n><br />
::: node.set_param('elevation',values[1]) <br /n><br />
:: except AttributeError as err: <br /n><br />
::: showError("Sunlight not in project. Refresh list.") <br /n><br />
:: except ConnectionError as e: <br /n><br />
::: showError("Terragen RPC connection error: " + str(e)) <br /n><br />
:: except TimeoutError as e: <br /n><br />
::: showError("Terragen RPC timeout error: " + str(e)) <br /n><br />
:: except tg.ReplyError as e: <br /n><br />
::: showError("Terragen RPC server reply error: " + str(e)) <br /n><br />
:: except tg.ApiError: <br /n><br />
::: showError("Terragen RPC API error") <br /n><br />
::: raise <br />
</ul><br />
<br />
<br />
Here is the code for the setTerragenSunHeadingAndElevation function.<br />
[[File:RPCTOD_19_IDLEEditor_SetTGSunHeadElevFunction.jpg|none|800px|The setTerragenSunHeadingAndElevation function.]]<br />
<br />
<br />
The last step in this section is to create another function which displays any message passed to it by another part of the script. This function receives one input, the message to be displayed. <br />
<br />
<br />
<ul><br />
def showError(text): <br/n><br />
: errMsg.set(text)<br />
</ul><br />
<br />
<br />
This is the code for the showError() function.<br />
[[File:RPCTOD_20_IDLEEditor_showErrorFunction.jpg|none|800px|The showError function.]]<br />
<br />
<br />
We need to create one last variable to store any messages set by the error checking processes. We can define the new variable at the end of the variables section of the script.<br />
<br />
<br />
<ul><br />
errMsg = StringVar()<br />
</ul><br />
<br />
<br />
Here is the variable section of the script.<br />
[[File:RPCTOD_21_IDLEEditor_Variables.jpg|none|800px|The defined variables.]]<br />
<br />
<br />
Save and run the script. Try entering new values, especially for the hour of the day, and applying them to the Terragen sun.<br />
<br />
<br />
== Part 04: Modifying parameters more easily ==<br />
<br /n><br />
[[File:RPCTOD_23_IDLEEditor_GUI_Part04.jpg|none|452px|The GUI with sliders added.]]<br />
<br />
<br />
<br />
Now that we have a GUI and can modify the values for the time of day and location, let’s make it easier to make those modifications by adding sliders to the interface for the date and time parameters.<br />
<br />
A slider can be created with Tkinter’s Scale widget. We’ll use the prefix “s_” and the name of each parameter to define each slider. Scale widgets can also accept arguments, such as which window to be positioned in, a range of values to slide between, and the orientation of the slider. We’ll set an appropriate range for each slider, for example: 1 - 12, for the Month parameter, and orient the sliders horizontally so they best fit the layout of the GUI we’ve already created.<br />
<br />
Insert the following lines of code into the script, between the Labels section and the Entry section. <br />
<br />
<br />
<ul><br />
s_year = Scale(gui,from_= 1900, to = 2099,variable=year,orient=HORIZONTAL,showvalue=0).grid(row=0,column=1) <br /n><br />
s_month = Scale(gui,from_= 1, to = 12,variable=month,orient=HORIZONTAL,showvalue=0).grid(row=1,column=1) <br /n><br />
s_day = Scale(gui,from_= 1, to =31,variable=day,orient=HORIZONTAL,showvalue=0).grid(row=2,column=1) <br /n><br />
s_hour = Scale(gui,from_= 0, to = 23,variable=hour,orient=HORIZONTAL,showvalue=0).grid(row=3,column=1) <br /n><br />
s_minute = Scale(gui,from_= 0,to = 59,variable=minute,orient=HORIZONTAL,showvalue=0).grid(row=4,column=1) <br /n><br />
s_second = Scale(gui,from_= 0, to = 59,variable=second,orient=HORIZONTAL,showvalue=0).grid(row=5,column=1) <br /n><br />
s_timezone = Scale(gui,from_= -13, to = 13,variable=timezone,orient=HORIZONTAL,showvalue=0).grid(row=6,column=1) <br /n><br />
</ul><br />
<br />
<br />
Here’s the sliders section of the script:<br />
[[File:RPCTOD_24_IDLEEditor_Sliders.jpg|none|800px|Sliders section.]]<br />
<br />
<br />
You may have noticed in the lines of code above, that the slider’s grid position is located in column 1, and in the previous step the label’s grid position was located in column 0, while the entry’s grid position was located in column 2. This allows our slider to drop in between the label and the entry columns of the GUI. <br />
<br />
Visually, the GUI will look better if the latitude and longitude entry fields align with the sliders because of their width; and that the Apply button is centered within the GUI, so modify all their grid positions to column 1 as well.<br />
<br />
<br />
<ul><br />
e_latitude = Entry(gui, textvariable = latitude,width=16).grid(row=7, column =1) <br /n><br />
e_longitude = Entry(gui, textvariable = longitude,width=16).grid (row=8,column =1) <br /n><br />
b_ApplySun = Button(gui,text='Apply to Sun',bg='yellow',command=whenWhere).grid(row=11,column=1) <br /n><br />
</ul><br />
<br />
<br />
If you save the script and run it, you’ll notice how much easier it is to adjust the time and date parameters with the sliders, while still being able to enter a precise numerical value in the Entry field.<br />
<br />
<br />
== Part 05: Multiple sunlight nodes ==<br />
<br /n><br />
[[File:RPCTOD_27_IDLEEditor_GUI_Part05.jpg|none|800px|The final GUI for Part 05.]]<br />
<br />
<br />
<i>“But, what if there is more than one sunlight node in the Terragen project, or it’s named something else, or it’s missing entirely?” </i> Good questions, and in this section we’ll address them while expanding the functionality of the script and GUI.<br />
<br />
<br />
So far all of the GUI components have been located in the “gui” window. Tkinter allows you to divide the window into smaller frames. You can then tell each component which frame to be displayed in. <br />
<br />
To accommodate the features we’ll be adding to the script we can start by making the GUI larger. Edit the existing line of code that sets the width and height of the GUI as indicated below.<br />
<br />
<br />
<ul><br />
gui.geometry("900x600")<br />
</ul><br />
<br />
<br />
We define a frame using the LabelFrame widget. Just like the other widgets, it accepts options for which window to be located in, and what text to display, if any. For starters we’ll create two frames. The first frame will be used to select the available Sunlight nodes in the project, and the second frame will contain the parameters we’ve already coded. <br />
<br />
To define the frames, enter the following lines of code right below the existing code that defines the window.<br />
<br />
The first frame, frame0, will reside in the “gui” window and display the text “Select sunlight node / Click drop-down arrow to refresh list:”. It has 20 pixels of padding on its left and right, and 10 pixels of padding above and below it. No border will be drawn around the frame because “relief” set to FLAT.<br />
<br />
<br />
<ul><br />
frame0 = LabelFrame(gui,text="Select sunlight node / Click drop-down arrow to refresh list:",relief=FLAT, padx=20,pady=10)<br />
</ul><br />
<br />
<br />
The second frame, frame1, will aslo reside in the “gui” window. It will display the text “Enter time and location:”. It has 10 pixels of padding all around it and will display a border.<br />
<br />
<br />
<ul><br />
frame1 = LabelFrame(gui,text="Enter time and location: ",padx=10,pady=10)<br />
</ul><br />
<br />
<br />
Then skip a line and enter the following lines of code which define where the frames are positioned within the gui window.<br />
<br />
<br />
<ul><br />
frame0.grid(row=0,column=0,padx=5,pady=5) <br /><br />
frame1.grid(row=10,column=0,padx=25,pady=0,sticky='w')<br />
</ul><br />
<br />
<br />
The code should look like this:<br />
[[File:RPCTOD_25_IDLEEditor_DefineFrames.jpg|none|800px|Defining the frame.]]<br />
<br />
<br />
In order for the frames to have any effect, the display option for the labels, sliders, entry, and buttons needs to be changed from “gui” to the one of the frames. <span style="color:red">Edit the lines of code for each labels, sliders, entry, and button which are currently coded to “gui” to “frame1”. </span><br />
<br />
The code for the labels, sliders, entry and button now look like this:<br />
[[File:RPCTOD_26_IDLEEditor_LabelsSlidersEntryButtonFrames.jpg|none|800px|The labels, sliders, entry and buttons edited to reflect the new frame location.]]<br />
<br />
<br />
So far the design of our script has been to send certain information to Terragen in order to change something in the project, but now we want Terragen to provide some information about the project first. Specifically, we want to know what sunlight type nodes are in the project as soon as the script is run. Our GUI needs to reflect this information and provide us with the ability to select among the sunlight nodes.<br />
<br />
Coding the query to Terragen consists of two parts. Part one will be a way in which to store the sunlight node names and IDs returned from Terragen. Part two will be a function to make the request to Terragen for the sunlight node information.<br />
<br />
Enter the following line of code into the script, after the functions already defined and before the labels section.<br />
<br />
This creates the variable “sunNodes” which will store the returned information from the getSunlightInProject() function. <br />
<br />
<br />
<ul><br />
sunNodes = getSunlightInProject()<br />
</ul><br />
<br />
<br />
Next, create the new function getSunlightInProject() just below the last function currently in the script by entering the lines of code below. Note, that this function also includes the same kind of error checking as in Part 3; please see that section for more explanation on capturing errors and exceptions. This function initializes the variables “nodeIDs” and “nodeNames” so that they’re empty each time this function is run, then populates them with the current information from the Terragen project. <br />
<br />
<br />
<ul><br />
def getSunlightInProject(): <br /n><br />
: try: <br /n><br />
:: project = tg.root() <br /n><br />
:: nodeIDs = [] <br /n><br />
:: nodeNames = [] <br /n><br />
<!-- :: nodeIDs = tg.children_filtered_by_class(project,'sunlight') <br /n> --><br />
:: nodeIDs = project.children_filtered_by_class(‘sunlight’) <br /n><br />
:: for nodes in nodeIDs: <br /n><br />
<!-- ::: nodeNames.append(tg.name(nodes)) <br /n> --><br />
::: nodeNames.append(nodes.name()) <br /n><br />
:: return nodeIDs,nodeNames <br /n><br />
: except ConnectionError as e: <br /n><br />
:: showError("Terragen RPC connection error: " + str(e)) <br /n><br />
: except TimeoutError as e: <br /n><br />
:: showError("Terragen RPC timeout error: " + str(e)) <br /n><br />
: except tg.ReplyError as e: <br /n><br />
:: showError("Terragen RPC server reply error: " + str(e)) <br /n><br />
: except tg.ApiError: <br /n><br />
:: showError("Terragen RPC API error") <br /n><br />
:: raise<br />
</ul><br />
<br />
<br />
The code for the function call looks like this:<br />
<!-- [[File:RPCTOD_28_IDLEEditor_GetSunlightInProjectFunction.jpg|none|800px|The getSunlightInProject() function.]] --><br />
[[File:RPCTOD_52_IDLEEditor_GetSunlightInProjectFunction.jpg|none|800px|The getSunlightInProject() function.]]<br />
<br />
<br />
Now that we have the names of the sunlight nodes in the project, we can add them to our GUI. Currently, the labels, sliders, entries and Apply button reside in the “frame1” portion of the GUI, so we’ll place the names of the sunlight nodes in the “frame0” portion of the GUI and use a widget known as a Combobox to do so. As we’ve seen with other widgets, Comboboxes also accept options such as which part of the window to be displayed in. <br />
<br />
Enter the following lines of code right after the statement to call the getSunlightInProject() function and before the Labels section.<br />
<br />
The postcommand option is run each time a change is made to the Combobox. In this example, the updateCBList function is run, ensuring that the list of Sunlight nodes is always up to date. The textvariable “num” acts as a pointer to which item in the list is acted upon.<br />
<br />
<br />
<ul><br />
cb_suns = ttk.Combobox(frame0,textvariable=num,postcommand=updateCbList)<br />
</ul><br />
<br />
<br />
Here's the code for the Combobox:<br />
[[File:RPCTOD_29_IDLEEditor_ComboBox.jpg|none|800px|Combobox.]]<br />
<br />
<br />
Next we need to define the new variable and function that we referenced in the above line of code.<br />
<br />
In the variables section of our script, add the following to define the pointer variable “num”. By using tkinter’s StringVar() function we can later retrieve and store new values to the variable with the get() and set() functions.<br />
<br />
<br />
<ul><br />
num = StringVar()<br />
</ul><br />
<br />
<br />
Here is the variables section of the script after adding the num variable:<br />
[[File:RPCTOD_30_IDLEEditor_VariablesNum.jpg|none|800px|Add the num variable in the variables section.]]<br />
<br />
<br />
Following the last function we defined in our script, add the following lines of code. Note there is error checking in this function because it’s possible that a sunlight node is no longer in the Terragen project or the names have been changed since the last time this query was made.<br />
<br />
<br />
<ul><br />
def updateCbList(): <br /n><br />
: updateSuns = getSunlightInProject() <br /n><br />
: try: <br /n><br />
:: updateSunID.set(updateSuns[0]) <br /n><br />
:: updateSunNames.set(updateSuns[1]) <br /n><br />
:: cb_suns["values"] = updateSuns[1] <br /n><br />
:: showError(" ") <br /n><br />
: except TypeError as e: <br /n><br />
:: showError("Sunlight nodes not found. " + str(e))<br />
</ul><br />
<br />
<br />
The code for this function looks like this:<br />
[[File:RPCTOD_31_IDLEEditor_updateCbListFunction.jpg|none|800px|The updateCbList() function.]]<br />
<br />
<br />
We need to create the two variables we just reference in the above function so they can store the sunlight node ID numbers and names, and get updated whenever the updateCbList() function is called. At the end of the variables section in the script add these lines of code:<br />
<br />
<br />
<ul><br />
updateSunID = StringVar() <br /n><br />
updateSunNames = StringVar()”<br />
</ul><br />
<br />
<br />
The code for the variable section now looks like this:<br />
[[File:RPCTOD_32_IDLEEditor_Variables.jpg|none|800px|The variables section.]]<br />
<br />
<br />
Return to the script and directly beneath the line of code that creates the Combobox add these lines of code, in order to display the updated list of Sunlight nodes and to catch any errors or exceptions. <br />
<br />
<br />
<ul><br />
Try: <br /n><br />
: cb_suns["values"]=r[1]<br />
: cb_suns.current(0)<br />
except TypeError as e:<br />
: showError("No sunlight nodes in project or Terragen not running." + str(e))<br />
</ul><br />
<br />
<br />
To position the Combobox within frame0 add this line of code.<br />
<br />
<br />
<ul><br />
cb_suns.grid(row=0,column=0)<br />
</ul><br />
<br />
<br />
When dealing with multiple sunlight nodes in the project, it would be helpful if there was a way within the script to enable or disable each sunlight node. Since we’ve already created a Combobox to select a sunlight node, we can easily add a bit of code to enable or disable the selected node. We’ll use a button widget for this.<br />
<br />
Just after the Combobox code and before the Labels code, add this line of code to create the button to create a button to the right of the Combobox position that will execute the ckboxSun command when clicked.<br />
<br />
<br />
<ul><br />
b_SunOnOff = Button(frame0,text="Toggle sun on/off",bg='pink',command=ckboxSun).grid(row=0,column=1,padx=10)<br />
</ul><br />
<br />
<br />
The code for the Combobox should look like this:<br />
[[File:RPCTOD_33_IDLEEditor_MainAndComboBox.jpg|none|800px|The Combobox and button.]]<br />
<br />
<br />
Now we’ll create the ckboxSun() function that gets called when the button is clicked. Just below the lines of code for the last function we wrote add the following to call the toggleTerragenSun() function, passing it the index number for the currently selected item in the Combobox.<br />
<br />
<br />
<ul><br />
def ckboxSun(): <br /n><br />
: toggleTerragenSun(num.get())<br />
</ul><br />
<br />
<br />
You might wonder why we call one function when the button gets clicked and then another function. The reason is to keep the functions themselves as simple as possible. That way they can be used by other functions within the script. Let’s create the toggleTerragenSun() function so we can turn the sun on and off. Just below the last function type the following lines of code, which also contain some error checking.<br />
<br />
<br />
<ul><br />
def toggleTerragenSun(name): <br /n><br />
: try: <br /n><br />
:: node = tg.node_by_path(name) <br /n><br />
:: try: <br /n><br />
::: x = node.get_param_as_int('enable') <br /n><br />
:: except AttributeError as err: <br /n><br />
::: showError("Sunlight not in project. Refresh list.") <br /n><br />
:: else: <br /n><br />
::: if x == 1: <br /n><br />
:::: node.set_param('enable',0) <br /n><br />
::: else: <br /n><br />
:::: node.set_param('enable',1) <br /n><br />
: except ConnectionError as e: <br /n><br />
:: showError("Terragen RPC connection error: " + str(e)) <br /n><br />
: except TimeoutError as e: <br /n><br />
:: showError("Terragen RPC timeout error: " + str(e)) <br /n><br />
: except tg.ReplyError as e: <br /n><br />
:: showError("Terragen RPC server reply error: " + str(e)) <br /n><br />
: except tg.ApiError: <br /n><br />
:: showError("Terragen RPC API error") <br /n><br />
:: raise <br />
</ul><br />
<br />
<br />
Here is the code for the above functions.<br />
[[File:RPCTOD_34_IDLEEditor_ckBoxSunAndToggleTGSunFunctions.jpg|none|800px|Code for the ckboxSun() function and toggleTerragenSun() function.]]<br />
<br />
<br />
Save the script and run it. In Terragen, duplicate the Sunlight object a few times, then click in the Combobox and enable or disable the lights, and change their heading and elevation.<br />
<br />
<br />
== Part 06: Presets ==<br />
<br /n><br />
[[File:RPCTOD_43_GUI_Part06.jpg|none|800px|GUI for Part 06.]]<br />
<br />
<br />
Let’s add one more section to our GUI which contains presets for selected locations and timezones around the world. This way we can jump from the Los Angeles timezone to any other timezone around the world with a click of a button. <br />
<br />
[[File:RPCTOD_44_GUI_PresetsDropDownList.jpg|none|417px|Presets drop down list.]]<br />
<br />
<br />
We’ll create an external text file to hold the data so that it can be easily customized in any text editor, such as Notepad, and save the file in the CSV format which stands for “comma separated values”. As the format name implies each piece of data is separated from the next by a comma. Each row in the file should be formatted as follows: Timezone City/Country , longitude, latitude, timezone offset<br />
<br />
Copy and paste the following data into a text file and save it as presets_latLong.csv. As you can see from the “Timezone City/Country” data the entries are organized from timezone -11 to timezone +13. This is the order that the data will show up in the GUI.<br />
<br />
<ul><br />
-11 Liku/Niue, -169.792327,-19.053680, -11.0 <br /n><br />
-10 Honolulu/Hawaii, -157.854995, 21.306647, -10.0 <br /n><br />
-9 Adak/Adak Island, -176.660156, 51.862923, -9.0 <br /n><br />
-8 Anchorage/Alaska, -149.721679, 61.227957, -8.0 <br /n><br />
-7 Los Angeles/USA, -104.741667, 34.052235, -7.0 <br /n><br />
-7 Vancouver/Canada, -123.108673, 49.263323,-7.0 <br /n><br />
-6 Calgary/Canada, -114.071044, 51.058660, -6.0 <br /n><br />
-5 Winnipeg/Canada, -97.141113, 49.866316, -5.0 <br /n><br />
-4 Igloolik/Igloolik Island, -81.806259, 69.379587, -4.0 <br /n><br />
-4 Ontario/Canada, -84.887696, 49.315573, -4.0 <br /n><br />
-3 Salvador/Brazil, -38.480987, -12.973780, -3.0 <br /n><br />
-3 Goose Bay/Newfoundland, -60.342407, 53.304621,-3.0 <br /n><br />
-2 Nuuk/Greenland, -51.723632, 64.182464, -2.0 <br /n><br />
-1 Sao Filipe/Fogo, -24.483718, 14.890709,-1.0 <br /n><br />
0 Reykjavik/Iceland, -21.928710,64.144161, 0.0 <br /n><br />
+1 Dublin/Ireland, -6.249847, 53.350551, 1.0 <br /n><br />
+1 Greenwhich/UK, -0.193634, 51.489293, 1.0 <br /n><br />
+2 Brussels/Belgium, 4.390869, 50.824444, 2.0 <br /n><br />
+2 Berlin/Germany, 13.405151, 52.511763, 2.0 <br /n><br />
+2 Krakow/Poland, 19.929199, 50.050084, 2.0 <br /n><br />
+3 Odesa/Ukraine, 30.739746, 46.468132, 3.0 <br /n><br />
+3 Novgorodd/Russia, 43.978271, 56.307396, 3.0 <br /n><br />
+4 Baku/Azerbaijan, 49.844970, 40.390488, 4.0 <br /n><br />
+4 Dubai/UAE, 55.341795, 25.138654, 4.0 <br /n><br />
+5 Islamabad/Pakistan, 73.044433, 33.706061, 5.0 <br /n><br />
+6 Jahore/Bangladesh, 89.229125, 23.179080, 6.0 <br /n><br />
+7 Bangkok/Thailand, 100.546875, 13.496472, 7.0 <br /n><br />
+8 Beijing/China, 116.389160, 39.884450, 8.0 <br /n><br />
+8 Manilla/Philippines, 120.878899, 14.491408, 8.0 <br /n><br />
+9 Sapporo/Japan, 141.344604, 43.056847, 9.0 <br /n><br />
+10 Andersen AFB/Guam, 144.924087, 13.580586, 10.0 <br /n><br />
+11 Magadan/Russia, 150.805206, 59.568418, 11.0 <br /n><br />
+12 Queenstown/New Zealand, 168.618640, -45.104546, 12.0 <br /n><br />
+12 McMurdo Station/Antartica, 166.690063, -77.84011, 12.0 <br /n><br />
+13 Nomuka/Tonga, -174.802093, -20.251890, 13.0 <br /n><br />
</ul><br />
<br />
<br />
With the presets file saved, let’s load the data into our script. Immediately following the line of code that retrieves the Sunlight nodes in the Terragen project add the line of code:<br />
<br />
<br />
<ul><br />
presets()<br />
</ul><br />
<br />
<br />
[[File:RPCTOD_35_IDLEEditor_Presets.jpg|none|800px|The statement that calls the presets() function.]]<br />
<br />
<br />
Then create the new presets() function with the following lines of code, which will read the contents of the text file in “read only” mode, one line at a time. Each line is split into data each time the comma separator is encountered and these bits of data are appended to new variables for the Timezone City/Country, longitude, latitude and timezone offset.<br />
<br />
<br />
<ul><br />
def presets(): <br /n><br />
: file = open('presets_latlong.csv',"r") <br /n><br />
: content = file.readlines() <br /n><br />
: for line in content: <br /n><br />
:: x,y,z,a = line.strip().split(',') <br /n><br />
:: plocation.append(x) <br /n><br />
:: plat.append(z) <br /n><br />
:: plon.append(y) <br /n><br />
:: ptz.append(a) <br /n><br />
: file.close()<br />
</ul><br />
<br />
<br />
The code for the presets() function looks like this:<br />
[[File:RPCTOD_36_IDLEEditor_PresetsFunction.jpg|none|800px|The presets function.]]<br />
<br />
<br />
We referenced four new variables in the presets() function above, which store the data from the presets text file. Since these variables are created when the script is first run and their values do not change during the execution of the script, we can define them using standard Python variables, and not tkinter variables such as with the StringVar() function. Add these variables to the variables section of the script.<br />
<br />
<br />
<ul><br />
plocation = [] <br/n><br />
plat = [] <br/n><br />
plon = [] <br/n><br />
ptz = []<br />
</ul><br />
<br />
<br />
Here's the updated variables section in our script:<br />
[[File:RPCTOD_37_IDLEEditor_Variables.jpg|none|800px|The variables section of the script.]]<br />
<br />
<br />
We’ll add another frame to our main window and place the GUI components to display the presets there. We’ll need to add another Combobox to display the preset choices, labels for descriptions, and a button in order to update the variables for the timezone, latitude and longitude. Following the last line of code in the script add the lines below:<br />
<br />
<br />
<ul><br />
cb_presets = ttk.Combobox(frame3,textvariable=pnum) <br /n><br />
cb_presets["values"]=plocation <br /n><br />
cb_presets.current(16) <br /n><br />
cb_presets.grid(row=0,column=1,sticky='w') <br /n><br />
<br /n><br />
l_presetlocation = Label(frame3,text="Location: ").grid(row=0,column=0,sticky='w') <br /n><br />
l_presetLat = Label(frame3,text="Latitude:").grid(row=1,column=0,sticky='w') <br /n><br />
l_presetLat = Label(frame3,text="Longitude:").grid(row=2,column=0,sticky='w') <br /n><br />
l_presetTz = Label(frame3,text="Timezone:").grid(row=3,column=0,sticky='w') <br /n><br />
l_presetDispLat = Label(frame3,textvariable=displayLat).grid(row=1,column=1,sticky='w') <br /n><br />
l_presetDispLon = Label(frame3,textvariable=displayLon).grid(row=2,column=1,sticky='w') <br /n><br />
l_presetDispTz = Label (frame3, textvariable=displayTz).grid(row=3,column=1,sticky='w') <br /n><br />
<br /n><br />
b1 = Button(frame3,text="Apply Preset",command=setLatLon).grid(row=5,column=1,pady=5,sticky='w') <br /n><br />
</ul><br />
<br />
<br />
Here is the code for the frame3 components:<br />
[[File:RPCTOD_38_IDLEEditor_ComboboxPresets.jpg|none|800px|The Combobox for the Presets.]]<br />
<br />
<br />
We referenced a new frame and variable in the above lines of code to define the Combobox, so let’s add both those now.<br />
<br />
Append the following line of code to the section of our script where we’ve defined the window title, size and frames. Note, we will define frame2 in the last section of this tutorial.<br />
<br />
<br />
<ul><br />
frame3 = LabelFrame(gui,text="Location presets: ",padx=10,pady=10) <br /n><br />
frame3.grid(row=15,column=0,padx=25,pady=10,sticky='w')<br />
</ul><br />
<br />
<br />
Here's the updated lines of code for the window section:<br />
[[File:RPCTOD_39_IDLEEditor_Frame3Window.jpg|none|800px|Defining frame3 in the GUI window.]]<br />
<br />
<br />
Then add the following line of code the the variables section of our script to account for the new variable, which identifies the current item in the preset list.<br />
<br />
<br />
<ul><br />
pnum=StringVar()<br />
</ul><br />
<br />
<br />
Here's the updated variables section:<br />
[[File:RPCTOD_40_IDLEEditor_Variables.jpg|none|800px|Variables section.]]<br />
<br />
<br />
The last step to our preset code is to define the following function which will update and display the latitude, longitude and timezone variables to those of the selected preset.<br />
<br />
<br />
<ul><br />
def setLatLon(): <br /n><br />
: v = cb_presets.current() <br /n><br />
: displayLat.set(plat[v]) <br /n><br />
: displayLon.set(plon[v]) <br /n><br />
: displayTz.set(ptz[v]) <br /n><br />
: latitude.set(plat[v]) <br /n><br />
: longitude.set(plon[v])<br /n><br />
: timezone.set(ptz[v])<br />
</ul><br />
<br />
<br />
Here's the code for the setLatLon() function:<br />
[[File:RPCTOD_41_IDLEEditor_SetLatLongFunction.jpg|none|800px|The setLatLon() function.]]<br />
<br />
<br />
Three new variables were referenced in the setLatLon() function above, so we need to add them to the variable section, using the tkinter format so they can be updated throughout the script with the get() and set() functions. Add these lines of code following the last entry in the variable section of the script.<br />
<br />
<br />
<ul><br />
displayLat = StringVar() <br /n><br />
displayLon = StringVar() <br /n><br />
displayTz = StringVar()<br />
</ul><br />
<br />
<br />
Here's the updated variables section of our script:<br />
[[File:RPCTOD_42_IDLEEditor_Variables.jpg|none|800px|Variables section.]]<br />
<br />
<br />
== Part 07 - Displaying previous values, errors & exceptions ==<br />
<br /n><br />
[[File:RPCTOD_49_GUI_Part07.jpg|none|800px|GUI for Part 07.]]<br />
<br />
<br />
Several of the functions throughout this script have accounted for errors and exceptions, but up to now we haven’t displayed those messages. In addition, as we modify the Sunlight nodes in Terragen, it would be useful if we could see those values displayed in the GUI. We’ll use frame2, the empty right portion of our window to display any of these kinds of information.<br />
<br />
<br />
We can begin by defining the frame, which will consist of a border with a description and labels. In the windows section of the script insert the following line of code between frame1 and frame3.<br />
<br />
<br />
<ul><br />
frame2 = LabelFrame(gui,text="Last values plotted were: ",padx=10,pady=10) <br /n><br />
frame2.grid(row=10,column=1,padx=0,pady=0,sticky='nw')<br />
</ul><br />
<br />
<br />
The code for the GUI window now looks like this:<br />
[[File:RPCTOD_45_GUI_Window.jpg|none|800px|Defining the frame2 area within the main window.]]<br />
<br />
<br />
Since the display of this information is mostly in the form of Labels, let’s add the following code at the end of the Labels section and before the Sliders section of our script to display the last Sunlight node that was modified via our script, and the values used to do so.<br />
<br />
<br />
<ul><br />
l_lastSun = Label(frame2,text='Node: ').grid(row=12,column=0,sticky='w') <br /n><br />
l_lastSunName = Label(frame2,textvariable=displayLastSun).grid(row=12,column=1,sticky='w') <br /n><br />
l_dateTime = Label(frame2,text='Date & time: ').grid(row=13, column=0,sticky='w') <br /n><br />
l_dateTimeData = Label(frame2,textvariable=displayTimeData).grid(row=13, column=1,sticky='w') <br /n><br />
l_location = Label(frame2,text='Location: ').grid(row=14,column=0,sticky='w') <br /n><br />
l_locationData = Label(frame2,textvariable=displayLocation).grid(row=14,column=1,sticky='w') <br /n><br />
l_azimuth = Label(frame2,text='Azimuth:').grid(row=15,column=0,sticky='w') <br /n><br />
l_azimuthData = Label (frame2,textvariable=azimuth).grid(row=15,column=1,sticky='w') <br /n><br />
l_elevation = Label(frame2,text='Elevation:').grid(row=16,column=0,sticky='w') <br /n><br />
l_elevationData = Label(frame2,textvariable=elevation).grid(row=16,column=1,sticky='w') <br /n><br />
l_errMsg = Label(frame2,textvariable=errMsg,fg='red').grid(row=17,columnspan=2,sticky='w') <br /n><br />
</ul><br />
<br />
<br />
The labels section of the script, with frame2 components added.<br />
[[File:RPCTOD_46_IDLEEditor_Labels.jpg|none|800px|Labels section of the script.]]<br />
<br />
<br />
Now we’ll add the new variables we referenced in the lines of code above, so they can be updated by different functions in the script with the get() and set() functions. Append the following lines of code to the Variables section of the script.<br />
<br />
<br />
<ul><br />
displayLastSun = StringVar() <br /n><br />
displayTimeData=StringVar() <br /n><br />
displayLocation=StringVar() <br /n><br />
azimuth = StringVar() <br /n><br />
elevation = StringVar()<br />
</ul><br />
<br />
<br />
The updated variables section of the script:<br />
[[File:RPCTOD_47_IDLEEditor_Variables.jpg|none|800px|Variables section of the script.]]<br />
<br />
<br />
The last step is to actually display the information, whenever the “Apply to Sun” button is clicked. To do this we need to update some of the whenWhere() function we’ve already created. Insert the following lines of code into that function, taking care to position them in the script as indicated in the screen shot.<br />
<br />
<br />
<ul><br />
displayTimeData.set(lookupTime) <br /n><br />
displayLocation.set(location) <br /n><br />
azimuth.set(results[0]) <br /n><br />
elevation.set(results[1]) <br /n><br />
displayLastSun.set(num.get())<br />
</ul><br />
<br />
<br />
The updated whenWhere() function now looks like this:<br />
[[File:RPCTOD_48_IDLEEditor_whenWhereFunction.jpg|none|800px|The updated whenWhere() function will now display messages.]]<br />
<br />
<br />
<br />
== In Conclusion ==<br />
<br />
This tutorial is meant to show you the possibilities of Terragen’s new RPC feature. How might you further modify this script to add even more functionality to it? <br />
* How about adding the ability to modify the camera’s exposure when the Sunlight node is below the horizon? What if more than one camera existed in the project? <br />
* How about a button that resets the sun’s heading and elevation to their default settings? <br />
* How would you add your location and timezone to the presets?<br />
* How would you turn off all the sunlight nodes at once? Back on again?<br />
<br />
Special thanks to John Clark Craig for making his Python script available to the public. <br />
<br />
<!-- The Terragen RPC Time of Day example script may be downloaded from Planetside Software [https://www.dropbox.com/s/x7gzd06mkqe8eoi/TerragenRPC_TimeOfDayExample.zip?dl=0 here]. --><br />
The Terragen RPC Time of Day example script may be downloaded from Planetside Software [https://www.dropbox.com/s/takc4hrcp59snum/TerragenRPC_TimeOfDayExample_v2.zip?dl=0 here].<br />
<br />
<br /n></div>Redmawhttps://planetside.co.uk/wiki/index.php?title=Terragen_RPC:_Time_of_Day_Example_Script&diff=16665Terragen RPC: Time of Day Example Script2023-06-28T18:22:06Z<p>Redmaw: Removed terminology referencing source code.</p>
<hr />
<div>Terragen RPC: Time of Day Example Script<br />
<br />
== Overview ==<br />
Terragen 4.6.30 Professional introduces a new feature for remote procedure calling, or RPC. With a running instance of Terragen acting as a server, other programs can make remote procedure calls to query and modify a Terragen project. <br />
<br />
The goal of this tutorial is to walk you through a step-by-step procedure using the Python programming language to create a script that changes the time of day in a Terragen project; in other words, provide the ability to modify a Sunlight node’s heading and elevation parameters outside of the Terragen program.<br />
<br />
__TOC__<br />
<br />
<br />
== Part 01: Set up ==<br />
For this tutorial you’ll first need to download and install Terragen 4.6.30 Professional as well as [http://www.python.org%20Python Python]. Once you’ve installed the new build of Terragen be sure to launch it.<br />
<br />
On a Windows computer you can verify that Python is installed by querying the operating system via the Command Prompt window.<br />
<br />
Type “command prompt” or “cmd” in the Windows search box and press “enter” to open the Command Prompt window.<br />
Type “python” on the command line of the Command Prompt window, and press “enter”.<br />
If installed, the version of Python will be displayed.<br />
<br />
[[File:RPCTOD_03_CmdPromptPython.jpg|none|800px|Command prompt windows displaying the installed version of Python.]]<br />
<br />
<br />
Next, create a folder for this project that is accessible to your computer network. We’ll be saving and executing the python script from this location, as well as other files and resources.<br />
<br />
This project uses the Python script “sunpos.py” created by John Clark Craig to calculate the sun’s heading and elevation values based on the time of day and a given location. You’ll need to download the script and save it in your project folder.<br />
<br />
* Download the “[https://levelup.gitconnected.com/python-sun-position-for-solar-energy-and-research-7a4ead801777 sunpos.py]” script and save it in your project folder as sunpos.py. <br /n><br />
* https://levelup.gitconnected.com/python-sun-position-for-solar-energy-and-research-7a4ead801777<br />
<br />
<br />
== Part 02: Coding the basic functionality ==<br />
In this section our goal is to simply pass the minimum requirements needed by the sunpos.py in order for it to calculate the sun’s heading and elevation; then pass along these results to the default Sun node in Terragen. <br />
<br />
We’ll use Python’s Integrated Development and Learning Environment, or IDLE, to write the code for our script.<br />
<br />
Type “IDLE” in the Windows search box and press “enter” to open the IDLE Shell window.<br />
<br />
[[File:RPCTOD_04_IDLEShell.jpg|None| 800px|IDLE Shell.]]<br />
<br />
<br />
While we can type and execute python commands directly in the IDLE shell, we’ll use the built-in file editor to create our script file so we can run it multiple times as we refine it. <br />
<br />
Select “New File” from the IDLE shell menu to open the file editor window.<br />
<br />
[[File:RPCTOD_05_IDLEShell_NewFile.jpg|none|800px|Select New File to open the file editor.]]<br />
<br />
<br />
Right now our script is completely blank, and in order for it to do something we first need to instruct Python to load certain modules or libraries when the script is run. Not surprisingly, we need to tell Python to load the Terragen RPC library and the sunpos.py script we previously downloaded. Additionally, you might need to tell Python where the Terragen RPC module was installed, as well as have the ability to query the operating system in order to access the date and time.<br />
[[File:RPCTOD_06_IDLEEditor.jpg|none|800px|Editor window.]]<br />
<br />
<br />
We’ll use the “import” keyword to insert a module into our script at run time. For some modules we’ll also use the “as” keyword to provide the module with an alias that we reference within the script.<br />
<br />
Type the following lines of code into the file editor window. You can also copy and paste them. <span style="color:red">Note that your Terragen RPC installation path may vary.</span><br />
<br />
<br />
<ul><br />
import sys <br/n><br />
sys.path.insert(0,'P:/PlanetsideSoftware/RPC/terragen_rpc-0.9.0/terragen_rpc-0.9.0') <br/n><br />
import terragen_rpc as tg <br/n><br />
import sunpos as sp<br />
</ul><br />
<br />
<br />
In order for the sunpos module to determine the sun’s heading and elevation, it needs to know a date, time, and a location. Initially we can supply this information via Python’s datetime module, so we’ll load that as well, this time using the “from” keyword instead of the “import” keyword in order to load just the portion of the module we want.<br />
<br />
<br />
<ul><br />
from datetime import datetime<br />
</ul><br />
<br />
<br />
Our script should look like this:<br />
[[File:RPCTOD_07_IDLEEditor_ImportModules.jpg|none|800px|Import the modules into the script.]]<br />
<br />
<br />
With the library modules imported into the script, our next step is to define the values which the sunpos module requires to make its calculations. Variables are used to store information that can be referenced or manipulated within a computer program, such as text and numbers. In Python we can define the variable and its value in the same statement.<br />
<br />
Let’s create four variables to hold this information, one for the computer system’s date and time, one for the location on Earth utilizing latitude and longitude coordinates, one for a timezone, and one last variable to format the date, time and timezone information the way that the sunpos module expects it to be.<br />
<br />
For this example, we’ll use the timezone, latitude, and longitude for Los Angeles, CA, USA. Notice that the location variable and the lookupTime variable consist of multiple components. This is referred to as a list in Python, and is somewhat similar to an array in other programming languages.<br />
<br />
<br />
<ul><br />
sysTime = datetime.now() <br /n><br />
timezone = -7 <br /n><br />
location = [34.052235, -104.741667] <br /n><br />
lookupTime = [sysTime.year, sysTime.month, sysTime.day, sysTime.hour, sysTime.minute, sysTime.second, timezone] <br /n><br />
</ul><br />
<br />
<br />
Our script now looks like this:<br />
[[File:RPCTOD_08_IDLEEditor_DefineVariables.jpg|none|800px|Define the variables.]]<br />
<br />
<br />
Now that the variables have been defined that will be passed to the sunpos module, we can call the module and save its results. All of this can be done with one Python statement. We’ll define a new variable on the left of the equals sign called “results” to store the sun’s heading and elevation information which will be returned by the call to the sunpos module.<br />
<br />
The statement to the right of the equals sign is the call to the sunpos module. The “sp” is the alias we defined at the start of the script to reference the sunpos module, and the “sunpos()” is the function within the module that gets run and calculates the sun’s heading and elevation. All the data needed to perform the calculations are passed to the module by listing the variables between the two parenthesis <br />
<br />
<br />
<ul><br />
results = sp.sunpos(lookupTime, location, True)<br />
</ul><br />
<br />
<br />
[[File:RPCTOD_09_IDLEEditor_ScriptResults.jpg|None|800px|Define a variable for the results from the sunpos module.]]<br />
<br />
<br />
The last step is to apply the results to our Terragen project via RPC. We’ll do that by telling Terragen which item we want to act upon, the default Sun node, then setting the new values for its heading and elevation parameters.<br />
<br />
The code below creates an instance of the item “Sunlight 01” called “node” which can be manipulated by our script. The “set_param” function then changes the heading and elevation to the values in the results[] list.<br />
<br />
<br />
<ul><br />
node = tg.node_by_path("Sunlight 01")<br />
node.set_param('heading',results[0])<br />
node.set_param('elevation',results[1])<br />
</ul><br />
<br />
<br />
Our script now looks like this:<br />
[[File:RPCTOD_10_IDLEEditor_CallTGPRC.jpg|none|800px|Call the Terragen RPC module]]<br />
<br />
<br />
Save the script in the project folder by selecting “File Save As” from the IDLE main menu and giving the script a name. Then press “F5” or select “Run > Run Module” from the IDLE main menu to run the script. The 3D Preview window in Terragen will update to show the new direction of the sun.<br />
<br />
[[File:RPCTOD_51_3DPreview.jpg|none|800px|The Sunlight 01 node's heading and elevation changed by the script.]]<br />
<br />
<br />
== Part 03: Coding a basic GUI ==<br />
<br /n><br />
[[File:RPCTOD_11_GUI.jpg|none|452px|A very simple GUI in order to manipulate the parameter values easier.]]<br />
<br />
<br />
In the previous section we showed how a simple script can be created to manipulate the sun’s heading and position. In this section we’ll make the simple graphical interface, or GUI, seen below in order to manipulate the values of each variable much easier.<br />
<br />
To build the GUI for our script we’ll take advantage of the “tk interface” package, or “tkinter”, included in Python. Note that in some cases, tkinter’s syntax may appear slightly different than the coding in the section above, for example in defining a variable.<br />
<br />
Just as in the previous section, the first step is to import the tkinter package into our script. When creating a script that includes a GUI these are typically the first lines of code in the script. So we’ll prepend our existing script with these two lines of code.<br />
<br />
The first line of code imports all the functions and built-in modules in the tkinter package into the script, while the second line of code imports the ttk package which is responsible for the style of the widgets (buttons, etc.)<br />
<br />
<br />
<ul><br />
From tkinter import * <br /n><br />
From tkinter import ttk<br />
</ul><br />
<br />
<br />
[[File:RPCTOD_12_IDLEEditor_ImportTkinter.jpg|none|800px|Import the tkinter package into the script.]]<br />
<br />
<br />
Next we’ll build the window for the GUI, starting with its size and title. We’ll insert these lines of code after the import statements and before the variables. It’s also useful to add notes or comments in a script as well. The “#” symbol instructs Python to ignore anything in the line of code that follows it. We’ll make use of this symbol to set reminders and stay organized in our script. <br />
<br />
In Python scripts the basic window is often defined as “root”, but for this example we’ll use the word “gui”.<br />
<br />
<br />
In the “title” attribute we can define text to display in the windows title bar. The “geometry” attribute sets the width and height of the window in pixels.<br />
<br />
<br />
<ul><br />
gui = Tk() <br /n><br />
gui.title("RPC - Time of day") <br /n><br />
gui.geometry("300x250") <br /n><br />
</ul><br />
<br />
<br />
Whenever you make a GUI, you want to include a looping function for that window at the end of the script, which in effect causes the script to wait for user input. This will remain the last line of code in our script. Add the following line of code at the bottom of the script.<br />
<br />
<br />
<ul><br />
gui.mainloop()<br />
</ul><br />
<br />
<br />
Here's the script so far:<br />
[[File:RPCTOD_50_IDLEEditor_DefineWindowMainLoop.jpg|none|800px|Create the GUI window.]]<br />
<br />
<br />
In order to individually adjust each component that makes up the date and time information, we need to define a variable for each part. We’ll create these variables using tkinter functions, such as IntVar(). These functions accept many arguments such as a window name or a value. In our script, “gui” is the window name, and the value comes from the sysTime variable’s attributes we previously defined.<br />
<br />
Insert the lines of code below, following the sysTime variable statement.<br />
<br />
<br />
<ul><br />
year = IntVar(gui,value = sysTime.year) <br /n><br />
month = IntVar(gui,value = sysTime.month) <br /n><br />
day = IntVar(gui,value = sysTime.day) <br /n><br />
hour = IntVar(gui,value = sysTime.hour) <br /n><br />
minute = IntVar(gui,value = sysTime.minute) <br /n><br />
second = IntVar(gui,value = sysTime.second) <br /n><br />
</ul><br />
<br />
<br />
We need to rewrite the timezone variable definition in the tkinter syntax too.<br />
<br />
<br />
<ul><br />
timezone = IntVar(gui,value = -7) <br />
</ul><br />
<br />
<br />
Just as with the date and time information, we need to split the location data into latitude and longitude, which use decimal precision and therefore require a different tkinter function, DoubleVar(). As these variables will replace the existing location variable, we can comment out that line of code so that Python ignores it.<br />
<br />
The “#” at the beginning of the line of code defining the location variable, so Python ignores it.<br />
<br />
<br />
<ul><br />
<nowiki>#</nowiki> location = [34.052235, -104.741667] <br /n><br />
latitude = DoubleVar(gui,value = 34.052235) <br /n><br />
longitude = DoubleVar(gui,value = -104.741667) <br /n><br />
</ul><br />
<br />
<br />
[[File:RPCTOD_14_IDLEEditor_DefineVariables.jpg|none|800px|Defining the variables for each part of the time, date and location data.]]<br />
<br />
<br />
Now that we’ve accounted for all the individual variables we can add them to the GUI. There are several ways to do this in Python, and for this tutorial we’ll use the grid method, which will allow us to align each variable with a corresponding text label to describe it.<br />
<br />
Python refers to the GUI components, such as labels and buttons, as widgets. We’ll begin by creating a widget for each label. Each widget needs a unique name, so for the label widgets we’ll use “l_” followed by the name of the data it contains, for example “l_year”. The labels themselves will include information for the window it belongs on, the text to display, its position within the window, and how it is aligned. All the labels will belong to the “gui” window for now, and the text to be displayed is simply their name. The grid method uses rows and columns to position the label, and a “sticky” attribute to align the label.<br />
<br />
<br />
The last widget allows us to insert a blank line that spans both columns.<br />
<br />
<br />
<ul><br />
l_year = Label(gui,text = 'Year').grid(row=0,column=0,sticky='w') <br /n><br />
l_month = Label(gui,text = 'Month').grid(row=1, column=0,sticky='w') <br /n><br />
l_day = Label(gui,text = 'Day').grid(row=2, column=0,sticky='w') <br /n><br />
l_hour = Label(gui,text = 'Hour').grid(row=3, column=0,sticky='w') <br /n><br />
l_minute = Label(gui,text = 'Minute').grid(row=4, column=0,sticky='w') <br /n><br />
l_second = Label(gui,text = 'Second').grid(row=5, column=0,sticky='w') <br /n><br />
l_timezone = Label(gui,text = 'Timezone').grid(row=6, column=0,sticky='w') <br /n><br />
l_latitude = Label(gui,text='Latitude (N)').grid(row=7,column=0,sticky='w') <br /n><br />
l_longitude = Label(gui,text='Longitude (W)').grid(row=8,column=0,sticky='w') <br /n><br />
l_null = Label(gui,text=" ").grid(row=9,columnspan=2) <br /n><br />
</ul><br />
<br />
<br />
The code for the labels should look like this:<br />
<br />
[[File:RPCTOD_15_IDLEEditor_DefineLabels.jpg|none|800px|Defining the label widgets.]]<br />
<br />
<br />
While the Label widget is used to display text and can not be changed by the user, the Entry widget accepts a starting value and user input. We’ll set each Entry’s “textvariable” to the corresponding initial value we assigned to the variables. Later, the user can change these values by simply entering a different value. To describe the widget for each Entry, we’ll use the prefix “e_” and the name of the Entry, for example “e_year”.<br />
<br />
<br />
<ul><br />
e_year = Entry(gui, textvariable = year,width=6).grid(row=0,column=2, sticky='e') <br /n><br />
e_month = Entry(gui, textvariable = month,width=6).grid(row=1,column=2, sticky='e') <br /n><br />
e_day = Entry(gui, textvariable = day,width=6).grid(row=2,column=2, sticky='e') <br /n><br />
e_hour = Entry(gui, textvariable = hour,width=6).grid(row=3,column=2, sticky='e') <br /n><br />
e_minute = Entry(gui, textvariable = minute,width=6).grid(row=4,column=2, sticky='e') <br /n><br />
e_second = Entry(gui, textvariable = second,width=6).grid(row=5,column=2, sticky='e') <br /n><br />
e_timezone = Entry(gui, textvariable = timezone,width=6).grid(row=6, column = 2, sticky='e') <br /n><br />
e_latitude = Entry(gui, textvariable = latitude,width=16).grid(row=7, column =2) <br /n><br />
e_longitude = Entry(gui, textvariable = longitude,width=16).grid (row=8,column =2) <br /n><br />
</ul><br />
<br />
<br />
The code for the Entry section should look like this:<br />
[[File:RPCTOD_16_IDLEEditor_DefineEntry.jpg|none|800px|Defining the entry widgets.]]<br />
<br />
<br />
Now that we can change the initial values for each parameter, we need a way to tell the script when to calculate our changes. For this we’ll use a button widget. Like the other widgets, the button can contain several bits of information, such as the text to display on the button. Most importantly it contains a command attribute, telling it what to do when the button has been clicked. In this example, the script will run the function “whenWhere”, which is yet to be defined. <br />
<br />
<br />
<ul><br />
b_ApplySun = Button(gui,text='Apply to Sun',bg='yellow',command=whenWhere).grid(row=11,column=2)<br />
</ul><br />
<br />
<br />
This is the code for the button section of the script.<br />
[[File:RPCTOD_17_IDLEEditor_DefineButton.jpg|none|800px|Defining the button widget.]]<br />
<br />
<br />
To define the whenWhere() function, insert the code below into the script following the variables section and before the labels section. Note that it’s important to indent the lines of code after defining the function. <br />
<br />
Using the get() function the “lookupTime” variable is updated with the current date and time values and the “location” variable is updated with the current latitude and longitude values. Note, some of these variables will be defined immediately after this step.<br />
<br />
The next statement first passes three values to the sunpos module, then it accepts the returned data in the “results” variable as a list containing the heading and elevation.<br />
<br />
Then the function “setTerragenSunHeadingAndElevation” is called, passing along the text “Sunlight 01” and the results variable containing the sun heading and elevation.<br />
<br />
<br />
<ul><br />
def whenWhere(): <br /n><br />
: lookupTime = [year.get(), month.get(), day.get(), hour.get(), minute.get(), second.get(),timezone.get()] <br /n><br />
: location = [latitude.get(),longitude.get()] <br /n><br />
: results = sp.sunpos(lookupTime,location,True) <br /n><br />
: setTerragenSunHeadingAndElevation(“Sunlight 01”,results) <br /n><br />
</ul><br />
<br />
<br />
This is the code defining the whenWhere() function.<br />
[[File:RPCTOD_18_IDLEEditor_whenWhereFunction.jpg|none|800px|Create the lookupTime function.]]<br />
<br />
<br />
<br />
Since we’ve included the “lookupTime” variable within the whenWhere() function, and it is updated by the get() functions whenever the whenWhere() function is called, we no longer need it in the variables section. <br />
<br />
<span style="color:red">Delete the line of code in the variables section that defines the “lookupTime” variable.</span><br />
<br />
Also included in the whenWhere() function was the call to a function that performs the actual RPC commands and updates Terragen. The function will accept two arguments, one for the item to modify in Terragen which is the Sunlight 01 node, and the other, a list, containing the heading and elevation values. Let’s code that function directly beneath the previous function in the script. <br />
<br />
Once you’ve completed coding the function below you can <span style="color:red">remove the previous lines of code created in Part 02 of this tutorial</span> that did the same thing.<br />
<br />
This function includes error checking commands such as “try”, “except”, and “raise”. Checking for potential errors or exceptions allows the program to handle them before they cause a problem, like crashing the application. For example, if a user entered text data in a numeric field, and the program expected numeric data, then an error would occur and the application might crash. With error handling, the application could display a message indicating the wrong type of data was entered and giving the user an opportunity to change the data. You can read more about the error checking command in the Terragen RPC documentation. <br />
<br />
<br />
<ul><br />
def setTerragenSunHeadingAndElevation(name,values): <br /n><br />
: try: <br /n><br />
:: node = tg.node_by_path(name) <br /n><br />
:: try: <br /n><br />
::: node.set_param('heading',values[0]) <br /n><br />
::: node.set_param('elevation',values[1]) <br /n><br />
:: except AttributeError as err: <br /n><br />
::: showError("Sunlight not in project. Refresh list.") <br /n><br />
:: except ConnectionError as e: <br /n><br />
::: showError("Terragen RPC connection error: " + str(e)) <br /n><br />
:: except TimeoutError as e: <br /n><br />
::: showError("Terragen RPC timeout error: " + str(e)) <br /n><br />
:: except tg.ReplyError as e: <br /n><br />
::: showError("Terragen RPC server reply error: " + str(e)) <br /n><br />
:: except tg.ApiError: <br /n><br />
::: showError("Terragen RPC API error") <br /n><br />
::: raise <br />
</ul><br />
<br />
<br />
Here is the code for the setTerragenSunHeadingAndElevation function.<br />
[[File:RPCTOD_19_IDLEEditor_SetTGSunHeadElevFunction.jpg|none|800px|The setTerragenSunHeadingAndElevation function.]]<br />
<br />
<br />
The last step in this section is to create another function which displays any message passed to it by another part of the script. This function receives one input, the message to be displayed. <br />
<br />
<br />
<ul><br />
def showError(text): <br/n><br />
: errMsg.set(text)<br />
</ul><br />
<br />
<br />
This is the code for the showError() function.<br />
[[File:RPCTOD_20_IDLEEditor_showErrorFunction.jpg|none|800px|The showError function.]]<br />
<br />
<br />
We need to create one last variable to store any messages set by the error checking processes. We can define the new variable at the end of the variables section of the script.<br />
<br />
<br />
<ul><br />
errMsg = StringVar()<br />
</ul><br />
<br />
<br />
Here is the variable section of the script.<br />
[[File:RPCTOD_21_IDLEEditor_Variables.jpg|none|800px|The defined variables.]]<br />
<br />
<br />
Save and run the script. Try entering new values, especially for the hour of the day, and applying them to the Terragen sun.<br />
<br />
<br />
== Part 04: Modifying parameters more easily ==<br />
<br /n><br />
[[File:RPCTOD_23_IDLEEditor_GUI_Part04.jpg|none|452px|The GUI with sliders added.]]<br />
<br />
<br />
<br />
Now that we have a GUI and can modify the values for the time of day and location, let’s make it easier to make those modifications by adding sliders to the interface for the date and time parameters.<br />
<br />
A slider can be created with Tkinter’s Scale widget. We’ll use the prefix “s_” and the name of each parameter to define each slider. Scale widgets can also accept arguments, such as which window to be positioned in, a range of values to slide between, and the orientation of the slider. We’ll set an appropriate range for each slider, for example: 1 - 12, for the Month parameter, and orient the sliders horizontally so they best fit the layout of the GUI we’ve already created.<br />
<br />
Insert the following lines of code into the script, between the Labels section and the Entry section. <br />
<br />
<br />
<ul><br />
s_year = Scale(gui,from_= 1900, to = 2099,variable=year,orient=HORIZONTAL,showvalue=0).grid(row=0,column=1) <br /n><br />
s_month = Scale(gui,from_= 1, to = 12,variable=month,orient=HORIZONTAL,showvalue=0).grid(row=1,column=1) <br /n><br />
s_day = Scale(gui,from_= 1, to =31,variable=day,orient=HORIZONTAL,showvalue=0).grid(row=2,column=1) <br /n><br />
s_hour = Scale(gui,from_= 0, to = 23,variable=hour,orient=HORIZONTAL,showvalue=0).grid(row=3,column=1) <br /n><br />
s_minute = Scale(gui,from_= 0,to = 59,variable=minute,orient=HORIZONTAL,showvalue=0).grid(row=4,column=1) <br /n><br />
s_second = Scale(gui,from_= 0, to = 59,variable=second,orient=HORIZONTAL,showvalue=0).grid(row=5,column=1) <br /n><br />
s_timezone = Scale(gui,from_= -13, to = 13,variable=timezone,orient=HORIZONTAL,showvalue=0).grid(row=6,column=1) <br /n><br />
</ul><br />
<br />
<br />
Here’s the sliders section of the script:<br />
[[File:RPCTOD_24_IDLEEditor_Sliders.jpg|none|800px|Sliders section.]]<br />
<br />
<br />
You may have noticed in the lines of code above, that the slider’s grid position is located in column 1, and in the previous step the label’s grid position was located in column 0, while the entry’s grid position was located in column 2. This allows our slider to drop in between the label and the entry columns of the GUI. <br />
<br />
Visually, the GUI will look better if the latitude and longitude entry fields align with the sliders because of their width; and that the Apply button is centered within the GUI, so modify all their grid positions to column 1 as well.<br />
<br />
<br />
<ul><br />
e_latitude = Entry(gui, textvariable = latitude,width=16).grid(row=7, column =1) <br /n><br />
e_longitude = Entry(gui, textvariable = longitude,width=16).grid (row=8,column =1) <br /n><br />
b_ApplySun = Button(gui,text='Apply to Sun',bg='yellow',command=whenWhere).grid(row=11,column=1) <br /n><br />
</ul><br />
<br />
<br />
If you save the script and run it, you’ll notice how much easier it is to adjust the time and date parameters with the sliders, while still being able to enter a precise numerical value in the Entry field.<br />
<br />
<br />
== Part 05: Multiple sunlight nodes ==<br />
<br /n><br />
[[File:RPCTOD_27_IDLEEditor_GUI_Part05.jpg|none|800px|The final GUI for Part 05.]]<br />
<br />
<br />
<i>“But, what if there is more than one sunlight node in the Terragen project, or it’s named something else, or it’s missing entirely?” </i> Good questions, and in this section we’ll address them while expanding the functionality of the script and GUI.<br />
<br />
<br />
So far all of the GUI components have been located in the “gui” window. Tkinter allows you to divide the window into smaller frames. You can then tell each component which frame to be displayed in. <br />
<br />
To accommodate the features we’ll be adding to the script we can start by making the GUI larger. Edit the existing line of code that sets the width and height of the GUI as indicated below.<br />
<br />
<br />
<ul><br />
gui.geometry("900x600")<br />
</ul><br />
<br />
<br />
We define a frame using the LabelFrame widget. Just like the other widgets, it accepts options for which window to be located in, and what text to display, if any. For starters we’ll create two frames. The first frame will be used to select the available Sunlight nodes in the project, and the second frame will contain the parameters we’ve already coded. <br />
<br />
To define the frames, enter the following lines of code right below the existing code that defines the window.<br />
<br />
The first frame, frame0, will reside in the “gui” window and display the text “Select sunlight node / Click drop-down arrow to refresh list:”. It has 20 pixels of padding on its left and right, and 10 pixels of padding above and below it. No border will be drawn around the frame because “relief” set to FLAT.<br />
<br />
<br />
<ul><br />
frame0 = LabelFrame(gui,text="Select sunlight node / Click drop-down arrow to refresh list:",relief=FLAT, padx=20,pady=10)<br />
</ul><br />
<br />
<br />
The second frame, frame1, will aslo reside in the “gui” window. It will display the text “Enter time and location:”. It has 10 pixels of padding all around it and will display a border.<br />
<br />
<br />
<ul><br />
frame1 = LabelFrame(gui,text="Enter time and location: ",padx=10,pady=10)<br />
</ul><br />
<br />
<br />
Then skip a line and enter the following lines of code which define where the frames are positioned within the gui window.<br />
<br />
<br />
<ul><br />
frame0.grid(row=0,column=0,padx=5,pady=5) <br /><br />
frame1.grid(row=10,column=0,padx=25,pady=0,sticky='w')<br />
</ul><br />
<br />
<br />
The code should look like this:<br />
[[File:RPCTOD_25_IDLEEditor_DefineFrames.jpg|none|800px|Defining the frame.]]<br />
<br />
<br />
In order for the frames to have any effect, the display option for the labels, sliders, entry, and buttons needs to be changed from “gui” to the one of the frames. <span style="color:red">Edit the lines of code for each labels, sliders, entry, and button which are currently coded to “gui” to “frame1”. </span><br />
<br />
The code for the labels, sliders, entry and button now look like this:<br />
[[File:RPCTOD_26_IDLEEditor_LabelsSlidersEntryButtonFrames.jpg|none|800px|The labels, sliders, entry and buttons edited to reflect the new frame location.]]<br />
<br />
<br />
So far the design of our script has been to send certain information to Terragen in order to change something in the project, but now we want Terragen to provide some information about the project first. Specifically, we want to know what sunlight type nodes are in the project as soon as the script is run. Our GUI needs to reflect this information and provide us with the ability to select among the sunlight nodes.<br />
<br />
Coding the query to Terragen consists of two parts. Part one will be a way in which to store the sunlight node names and IDs returned from Terragen. Part two will be a function to make the request to Terragen for the sunlight node information.<br />
<br />
Enter the following line of code into the script, after the functions already defined and before the labels section.<br />
<br />
This creates the variable “sunNodes” which will store the returned information from the getSunlightInProject() function. <br />
<br />
<br />
<ul><br />
sunNodes = getSunlightInProject()<br />
</ul><br />
<br />
<br />
Next, create the new function getSunlightInProject() just below the last function currently in the script by entering the lines of code below. Note, that this function also includes the same kind of error checking as in Part 3; please see that section for more explanation on capturing errors and exceptions. This function initializes the variables “nodeIDs” and “nodeNames” so that they’re empty each time this function is run, then populates them with the current information from the Terragen project. <br />
<br />
<br />
<ul><br />
def getSunlightInProject(): <br /n><br />
: try: <br /n><br />
:: project = tg.root() <br /n><br />
:: nodeIDs = [] <br /n><br />
:: nodeNames = [] <br /n><br />
<!-- :: nodeIDs = tg.children_filtered_by_class(project,'sunlight') <br /n> --><br />
:: nodeIDs = project.children_filtered_by_class(‘sunlight’) <br /n><br />
:: for nodes in nodeIDs: <br /n><br />
<!-- ::: nodeNames.append(tg.name(nodes)) <br /n> --><br />
::: nodeNames.append(nodes.name()) <br /n><br />
:: return nodeIDs,nodeNames <br /n><br />
: except ConnectionError as e: <br /n><br />
:: showError("Terragen RPC connection error: " + str(e)) <br /n><br />
: except TimeoutError as e: <br /n><br />
:: showError("Terragen RPC timeout error: " + str(e)) <br /n><br />
: except tg.ReplyError as e: <br /n><br />
:: showError("Terragen RPC server reply error: " + str(e)) <br /n><br />
: except tg.ApiError: <br /n><br />
:: showError("Terragen RPC API error") <br /n><br />
:: raise<br />
</ul><br />
<br />
<br />
The code for the function call looks like this:<br />
<!-- [[File:RPCTOD_28_IDLEEditor_GetSunlightInProjectFunction.jpg|none|800px|The getSunlightInProject() function.]] --><br />
[[File:RPCTOD_52_IDLEEditor_GetSunlightInProjectFunction.jpg|none|800px|The getSunlightInProject() function.]]<br />
<br />
<br />
Now that we have the names of the sunlight nodes in the project, we can add them to our GUI. Currently, the labels, sliders, entries and Apply button reside in the “frame1” portion of the GUI, so we’ll place the names of the sunlight nodes in the “frame0” portion of the GUI and use a widget known as a Combobox to do so. As we’ve seen with other widgets, Comboboxes also accept options such as which part of the window to be displayed in. <br />
<br />
Enter the following lines of code right after the statement to call the getSunlightInProject() function and before the Labels section.<br />
<br />
The postcommand option is run each time a change is made to the Combobox. In this example, the updateCBList function is run, ensuring that the list of Sunlight nodes is always up to date. The textvariable “num” acts as a pointer to which item in the list is acted upon.<br />
<br />
<br />
<ul><br />
cb_suns = ttk.Combobox(frame0,textvariable=num,postcommand=updateCbList)<br />
</ul><br />
<br />
<br />
Here's the code for the Combobox:<br />
[[File:RPCTOD_29_IDLEEditor_ComboBox.jpg|none|800px|Combobox.]]<br />
<br />
<br />
Next we need to define the new variable and function that we referenced in the above line of code.<br />
<br />
In the variables section of our script, add the following to define the pointer variable “num”. By using tkinter’s StringVar() function we can later retrieve and store new values to the variable with the get() and set() functions.<br />
<br />
<br />
<ul><br />
num = StringVar()<br />
</ul><br />
<br />
<br />
Here is the variables section of the script after adding the num variable:<br />
[[File:RPCTOD_30_IDLEEditor_VariablesNum.jpg|none|800px|Add the num variable in the variables section.]]<br />
<br />
<br />
Following the last function we defined in our script, add the following lines of code. Note there is error checking in this function because it’s possible that a sunlight node is no longer in the Terragen project or the names have been changed since the last time this query was made.<br />
<br />
<br />
<ul><br />
def updateCbList(): <br /n><br />
: updateSuns = getSunlightInProject() <br /n><br />
: try: <br /n><br />
:: updateSunID.set(updateSuns[0]) <br /n><br />
:: updateSunNames.set(updateSuns[1]) <br /n><br />
:: cb_suns["values"] = updateSuns[1] <br /n><br />
:: showError(" ") <br /n><br />
: except TypeError as e: <br /n><br />
:: showError("Sunlight nodes not found. " + str(e))<br />
</ul><br />
<br />
<br />
The code for this function looks like this:<br />
[[File:RPCTOD_31_IDLEEditor_updateCbListFunction.jpg|none|800px|The updateCbList() function.]]<br />
<br />
<br />
We need to create the two variables we just reference in the above function so they can store the sunlight node ID numbers and names, and get updated whenever the updateCbList() function is called. At the end of the variables section in the script add these lines of code:<br />
<br />
<br />
<ul><br />
updateSunID = StringVar() <br /n><br />
updateSunNames = StringVar()”<br />
</ul><br />
<br />
<br />
The code for the variable section now looks like this:<br />
[[File:RPCTOD_32_IDLEEditor_Variables.jpg|none|800px|The variables section.]]<br />
<br />
<br />
Return to the script and directly beneath the line of code that creates the Combobox add these lines of code, in order to display the updated list of Sunlight nodes and to catch any errors or exceptions. <br />
<br />
<br />
<ul><br />
Try: <br /n><br />
: cb_suns["values"]=r[1]<br />
: cb_suns.current(0)<br />
except TypeError as e:<br />
: showError("No sunlight nodes in project or Terragen not running." + str(e))<br />
</ul><br />
<br />
<br />
To position the Combobox within frame0 add this line of code.<br />
<br />
<br />
<ul><br />
cb_suns.grid(row=0,column=0)<br />
</ul><br />
<br />
<br />
When dealing with multiple sunlight nodes in the project, it would be helpful if there was a way within the script to enable or disable each sunlight node. Since we’ve already created a Combobox to select a sunlight node, we can easily add a bit of code to enable or disable the selected node. We’ll use a button widget for this.<br />
<br />
Just after the Combobox code and before the Labels code, add this line of code to create the button to create a button to the right of the Combobox position that will execute the ckboxSun command when clicked.<br />
<br />
<br />
<ul><br />
b_SunOnOff = Button(frame0,text="Toggle sun on/off",bg='pink',command=ckboxSun).grid(row=0,column=1,padx=10)<br />
</ul><br />
<br />
<br />
The code for the Combobox should look like this:<br />
[[File:RPCTOD_33_IDLEEditor_MainAndComboBox.jpg|none|800px|The Combobox and button.]]<br />
<br />
<br />
Now we’ll create the ckboxSun() function that gets called when the button is clicked. Just below the lines of code for the last function we wrote add the following to call the toggleTerragenSun() function, passing it the index number for the currently selected item in the Combobox.<br />
<br />
<br />
<ul><br />
def ckboxSun(): <br /n><br />
: toggleTerragenSun(num.get())<br />
</ul><br />
<br />
<br />
You might wonder why we call one function when the button gets clicked and then another function. The reason is to keep the functions themselves as simple as possible. That way they can be used by other functions within the script. Let’s create the toggleTerragenSun() function so we can turn the sun on and off. Just below the last function type the following lines of code, which also contain some error checking.<br />
<br />
<br />
<ul><br />
def toggleTerragenSun(name): <br /n><br />
: try: <br /n><br />
:: node = tg.node_by_path(name) <br /n><br />
:: try: <br /n><br />
::: x = node.get_param_as_int('enable') <br /n><br />
:: except AttributeError as err: <br /n><br />
::: showError("Sunlight not in project. Refresh list.") <br /n><br />
:: else: <br /n><br />
::: if x == 1: <br /n><br />
:::: node.set_param('enable',0) <br /n><br />
::: else: <br /n><br />
:::: node.set_param('enable',1) <br /n><br />
: except ConnectionError as e: <br /n><br />
:: showError("Terragen RPC connection error: " + str(e)) <br /n><br />
: except TimeoutError as e: <br /n><br />
:: showError("Terragen RPC timeout error: " + str(e)) <br /n><br />
: except tg.ReplyError as e: <br /n><br />
:: showError("Terragen RPC server reply error: " + str(e)) <br /n><br />
: except tg.ApiError: <br /n><br />
:: showError("Terragen RPC API error") <br /n><br />
:: raise <br />
</ul><br />
<br />
<br />
Here is the code for the above functions.<br />
[[File:RPCTOD_34_IDLEEditor_ckBoxSunAndToggleTGSunFunctions.jpg|none|800px|Code for the ckboxSun() function and toggleTerragenSun() function.]]<br />
<br />
<br />
Save the script and run it. In Terragen, duplicate the Sunlight object a few times, then click in the Combobox and enable or disable the lights, and change their heading and elevation.<br />
<br />
<br />
== Part 06: Presets ==<br />
<br /n><br />
[[File:RPCTOD_43_GUI_Part06.jpg|none|800px|GUI for Part 06.]]<br />
<br />
<br />
Let’s add one more section to our GUI which contains presets for selected locations and timezones around the world. This way we can jump from the Los Angeles timezone to any other timezone around the world with a click of a button. <br />
<br />
[[File:RPCTOD_44_GUI_PresetsDropDownList.jpg|none|417px|Presets drop down list.]]<br />
<br />
<br />
We’ll create an external text file to hold the data so that it can be easily customized in any text editor, such as Notepad, and save the file in the CSV format which stands for “comma separated values”. As the format name implies each piece of data is separated from the next by a comma. Each row in the file should be formatted as follows: Timezone City/Country , longitude, latitude, timezone offset<br />
<br />
Copy and paste the following data into a text file and save it as presets_latLong.csv. As you can see from the “Timezone City/Country” data the entries are organized from timezone -11 to timezone +13. This is the order that the data will show up in the GUI.<br />
<br />
<ul><br />
-11 Liku/Niue, -169.792327,-19.053680, -11.0 <br /n><br />
-10 Honolulu/Hawaii, -157.854995, 21.306647, -10.0 <br /n><br />
-9 Adak/Adak Island, -176.660156, 51.862923, -9.0 <br /n><br />
-8 Anchorage/Alaska, -149.721679, 61.227957, -8.0 <br /n><br />
-7 Los Angeles/USA, -104.741667, 34.052235, -7.0 <br /n><br />
-7 Vancouver/Canada, -123.108673, 49.263323,-7.0 <br /n><br />
-6 Calgary/Canada, -114.071044, 51.058660, -6.0 <br /n><br />
-5 Winnipeg/Canada, -97.141113, 49.866316, -5.0 <br /n><br />
-4 Igloolik/Igloolik Island, -81.806259, 69.379587, -4.0 <br /n><br />
-4 Ontario/Canada, -84.887696, 49.315573, -4.0 <br /n><br />
-3 Salvador/Brazil, -38.480987, -12.973780, -3.0 <br /n><br />
-3 Goose Bay/Newfoundland, -60.342407, 53.304621,-3.0 <br /n><br />
-2 Nuuk/Greenland, -51.723632, 64.182464, -2.0 <br /n><br />
-1 Sao Filipe/Fogo, -24.483718, 14.890709,-1.0 <br /n><br />
0 Reykjavik/Iceland, -21.928710,64.144161, 0.0 <br /n><br />
+1 Dublin/Ireland, -6.249847, 53.350551, 1.0 <br /n><br />
+1 Greenwhich/UK, -0.193634, 51.489293, 1.0 <br /n><br />
+2 Brussels/Belgium, 4.390869, 50.824444, 2.0 <br /n><br />
+2 Berlin/Germany, 13.405151, 52.511763, 2.0 <br /n><br />
+2 Krakow/Poland, 19.929199, 50.050084, 2.0 <br /n><br />
+3 Odesa/Ukraine, 30.739746, 46.468132, 3.0 <br /n><br />
+3 Novgorodd/Russia, 43.978271, 56.307396, 3.0 <br /n><br />
+4 Baku/Azerbaijan, 49.844970, 40.390488, 4.0 <br /n><br />
+4 Dubai/UAE, 55.341795, 25.138654, 4.0 <br /n><br />
+5 Islamabad/Pakistan, 73.044433, 33.706061, 5.0 <br /n><br />
+6 Jahore/Bangladesh, 89.229125, 23.179080, 6.0 <br /n><br />
+7 Bangkok/Thailand, 100.546875, 13.496472, 7.0 <br /n><br />
+8 Beijing/China, 116.389160, 39.884450, 8.0 <br /n><br />
+8 Manilla/Philippines, 120.878899, 14.491408, 8.0 <br /n><br />
+9 Sapporo/Japan, 141.344604, 43.056847, 9.0 <br /n><br />
+10 Andersen AFB/Guam, 144.924087, 13.580586, 10.0 <br /n><br />
+11 Magadan/Russia, 150.805206, 59.568418, 11.0 <br /n><br />
+12 Queenstown/New Zealand, 168.618640, -45.104546, 12.0 <br /n><br />
+12 McMurdo Station/Antartica, 166.690063, -77.84011, 12.0 <br /n><br />
+13 Nomuka/Tonga, -174.802093, -20.251890, 13.0 <br /n><br />
</ul><br />
<br />
<br />
With the presets file saved, let’s load the data into our script. Immediately following the line of code that retrieves the Sunlight nodes in the Terragen project add the line of code:<br />
<br />
<br />
<ul><br />
presets()<br />
</ul><br />
<br />
<br />
[[File:RPCTOD_35_IDLEEditor_Presets.jpg|none|800px|The statement that calls the presets() function.]]<br />
<br />
<br />
Then create the new presets() function with the following lines of code, which will read the contents of the text file in “read only” mode, one line at a time. Each line is split into data each time the comma separator is encountered and these bits of data are appended to new variables for the Timezone City/Country, longitude, latitude and timezone offset.<br />
<br />
<br />
<ul><br />
def presets(): <br /n><br />
: file = open('presets_latlong.csv',"r") <br /n><br />
: content = file.readlines() <br /n><br />
: for line in content: <br /n><br />
:: x,y,z,a = line.strip().split(',') <br /n><br />
:: plocation.append(x) <br /n><br />
:: plat.append(z) <br /n><br />
:: plon.append(y) <br /n><br />
:: ptz.append(a) <br /n><br />
: file.close()<br />
</ul><br />
<br />
<br />
The code for the presets() function looks like this:<br />
[[File:RPCTOD_36_IDLEEditor_PresetsFunction.jpg|none|800px|The presets function.]]<br />
<br />
<br />
We referenced four new variables in the presets() function above, which store the data from the presets text file. Since these variables are created when the script is first run and their values do not change during the execution of the script, we can define them using standard Python variables, and not tkinter variables such as with the StringVar() function. Add these variables to the variables section of the script.<br />
<br />
<br />
<ul><br />
plocation = [] <br/n><br />
plat = [] <br/n><br />
plon = [] <br/n><br />
ptz = []<br />
</ul><br />
<br />
<br />
Here's the updated variables section in our script:<br />
[[File:RPCTOD_37_IDLEEditor_Variables.jpg|none|800px|The variables section of the script.]]<br />
<br />
<br />
We’ll add another frame to our main window and place the GUI components to display the presets there. We’ll need to add another Combobox to display the preset choices, labels for descriptions, and a button in order to update the variables for the timezone, latitude and longitude. Following the last line of code in the script add the lines below:<br />
<br />
<br />
<ul><br />
cb_presets = ttk.Combobox(frame3,textvariable=pnum) <br /n><br />
cb_presets["values"]=plocation <br /n><br />
cb_presets.current(16) <br /n><br />
cb_presets.grid(row=0,column=1,sticky='w') <br /n><br />
<br /n><br />
l_presetlocation = Label(frame3,text="Location: ").grid(row=0,column=0,sticky='w') <br /n><br />
l_presetLat = Label(frame3,text="Latitude:").grid(row=1,column=0,sticky='w') <br /n><br />
l_presetLat = Label(frame3,text="Longitude:").grid(row=2,column=0,sticky='w') <br /n><br />
l_presetTz = Label(frame3,text="Timezone:").grid(row=3,column=0,sticky='w') <br /n><br />
l_presetDispLat = Label(frame3,textvariable=displayLat).grid(row=1,column=1,sticky='w') <br /n><br />
l_presetDispLon = Label(frame3,textvariable=displayLon).grid(row=2,column=1,sticky='w') <br /n><br />
l_presetDispTz = Label (frame3, textvariable=displayTz).grid(row=3,column=1,sticky='w') <br /n><br />
<br /n><br />
b1 = Button(frame3,text="Apply Preset",command=setLatLon).grid(row=5,column=1,pady=5,sticky='w') <br /n><br />
</ul><br />
<br />
<br />
Here is the code for the frame3 components:<br />
[[File:RPCTOD_38_IDLEEditor_ComboboxPresets.jpg|none|800px|The Combobox for the Presets.]]<br />
<br />
<br />
We referenced a new frame and variable in the above lines of code to define the Combobox, so let’s add both those now.<br />
<br />
Append the following line of code to the section of our script where we’ve defined the window title, size and frames. Note, we will define frame2 in the last section of this tutorial.<br />
<br />
<br />
<ul><br />
frame3 = LabelFrame(gui,text="Location presets: ",padx=10,pady=10) <br /n><br />
frame3.grid(row=15,column=0,padx=25,pady=10,sticky='w')<br />
</ul><br />
<br />
<br />
Here's the updated lines of code for the window section:<br />
[[File:RPCTOD_39_IDLEEditor_Frame3Window.jpg|none|800px|Defining frame3 in the GUI window.]]<br />
<br />
<br />
Then add the following line of code the the variables section of our script to account for the new variable, which identifies the current item in the preset list.<br />
<br />
<br />
<ul><br />
pnum=StringVar()<br />
</ul><br />
<br />
<br />
Here's the updated variables section:<br />
[[File:RPCTOD_40_IDLEEditor_Variables.jpg|none|800px|Variables section.]]<br />
<br />
<br />
The last step to our preset code is to define the following function which will update and display the latitude, longitude and timezone variables to those of the selected preset.<br />
<br />
<br />
<ul><br />
def setLatLon(): <br /n><br />
: v = cb_presets.current() <br /n><br />
: displayLat.set(plat[v]) <br /n><br />
: displayLon.set(plon[v]) <br /n><br />
: displayTz.set(ptz[v]) <br /n><br />
: latitude.set(plat[v]) <br /n><br />
: longitude.set(plon[v])<br /n><br />
: timezone.set(ptz[v])<br />
</ul><br />
<br />
<br />
Here's the code for the setLatLon() function:<br />
[[File:RPCTOD_41_IDLEEditor_SetLatLongFunction.jpg|none|800px|The setLatLon() function.]]<br />
<br />
<br />
Three new variables were referenced in the setLatLon() function above, so we need to add them to the variable section, using the tkinter format so they can be updated throughout the script with the get() and set() functions. Add these lines of code following the last entry in the variable section of the script.<br />
<br />
<br />
<ul><br />
displayLat = StringVar() <br /n><br />
displayLon = StringVar() <br /n><br />
displayTz = StringVar()<br />
</ul><br />
<br />
<br />
Here's the updated variables section of our script:<br />
[[File:RPCTOD_42_IDLEEditor_Variables.jpg|none|800px|Variables section.]]<br />
<br />
<br />
== Part 07 - Displaying previous values, errors & exceptions ==<br />
<br /n><br />
[[File:RPCTOD_49_GUI_Part07.jpg|none|800px|GUI for Part 07.]]<br />
<br />
<br />
Several of the functions throughout this script have accounted for errors and exceptions, but up to now we haven’t displayed those messages. In addition, as we modify the Sunlight nodes in Terragen, it would be useful if we could see those values displayed in the GUI. We’ll use frame2, the empty right portion of our window to display any of these kinds of information.<br />
<br />
<br />
We can begin by defining the frame, which will consist of a border with a description and labels. In the windows section of the script insert the following line of code between frame1 and frame3.<br />
<br />
<br />
<ul><br />
frame2 = LabelFrame(gui,text="Last values plotted were: ",padx=10,pady=10) <br /n><br />
frame2.grid(row=10,column=1,padx=0,pady=0,sticky='nw')<br />
</ul><br />
<br />
<br />
The code for the GUI window now looks like this:<br />
[[File:RPCTOD_45_GUI_Window.jpg|none|800px|Defining the frame2 area within the main window.]]<br />
<br />
<br />
Since the display of this information is mostly in the form of Labels, let’s add the following code at the end of the Labels section and before the Sliders section of our script to display the last Sunlight node that was modified via our script, and the values used to do so.<br />
<br />
<br />
<ul><br />
l_lastSun = Label(frame2,text='Node: ').grid(row=12,column=0,sticky='w') <br /n><br />
l_lastSunName = Label(frame2,textvariable=displayLastSun).grid(row=12,column=1,sticky='w') <br /n><br />
l_dateTime = Label(frame2,text='Date & time: ').grid(row=13, column=0,sticky='w') <br /n><br />
l_dateTimeData = Label(frame2,textvariable=displayTimeData).grid(row=13, column=1,sticky='w') <br /n><br />
l_location = Label(frame2,text='Location: ').grid(row=14,column=0,sticky='w') <br /n><br />
l_locationData = Label(frame2,textvariable=displayLocation).grid(row=14,column=1,sticky='w') <br /n><br />
l_azimuth = Label(frame2,text='Azimuth:').grid(row=15,column=0,sticky='w') <br /n><br />
l_azimuthData = Label (frame2,textvariable=azimuth).grid(row=15,column=1,sticky='w') <br /n><br />
l_elevation = Label(frame2,text='Elevation:').grid(row=16,column=0,sticky='w') <br /n><br />
l_elevationData = Label(frame2,textvariable=elevation).grid(row=16,column=1,sticky='w') <br /n><br />
l_errMsg = Label(frame2,textvariable=errMsg,fg='red').grid(row=17,columnspan=2,sticky='w') <br /n><br />
</ul><br />
<br />
<br />
The labels section of the script, with frame2 components added.<br />
[[File:RPCTOD_46_IDLEEditor_Labels.jpg|none|800px|Labels section of the script.]]<br />
<br />
<br />
Now we’ll add the new variables we referenced in the lines of code above, so they can be updated by different functions in the script with the get() and set() functions. Append the following lines of code to the Variables section of the script.<br />
<br />
<br />
<ul><br />
displayLastSun = StringVar() <br /n><br />
displayTimeData=StringVar() <br /n><br />
displayLocation=StringVar() <br /n><br />
azimuth = StringVar() <br /n><br />
elevation = StringVar()<br />
</ul><br />
<br />
<br />
The updated variables section of the script:<br />
[[File:RPCTOD_47_IDLEEditor_Variables.jpg|none|800px|Variables section of the script.]]<br />
<br />
<br />
The last step is to actually display the information, whenever the “Apply to Sun” button is clicked. To do this we need to update some of the whenWhere() function we’ve already created. Insert the following lines of code into that function, taking care to position them in the script as indicated in the screen shot.<br />
<br />
<br />
<ul><br />
displayTimeData.set(lookupTime) <br /n><br />
displayLocation.set(location) <br /n><br />
azimuth.set(results[0]) <br /n><br />
elevation.set(results[1]) <br /n><br />
displayLastSun.set(num.get())<br />
</ul><br />
<br />
<br />
The updated whenWhere() function now looks like this:<br />
[[File:RPCTOD_48_IDLEEditor_whenWhereFunction.jpg|none|800px|The updated whenWhere() function will now display messages.]]<br />
<br />
<br />
<br />
== In Conclusion ==<br />
<br />
This tutorial is meant to show you the possibilities of Terragen’s new RPC feature. How might you further modify this script to add even more functionality to it? <br />
* How about adding the ability to modify the camera’s exposure when the Sunlight node is below the horizon? What if more than one camera existed in the project? <br />
* How about a button that resets the sun’s heading and elevation to their default settings? <br />
* How would you add your location and timezone to the presets?<br />
* How would you turn off all the sunlight nodes at once? Back on again?<br />
<br />
Special thanks to John Clark Craig for making his Python script available to the public. <br />
<br />
<!-- The Terragen RPC Time of Day example script may be downloaded from Planetside Software [https://www.dropbox.com/s/x7gzd06mkqe8eoi/TerragenRPC_TimeOfDayExample.zip?dl=0 here]. --><br />
The Terragen RPC Time of Day example script may be downloaded from Planetside Software [https://www.dropbox.com/s/takc4hrcp59snum/TerragenRPC_TimeOfDayExample_v2.zip?dl=0 here].<br />
<br />
<br /n></div>Redmawhttps://planetside.co.uk/wiki/index.php?title=Micro_Exporter&diff=16664Micro Exporter2023-05-04T02:44:15Z<p>Redmaw: Updated descriptions, added images, and conformed page to new layout.</p>
<hr />
<div>[[File:00_MicroExport_GUI.PNG|none|507 px|Micro Exporter]]<br />
<br />
<br />
__TOC__<br />
<br />
<br />
== Overview ==<br />
The Micro exporter feature is only available in the Professional version of Terragen. It allows you to export procedural terrains as geometry, including displacement and overhangs. Exporting a terrain as a heightfield would not allow this. The Micro exporter can export geometry (polygons), normals and texture coordinates. It supports TGO, FBX, OBJ and Lightwave LW02 formats.<br />
<br />
[[File:03_MicroExport_RenderVsGeometry.jpg|none | 800px | Mesh generated by Micro exporter from procedural terrain.]]<br />
<br />
<br />
The Micro exporter is used in conjunction with a Render node to export the micropolygons generated during a render as geometry. For this reason the Render node’s Detail setting controls the size of the polygons generated. Lower values generate a coarser mesh, and as polygons get further away from the camera they also get larger.<br />
<br />
[[File:04_MicroExport_MeshDetailComparision_0001.jpg|none | 800px | Comparison of the geometry exported from the procedural terrain at different Detail values.]]<br />
<br />
<br />
The camera assigned to the Render node, along with the Near distance and Far distance parameters, define the area of terrain to export. Only geometry within the camera’s field of view is exported, including Backfacing polygons, which are those facing away from the camera.<br />
<br />
[[File:05_MicroExport_FarDistanceComparision_0001.jpg|none | 800px|The Near and Far distance parameters can limit the area of the terrain to export. ]]<br />
<br />
<br />
'''Settings:'''<br />
<br />
*'''Name:''' This setting allows you to apply a descriptive name to the node, which can be helpful when using multiple Micro Exporter nodes in a project.<br />
<br />
*'''Normals (from Compute Terrain or Compute Normal)''': When checked, the terrain’s normal values calculated by the upstream Compute Terrain node or Compute Normal node are exported to the OBJ file, and are available to other 3D software packages that load the OBJ file. When unchecked, the terrain's normal values are not saved with the object, resulting in a slightly smaller file size for the exported mesh.<br />
<ul><br />
[[File:06_MicroExport_NormalsOnOff_0001.jpg|none | 800px | When checked, the terrain normals are saved om the OBJ file and can be read by other 3D software packages.]]<br />
</ul><br />
<br />
<br />
*'''Texture coords (UVs)''': This pop-up menu has two options for exporting the uv data.<br />
<ul><br />
[[File:09_MicroExport_GUI_TextureCoords.PNG|None | 705 px| Texture coords options.]]<br />
<br />
<br />
*'''Image UV (render Projection)''': When selected, the uv coordinates are mapped into the 0 to 1 uv space from the render camera’s point of view. <br />
*'''UVW from Compute Terrain or Tex Coords:''' When selected the uv coordinates are mapped into the world space coordinates as determined by the last Compute terrain node or Tex Coords node These coordinate values can exceed 0 to 1.<br />
<ul><br />
[[File:07_MicroExport_TextureCoords_PerspectiveCamera.jpg|none|800px|Examples of the terrain geometry mapped into the UV texture coordinate space from a perspective camera’s point of view.]] <br /n><br />
<br />
[[File:08_MicroExport_TextureCoords_OrthoCamera.jpg|none| 800px | Examples of the terrain geometry mappedinto the UV texture coordinate space from an orthographic camera’s point of view.]]<br />
</ul><br />
</ul><br />
<br />
<br />
*'''Nearest distance:''' This sets the distance from the camera where geometry starts to be exported. When set to 0 geometry is exported right from the camera position. If you set this to 1000, then only parts of the terrain 1000 metres and more from the camera will be exported.<br />
<br />
<br />
*'''Farthest distance:''' This sets the furthest distance from the camera that geometry will be exported. Parts of the terrain beyond this distance are not exported.<br />
<br />
<br />
*'''Filename:''' This setting specifies the file that the exported geometry will be saved to. Wavefront OBJ, Autodesk FBX, Terragen TGO, and Lightwave LWO (format v2) are supported.<br />
<br />
<br />
== Fun with the Micro Exporter ==<br />
<br />
Large amounts of geometry can be created by the Micro exporter, which can be managed by using the Near distance and Far distance settings to limit the area exported, and using detail levels of 0.5 or below in the Render node.<br />
<br />
To export geometry from a procedural terrain follow these steps:<br />
# Go to the Sequence/Output tab of the Render node.<br />
# Check the '''Micro exporter''' checkbox.<br />
# Click the Assign button (green plus icon) to the right of the '''Micro exporter''' parameter.<br />
# Choose "Create new micro handler" from the menu which pops up.<br />
# Choose "Micro exporter" from the submenu.<br />
<br />
Note that the Lwo micro exporter which is also available in the "Create new micro handler" submenu is an older node which remains for compatibility reasons and is deprecated. You can export LWO files from the Micro exporter itself, and should use it instead.<br />
<br />
<br /n><br />
<br />
<br />
<br />
[[Category:Other Nodes]]</div>Redmawhttps://planetside.co.uk/wiki/index.php?title=File:08_MicroExport_TextureCoords_OrthoCamera.jpg&diff=16663File:08 MicroExport TextureCoords OrthoCamera.jpg2023-05-04T02:24:59Z<p>Redmaw: File uploaded with MsUpload</p>
<hr />
<div>File uploaded with MsUpload</div>Redmawhttps://planetside.co.uk/wiki/index.php?title=File:07_MicroExport_TextureCoords_PerspectiveCamera.jpg&diff=16662File:07 MicroExport TextureCoords PerspectiveCamera.jpg2023-05-04T02:24:33Z<p>Redmaw: File uploaded with MsUpload</p>
<hr />
<div>File uploaded with MsUpload</div>Redmawhttps://planetside.co.uk/wiki/index.php?title=File:09_MicroExport_GUI_TextureCoords.PNG&diff=16661File:09 MicroExport GUI TextureCoords.PNG2023-05-04T02:22:57Z<p>Redmaw: File uploaded with MsUpload</p>
<hr />
<div>File uploaded with MsUpload</div>Redmawhttps://planetside.co.uk/wiki/index.php?title=File:02_MicroExport_GUI_TextureCoords.PNG&diff=16660File:02 MicroExport GUI TextureCoords.PNG2023-05-04T02:21:13Z<p>Redmaw: File uploaded with MsUpload</p>
<hr />
<div>File uploaded with MsUpload</div>Redmawhttps://planetside.co.uk/wiki/index.php?title=File:06_MicroExport_NormalsOnOff_0001.jpg&diff=16659File:06 MicroExport NormalsOnOff 0001.jpg2023-05-04T02:20:29Z<p>Redmaw: File uploaded with MsUpload</p>
<hr />
<div>File uploaded with MsUpload</div>Redmawhttps://planetside.co.uk/wiki/index.php?title=File:05_MicroExport_FarDistanceComparision_0001.jpg&diff=16658File:05 MicroExport FarDistanceComparision 0001.jpg2023-05-04T02:16:55Z<p>Redmaw: File uploaded with MsUpload</p>
<hr />
<div>File uploaded with MsUpload</div>Redmawhttps://planetside.co.uk/wiki/index.php?title=File:04_MicroExport_MeshDetailComparision_0001.jpg&diff=16657File:04 MicroExport MeshDetailComparision 0001.jpg2023-05-04T02:16:19Z<p>Redmaw: File uploaded with MsUpload</p>
<hr />
<div>File uploaded with MsUpload</div>Redmawhttps://planetside.co.uk/wiki/index.php?title=File:03_MicroExport_RenderVsGeometry.jpg&diff=16656File:03 MicroExport RenderVsGeometry.jpg2023-05-04T02:14:47Z<p>Redmaw: File uploaded with MsUpload</p>
<hr />
<div>File uploaded with MsUpload</div>Redmawhttps://planetside.co.uk/wiki/index.php?title=File:00_MicroExport_GUI.PNG&diff=16655File:00 MicroExport GUI.PNG2023-05-04T02:13:29Z<p>Redmaw: File uploaded with MsUpload</p>
<hr />
<div>File uploaded with MsUpload</div>Redmawhttps://planetside.co.uk/wiki/index.php?title=Spotlight&diff=16654Spotlight2023-02-10T23:54:46Z<p>Redmaw: Added description and example images for Penumbra settings.</p>
<hr />
<div>[[File:Spotlight_00_GUI.png|none|470px|Spotlight]]<br />
<br />
<br />
== Overview ==<br />
<br />
Spotlight nodes provide a directional light source which can also be scaled. It is useful for creating beams or shafts of light.<br />
<br />
'''Settings:'''<br />
<br />
*'''Name:''' This setting allows you to apply a descriptive name to the node, which can be helpful when using multiple Spotlight nodes in a project.<br />
<br />
*'''Enable:''' When checked, the spotlight is enabled and will contribute lighting information to the scene. When unchecked, the spotlight is completely turned off and will no longer contribute to the scene.<br />
<br />
*'''Light surfaces:''' When checked, the light will illuminate the surfaces of the terrain or objects in the scene.<br />
<br />
*'''Light Atmo/Cloud:''' When checked, the spotlight will illuminate the atmosphere and cloud layers.<br />
<ul><br />
[[File:Spotlight_07_LightSurfAtmoCloud.jpg|None|800px| Comparison with Spotlight affecting surfaces, atmosphere and clouds.]]<br />
</ul><br />
<br />
<br />
*'''Position:''' This setting allows you to position the spotlight along the XYZ axis.<br />
<br />
*'''Rotation:''' This setting allows you to rotate the spotlight’s pitch, heading and bank along its XYZ axis.<br />
<br />
*'''Colour:''' This setting controls the colour of the light emanating from the spotlight.<br />
<ul><br />
[[File:Spotlight_04_Colour.jpg|none|800px|Spotlight colour approximating kelvin temperature and sRBG values.]]<br />
</ul><br />
<br />
<br />
*'''Strength:''' This setting controls the intensity of the spotlight.<br />
<ul><br />
[[File:Spotlight_05_Strength.jpg|none|800px|Spotlight strength.]]<br />
</ul><br />
<br />
<br />
*'''Max Distance:''' This is the maximum distance calculated for light rays emanating from the spotlight. Note that this value does not affect the falloff of the light over the maximum distance.<br />
<ul><br />
[[File:Spotlight_06_MaxDistance.jpg|none|800px|The Max distance value limits the distance over which the light rays are calculated.]]<br />
</ul><br />
<br />
<br />
== Beam Tab ==<br />
[[File:Spotlight_01_BeamTab.png|none|470px|Beam Tab]]<br />
<br />
<br />
*'''Shape:''' This pop-up provides two options which determine the shape of the spotlight’s aperture and therefore beam.<br />
<ul><br />
[[File:Spotlight_17_Shape_GUI.png|none|362px|Shape options.]]<br />
*Circle: Selecting this option causes the shape of the beam to be circular. <br /n><br />
*Square: Selecting this option causes the shape of the beam to be square. <br /n><br />
</ul><br />
<ul><br />
[[File:Spotlight_08_Shape.jpg|none|800px|Spotlight shape options.]]<br />
</ul><br />
<br />
<br />
*'''Aperture width:''' This setting controls the size of the aperture which emits light. Note in the example image below, as the value increases the size of the aperture gets larger, and light gets fainter as it is dispersed through a larger area.<br />
<ul><br />
[[File:Spotlight_09_ApertureWidth.jpg|none|800px|Aperture width]]<br />
</ul><br />
<br />
<br />
*'''Falloff power:''' This setting controls the exponent used to determine the attenuation, or falloff, of the light. A value of 0 is no falloff, a value of 1 is inverse-linear, the default value of 2 is equivalent to the inverse-square law, and a value of 3 would be inverse-cubed, etc.<br />
<ul><br />
[[File:Spotlight_10_FalloffPower.jpg|None|800px| Falloff power]]<br />
</ul><br />
<br />
<br />
*'''Outer angle:''' This setting describes the angle created by an imaginary line projecting from the center of the Spotlight to the outer edge of the Spotlight in degrees. Reducing the angle makes the beam narrower, while increasing the angle expands and widens the beam. <br />
<ul><br />
[[File:Spotlight_11_OuterAngle.jpg|None|800px| Outer angle]]<br />
</ul><br />
<br />
<br />
*'''Inner angle:''' This setting describes the angle created by an imaginary line projecting from the center of the Spotlight to the inner edge of the Spotlight in degrees. The appearance of light is strongest within this angle, and falls off between this value and the Outer angle value. The closer the Inner angle value is to the Outer angle value the sharper the light beam will appear.<br />
<ul><br />
[[File:Spotlight_12_InnerAngle.jpg|none|800px|Inner angle]]<br />
</ul><br />
<br />
<br />
*'''Penumbra gamma:''' This setting brightens the beam of light between the Inner and Outer Angle values.<br />
<ul><br />
[[File:Spotlight_43_PenumbraGamma.jpg|none|800px|Penumbra gamma comparison. S-curve on penumbra checked. Reverse gamma curve unchecked. Spotlight Aperture = 0.]]<br />
</ul><br />
<br />
<br />
*'''S-curve on penumbra:''' When checked, an “S” curve is applied to the falloff area between the Inner angle and Outer angle of the Spotlight’s beam.<br />
<ul><br />
[[File:Spotlight_46_SCurveOnPenumbraOnOff.gif|none|400px|S-Curve on penumbra comparison.]]<br />
</ul><br />
<br />
<br />
*'''Reverse gamma curve: ''' When checked, the gamma is reversed.<br />
<ul><br />
[[File:Spotlight_47_ReverseGammaCurveOnOff.gif|none|400px|Reverse gamma on and off.]]<br />
<br /n><br />
[[File:Spotlight_48_ReverseGammaOffOn.jpg|none|800px|Reverse gamma on and off.]]<br />
</ul><br />
<br />
<br />
<br />
==Effects Tab==<br />
[[File:Spotlight_02_EffectsTab.png|none|467px|Effects Tab]]<br />
<br />
<br />
*'''Cast Shadows:''' When checked, the Spotlight will cast shadows. When unchecked, it will not cast shadows.<br />
<ul><br />
[[File:Spotlight_36_CastShadowsOnOff.jpg|none|800px|Cast shadows on and off.]]<br />
</ul><br />
<br />
<br />
*'''Shadows of surfaces:''' When checked, the Spotlight casts shadows of surfaces. When unchecked, the spotlight will not cast shadows from an object's surfaces.<br />
<ul><br />
[[File:Spotlight_37_CastShadowsOnOff.jpg|none|800px|Shadows on surfaces on and off.]]<br />
</ul><br />
<br />
<br />
*'''Shadows of atmosphere and clouds:''' When checked, the Spotlight casts shadows for atmosphere and cloud layers. When unchecked, the spotlight will not cast shadows for cloud layers or the atmosphere.<br />
<ul><br />
[[File:Spotlight_38_ShadowOfAtmoCldsOnOff.jpg|none|800px|Shadows of atmosphere and clouds on and off.]]<br />
</ul><br />
<br />
<br />
*'''Do soft shadows:''' When checked and the Spotlight’s Aperture value is greater than 0, the Spotlight will soften the shadow edges according to the Soft shadow samples value. <br />
<ul><br />
[[File:Spotlight_39_DoSoftShadows9OnOff.jpg|none|800px|Do Soft Shadows on and off.]]<br />
</ul><br />
<br />
<br />
*'''Glow in atmosphere and clouds:''' When checked a glow effect is applied to the atmosphere and clouds within the Spotlight beam.<br />
<ul><br />
[[File:Spotlight_40_GlowInAtmoOnOff.jpg|none|800px|Glow in atmosphere and clouds on and off.]]<br />
</ul><br />
<br />
<br />
*'''Specular highlights:''' When checked, diffuse and specular lighting for a surface illuminated by the Spotlight will be rendered. When unchecked, only diffuse lighting is rendered.<br />
<ul><br />
[[File:Spotlight_41_SpecHighlightOnOff.jpg|none|800px|Specular highlights on and off.]]<br />
</ul><br />
<br />
<br />
== Import Tab ==<br />
[[File:Spotlight_03_ImportTab.png|none|467px|Import Tab]]<br />
<br />
These parameters apply to .chan/.mov and FBX import.<br />
<br />
*'''Import offset:''' This setting allows you to offset the positions imported from the file. As an example, you might want the positions imported from the file to be moved 10 metres in the X direction. To do that enter 10 for the X coordinate and import the file.<br />
*'''Import scale:''' You can use this setting to scale values imported from the file.<br />
*'''Import Chan file:''' This param specifies the file to be imported. When you a choose a new file you will be prompted to import the file. If you choose not to import it immediately you can click the Import chan File button to perform the import.<br />
<br /n></div>Redmawhttps://planetside.co.uk/wiki/index.php?title=File:Spotlight_48_ReverseGammaOffOn.jpg&diff=16653File:Spotlight 48 ReverseGammaOffOn.jpg2023-02-10T23:47:58Z<p>Redmaw: File uploaded with MsUpload</p>
<hr />
<div>File uploaded with MsUpload</div>Redmawhttps://planetside.co.uk/wiki/index.php?title=File:Spotlight_27_PenumbraGamma_0001.jpg&diff=16652File:Spotlight 27 PenumbraGamma 0001.jpg2023-02-10T23:41:19Z<p>Redmaw: File uploaded with MsUpload</p>
<hr />
<div>File uploaded with MsUpload</div>Redmawhttps://planetside.co.uk/wiki/index.php?title=File:Spotlight_47_ReverseGammaCurveOnOff.gif&diff=16651File:Spotlight 47 ReverseGammaCurveOnOff.gif2023-02-10T23:40:23Z<p>Redmaw: File uploaded with MsUpload</p>
<hr />
<div>File uploaded with MsUpload</div>Redmawhttps://planetside.co.uk/wiki/index.php?title=File:Spotlight_46_SCurveOnPenumbraOnOff.gif&diff=16650File:Spotlight 46 SCurveOnPenumbraOnOff.gif2023-02-10T23:38:36Z<p>Redmaw: File uploaded with MsUpload</p>
<hr />
<div>File uploaded with MsUpload</div>Redmawhttps://planetside.co.uk/wiki/index.php?title=File:Spotlight_43_PenumbraGamma.jpg&diff=16649File:Spotlight 43 PenumbraGamma.jpg2023-02-10T23:36:53Z<p>Redmaw: File uploaded with MsUpload</p>
<hr />
<div>File uploaded with MsUpload</div>Redmawhttps://planetside.co.uk/wiki/index.php?title=File:Spotlight_01_BeamTab.png&diff=16648File:Spotlight 01 BeamTab.png2023-02-10T23:34:26Z<p>Redmaw: File uploaded with MsUpload</p>
<hr />
<div>File uploaded with MsUpload</div>Redmawhttps://planetside.co.uk/wiki/index.php?title=Spotlight&diff=16647Spotlight2023-02-08T00:48:07Z<p>Redmaw: Added descriptions and example images for Effects Tab and Import Tab. Soft Shadow Samples pending. Penumbra parameters pending.</p>
<hr />
<div>[[File:Spotlight_00_GUI.png|none|470px|Spotlight]]<br />
<br />
<br />
== Overview ==<br />
<br />
Spotlight nodes provide a directional light source which can also be scaled. It is useful for creating beams or shafts of light.<br />
<br />
'''Settings:'''<br />
<br />
*'''Name:''' This setting allows you to apply a descriptive name to the node, which can be helpful when using multiple Spotlight nodes in a project.<br />
<br />
*'''Enable:''' When checked, the spotlight is enabled and will contribute lighting information to the scene. When unchecked, the spotlight is completely turned off and will no longer contribute to the scene.<br />
<br />
*'''Light surfaces:''' When checked, the light will illuminate the surfaces of the terrain or objects in the scene.<br />
<br />
*'''Light Atmo/Cloud:''' When checked, the spotlight will illuminate the atmosphere and cloud layers.<br />
<ul><br />
[[File:Spotlight_07_LightSurfAtmoCloud.jpg|None|800px| Comparison with Spotlight affecting surfaces, atmosphere and clouds.]]<br />
</ul><br />
<br />
<br />
*'''Position:''' This setting allows you to position the spotlight along the XYZ axis.<br />
<br />
*'''Rotation:''' This setting allows you to rotate the spotlight’s pitch, heading and bank along its XYZ axis.<br />
<br />
*'''Colour:''' This setting controls the colour of the light emanating from the spotlight.<br />
<ul><br />
[[File:Spotlight_04_Colour.jpg|none|800px|Spotlight colour approximating kelvin temperature and sRBG values.]]<br />
</ul><br />
<br />
<br />
*'''Strength:''' This setting controls the intensity of the spotlight.<br />
<ul><br />
[[File:Spotlight_05_Strength.jpg|none|800px|Spotlight strength.]]<br />
</ul><br />
<br />
<br />
*'''Max Distance:''' This is the maximum distance calculated for light rays emanating from the spotlight. Note that this value does not affect the falloff of the light over the maximum distance.<br />
<ul><br />
[[File:Spotlight_06_MaxDistance.jpg|none|800px|The Max distance value limits the distance over which the light rays are calculated.]]<br />
</ul><br />
<br />
<br />
== Beam Tab ==<br />
*'''Shape:''' This pop-up provides two options which determine the shape of the spotlight’s aperture and therefore beam.<br />
<ul><br />
[[File:Spotlight_17_Shape_GUI.png|none|362px|Shape options.]]<br />
*Circle: Selecting this option causes the shape of the beam to be circular. <br /n><br />
*Square: Selecting this option causes the shape of the beam to be square. <br /n><br />
</ul><br />
<ul><br />
[[File:Spotlight_08_Shape.jpg|none|800px|Spotlight shape options.]]<br />
</ul><br />
<br />
<br />
*'''Aperture width:''' This setting controls the size of the aperture which emits light. Note in the example image below, as the value increases the size of the aperture gets larger, and light gets fainter as it is dispersed through a larger area.<br />
<ul><br />
[[File:Spotlight_09_ApertureWidth.jpg|none|800px|Aperture width]]<br />
</ul><br />
<br />
<br />
*'''Falloff power:''' This setting controls the exponent used to determine the attenuation, or falloff, of the light. A value of 0 is no falloff, a value of 1 is inverse-linear, the default value of 2 is equivalent to the inverse-square law, and a value of 3 would be inverse-cubed, etc.<br />
<ul><br />
[[File:Spotlight_10_FalloffPower.jpg|None|800px| Falloff power]]<br />
</ul><br />
<br />
<br />
*'''Outer angle:''' This setting describes the angle created by an imaginary line projecting from the center of the Spotlight to the outer edge of the Spotlight in degrees. Reducing the angle makes the beam narrower, while increasing the angle expands and widens the beam. <br />
<ul><br />
[[File:Spotlight_11_OuterAngle.jpg|None|800px| Outer angle]]<br />
</ul><br />
<br />
<br />
*'''Inner angle:''' This setting describes the angle created by an imaginary line projecting from the center of the Spotlight to the inner edge of the Spotlight in degrees. The appearance of light is strongest within this angle, and falls off between this value and the Outer angle value. The closer the Inner angle value is to the Outer angle value the sharper the light beam will appear.<br />
<ul><br />
[[File:Spotlight_12_InnerAngle.jpg|none|800px|Inner angle]]<br />
</ul><br />
<br />
<!--<br />
*'''Penumbra gamma:''' This setting brightens the penumbra of the shadow.<br />
<ul><br />
{|<br />
|-<br />
| [[File:Spotlight_22_PenumbraGamma_PointLight.jpg|none|800px|Prenumbra gamma comparison. Spotlight Aperture 0.]]<br />
|-<br />
| [[File:Spotlight_26_PenumbraGamma_Aperture10.jpg|none|800px|Penumbra gamma comparison. Spotlight Aperture 10.]]<br />
|}<br />
</ul><br />
<br />
<br />
*'''S-curve on penumbra:''' This applies an “S” curve to the falloff area between the Inner angle and Outer angle.<br />
<ul><br />
{|<br />
|-<br />
| [[File:Spotlight_23_SCurveOFF_PointLight.jpg|none|800px|S-Curve on penumbra unchecked. Spotlight Aperture 0.]]<br />
|-<br />
| [[File:Spotlight_27_SCurveOFF_Aperture10.jpg|none|800px|S-Curve on penumbra unchecked. Spotlight Aperture 10.]]<br />
|}<br />
</ul><br />
<br />
<br />
*'''Reverse gamma curve: ''' Description coming.<br />
<ul><br />
{|<br />
|-<br />
| [[File:Spotlight_24_ReverseON_PointLight.jpg|none|800px|Reverse gamma curve checked. Spotlight Aperture 0.]]<br />
|-<br />
| [[File:Spotlight_28_ReverseON_Aperture10.jpg|none|800px|Reverse gamma curve checked. Spotlight Aperture 10.]]<br />
|-<br />
| [[File:Spotlight_25_SCurveONReverseON_PointLight.jpg|none|800px|S-curve on penumbra checked. Reverse gamma curve checked. Spotlight Aperture 0.]]<br />
|-<br />
| [[File:Spotlight_29_SCurveONReverseON_Aperture10.jpg|none|800px|S-curve on penumbra checked. Reverse gamma curve checked. Spotlight Aperture 10.]]<br />
|}<br />
</ul><br />
<br />
--><br />
<br />
==Effects Tab==<br />
[[File:Spotlight_02_EffectsTab.png|none|467px|Effects Tab]]<br />
<br />
<br />
*'''Cast Shadows:''' When checked, the Spotlight will cast shadows. When unchecked, it will not cast shadows.<br />
<ul><br />
[[File:Spotlight_36_CastShadowsOnOff.jpg|none|800px|Cast shadows on and off.]]<br />
</ul><br />
<br />
<br />
*'''Shadows of surfaces:''' When checked, the Spotlight casts shadows of surfaces. When unchecked, the spotlight will not cast shadows from an object's surfaces.<br />
<ul><br />
[[File:Spotlight_37_CastShadowsOnOff.jpg|none|800px|Shadows on surfaces on and off.]]<br />
</ul><br />
<br />
<br />
*'''Shadows of atmosphere and clouds:''' When checked, the Spotlight casts shadows for atmosphere and cloud layers. When unchecked, the spotlight will not cast shadows for cloud layers or the atmosphere.<br />
<ul><br />
[[File:Spotlight_38_ShadowOfAtmoCldsOnOff.jpg|none|800px|Shadows of atmosphere and clouds on and off.]]<br />
</ul><br />
<br />
<br />
*'''Do soft shadows:''' When checked and the Spotlight’s Aperture value is greater than 0, the Spotlight will soften the shadow edges according to the Soft shadow samples value. <br />
<ul><br />
[[File:Spotlight_39_DoSoftShadows9OnOff.jpg|none|800px|Do Soft Shadows on and off.]]<br />
</ul><br />
<br />
<br />
*'''Glow in atmosphere and clouds:''' When checked a glow effect is applied to the atmosphere and clouds within the Spotlight beam.<br />
<ul><br />
[[File:Spotlight_40_GlowInAtmoOnOff.jpg|none|800px|Glow in atmosphere and clouds on and off.]]<br />
</ul><br />
<br />
<br />
*'''Specular highlights:''' When checked, diffuse and specular lighting for a surface illuminated by the Spotlight will be rendered. When unchecked, only diffuse lighting is rendered.<br />
<ul><br />
[[File:Spotlight_41_SpecHighlightOnOff.jpg|none|800px|Specular highlights on and off.]]<br />
</ul><br />
<br />
<br />
== Import Tab ==<br />
[[File:Spotlight_03_ImportTab.png|none|467px|Import Tab]]<br />
<br />
These parameters apply to .chan/.mov and FBX import.<br />
<br />
*'''Import offset:''' This setting allows you to offset the positions imported from the file. As an example, you might want the positions imported from the file to be moved 10 metres in the X direction. To do that enter 10 for the X coordinate and import the file.<br />
*'''Import scale:''' You can use this setting to scale values imported from the file.<br />
*'''Import Chan file:''' This param specifies the file to be imported. When you a choose a new file you will be prompted to import the file. If you choose not to import it immediately you can click the Import chan File button to perform the import.<br />
<br /n></div>Redmawhttps://planetside.co.uk/wiki/index.php?title=File:Spotlight_02_EffectsTab.png&diff=16646File:Spotlight 02 EffectsTab.png2023-02-08T00:43:26Z<p>Redmaw: File uploaded with MsUpload</p>
<hr />
<div>File uploaded with MsUpload</div>Redmawhttps://planetside.co.uk/wiki/index.php?title=File:Spotlight_03_ImportTab.png&diff=16645File:Spotlight 03 ImportTab.png2023-02-08T00:42:05Z<p>Redmaw: File uploaded with MsUpload</p>
<hr />
<div>File uploaded with MsUpload</div>Redmawhttps://planetside.co.uk/wiki/index.php?title=File:Spotlight_41_SpecHighlightOnOff.jpg&diff=16644File:Spotlight 41 SpecHighlightOnOff.jpg2023-02-08T00:41:34Z<p>Redmaw: File uploaded with MsUpload</p>
<hr />
<div>File uploaded with MsUpload</div>Redmawhttps://planetside.co.uk/wiki/index.php?title=File:Spotlight_40_GlowInAtmoOnOff.jpg&diff=16643File:Spotlight 40 GlowInAtmoOnOff.jpg2023-02-08T00:40:58Z<p>Redmaw: File uploaded with MsUpload</p>
<hr />
<div>File uploaded with MsUpload</div>Redmawhttps://planetside.co.uk/wiki/index.php?title=File:Spotlight_39_DoSoftShadows9OnOff.jpg&diff=16642File:Spotlight 39 DoSoftShadows9OnOff.jpg2023-02-08T00:35:44Z<p>Redmaw: File uploaded with MsUpload</p>
<hr />
<div>File uploaded with MsUpload</div>Redmawhttps://planetside.co.uk/wiki/index.php?title=File:Spotlight_38_ShadowOfAtmoCldsOnOff.jpg&diff=16641File:Spotlight 38 ShadowOfAtmoCldsOnOff.jpg2023-02-08T00:35:05Z<p>Redmaw: File uploaded with MsUpload</p>
<hr />
<div>File uploaded with MsUpload</div>Redmawhttps://planetside.co.uk/wiki/index.php?title=File:Spotlight_37_CastShadowsOnOff.jpg&diff=16640File:Spotlight 37 CastShadowsOnOff.jpg2023-02-08T00:34:24Z<p>Redmaw: File uploaded with MsUpload</p>
<hr />
<div>File uploaded with MsUpload</div>Redmawhttps://planetside.co.uk/wiki/index.php?title=File:Spotlight_36_CastShadowsOnOff.jpg&diff=16639File:Spotlight 36 CastShadowsOnOff.jpg2023-02-08T00:33:35Z<p>Redmaw: File uploaded with MsUpload</p>
<hr />
<div>File uploaded with MsUpload</div>Redmawhttps://planetside.co.uk/wiki/index.php?title=Spotlight&diff=16638Spotlight2023-02-03T18:26:53Z<p>Redmaw: Fix typo.</p>
<hr />
<div>[[File:Spotlight_00_GUI.png|none|470px|Spotlight]]<br />
<br />
<br />
== Overview ==<br />
<br />
Spotlight nodes provide a directional light source which can also be scaled. It is useful for creating beams or shafts of light.<br />
<br />
'''Settings:'''<br />
<br />
*'''Name:''' This setting allows you to apply a descriptive name to the node, which can be helpful when using multiple Spotlight nodes in a project.<br />
<br />
*'''Enable:''' When checked, the spotlight is enabled and will contribute lighting information to the scene. When unchecked, the spotlight is completely turned off and will no longer contribute to the scene.<br />
<br />
*'''Light surfaces:''' When checked, the light will illuminate the surfaces of the terrain or objects in the scene.<br />
<br />
*'''Light Atmo/Cloud:''' When checked, the spotlight will illuminate the atmosphere and cloud layers.<br />
<ul><br />
[[File:Spotlight_07_LightSurfAtmoCloud.jpg|None|800px| Comparison with Spotlight affecting surfaces, atmosphere and clouds.]]<br />
</ul><br />
<br />
<br />
*'''Position:''' This setting allows you to position the spotlight along the XYZ axis.<br />
<br />
*'''Rotation:''' This setting allows you to rotate the spotlight’s pitch, heading and bank along its XYZ axis.<br />
<br />
*'''Colour:''' This setting controls the colour of the light emanating from the spotlight.<br />
<ul><br />
[[File:Spotlight_04_Colour.jpg|none|800px|Spotlight colour approximating kelvin temperature and sRBG values.]]<br />
</ul><br />
<br />
<br />
*'''Strength:''' This setting controls the intensity of the spotlight.<br />
<ul><br />
[[File:Spotlight_05_Strength.jpg|none|800px|Spotlight strength.]]<br />
</ul><br />
<br />
<br />
*'''Max Distance:''' This is the maximum distance calculated for light rays emanating from the spotlight. Note that this value does not affect the falloff of the light over the maximum distance.<br />
<ul><br />
[[File:Spotlight_06_MaxDistance.jpg|none|800px|The Max distance value limits the distance over which the light rays are calculated.]]<br />
</ul><br />
<br />
<br />
== Beam Tab ==<br />
*'''Shape:''' This pop-up provides two options which determine the shape of the spotlight’s aperture and therefore beam.<br />
<ul><br />
[[File:Spotlight_17_Shape_GUI.png|none|362px|Shape options.]]<br />
*Circle: Selecting this option causes the shape of the beam to be circular. <br /n><br />
*Square: Selecting this option causes the shape of the beam to be square. <br /n><br />
</ul><br />
<ul><br />
[[File:Spotlight_08_Shape.jpg|none|800px|Spotlight shape options.]]<br />
</ul><br />
<br />
<br />
*'''Aperture width:''' This setting controls the size of the aperture which emits light. Note in the example image below, as the value increases the size of the aperture gets larger, and light gets fainter as it is dispersed through a larger area.<br />
<ul><br />
[[File:Spotlight_09_ApertureWidth.jpg|none|800px|Aperture width]]<br />
</ul><br />
<br />
<br />
*'''Falloff power:''' This setting controls the exponent used to determine the attenuation, or falloff, of the light. A value of 0 is no falloff, a value of 1 is inverse-linear, the default value of 2 is equivalent to the inverse-square law, and a value of 3 would be inverse-cubed, etc.<br />
<ul><br />
[[File:Spotlight_10_FalloffPower.jpg|None|800px| Falloff power]]<br />
</ul><br />
<br />
<br />
*'''Outer angle:''' This setting describes the angle created by an imaginary line projecting from the center of the Spotlight to the outer edge of the Spotlight in degrees. Reducing the angle makes the beam narrower, while increasing the angle expands and widens the beam. <br />
<ul><br />
[[File:Spotlight_11_OuterAngle.jpg|None|800px| Outer angle]]<br />
</ul><br />
<br />
<br />
*'''Inner angle:''' This setting describes the angle created by an imaginary line projecting from the center of the Spotlight to the inner edge of the Spotlight in degrees. The appearance of light is strongest within this angle, and falls off between this value and the Outer angle value. The closer the Inner angle value is to the Outer angle value the sharper the light beam will appear.<br />
<ul><br />
[[File:Spotlight_12_InnerAngle.jpg|none|800px|Inner angle]]<br />
</ul><br />
<br />
<!--<br />
*'''Penumbra gamma:''' This setting brightens the penumbra of the shadow.<br />
<ul><br />
{|<br />
|-<br />
| [[File:Spotlight_22_PenumbraGamma_PointLight.jpg|none|800px|Prenumbra gamma comparison. Spotlight Aperture 0.]]<br />
|-<br />
| [[File:Spotlight_26_PenumbraGamma_Aperture10.jpg|none|800px|Penumbra gamma comparison. Spotlight Aperture 10.]]<br />
|}<br />
</ul><br />
<br />
<br />
*'''S-curve on penumbra:''' This applies an “S” curve to the falloff area between the Inner angle and Outer angle.<br />
<ul><br />
{|<br />
|-<br />
| [[File:Spotlight_23_SCurveOFF_PointLight.jpg|none|800px|S-Curve on penumbra unchecked. Spotlight Aperture 0.]]<br />
|-<br />
| [[File:Spotlight_27_SCurveOFF_Aperture10.jpg|none|800px|S-Curve on penumbra unchecked. Spotlight Aperture 10.]]<br />
|}<br />
</ul><br />
<br />
<br />
*'''Reverse gamma curve: ''' Description coming.<br />
<ul><br />
{|<br />
|-<br />
| [[File:Spotlight_24_ReverseON_PointLight.jpg|none|800px|Reverse gamma curve checked. Spotlight Aperture 0.]]<br />
|-<br />
| [[File:Spotlight_28_ReverseON_Aperture10.jpg|none|800px|Reverse gamma curve checked. Spotlight Aperture 10.]]<br />
|-<br />
| [[File:Spotlight_25_SCurveONReverseON_PointLight.jpg|none|800px|S-curve on penumbra checked. Reverse gamma curve checked. Spotlight Aperture 0.]]<br />
|-<br />
| [[File:Spotlight_29_SCurveONReverseON_Aperture10.jpg|none|800px|S-curve on penumbra checked. Reverse gamma curve checked. Spotlight Aperture 10.]]<br />
|}<br />
</ul><br />
<br />
--><br />
<br />
==Effects Tab==<br />
Coming soon.<br />
<br />
==Import Tab ==<br />
Coming soon.</div>Redmawhttps://planetside.co.uk/wiki/index.php?title=Spotlight&diff=16637Spotlight2023-02-03T18:25:13Z<p>Redmaw: Commented out Penumbra section. Update coming soon.</p>
<hr />
<div>[[File:Spotlight_00_GUI.png|none|470px|Spotlight]]<br />
<br />
<br />
== Overview ==<br />
<br />
Spotlight nodes provide a directional light source which can also be scaled. It is useful for creating beams or shafts of light.<br />
<br />
'''Settings:'''<br />
<br />
*'''Name:''' This setting allows you to apply a descriptive name to the node, which can be helpful when using multiple Spotlight nodes in a project.<br />
<br />
*'''Enable:''' When checked, the spotlight is enabled and will contribute lighting information to the scene. When unchecked, the spotlight is completely turned off and will no longer contribute to the scene.<br />
<br />
*'''Light surfaces:''' When checked, the light will illuminate the surfaces of the terrain or objects in the scene.<br />
<br />
*'''Light Atmo/Cloud:''' When checked, the spotlight will illuminate the atmosphere and cloud layers.<br />
<ul><br />
[[File:Spotlight_07_LightSurfAtmoCloud.jpg|None|800px| Comparison with Spotlight affecting surfaces, atmosphere and clouds.]]<br />
</ul><br />
<br />
<br />
*'''Position:''' This setting allows you to position the spotlight along the XYZ axis.<br />
<br />
*'''Rotation:''' This setting allows you to rotate the spotlight’s pitch, heading and bank along its XYZ axis.<br />
<br />
*'''Colour:''' This setting controls the colour of the light emanating from the spotlight.<br />
<ul><br />
[[File:Spotlight_04_Colour.jpg|none|800px|Spotlight colour approximating kelvin temperature and sRBG values.]]<br />
</ul><br />
<br />
<br />
*'''Strength:''' This setting controls the intensity of the spotlight.<br />
<ul><br />
[[File:Spotlight_05_Strength.jpg|none|800px|Spotlight strength.]]<br />
</ul><br />
<br />
<br />
*'''Max Distance:''' This is the maximum distance calculated for light rays emanating from the spotlight. Note that this value does not affect the falloff of the light over the maximum distance.<br />
<ul><br />
[[File:Spotlight_06_MaxDistance.jpg|none|800px|The Max distance value limits the distance over which the light rays are calculated.]]<br />
</ul><br />
<br />
<br />
== Beam Tab ==<br />
*'''Shape:''' This pop-up provides two options which determine the shape of the spotlight’s aperture and therefore beam.<br />
<ul><br />
[[File:Spotlight_17_Shape_GUI.png|none|362px|Shape options.]]<br />
*Circle: Selecting this option causes the shape of the beam to be circular. <br /n><br />
*Square: Selecting this option causes the shape of the beam to be square. <br /n><br />
</ul><br />
<ul><br />
[[File:Spotlight_08_Shape.jpg|none|800px|Spotlight shape options.]]<br />
</ul><br />
<br />
<br />
*'''Aperture width:''' This setting controls the size of the aperture which emits light. Note in the example image below, as the value increases the size of the aperture gets larger, and light gets fainter as it is dispersed through a larger area.<br />
<ul><br />
[[File:Spotlight_09_ApertureWidth.jpg|none|800px|Aperture width]]<br />
</ul><br />
<br />
<br />
*'''Falloff power:''' This setting controls the exponent used to determine the attenuation, or falloff, of the light. A value of 0 is no falloff, a value of 1 is inverse-linear, the default value of 2 is equivalent to the inverse-square law, and a value of 3 would be inverse-cubed, etc.<br />
<ul><br />
[[File:Spotlight_10_FalloffPower.jpg|None|800px| Falloff power]]<br />
</ul><br />
<br />
<br />
*'''Outer angle:''' This setting describes the angle created by an imaginary line projecting from the center of the Spotlight to the outer edge of the Spotlight in degrees. Reducing the angle makes the beam narrower, while increasing the angle expands and widens the beam. <br />
<ul><br />
[[File:Spotlight_11_OuterAngle.jpg|None|800px| Outer angle]]<br />
</ul><br />
<br />
<br />
*'''Inner angle:''' This setting describes the angle created by an imaginary line projecting from the center of the Spotlight to the inner edge of the Spotlight in degrees. The appearance of light is strongest within this angle, and falls off between this value and the Outer angle value. The closer the Inner angle value is to the Outer angle value the sharper the light beam will appear.<br />
<ul><br />
[[File:Spotlight_12_InnerAngle.jpg|none|800px|Inner angle]]<br />
</ul><br />
<br />
<--<br />
*'''Penumbra gamma:''' This setting brightens the penumbra of the shadow.<br />
<ul><br />
{|<br />
|-<br />
| [[File:Spotlight_22_PenumbraGamma_PointLight.jpg|none|800px|Prenumbra gamma comparison. Spotlight Aperture 0.]]<br />
|-<br />
| [[File:Spotlight_26_PenumbraGamma_Aperture10.jpg|none|800px|Penumbra gamma comparison. Spotlight Aperture 10.]]<br />
|}<br />
</ul><br />
<br />
<br />
*'''S-curve on penumbra:''' This applies an “S” curve to the falloff area between the Inner angle and Outer angle.<br />
<ul><br />
{|<br />
|-<br />
| [[File:Spotlight_23_SCurveOFF_PointLight.jpg|none|800px|S-Curve on penumbra unchecked. Spotlight Aperture 0.]]<br />
|-<br />
| [[File:Spotlight_27_SCurveOFF_Aperture10.jpg|none|800px|S-Curve on penumbra unchecked. Spotlight Aperture 10.]]<br />
|}<br />
</ul><br />
<br />
<br />
*'''Reverse gamma curve: ''' Description coming.<br />
<ul><br />
{|<br />
|-<br />
| [[File:Spotlight_24_ReverseON_PointLight.jpg|none|800px|Reverse gamma curve checked. Spotlight Aperture 0.]]<br />
|-<br />
| [[File:Spotlight_28_ReverseON_Aperture10.jpg|none|800px|Reverse gamma curve checked. Spotlight Aperture 10.]]<br />
|-<br />
| [[File:Spotlight_25_SCurveONReverseON_PointLight.jpg|none|800px|S-curve on penumbra checked. Reverse gamma curve checked. Spotlight Aperture 0.]]<br />
|-<br />
| [[File:Spotlight_29_SCurveONReverseON_Aperture10.jpg|none|800px|S-curve on penumbra checked. Reverse gamma curve checked. Spotlight Aperture 10.]]<br />
|}<br />
</ul><br />
<br />
--><br />
<br />
==Effects Tab==<br />
Coming soon.<br />
<br />
==Import Tab ==<br />
Coming soon.</div>Redmawhttps://planetside.co.uk/wiki/index.php?title=Spotlight&diff=16636Spotlight2023-02-02T05:30:40Z<p>Redmaw: Added tab headings.</p>
<hr />
<div>[[File:Spotlight_00_GUI.png|none|470px|Spotlight]]<br />
<br />
<br />
== Overview ==<br />
<br />
Spotlight nodes provide a directional light source which can also be scaled. It is useful for creating beams or shafts of light.<br />
<br />
'''Settings:'''<br />
<br />
*'''Name:''' This setting allows you to apply a descriptive name to the node, which can be helpful when using multiple Spotlight nodes in a project.<br />
<br />
*'''Enable:''' When checked, the spotlight is enabled and will contribute lighting information to the scene. When unchecked, the spotlight is completely turned off and will no longer contribute to the scene.<br />
<br />
*'''Light surfaces:''' When checked, the light will illuminate the surfaces of the terrain or objects in the scene.<br />
<br />
*'''Light Atmo/Cloud:''' When checked, the spotlight will illuminate the atmosphere and cloud layers.<br />
<ul><br />
[[File:Spotlight_07_LightSurfAtmoCloud.jpg|None|800px| Comparison with Spotlight affecting surfaces, atmosphere and clouds.]]<br />
</ul><br />
<br />
<br />
*'''Position:''' This setting allows you to position the spotlight along the XYZ axis.<br />
<br />
*'''Rotation:''' This setting allows you to rotate the spotlight’s pitch, heading and bank along its XYZ axis.<br />
<br />
*'''Colour:''' This setting controls the colour of the light emanating from the spotlight.<br />
<ul><br />
[[File:Spotlight_04_Colour.jpg|none|800px|Spotlight colour approximating kelvin temperature and sRBG values.]]<br />
</ul><br />
<br />
<br />
*'''Strength:''' This setting controls the intensity of the spotlight.<br />
<ul><br />
[[File:Spotlight_05_Strength.jpg|none|800px|Spotlight strength.]]<br />
</ul><br />
<br />
<br />
*'''Max Distance:''' This is the maximum distance calculated for light rays emanating from the spotlight. Note that this value does not affect the falloff of the light over the maximum distance.<br />
<ul><br />
[[File:Spotlight_06_MaxDistance.jpg|none|800px|The Max distance value limits the distance over which the light rays are calculated.]]<br />
</ul><br />
<br />
<br />
== Beam Tab ==<br />
*'''Shape:''' This pop-up provides two options which determine the shape of the spotlight’s aperture and therefore beam.<br />
<ul><br />
[[File:Spotlight_17_Shape_GUI.png|none|362px|Shape options.]]<br />
*Circle: Selecting this option causes the shape of the beam to be circular. <br /n><br />
*Square: Selecting this option causes the shape of the beam to be square. <br /n><br />
</ul><br />
<ul><br />
[[File:Spotlight_08_Shape.jpg|none|800px|Spotlight shape options.]]<br />
</ul><br />
<br />
<br />
*'''Aperture width:''' This setting controls the size of the aperture which emits light. Note in the example image below, as the value increases the size of the aperture gets larger, and light gets fainter as it is dispersed through a larger area.<br />
<ul><br />
[[File:Spotlight_09_ApertureWidth.jpg|none|800px|Aperture width]]<br />
</ul><br />
<br />
<br />
*'''Falloff power:''' This setting controls the exponent used to determine the attenuation, or falloff, of the light. A value of 0 is no falloff, a value of 1 is inverse-linear, the default value of 2 is equivalent to the inverse-square law, and a value of 3 would be inverse-cubed, etc.<br />
<ul><br />
[[File:Spotlight_10_FalloffPower.jpg|None|800px| Falloff power]]<br />
</ul><br />
<br />
<br />
*'''Outer angle:''' This setting describes the angle created by an imaginary line projecting from the center of the Spotlight to the outer edge of the Spotlight in degrees. Reducing the angle makes the beam narrower, while increasing the angle expands and widens the beam. <br />
<ul><br />
[[File:Spotlight_11_OuterAngle.jpg|None|800px| Outer angle]]<br />
</ul><br />
<br />
<br />
*'''Inner angle:''' This setting describes the angle created by an imaginary line projecting from the center of the Spotlight to the inner edge of the Spotlight in degrees. The appearance of light is strongest within this angle, and falls off between this value and the Outer angle value. The closer the Inner angle value is to the Outer angle value the sharper the light beam will appear.<br />
<ul><br />
[[File:Spotlight_12_InnerAngle.jpg|none|800px|Inner angle]]<br />
</ul><br />
<br />
<br />
*'''Penumbra gamma:''' This setting brightens the penumbra of the shadow.<br />
<ul><br />
{|<br />
|-<br />
| [[File:Spotlight_22_PenumbraGamma_PointLight.jpg|none|800px|Prenumbra gamma comparison. Spotlight Aperture 0.]]<br />
|-<br />
| [[File:Spotlight_26_PenumbraGamma_Aperture10.jpg|none|800px|Penumbra gamma comparison. Spotlight Aperture 10.]]<br />
|}<br />
</ul><br />
<br />
<br />
*'''S-curve on penumbra:''' This applies an “S” curve to the falloff area between the Inner angle and Outer angle.<br />
<ul><br />
{|<br />
|-<br />
| [[File:Spotlight_23_SCurveOFF_PointLight.jpg|none|800px|S-Curve on penumbra unchecked. Spotlight Aperture 0.]]<br />
|-<br />
| [[File:Spotlight_27_SCurveOFF_Aperture10.jpg|none|800px|S-Curve on penumbra unchecked. Spotlight Aperture 10.]]<br />
|}<br />
</ul><br />
<br />
<br />
*'''Reverse gamma curve: ''' Description coming.<br />
<ul><br />
{|<br />
|-<br />
| [[File:Spotlight_24_ReverseON_PointLight.jpg|none|800px|Reverse gamma curve checked. Spotlight Aperture 0.]]<br />
|-<br />
| [[File:Spotlight_28_ReverseON_Aperture10.jpg|none|800px|Reverse gamma curve checked. Spotlight Aperture 10.]]<br />
|-<br />
| [[File:Spotlight_25_SCurveONReverseON_PointLight.jpg|none|800px|S-curve on penumbra checked. Reverse gamma curve checked. Spotlight Aperture 0.]]<br />
|-<br />
| [[File:Spotlight_29_SCurveONReverseON_Aperture10.jpg|none|800px|S-curve on penumbra checked. Reverse gamma curve checked. Spotlight Aperture 10.]]<br />
|}<br />
</ul><br />
<br />
<br />
==Effects Tab==<br />
Coming soon.<br />
<br />
==Import Tab ==<br />
Coming soon.</div>Redmawhttps://planetside.co.uk/wiki/index.php?title=Spotlight&diff=16635Spotlight2023-02-02T05:29:22Z<p>Redmaw: Created wiki page for this node. Work in progress. Beam tab needs descriptions. Effects & Import tab coming soon.</p>
<hr />
<div>[[File:Spotlight_00_GUI.png|none|470px|Spotlight]]<br />
<br />
<br />
== Overview ==<br />
<br />
Spotlight nodes provide a directional light source which can also be scaled. It is useful for creating beams or shafts of light.<br />
<br />
'''Settings:'''<br />
<br />
*'''Name:''' This setting allows you to apply a descriptive name to the node, which can be helpful when using multiple Spotlight nodes in a project.<br />
<br />
*'''Enable:''' When checked, the spotlight is enabled and will contribute lighting information to the scene. When unchecked, the spotlight is completely turned off and will no longer contribute to the scene.<br />
<br />
*'''Light surfaces:''' When checked, the light will illuminate the surfaces of the terrain or objects in the scene.<br />
<br />
*'''Light Atmo/Cloud:''' When checked, the spotlight will illuminate the atmosphere and cloud layers.<br />
<ul><br />
[[File:Spotlight_07_LightSurfAtmoCloud.jpg|None|800px| Comparison with Spotlight affecting surfaces, atmosphere and clouds.]]<br />
</ul><br />
<br />
<br />
*'''Position:''' This setting allows you to position the spotlight along the XYZ axis.<br />
<br />
*'''Rotation:''' This setting allows you to rotate the spotlight’s pitch, heading and bank along its XYZ axis.<br />
<br />
*'''Colour:''' This setting controls the colour of the light emanating from the spotlight.<br />
<ul><br />
[[File:Spotlight_04_Colour.jpg|none|800px|Spotlight colour approximating kelvin temperature and sRBG values.]]<br />
</ul><br />
<br />
<br />
*'''Strength:''' This setting controls the intensity of the spotlight.<br />
<ul><br />
[[File:Spotlight_05_Strength.jpg|none|800px|Spotlight strength.]]<br />
</ul><br />
<br />
<br />
*'''Max Distance:''' This is the maximum distance calculated for light rays emanating from the spotlight. Note that this value does not affect the falloff of the light over the maximum distance.<br />
<ul><br />
[[File:Spotlight_06_MaxDistance.jpg|none|800px|The Max distance value limits the distance over which the light rays are calculated.]]<br />
</ul><br />
<br />
<br />
== Beam Tab ==<br />
*'''Shape:''' This pop-up provides two options which determine the shape of the spotlight’s aperture and therefore beam.<br />
<ul><br />
[[File:Spotlight_17_Shape_GUI.png|none|362px|Shape options.]]<br />
*Circle: Selecting this option causes the shape of the beam to be circular. <br /n><br />
*Square: Selecting this option causes the shape of the beam to be square. <br /n><br />
</ul><br />
<ul><br />
[[File:Spotlight_08_Shape.jpg|none|800px|Spotlight shape options.]]<br />
</ul><br />
<br />
<br />
*'''Aperture width:''' This setting controls the size of the aperture which emits light. Note in the example image below, as the value increases the size of the aperture gets larger, and light gets fainter as it is dispersed through a larger area.<br />
<ul><br />
[[File:Spotlight_09_ApertureWidth.jpg|none|800px|Aperture width]]<br />
</ul><br />
<br />
<br />
*'''Falloff power:''' This setting controls the exponent used to determine the attenuation, or falloff, of the light. A value of 0 is no falloff, a value of 1 is inverse-linear, the default value of 2 is equivalent to the inverse-square law, and a value of 3 would be inverse-cubed, etc.<br />
<ul><br />
[[File:Spotlight_10_FalloffPower.jpg|None|800px| Falloff power]]<br />
</ul><br />
<br />
<br />
*'''Outer angle:''' This setting describes the angle created by an imaginary line projecting from the center of the Spotlight to the outer edge of the Spotlight in degrees. Reducing the angle makes the beam narrower, while increasing the angle expands and widens the beam. <br />
<ul><br />
[[File:Spotlight_11_OuterAngle.jpg|None|800px| Outer angle]]<br />
</ul><br />
<br />
<br />
*'''Inner angle:''' This setting describes the angle created by an imaginary line projecting from the center of the Spotlight to the inner edge of the Spotlight in degrees. The appearance of light is strongest within this angle, and falls off between this value and the Outer angle value. The closer the Inner angle value is to the Outer angle value the sharper the light beam will appear.<br />
<ul><br />
[[File:Spotlight_12_InnerAngle.jpg|none|800px|Inner angle]]<br />
</ul><br />
<br />
<br />
*'''Penumbra gamma:''' This setting brightens the penumbra of the shadow.<br />
<ul><br />
{|<br />
|-<br />
| [[File:Spotlight_22_PenumbraGamma_PointLight.jpg|none|800px|Prenumbra gamma comparison. Spotlight Aperture 0.]]<br />
|-<br />
| [[File:Spotlight_26_PenumbraGamma_Aperture10.jpg|none|800px|Penumbra gamma comparison. Spotlight Aperture 10.]]<br />
|}<br />
</ul><br />
<br />
<br />
*'''S-curve on penumbra:''' This applies an “S” curve to the falloff area between the Inner angle and Outer angle.<br />
<ul><br />
{|<br />
|-<br />
| [[File:Spotlight_23_SCurveOFF_PointLight.jpg|none|800px|S-Curve on penumbra unchecked. Spotlight Aperture 0.]]<br />
|-<br />
| [[File:Spotlight_27_SCurveOFF_Aperture10.jpg|none|800px|S-Curve on penumbra unchecked. Spotlight Aperture 10.]]<br />
|}<br />
</ul><br />
<br />
<br />
*'''Reverse gamma curve: ''' Description coming.<br />
<ul><br />
{|<br />
|-<br />
| [[File:Spotlight_24_ReverseON_PointLight.jpg|none|800px|Reverse gamma curve checked. Spotlight Aperture 0.]]<br />
|-<br />
| [[File:Spotlight_28_ReverseON_Aperture10.jpg|none|800px|Reverse gamma curve checked. Spotlight Aperture 10.]]<br />
|-<br />
| [[File:Spotlight_25_SCurveONReverseON_PointLight.jpg|none|800px|S-curve on penumbra checked. Reverse gamma curve checked. Spotlight Aperture 0.]]<br />
|-<br />
| [[File:Spotlight_29_SCurveONReverseON_Aperture10.jpg|none|800px|S-curve on penumbra checked. Reverse gamma curve checked. Spotlight Aperture 10.]]<br />
|}<br />
</ul></div>Redmawhttps://planetside.co.uk/wiki/index.php?title=File:Spotlight_29_SCurveONReverseON_Aperture10.jpg&diff=16634File:Spotlight 29 SCurveONReverseON Aperture10.jpg2023-02-02T05:25:46Z<p>Redmaw: File uploaded with MsUpload</p>
<hr />
<div>File uploaded with MsUpload</div>Redmawhttps://planetside.co.uk/wiki/index.php?title=File:Spotlight_28_ReverseON_Aperture10.jpg&diff=16633File:Spotlight 28 ReverseON Aperture10.jpg2023-02-02T05:25:44Z<p>Redmaw: File uploaded with MsUpload</p>
<hr />
<div>File uploaded with MsUpload</div>Redmawhttps://planetside.co.uk/wiki/index.php?title=File:Spotlight_25_SCurveONReverseON_PointLight.jpg&diff=16632File:Spotlight 25 SCurveONReverseON PointLight.jpg2023-02-02T05:25:43Z<p>Redmaw: File uploaded with MsUpload</p>
<hr />
<div>File uploaded with MsUpload</div>Redmawhttps://planetside.co.uk/wiki/index.php?title=File:Spotlight_24_ReverseON_PointLight.jpg&diff=16631File:Spotlight 24 ReverseON PointLight.jpg2023-02-02T05:25:42Z<p>Redmaw: File uploaded with MsUpload</p>
<hr />
<div>File uploaded with MsUpload</div>Redmawhttps://planetside.co.uk/wiki/index.php?title=File:Spotlight_27_SCurveOFF_Aperture10.jpg&diff=16630File:Spotlight 27 SCurveOFF Aperture10.jpg2023-02-02T05:23:36Z<p>Redmaw: File uploaded with MsUpload</p>
<hr />
<div>File uploaded with MsUpload</div>Redmaw