diff --git a/help/nodes/obj/TaleSpire_Biome.txt b/help/nodes/obj/TaleSpire_Biome.txt
new file mode 100644
index 0000000..52ebd8a
--- /dev/null
+++ b/help/nodes/obj/TaleSpire_Biome.txt
@@ -0,0 +1,112 @@
+= TaleSpire Biome =
+
+"""Defines a Biome which controls what TaleSpire Tiles and Props get put on to the terrain."""
+=== Overview ===
+Defines a TaleSpire biome.
+
+All biomes except for the one marked "Default Biome" in the Biome Settings require a mask. These masks are set in the
+"Terrain Edit" area of the TaleSpire_Terrain node and accessible from the "Biome Mask Layer Name" on this node.
+
+NOTE:
+ When you set a node as the "Default Biome" it will disable that option on all other nodes in this network, however,
+ if you copy a node from somewhere else that has this toggle enabled you could end up with two biomes set as the
+ default biome and end up with unpredictable results.
+
+If this node is a sub-biome of another biome node, check the "Use as Sub-Biome" option, this will access the
+"Sub-Biome Settings" panel for more options.
+
+== Sub-Biomes ==
+Sub-Biomes do not place tiles or props like a normal biome, these networks can be ignored or their contents deleted.
+Instead of placing objects the sub-biome just instructs the main biome which objects to swap out. In
+"Edit Biome Objects", keep all the network boxes and objects in place with the same names but change the assets the
+TaleSpire_Object nodes are accessing.
+
+The point of a sub-biome is to replace the existing objects in the biome with different objects in the defined regions.
+So you can make a winter version of a map keeping all the trees and bushes in place and just changing them with
+something else.
+
+@parameters
+== General ==
+Disable Biome:
+ #id: disable_biome
+
+ If enabled this biome will not be used.
+
+Use as Sub-Biome:
+ #id: enable_sub_biome
+
+ If enabled this biome will be used as a sub-biome.
+
+Biome Mask Layer Name:
+ #id: biome_layer_name
+
+ The name of the heightfield layer to use as a mask for defining the region of this biome.
+
+Biome Priority:
+ #id: biome_priority
+
+ Priority for when biomes overlap. The higher priority will be applied in the case where the biome masks are equal.
+
+== Biome Settings ==
+Default Biome:
+ #id: default_biome
+
+ Defines this as the default biome, which means it applies to everything that isn't defined in another biome.
+ There should be only a single default biome defined in a terrain. If you enable this on one biome it will automatically be disabled on all other biomes in the same network.
+
+Scattering:
+ #id: biome_exclusion
+
+ Sets the default scattering behavior for the biome. The default, "Excludes Other Biomes", will only scatter objects
+ within the current biome. Setting this to "Includes Other Biomes" will cause the biome to scatter objects into all
+ other biomes. This can be overridden on individual TaleSpire_Scatter nodes.
+
+Sub Biomes:
+ #id: sub_biomes
+
+ Sets the number of sub-biomes to use.
+
+Sub Biome Node:
+ #id: sub_biome_#
+
+ The path to another biome node to be used as a sub-biome. The referenced biome should have "Use as Sub-Biome" set.
+
+== Sub-Biome Settings ==
+Mask Threshold:
+ #id: sb_mask_threshold
+
+ If the mask value is above this threshold it will be included in the sub-biome.
+
+Mask Jitter:
+ #id: sb_mask_jitter
+
+ Noise added to the mask to randomize the edge of biomes. This works best if your mask has a large feather to the edge.
+
+== Navigation ==
+Edit Biome Objects:
+ #id: edit_objects
+
+ Navigates to the sub-network to define the TaleSpire objects used to define this biome.
+
+Edit Biome Masks:
+ #id: edit_masks
+
+ Navigates to the sub-network to define layer masks for this biome, the masks set in here can be accessed in both the
+ tile and props networks.
+
+Edit Tiles:
+ #id: edit_tiles
+
+ Navigates to the sub-network to define where tiles are placed on the terrain.
+
+Edit Props:
+ #id: edit_props
+
+ Navigates to the sub-network to define where objects are placed/scattered on the terrain.
+~~~
+Refresh Biome:
+ #id: refresh_biome
+ When changing the objects and sets in the "Edit Objects" area it is sometimes necessary to use this button to refresh the biome.
+
+@related
+* [Node:obj/TaleSpire_Terrain]
\ No newline at end of file
diff --git a/help/nodes/obj/TaleSpire_Terrain.txt b/help/nodes/obj/TaleSpire_Terrain.txt
new file mode 100644
index 0000000..c8d6fb7
--- /dev/null
+++ b/help/nodes/obj/TaleSpire_Terrain.txt
@@ -0,0 +1,262 @@
+
+
+= TaleSpire Terrain =
+
+"""The main node for creating a terrain for TaleSpire."""
+
+== Overview ==
+Controls and sub-networks for creating and exporting a terrain to TaleSpire.
+
+@parameters
+=== Map Setup ===
+Terrain Name:
+ #id: terrain_name
+
+ The name used to store data on disk. The default, $OS, uses the name of the node.
+
+Terrain Size:
+ #id: terrain_size
+
+ The size of the map in tiles in both X and Z. Y being Up.
+
+Max Tile Stack:
+ #id: terrain_stack_max
+
+ The maximum number of tiles to stack under the surface tile, this will fill vertical gaps in steep terrain.
+
+==== Water ====
+Enable Water Plane:
+ #id: enable_water
+
+ When enabled, the water plane is used within the system.
+
+Show Water Plane:
+ #id: show_water
+
+ When enabled, the water plane is visible in the viewport.
+
+Water Plane Level:
+ #id: water_level
+
+ The height of the water plane. For interactivity, you may want to turn off "Enable Water Plane" while adjusting this.
+
+Max Water Tile Depth:
+ #id: water_stack_max
+
+ The maximum number of tiles to stack under the water surface.
+
+Enable Water Tile Generation:
+ #id: enable_water_tile_gen
+
+ When enabled, water tiles will be generated when needed.
+
+Exclude Water Tiles Below Water Plane:
+ #id: exclude_tiles_below_water_plane
+
+ When enabled, and the water plane is active, no water tiles will be generated under the water plane.
+
+==== Ground ====
+Show TS Board Level:
+ #id: show_ground
+
+ When enabled, the board level (Ground Plane) in TaleSpire will be shown.
+
+Automatically Determine Board Level:
+ #id: auto_ground_plane
+
+ When enabled, the ground plane will be automatically set slighly under the lowest asset on the map.
+
+Board Level:
+ #id: ground_level
+
+ The height of the ground plane.
+=== Display Options ===
+Display Heightfield Terrain:
+ #id: display_terrain
+
+ Toggles visibility of the heightfield terrain.
+
+Display Tiles:
+ #id: display_tiles
+
+ Toggles visibility of the surface tiles of the terrain.
+
+Display Props:
+ #id: display_props
+
+ Toggles visibility of the props/objects on the terrain.
+
+Textured Tiles:
+ #id: textured_tiles
+
+ Toggles the visibility of textured floor tiles.
+
+BBOX LOD Size Cutoff:
+ #id: bbox_lod_vis_size
+
+ A threshold for turning off the display of objects represented by bounding boxes based on their size.
+ The higher this value, large and larger bounding boxes will be hidden from view but the objects will still be exported.
+
+=== Edit ===
+Edit Terrain:
+ #id: edit_terrain
+
+ Navigates to the terrain editing network to sculpt the terrain and setup masks.
+
+Build/Cache Terrain Tiles:
+ #id: cache_terrain_tiles
+
+ Re-calculates and saves out the base tiles for the terrain.
+ This builds the 1x1 and 2x2 tiles of the surface terrain, it should be done every time the terrain is edited,
+ and when new biomes are defined or biome masks are changed.
+
+Edit Biomes:
+ #id: edit_biomes
+
+ Navigates to the biomes network to create and edit the TaleSpire_Biome nodes that populate the terrain.
+
+=== Export ===
+
+==== Limit Area ====
+Export Limited Area:
+ #id: export_limited_area
+
+ When enabled, exporting of the map will be limited to an area defined by the "Bounding Box Geo"
+
+Bounding Box Geo:
+ #id: limit_bbox_geo
+
+ The path to an object to use as the bounding box to limit the area of the map to be exported.
+ A houdini object node can be dragged into this field to link it.
+
+==== Slab Orientation and Order ====
+Show North:
+ #id: show_north
+
+ Toggles the visibility of an arrow over the terrain that points North.
+
+North Direction:
+ #id: north_dir
+
+ Sets the north direction of the map.
+
+Reorient Slab Order:
+ #id: reorient_slab_order
+
+ Uses the "North Direction" to re-orient the slabs so the slabs are placed from West to East and North to South.
+
+Zig Zag Slab Order:
+ #id: zig_zag_slab_order
+
+ Reverses the order of the slabs every other row, so if you are placing slabs manually you don't have to move your camera as far.
+==== Export Controls ====
+Display Slab Bounds:
+ #id: display_current_slab
+
+ Toggles the visibility of the bounding box of the current slab.
+
+Slab Size:
+ #id: slabsize
+
+ Sets the max size of the slabs to be exported. If you get a slab size error when exporting this will need to be lowered.
+
+Get Slab Index Range:
+ #id: set_slab_range
+
+ Calculates how many slabs are needed, based off the the "Slabe Size", and sets the Slab Index range.
+
+Slab Index:
+ #id: slabindex
+
+ Two fields that depict the currently active slab and the total number of slabs.
+
+Use Registration Marks:
+ #id: use_reg_marks
+
+ When enabled, registration tiles will be exported along with the slab. These are red tiles below the surface of the
+ slab that help with manually placing the slabs in TaleSpire.
+
+Copy Current Slab:
+ #id: copy_current_slab
+
+ Copies the currently active slab to the clipboard so it can be pasted into TaleSpire.
+
+Copy Slab and Advance:
+ #id: copy_slab_plus
+
+ Copies the currently active slab to the clipboard and advances the slab index to the next slab.
+ This allows you to repeatedly press this button to manually place every slab in the map.
+
+Multi Copy Slabs:
+ #id: multi_copy_slabs
+
+ Copies all slabs to the clipboard at once. This requires the Multi-Paste Slabs mod to paste into TaleSpire but saves a ton of time.
+
+Use Range List:
+ #id: multi_use_list
+
+ Enable to use a list of slabs as an export, for testing, or if the export missed a few slabs.
+
+Range List:
+ #id: multi_range_list
+
+ A comma separated list of ranges to use for exporting slabs.
+ - Individual slabs: 8, 13, 22
+ - Ranges: 1-10, 21-30
+ - Ranges with increments: 1-30:2
+ - Full Range with increment: 1:3
+ - Full Range with offset increment: 2:3 ...
+ 3:3
+
+==== Multi Slab Offset ====
+Offset:
+ #id: multi_slab_offset
+
+ When using "Multi Copy Slabs" this offset applies to the entire map within TaleSpire.
+
+=== Attributions ===
+Generate:
+ #id: generate_attributions
+
+ Analyzes all the slabs used to make this map and generates a list of attributions.
+
+Copy to Clipboard:
+ #id: clip_attributions
+
+ Copies the list of attributions to the clipboard so they can be pasted elsewhere.
+
+Attributions:
+ #id: attribution_string
+
+ The attributions generated. To utilizes attributions they must be set on the TaleSpire_Object nodes in your biomes.
+=== Settings ===
+==== Configs ====
+Refresh Configs:
+ #id: cfg_refresh
+
+ Reloads the configs from disk in case they were changed while this file was open.
+
+TaleSpire Directory:
+ #id: cfg_talespire_directory
+
+ The base directory of where TaleSpire is installed on the filesystem. The toolset will not work if TaleSpire is not installed on the same machine.
+
+Textured Tiles Enabled by Default:
+ #id: cfg_textured_tiles
+
+ If enabled, all new TaleSpire_Terrain nodes will have textured_tiles enabled when placed.
+
+==== Hacks ====
+Force Recook all the things!:
+ #id: cook_the_things
+
+ Forces a recook of the terrain and biomes in case things are not looking as expected.
+ This shouldn't have to be used much anymore, most of the re-cooking issues have been dealt with.
\ No newline at end of file
diff --git a/otls/OBJ_TaleSpire_Biome.hdanc b/otls/OBJ_TaleSpire_Biome.hdanc
index 540853f..1dd212a 100644
Binary files a/otls/OBJ_TaleSpire_Biome.hdanc and b/otls/OBJ_TaleSpire_Biome.hdanc differ
diff --git a/otls/OBJ_TaleSpire_Cavern_Helper.hdanc b/otls/OBJ_TaleSpire_Cavern_Helper.hdanc
index 1e7bfe0..bba4678 100644
Binary files a/otls/OBJ_TaleSpire_Cavern_Helper.hdanc and b/otls/OBJ_TaleSpire_Cavern_Helper.hdanc differ
diff --git a/otls/OBJ_TaleSpire_Object.hdanc b/otls/OBJ_TaleSpire_Object.hdanc
index cbf3121..ee25984 100644
Binary files a/otls/OBJ_TaleSpire_Object.hdanc and b/otls/OBJ_TaleSpire_Object.hdanc differ
diff --git a/otls/OBJ_TaleSpire_Terrain.hdanc b/otls/OBJ_TaleSpire_Terrain.hdanc
index 18abbaf..875797c 100644
Binary files a/otls/OBJ_TaleSpire_Terrain.hdanc and b/otls/OBJ_TaleSpire_Terrain.hdanc differ
diff --git a/scripts/data/nodes/TaleSpire_Biome_biome_masks_contents.net b/scripts/data/nodes/TaleSpire_Biome_biome_masks_contents.net
new file mode 100644
index 0000000..ce64902
--- /dev/null
+++ b/scripts/data/nodes/TaleSpire_Biome_biome_masks_contents.net
@@ -0,0 +1 @@
+gAAAAABmpbl5-IfuiZgdBfeEeDG7uDg3gtpOCQt6gJgWOwI1ynX2HsDDM8T1EArcViDs9vTaDYZJKWCkNIW8LXxphSYVtmpFLwemh0Qa4MMYhwozOlgSQs0I0AqXJHKanYdHHg8qKDSv7vh85DONLd-52-4ti86Zn71Q6p4J84Fa1PwPyXrWP1WUkK3R8xUEdq1CAklBkpYdH5DY6l8JBJxTZFslxdAtml2ReYw_vs53x1NhX-g-zG6L_ovSPHvs_A_9UMXq4xnhtkZ34V-l4mK1WDa1OAdW8_rt1j3avX0vsl-H6B4yRFaevoNO8w_6K-77pY61_lxwIB9ipMB3eVmRCZs7dP_yY7TC9L27XbfYy8SAYLtU1OuRoy6Oq-FEgBDWW3q2_SQIGAPa-UmCSVdcG_kPRPy7PQbte88eATBcSpXdifUzmshoS3wq1fKVInze7-9KPqtcBhrh8sWRD5WzX0wlYm58evGrHUzQLHnVl1SJYvBUVfhOipLtPU8tfoCqwQV7eShJ5qj2Df5CIK6DC0ltWjcDKjW8RyZUA0SmlhL0Rh5Xun5_xZyO4hlGG8qmavS6k7F6-ns3ZJfcorh5TeNNc7tP2bO513zMPKA2NXMirJME8DUpLzMgmI7LjyWGyvC5OGj_gsuZtuI55FLoOWhqZf7JZUoCduC-RGQFFCVPkQj9keUhfU4YPGI77kkMZnupnURgSto8PTuY8vzO_RQztDlTTDhuWb--XJg8Plh8q2p5v3qVVtdszHIWBn0sBmhqCBz4WkZq-BsDe5IZzdvVCYKeJ3d6k6gsnOQkB9c7ItC6vITmdmTmgq42Ym77PMFGCZORRKUbRD0gG_SD8WK8H--iFErhG-Av3IX2P2iqFseEaQ3zUXeI1AIhcAJ2qAVbdkBdL2fS8xULY7zgQJai5OzB4j5E0Uvg7Yg7lmlhWNbBto-AOVhXLBxbVnqoCz84qXO5uHqejsDFmJx1FPJ50HcdhmqVgApjDttiO_VBBgds9hYaKQT5QDUWSoSAGwYoGE5izcnjVJMXI9M2VuV6bFBqMtOcaLvSN4xJl1fdhbFNhX3uHX2XfGyjLfZXPfFoNFmiRorUfioohb058BKo0a5Rv4O-5_a18AHmKd_KjeIHVuEVgCPnQexY2NjpG6_nWqBOfLYUfD2xAxhCE5pLPPShFXXO1LPMaZ-gJdQrJb04wHzWJDvxJM_zloU6pfk3D8Dfbi1XATjj1U-IrnHswf_tgUiTg3WysSZ_aY4TJHhr-30a2sitPsjzxi-WjADBQlSBV3azU9iLV4Ne189fbhhwag_54nke4pK2UsYDiCHLCuwHFYLrAdySHrcjYaTzUiASx3FhB_J2B2LqnPa2ww1y-zY3ybdCFdDph-ZO2pLKF1Via9du4X3sszemrwmMFAiGbNXHCypmGKlYdSmuEIbpmGy4BtkkKOQhScLWt6-SLMKEg4uIZm4cxISuKkkb8pz-eScdCtGGyCrJJYldXZm4zAx-llok6rKs4Csa4ELYyNCfq0RCE3XimcyXFaHLIPPUWgHP4DUehh4yIrnC3VjnWSxGWxphczu5zKr05NPJfbfdXDpP4Va6jDD1g_waO9M4GmAQEugx8eAtTHq0prgqPapkSo-y4rQ5GehkAX_vn2c_78M5xwC_aEEtRhpm8KIVq6cChe_XxaJqplDSshLiVRf3j4kKY-oDcsw7eAdVUibNKGMLZkwb1MowwDUJEheeqD0VVgmZxTZlaxaxGJa3q7W-PGkl7nr-QTuq4pexzycv6NuDnYyF0nGjtZu14T9cIyYHuDHpZmNwUlIzg2g7PwpPaGdPIokYra7QwsC7hAi6gvkU-UsbfiaZIFyMfLExvNUEAW1I7592T_Bl4gBcRqBdMkvEg7iMKzTXPw1TQMblL-o71xE-Hw7A_wmpuMnODkFPKWVIDj7wTvygUC7LTfIwcHj1w7iiRFMwjHP2azrld63JMApnd_GiVHeIXerhYQlEXCfyXGBeCQuR18XGiQE_1yy73R1xqch9j_lb7YEnuIB9LLg-4YKqYPRsPOAaHVElhomlvmTdRv--rvn0Y65VO0CEU4cubJlrxeP2Bhq0HIa1n7IPmeg1XvVCv60aMJS6uSldTGlb3g7k9skRg-nkuOLgt9CTJhq_r_Oy4qNFgSEepNzrAo-xxPWVNUZIu8UYKk12C-9JBtDFjyjlfHlYlRzmQoxfR5Pj0cTo4TpfZ7952CGECqj7_9pXRUQEKvr9r1VTrBAolBR3x72iW8uM7nmeB1rQgdH6_LJKTevqlNL6mlK2h9Dz59-b4Hu_yg-LA9Wz7WF2ueLkg_f3NCUqU82naeF6FZm1FzFWVMNL0GZgD1RmIwHPNxHp4ZiaOEPFagC1qGl_n74WbExpHXZTzhdGsdG6EZZUEBrq2LItcCLpKbnToH0k5yxiucCTb8Iks3aRG9RunqVr-O3CCFR867po9yhMnIttQFU-8PbIgDcJBsytlsMhWJg3Y4e4fRX-OlL0rHQce0Cb3YkGg_LcoqurVl2JOnVSbprMD8MYj2R4ebFNdPgG3Rq9YvNkcJ7pwBQ-ldPKPFQhKeqJkqhd7pjxApG71Aw2Ttm1xPzsg9vOE-Ntz2YfjZRMX4xc2Ev-cg1qwHGIaYdOJkfBQMuWFHXBTpMf0pXfWg6qySLZ743bP_pmUFXI9nxLpX4lOqqu6TMYon7kpejrRYfSez3Z4jIRrAMQOal6LFwL89XPWiJFi5N7BF5Ub5Z9rH6KSvnK-nJKl29DAFclT6GaF5JV2bBXQ2tb35rwqiwugxTcb8IMy1QqAu93OReHcjkrI9AsZERvpPuTMN6IpjpY9ZtvDSDFtuYusG-J7kTlT15rYin8BkS7tDk8cb3hJ1SNDtQ-Oi0ipaEsOVKoQvnFExijparSnNSUP6zehKuc1NqHf1dBmw-a9pWl-EWCedmqo-6AoOSjADVlImyX_9VHOZIud8PS1w4sv89Zhh_52Sz6qwVu6juPJHZOqpO1Hw9caVGsR_mvjbdg0sdog088ImxAjnkGO-wXY_kZn9PE5XIyFcjt9CNdlFlJJgiRF0XhoWVSGielj4bTQrJhfh9S2Z92DtlVIF2D7FFiNseY_mWwOkFxdVfc_BoYz4PeoORYXZUAhnH3gpl_iu-k92pUyyes3AlRavoMo6fZQgTJP4oQZxgjpiem7H0KWT8KZV7Jd2Ik0vBKMcpaI52LetZ2Zv4CdbLNlOWJjWwM7_Vf6ETNJj5ra7_1oQO12EoztT-5CjaawOP1lAaZgzZtqo5rqXdMD8jHXEWiZQVUbH2JQjE6Hv25kfd83rYOGDhzkM4B7CeAF5kDJd94T386pFru97B33kdJyqdvMlYvIbw1HO14RZTEf9NgPhw7bwNZ75DgQXnlHCm1CabhYpjxZXiEy1t7BwPkLQgyiWmYaxJcGUt-GhcxMViWuf-HQSd-Vt59LN92aiyMka8Hp4mDQf8LXQYPPHZaV_1gpiJMvTjzJJIthWC18SvGrzCBSRB5aW-0ykiUmXK9PMq87SnGlUZ1eNsJA3iYSKPzgg0Gp9YwSAce9IbGeTTRCufmS1DY58slBbIvfPoW1jFa-Z_EUcLzuqBeBbIkSBN-v8vN-DD7PmLIQ5nxiyUyeX7T_9iHUOg9GBh6MiCKxPaEXAvWL1eDcXvqDM_SaOGZ1CBVQfw-zqSN7aJuD99hlsimONv5eIrDMVg6abH3X8a3Yl2sYAbLMKbLE1d02mOincLXjH8wO84j_iH3WFGFJbYxpxF2-iNuNNleoNeKxnzmgwQQ_IuPWX3MdwGvF-G53H8t1iucZ7w9qYWETqAvbZ_ga9Hm7EEUHMrv-CCkYLivIZ0tL-mIEwqZ91KuULuRPu28cStbdipvGOHyvZODYHddzCcHcSD1XnPVKYiDfMX_6jc_nBGxCpLEIh0C98qwqMSpvavTOWSyj68FNFJIi5jd56hrV_9Xh-DB-ANLOOy7p819Nrqu8wK86S3qYtM5jinv7okvIffbnds8fz92Cj37Dowi9Ct2BE5lCiih0X7VpDYLFLkcz038zVv0r4k6FnrRzRcFDOjH0wd0-h-Ag4IjCH5ySLeEnDVB3PocJ66pIXf3zoQo7-jGJuFWxrvXGsn0qhl1uoPp5ludvqcnpCrwvHyUPKW20HeXDeXVJMPxpiTiCaHem3vxaP7RKGCA0tpFxyzQI9UJ7bZer_u8hHX0NnAE5Xt9wCEebE6_kYYwp_-MNxNHXWSyy_g8qWKXRbEtTWyFlC8-9kNYgbElADWjAJUrCVZV1VdU9CuZTN_O0GfZQENXqW8slIKc8mOfS_EWt5lMTiNJwx7OhN-L-wQl-NrMu1mMYzS2jcjJCrMg9Wp-kaFRHaSJkxvrKe9iNihFog-FpijaG4mUiH6Q9jOzVS54oJHKfwPJVT_TzLjOVY-VrNqlCqLmw26MjWRx1FTrdChLz7UAvrj2YBGusFIYhkgbqib1
\ No newline at end of file
diff --git a/scripts/python/htg/nodes/OBJ_TaleSpire_Biome.py b/scripts/python/htg/nodes/OBJ_TaleSpire_Biome.py
index 8bef167..128aedd 100644
--- a/scripts/python/htg/nodes/OBJ_TaleSpire_Biome.py
+++ b/scripts/python/htg/nodes/OBJ_TaleSpire_Biome.py
@@ -16,6 +16,17 @@ def edit_objects(node=None):
htg.utils.set_network(node, dest_node)
+def edit_masks(node=None):
+ networks = {
+ 'biome_masks': 'TaleSpire_Biome_biome_masks_contents.net'
+ }
+
+ dest_node = hou.node(node.path() + '/biome_masks')
+ if len(dest_node.children()) == 0:
+ ts_common.load_networks(node, networks)
+ htg.utils.set_network(node, dest_node)
+
+
def edit_tiles(node=None):
dest_node = hou.node(node.path() + '/biome_tiles')
htg.utils.set_network(node, dest_node)
@@ -49,6 +60,12 @@ def save_biome_objects_network(node=None):
ts_common.save_network(net_node, file_name, mode='node')
+def save_biome_masks_network(node=None):
+ file_name = 'TaleSpire_Biome_biome_masks_contents.net'
+ net_node = hou.node(node.path() + '/biome_masks')
+ ts_common.save_network(net_node, file_name, mode='node')
+
+
def save_biome_tiles_network(node=None):
file_name = 'TaleSpire_Biome_biome_tiles_contents.net'
net_node = hou.node(node.path() + '/biome_tiles')
diff --git a/scripts/python/htg/nodes/OBJ_TaleSpire_Terrain.py b/scripts/python/htg/nodes/OBJ_TaleSpire_Terrain.py
index d1b0ebc..589ad13 100644
--- a/scripts/python/htg/nodes/OBJ_TaleSpire_Terrain.py
+++ b/scripts/python/htg/nodes/OBJ_TaleSpire_Terrain.py
@@ -205,7 +205,7 @@ def copy_slab_and_advance(node=None):
def encode_slabs(node=None):
- num_assets = asset_count(node)
+ num_assets = asset_count(node)['num_assets']
do_export = True
if num_assets >= 1000000:
button_result = hou.ui.displayMessage('Warning: This map contains more that 1 Million assets.\n'
@@ -222,7 +222,14 @@ def encode_slabs(node=None):
og_index = slab_index_parm.eval()
slab_json = []
- for i in range(1, num_slabs + 1):
+ multi_use_list = node.parm('multi_use_list').eval() == 1
+ if multi_use_list:
+ range_list_string = node.parm('multi_range_list').eval()
+ range_list = multi_range(range_list_string, num_slabs)
+ else:
+ range_list = range(1, num_slabs + 1)
+
+ for i in range_list:
slab_index_parm.set(i)
slab_pos = get_slab_with_pos(node)
if slab_pos is not None:
@@ -231,6 +238,39 @@ def encode_slabs(node=None):
slab_index_parm.set(og_index)
+def multi_range(range_string, num_slabs=None):
+ range_list = [x.strip() for x in range_string.split(",")]
+ output_list = []
+ for range_item in range_list:
+ inc_split = range_item.split(":")
+ if len(inc_split) == 2:
+ inc = int(inc_split[-1])
+ else:
+ inc = None
+
+ range_pair = inc_split[0].split("-")
+ if len(range_pair) == 1:
+ r_start = int(range_pair[0])
+ if inc:
+ output_list += range(r_start, num_slabs+1, inc)
+ else:
+ output_list += [r_start]
+ elif len(range_pair) == 2:
+ r_start, r_end = range_pair
+ r_start = int(r_start)
+ if r_end == "NSLABS":
+ r_end = num_slabs
+ else:
+ r_end = int(r_end)
+
+ if inc:
+ output_list += range(r_start, r_end+1, inc)
+ else:
+ output_list += range(r_start, r_end+1)
+
+ return output_list
+
+
def get_slab_with_pos(node=None):
offset_node = hou.node(node.path() + '/Export_Slab/First_Tile')
offset_node.cook(force=True)
@@ -288,18 +328,36 @@ def get_asset(uuid):
return {"Id": uuid, "type": uuid_data['type']}
-def asset_count(node=None):
+def asset_count(node=None, unique=False):
+ results = {}
try:
- asset_node = hou.node(node.path() + '/Export_Slab/TILES_AND_PROPS')
+ asset_node = hou.node(node.path() + '/Export_Slab/FOR_EXPORT')
geo = asset_node.geometry()
- points = geo.points()
- num_assets = len(points)
+ num_assets = len(geo.points())
+ num_tiles = len(geo.findPointGroup('tiles').points())
+ num_props = len(geo.findPointGroup('props').points())
+ results = {
+ 'num_assets': num_assets,
+ 'num_tiles': num_tiles,
+ 'num_props': num_props
+ }
+ if unique:
+ unique_assets = set()
+ for point in geo.points():
+ unique_assets.add(point.attribValue('uuid'))
+ num_unique_assets = len(unique_assets)
+ results['num_unique_assets'] = num_unique_assets
+
except AttributeError:
- num_assets = 0
+ pass
- return num_assets
+ return results
def show_asset_count(node=None):
- num_assets = asset_count(node)
- hou.ui.displayMessage('Total Asset Count:\n{}'.format(num_assets), details=str(num_assets))
+ asset_data = asset_count(node, unique=True)
+ message = f"Tiles: {asset_data['num_tiles']}\n"\
+ f"Props: {asset_data['num_props']}\n"\
+ f"Total Asset Count: {asset_data['num_assets']}\n\n"\
+ f"Unique Assets: {asset_data['num_unique_assets']}"
+ hou.ui.displayMessage('', details=message, details_expanded=True)
diff --git a/scripts/python/version.py b/scripts/python/version.py
index 3cf36fb..9e803c6 100644
--- a/scripts/python/version.py
+++ b/scripts/python/version.py
@@ -1,4 +1,4 @@
# This is the version of the release.
# Should be updated whenever a push is done.
# Include branch name on daily pushes. Only upgrade Major and Minor version when Pulling into the master branch.
-version = '0.21.1'
+version = '0.22.0'