68 Commits

Author SHA1 Message Date
314284ed94 [feat] finish wide floor and sink platform auto gen.
- add height re-calc in derived block generation for fixing height issue when re-use Flat in sink platform.
- finish all wide floor prototype.
- finish all platform prototype.
2022-12-29 11:28:50 +08:00
0a815f04d6 [feat] change BMERevenge model read method
- divided json into small pieces for easy management.
- change json reading method for change (1)
2022-12-28 21:40:51 +08:00
2bd031784a [feat] update BMERevenge generation method
- use some laggy AST code to update BMERevenge generation method.
- after this change, BMERevenge become more programable and easy to create more complex models
- ready for wide floor generation development
2022-12-27 22:08:35 +08:00
d5cb8eb1ec make Virtools material can be optional 2022-11-20 12:02:15 +08:00
02c11ffe5a [feat] add more virtools group related operators
- support select/filter by virtools group.
- support group/ungroup/clear in object context menu.
- improve virtools group visit functions
2022-11-19 14:30:31 +08:00
240d5612df add new type rail uv: TT_ReflectionMapping 2022-08-29 10:52:34 +08:00
84dd5b76f1 add feature, optimize function argv passing.
- add support of 4 alpha fields intorduced in BM spec recently.
- optimize material creation functions argv passing strategy.
- change related func calls of (2).
- optimize material parameter pick code to reduce useless check.
2022-07-23 17:22:44 +08:00
2950857e3d do some improvement work
- split blender material creation function.
- allow user to apply virtools material to blender nodes in operator.
- add quick group picker in add virtools group operator.
2022-04-12 15:40:06 +08:00
dde95c3e4f finish vt_mtl and vt_group Panel system 2022-04-09 14:41:32 +08:00
4701164a6c add panel code basically. preparing for future dev 2022-04-08 21:50:31 +08:00
6c875d23ae add console output for rename system 2022-04-06 14:18:45 +08:00
3c36b8b9db finish rename system debug 2022-04-05 21:16:53 +08:00
c943264d05 finish rename system, without debug. 2022-04-05 20:45:00 +08:00
ca459c6185 add half code of rename system 2022-04-05 15:55:42 +08:00
1bfae63fe3 preparing rename system development 2022-04-04 22:04:11 +08:00
cb9609ac2c fix various bugs after refactor 2022-04-04 12:45:41 +08:00
c40f956771 fix loading and bm import/export error 2022-04-04 11:30:04 +08:00
e264c85a04 refactor project. preparing v3.0 development. no debug current 2022-04-03 22:48:12 +08:00
9c24569a06 fix add_floor texture problem again 2020-11-08 10:31:29 +08:00
031458bfc8 fix add_floor material error 2020-11-07 19:32:07 +08:00
3d1cf14334 update to bm spec 1.3 2020-11-06 21:34:47 +08:00
df44426195 fix suffix error when using add_element 2020-11-06 16:23:36 +08:00
670cc830fd finish version 2.0 2020-10-21 13:48:36 +08:00
582e96615a add rail_uv projection param. Update export bm UI (use PointerProperty) 2020-10-19 22:12:27 +08:00
ac9d196e3e update zh-cn document 2020-10-10 20:17:37 +08:00
f332e15791 finish flatten uv 2020-10-10 19:56:15 +08:00
0fb53f6827 add flatten uv. still have bug 2020-10-10 17:00:31 +08:00
c74141a346 fix move 2 cursor error 2020-10-10 11:11:08 +08:00
b3ee636848 finish all floor prototype 2020-10-10 10:27:15 +08:00
83ba270184 finish all basic floor protorype 2020-10-10 09:53:07 +08:00
027e267cc7 WIP adding floor prototype (2/3) 2020-10-09 21:59:06 +08:00
f5275b2abd WIP adding block prototype 2020-10-09 21:26:17 +08:00
93d077f18d add prototype 2020-10-09 17:16:36 +08:00
078dc952e7 add triangle face support 2020-10-09 16:39:20 +08:00
112b63319f WIP floor prototype 2020-10-09 12:53:00 +08:00
e2949708e6 finish add_derived_floor and debug 2020-10-08 21:19:06 +08:00
512cbce7fe WIP. write derived block creator 2020-10-08 17:12:38 +08:00
f0e3c3a597 finish add_basic_floor debug 2020-10-08 13:07:43 +08:00
75bfcbea02 add add_floor core function. WIP. no debug 2020-10-07 22:25:02 +08:00
4259d057fd fix rail_uv bbox calc bug 2020-10-07 20:38:01 +08:00
6ae4cbeddc finish add_floor ui display 2020-10-07 15:49:12 +08:00
35655a671d update message box and rail_uv 2020-10-07 15:20:51 +08:00
a8203dcb9c try use pointer property for rail_uv 2020-10-06 23:57:21 +08:00
612c641391 WIP, update some code 2020-10-06 12:57:25 +08:00
da722fe4bb adding add_floor function WIP 2020-10-05 22:30:06 +08:00
c5fc214384 increase bm version 2020-09-06 10:27:49 +08:00
ff24707186 update to bm 1.2 2020-09-06 10:11:48 +08:00
1fe77d843e add import opt, for BMERevenge import 2020-09-04 17:01:01 +08:00
d236925612 delete debug print 2020-09-04 11:10:46 +08:00
0af7f204dc finish rail section operator 2020-09-03 23:44:47 +08:00
79a04f1496 finish add elements 2020-09-03 13:57:06 +08:00
08eb6dcff9 [add] add ui and fix naming problem
- renaming all classes
- add conflict option for importing bm. no real code
- add shift + a menu, still no real code
2020-09-02 23:04:21 +08:00
751ff15b95 fix some name error and add no uv checker 2020-08-31 21:56:08 +08:00
97659f74cc remove debug print 2020-08-30 21:28:22 +08:00
41d3c5f3b7 use with syntax to save file and fix no uv bug 2020-08-30 21:22:25 +08:00
40756b0178 create document and bump version 2020-08-08 11:35:32 +08:00
af05f2ae21 fully finish bm import/export function 2020-08-07 14:17:13 +08:00
034f1e55aa update export module into bm 1.1 2020-08-07 10:41:50 +08:00
da4f07f37a add smooth feature 2020-08-06 14:43:31 +08:00
d90d05170b last edit 2020-08-06 12:17:06 +08:00
f61c3e1cef fix edit mode switch crash 2020-08-06 11:41:52 +08:00
bc13386f55 update mesh algo. but it still have bugs 2020-08-06 11:17:29 +08:00
72fe37040c add some prefs and finish bm import (no debug) 2020-08-05 16:19:38 +08:00
dcfc397421 fix fatal face export error 2020-07-23 11:21:11 +08:00
5b2314dbf8 move a settings' location 2020-07-20 16:14:39 +08:00
0dc526535c move some codes and add poll function for rail uv 2020-07-20 10:13:18 +08:00
6c00b2cb7b fix typo 2020-07-19 15:13:55 +08:00
654ce39a02 add pref and align tools 2020-07-19 15:05:43 +08:00
95 changed files with 8278 additions and 544 deletions

6
.gitattributes vendored Normal file
View File

@ -0,0 +1,6 @@
# our generated mesh should be save as binary
*.bin binary
# json is data and not good for human reading(althought I edit it on my own hand.)
# so set it as binary
ballance_blender_plugin/json/basic_blocks/*.json binary
ballance_blender_plugin/json/derived_blocks/*.json binary

View File

@ -1,9 +1,80 @@
# Ballance Blender Helper
This is a Blender plugin which is served for Ballance mapping.
[中文版本](README_ZH.md)
WIP. Only BM export function is finished. But it is enough. Following function will be added in the future and it just like the decoration of BM export. Because once we have BM export, every map can be converted from Blender to Virtools.
## Brief introduction
BM file spec can be seen in [there](https://github.com/yyc12345/gist/tree/master/BMFileSpec)
This is a Blender plugin which is served for Ballance mapping in Blender.
The latest commit may not be stable to use, please visit the Release page to get a stable version.
## Technical infomation
Used BM file spec can be found in [there](https://github.com/yyc12345/gist/blob/master/BMFileSpec/BMSpec_ZH.md)(Chinese only).
Used tools chain principle and the file format located in `meshes` can be found in [there](https://github.com/yyc12345/gist/blob/master/BMFileSpec/YYCToolsChainSpec_ZH.md)(Chinese only).
The format of the files which are under the `jsons` folder and belong to the BMERevenge section, can be found in [here](https://github.com/yyc12345/gist/blob/master/BMERevenge/DevDocument_ZH.md)
This plugin will continuously support Blender lastest **LTS** version. This plugin will migrate to new version when the new LTS version released. Currently, it based on Blender 2.83.x.
## Function introduction
### Plugin settings
* External texture folder: Please fill in the Texture directory of Ballance, the plugin will call the external texture file from this directory (the texture file originally with Ballance)
* No component collection: Objects in this collection will be forced to be set as non-Component. If left blank, this function will be shutdown.
* Temp texture folder: used to cache texture files extracted from BM files. Please arrange a directory that will not be automatically cleaned up. Since Blender will continue to read texture files from this directory, it cannot be emptied at will. And it also does not allow files with the same name to exist, that is, if I import two BMs for two maps, and there are two files with the same name but different images in the two BMs, the later files will overwrite the previous files , And in turn caused a texture error when the first blender document was opened again. For solving this problem, please refer to the subsequent BM import / export
### BM import / export
For import, in order to prevent texture errors, the best way is to force packaging once. After successfully importing the BM, choose to pack all into the blend file, and then clear the directory where the Temp texture folder is located, and then click Unpack to file if necessary, this operation will re-depend the textures in the texture library under the project folder.
For export, you can choose to export a collection or an object (Export mode), and specify the target (Export target).
It should be noted that once the BM is exported, all the faces in the file will be converted to triangular faces, please make a backup in advance. And it is recommended to use a flat collection structure, do not put a collections within another collection, which may cause some unnecessary problems.
### Ballance 3D
Ballance 3D is a set of light tools related to 3D operations, which can be found in the upper right corner of the 3D view.
#### 3ds Max Align
Provide 3ds Max like align tools. Current active will be seen as reference object. All selected objects(except active object) will be seen as operating object (So you can select multiple objects to align to the reference object).
#### Create Rail UV
To create UVs for the rails in the map, you need to select the objects that need to add UVs similar to the rails, and then click this button to create.
In the dialog, you can select the material to be used. You can also choose the unfolding mode. For shorter rails, you can choose Point mode. For longer rails, you can use Uniform mode. If you need to manually adjust the zoom ratio, please select Scale mode and specify the ratio (not recommended).
You can also select the projection axis for better UV distribution.
#### Flatten UV
In the object editing mode, it is a operator which is used to attach the currently selected surface to the UV. And you can specific the edge which will be attached into the V axis. Note that only convex faces are supported.
In the edit mode, select the surface, click Flatten UV, and then scroll the slider to select an edge as a reference. If the generated UV is not attached correctly, such as the FloorSide's band is pasted to the bottom, you can reselect the reference edge and redo the operation until it is correct.
### Add Menu
In the add menu, we have added a set of commonly used objects. After adding, the object will move to the 3D cursor.
#### Elements
Add elements, you can also specify attributes such as section when adding (it will not be displayed for unique objects such as start point)
#### Rail section
Add rail section, you can choose monorail or rail (just decide the number of rail section loops added, and will not help you rotate the angle), as well as rail radius and rail span.
#### Floors
Adding floor is part of the BMERevenge project. Basic floor is a basic floor component, and Derived floor is a common component composed of basic components. The extension(length) and the side configuration can be set according to its properties. It also has the advantage of reducing vertices.
It is recommended to merge the vertices by distance, unless there is a need to delete the surface after adding it
## Install
Put `ballance_blender_plugin` into Blender's plugin folder, `scripts/addons_contrib`. Then enable this plugin in Blender's preferences (Don't forget to configure this plugin's settings).
Support criteria: Only support the lastest **LTS** version.

80
README_ZH.md Normal file
View File

@ -0,0 +1,80 @@
# Ballance Blender Helper
[English version](README.md)
## 简介
这是一个用于Blender的插件其主要是服务于Ballance制图。
请使用Release中打tag的最新版本最新的commit不能保证其是稳定可用的
## 技术信息
使用的BM文件标准可以在[这里](https://github.com/yyc12345/gist/blob/master/BMFileSpec/BMSpec_ZH.md)查找
使用的制图链标准以及`meshes`文件夹下的文件的格式可以在[这里](https://github.com/yyc12345/gist/blob/master/BMFileSpec/YYCToolsChainSpec_ZH.md)查找
`jsons`文件夹下的隶属于BMERevenge部分的文件的格式可以在[这里](https://github.com/yyc12345/gist/blob/master/BMERevenge/DevDocument_ZH.md)查找
支持Blender的原则是支持当前最新的 **LTS** 版本在最新的LTS版本释出之后会花一些时间迁移插件。当前插件基于2.83.x版本
## 功能介绍
### 插件设置
* External texture folder请填写为Ballance的Texture目录插件将从此目录下调用外置贴图文件即Ballance原本带有的贴图文件
* No component collection处于此集合中的物体将被强制指定为非Component。如果留空则表示不需要这个功能。
* Temp texture folder用于缓存从BM文件中提取的贴图文件请安排一个平时不会被自动清理的目录。由于Blender会持续从这个目录读取贴图文件因此不能随意清空。并且其也不允许同名文件存在即如果我为2个地图分别导入两个BM这两个BM中存在贴图文件名相同但图像不同的两个文件那么后来的文件将会覆盖前面的文件并进而导致前者导入后的文档再次打开时出现贴图错误。关于解决这个问题的方法请参考后续的BM导入导出
### BM导入导出
对于导入而言为了防止贴图出错最好的方法是强制打包一次。在导入BM成功之后选择全部打包到blend文件然后清空Temp texture folder所在目录然后如果有需要可以再点击解包到文件将贴图重新依赖到工程文件夹下的贴图库内。
对于导出可以选择导出一个集合或者是一个物体Export mode并给定对象Export target即可。
需要注意的是一旦导出BM文件中所有的面将全部转换为三角形面请做好备份。并且建议使用平铺的集合结构不要在集合内嵌套集合可能会导致一些不必要的问题。
### Ballance 3D
Ballance 3D是一套简单的用于制图3D相关的轻型工具集合可以在3D视图右上角找到。
#### 3ds Max Align
提供一种类似于3ds Max的对齐方式。当前活动物体将被设为参照对象当前选中的所有物体如果参照也被选中则去掉参照对象将被视为操作对象因此可以选择多个物体一起对齐到参照对象
#### Create Rail UV
为地图中的钢轨创建UV你需要先选中需要添加类似钢轨UV的物体然后点击这个按钮以创建。
在弹出设置窗口中可以选择使用的材质。还可以选择展开模式对于较短的钢轨可以选择Point模式对于较长的钢轨可以使用Uniform模式如果需要手动调整缩放比请选择Scale模式并指定比率不推荐
还可以选择投影轴以获取更好的UV分布。
#### Flatten UV
在物体编辑模式下用于将当前选中面按某一边贴附到V轴上的模式展开到UV上。注意只支持凸边面。
编辑模式下选中面点击Flatten UV然后滚动滑条选中一个边作为参考如果最后生成的边贴附不对比如把路面花纹贴到了下部可以重新选择参考边再进行操作直到正确为止。
### 添加菜单
在添加菜单中我们添加了一套较为常用的物体。添加后物体会移动到3D游标处。
#### Elements
添加机关,添加时还可以指定添加的小节等属性(对于飞船等唯一物体不会显示)
#### Rail section
添加钢轨截面,可以选择单轨还是双轨(只是决定添加的界面数量,并不会帮你旋转角度),以及轨道半径和轨道间距
#### Floors
添加路面隶属于BMERevenge工程的拓展。Basic floor是基本的路面组件而Derived floor则是由基本组件组成的常用组件。可以根据其属性设置其延展以及各边是否显示。其还具有减少顶点的优点。
建议添加后除非有消除面的需求外,应该立即按距离合并顶点一次以避免各类问题
## 安装
`ballance_blender_plugin`直接复制到Blender插件目录`scripts/addons_contrib`内即可。然后在Blender偏好设置中启用即可记得配置插件设置

View File

@ -0,0 +1,343 @@
import bpy,bmesh,bpy_extras,mathutils
import pathlib,zipfile,time,os,tempfile,math
import struct, shutil
from bpy_extras import io_utils, node_shader_utils
from . import UTILS_constants, UTILS_functions, UTILS_file_io, UTILS_zip_helper, UTILS_virtools_prop
class BALLANCE_OT_export_bm(bpy.types.Operator, bpy_extras.io_utils.ExportHelper):
"""Save a Ballance Map File (BM file spec 1.4)"""
bl_idname = "ballance.export_bm"
bl_label = 'Export BM'
bl_options = {'PRESET'}
# ExportHelper mixin class uses this
filename_ext = ".bmx"
filter_glob: bpy.props.StringProperty(
default="*.bmx",
options={'HIDDEN'},
maxlen=255, # Max internal buffer length, longer would be clamped.
)
export_mode: bpy.props.EnumProperty(
name="Export mode",
items=(('COLLECTION', "Collection", "Export a collection"),
('OBJECT', "Objects", "Export an objects"),
),
)
def execute(self, context):
if ((self.export_mode == 'COLLECTION' and context.scene.BallanceBlenderPluginProperty.collection_picker is None) or
(self.export_mode == 'OBJECT' and context.scene.BallanceBlenderPluginProperty.object_picker is None)):
UTILS_functions.show_message_box(("No specific target", ), "Lost parameter", 'ERROR')
else:
prefs = bpy.context.preferences.addons[__package__].preferences
if self.export_mode == 'COLLECTION':
export_bm(context, self.filepath,
prefs.no_component_collection,
self.export_mode, context.scene.BallanceBlenderPluginProperty.collection_picker)
elif self.export_mode == 'OBJECT':
export_bm(context, self.filepath,
prefs.no_component_collection,
self.export_mode, context.scene.BallanceBlenderPluginProperty.object_picker)
return {'FINISHED'}
def draw(self, context):
layout = self.layout
layout.prop(self, "export_mode")
if self.export_mode == 'COLLECTION':
layout.prop(context.scene.BallanceBlenderPluginProperty, "collection_picker")
elif self.export_mode == 'OBJECT':
layout.prop(context.scene.BallanceBlenderPluginProperty, "object_picker")
def export_bm(context, bmx_filepath, prefs_fncg, opts_exportMode, opts_exportTarget):
# ============================================ alloc a temp folder
utils_tempFolderObj = tempfile.TemporaryDirectory()
utils_tempFolder = utils_tempFolderObj.name
utils_tempTextureFolder = os.path.join(utils_tempFolder, "Texture")
os.makedirs(utils_tempTextureFolder)
# ============================================
# find export target.
# do not need check them validation in there.
# just collect them.
if opts_exportMode== "COLLECTION":
objectList = opts_exportTarget.objects
else:
objectList = [opts_exportTarget, ]
# try get fncg collection
# fncg stands with forced non-component group
try:
object_fncgCollection = bpy.data.collections[prefs_fncg]
except:
object_fncgCollection = None
# ============================================ export
with open(os.path.join(utils_tempFolder, "index.bm"), "wb") as finfo:
UTILS_file_io.write_uint32(finfo, UTILS_constants.bmfile_currentVersion)
# ====================== export object
meshSet = set()
meshList = []
meshCount = 0
with open(os.path.join(utils_tempFolder, "object.bm"), "wb") as fobject:
for obj in objectList:
# only export mesh object
if obj.type != 'MESH':
continue
# clean no mesh object
object_blenderMesh = obj.data
if object_blenderMesh is None:
continue
# check component
if (object_fncgCollection is not None) and (obj.name in object_fncgCollection.objects):
# it should be set as normal object forcely
object_isComponent = False
else:
# check isComponent normally
object_isComponent = UTILS_functions.is_component(obj.name)
# triangle first and then group
if not object_isComponent:
if object_blenderMesh not in meshSet:
_mesh_triangulate(object_blenderMesh)
meshSet.add(object_blenderMesh)
meshList.append(object_blenderMesh)
object_meshIndex = meshCount
meshCount += 1
else:
object_meshIndex = meshList.index(object_blenderMesh)
else:
object_meshIndex = UTILS_functions.get_component_id(obj.name)
# get visibility
object_isHidden = not obj.visible_get()
# try get grouping data
object_groupList = UTILS_virtools_prop.get_virtools_group_data(obj)
# =======================
# write to files
# write finfo first
UTILS_file_io.write_string(finfo, obj.name)
UTILS_file_io.write_uint8(finfo, UTILS_constants.BmfileInfoType.OBJECT)
UTILS_file_io.write_uint64(finfo, fobject.tell())
# write fobject
UTILS_file_io.write_bool(fobject, object_isComponent)
UTILS_file_io.write_bool(fobject, object_isHidden)
UTILS_file_io.write_world_matrix(fobject, obj.matrix_world)
UTILS_file_io.write_uint32(fobject, len(object_groupList))
for item in object_groupList:
UTILS_file_io.write_string(fobject, item)
UTILS_file_io.write_uint32(fobject, object_meshIndex)
# ====================== export mesh
materialSet = set()
materialList = []
with open(os.path.join(utils_tempFolder, "mesh.bm"), "wb") as fmesh:
for mesh in meshList:
# split normals
mesh.calc_normals_split()
# write finfo first
UTILS_file_io.write_string(finfo, mesh.name)
UTILS_file_io.write_uint8(finfo, UTILS_constants.BmfileInfoType.MESH)
UTILS_file_io.write_uint64(finfo, fmesh.tell())
# write fmesh
# vertices
mesh_vecList = mesh.vertices[:]
UTILS_file_io.write_uint32(fmesh, len(mesh_vecList))
for vec in mesh_vecList:
#swap yz
UTILS_file_io.write_3vector(fmesh,vec.co[0],vec.co[2],vec.co[1])
# uv
mesh_faceIndexPairs = [(face, index) for index, face in enumerate(mesh.polygons)]
UTILS_file_io.write_uint32(fmesh, len(mesh_faceIndexPairs) * 3)
if mesh.uv_layers.active is not None:
uv_layer = mesh.uv_layers.active.data[:]
for f, f_index in mesh_faceIndexPairs:
# it should be triangle face, otherwise throw a error
if (f.loop_total != 3):
raise Exception("Not a triangle", f.poly.loop_total)
for loop_index in range(f.loop_start, f.loop_start + f.loop_total):
uv = uv_layer[loop_index].uv
# reverse v
UTILS_file_io.write_2vector(fmesh, uv[0], -uv[1])
else:
# no uv data. write garbage
for i in range(len(mesh_faceIndexPairs) * 3):
UTILS_file_io.write_2vector(fmesh, 0.0, 0.0)
# normals
UTILS_file_io.write_uint32(fmesh, len(mesh_faceIndexPairs) * 3)
for f, f_index in mesh_faceIndexPairs:
# no need to check triangle again
for loop_index in range(f.loop_start, f.loop_start + f.loop_total):
nml = mesh.loops[loop_index].normal
# swap yz
UTILS_file_io.write_3vector(fmesh, nml[0], nml[2], nml[1])
# face
# get material first
mesh_usedBlenderMtl = mesh.materials[:]
mesh_noMaterial = len(mesh_usedBlenderMtl) == 0
for mat in mesh_usedBlenderMtl:
if mat not in materialSet:
materialSet.add(mat)
materialList.append(mat)
UTILS_file_io.write_uint32(fmesh, len(mesh_faceIndexPairs))
mesh_vtIndex = []
mesh_vnIndex = []
mesh_vIndex = []
for f, f_index in mesh_faceIndexPairs:
# confirm material use
if mesh_noMaterial:
mesh_materialIndex = 0
else:
mesh_materialIndex = materialList.index(mesh_usedBlenderMtl[f.material_index])
# export face
mesh_vtIndex.clear()
mesh_vnIndex.clear()
mesh_vIndex.clear()
counter = 0
for loop_index in range(f.loop_start, f.loop_start + f.loop_total):
mesh_vIndex.append(mesh.loops[loop_index].vertex_index)
mesh_vnIndex.append(f_index * 3 + counter)
mesh_vtIndex.append(f_index * 3 + counter)
counter += 1
# reverse vertices sort
UTILS_file_io.write_face(fmesh,
mesh_vIndex[2], mesh_vtIndex[2], mesh_vnIndex[2],
mesh_vIndex[1], mesh_vtIndex[1], mesh_vnIndex[1],
mesh_vIndex[0], mesh_vtIndex[0], mesh_vnIndex[0])
# set used material
UTILS_file_io.write_bool(fmesh, not mesh_noMaterial)
UTILS_file_io.write_uint32(fmesh, mesh_materialIndex)
# free splited normals
mesh.free_normals_split()
# ====================== export material
textureSet = set()
textureList = []
textureCount = 0
with open(os.path.join(utils_tempFolder, "material.bm"), "wb") as fmaterial:
for material in materialList:
# write finfo first
UTILS_file_io.write_string(finfo, material.name)
UTILS_file_io.write_uint8(finfo, UTILS_constants.BmfileInfoType.MATERIAL)
UTILS_file_io.write_uint64(finfo, fmaterial.tell())
# try get original written data
(material_enableVirtoolsMat,
material_colAmbient, material_colDiffuse, material_colSpecular, material_colEmissive, material_specularPower,
material_alphaTest, material_alphaBlend, material_zBuffer, material_twoSided,
material_texture) = UTILS_virtools_prop.get_virtools_material_data(material)
# only try get from Principled BSDF when we couldn't get from virtools_material props
if not material_enableVirtoolsMat:
v = UTILS_functions.parse_material_nodes(material)
if v is not None:
(material_enableVirtoolsMat,
material_colAmbient, material_colDiffuse, material_colSpecular, material_colEmissive, material_specularPower,
material_alphaTest, material_alphaBlend, material_zBuffer, material_twoSided,
material_texture) = v
# check texture index
if material_texture is None:
material_useTexture = False
material_textureIndex = 0
else:
# add into texture list
if material_texture not in textureSet:
textureSet.add(material_texture)
textureList.append(material_texture)
textureIndex = textureCount
textureCount += 1
else:
textureIndex = textureList.index(material_texture)
material_useTexture = True
material_textureIndex = textureIndex
UTILS_file_io.write_color(fmaterial, material_colAmbient)
UTILS_file_io.write_color(fmaterial, material_colDiffuse)
UTILS_file_io.write_color(fmaterial, material_colSpecular)
UTILS_file_io.write_color(fmaterial, material_colEmissive)
UTILS_file_io.write_float(fmaterial, material_specularPower)
UTILS_file_io.write_bool(fmaterial, material_alphaTest)
UTILS_file_io.write_bool(fmaterial, material_alphaBlend)
UTILS_file_io.write_bool(fmaterial, material_zBuffer)
UTILS_file_io.write_bool(fmaterial, material_twoSided)
UTILS_file_io.write_bool(fmaterial, material_useTexture)
UTILS_file_io.write_uint32(fmaterial, material_textureIndex)
# ====================== export texture
texture_blenderFilePath = os.path.dirname(bpy.data.filepath)
texture_existedTextureFilepath = set()
with open(os.path.join(utils_tempFolder, "texture.bm"), "wb") as ftexture:
for texture in textureList:
# write finfo first
UTILS_file_io.write_string(finfo, texture.name)
UTILS_file_io.write_uint8(finfo, UTILS_constants.BmfileInfoType.TEXTURE)
UTILS_file_io.write_uint64(finfo, ftexture.tell())
# confirm whether it is internal texture
# get absolute texture path
texture_filepath = io_utils.path_reference(texture.filepath, texture_blenderFilePath, utils_tempTextureFolder,
'ABSOLUTE', "", None, texture.library)
# get file name and write it
texture_filename = os.path.basename(texture_filepath)
UTILS_file_io.write_string(ftexture, texture_filename)
if (_is_external_texture(texture_filename)):
# write directly, use Ballance texture
UTILS_file_io.write_bool(ftexture, True)
else:
# copy internal texture, if this file is copied, do not copy it again
UTILS_file_io.write_bool(ftexture, False)
if texture_filename not in texture_existedTextureFilepath:
shutil.copy(texture_filepath, os.path.join(utils_tempTextureFolder, texture_filename))
texture_existedTextureFilepath.add(texture_filename)
# ============================================
# save zip and clean up folder
UTILS_zip_helper.compress(utils_tempFolder, bmx_filepath)
utils_tempFolderObj.cleanup()
# ==========================================
# blender related functions
def _is_external_texture(name):
if name in UTILS_constants.bmfile_externalTextureSet:
return True
else:
return False
def _mesh_triangulate(me):
bm = bmesh.new()
bm.from_mesh(me)
bmesh.ops.triangulate(bm, faces=bm.faces)
bm.to_mesh(me)
bm.free()
def _set_value_when_none(obj, newValue):
if obj is None:
return newValue
else:
return obj

View File

@ -0,0 +1,372 @@
import bpy,bmesh,bpy_extras,mathutils
import pathlib,zipfile,time,os,tempfile,math
import struct, shutil
from bpy_extras import io_utils,node_shader_utils
from bpy_extras.io_utils import unpack_list
from bpy_extras.image_utils import load_image
from . import UTILS_constants, UTILS_functions, UTILS_file_io, UTILS_zip_helper, UTILS_virtools_prop
class BALLANCE_OT_import_bm(bpy.types.Operator, bpy_extras.io_utils.ImportHelper):
"""Load a Ballance Map File (BM file spec 1.4)"""
bl_idname = "ballance.import_bm"
bl_label = "Import BM "
bl_options = {'PRESET', 'UNDO'}
# ImportHelper mixin class uses this
filename_ext = ".bmx"
filter_glob: bpy.props.StringProperty(
default="*.bmx",
options={'HIDDEN'},
maxlen=255, # Max internal buffer length, longer would be clamped.
)
texture_conflict_strategy: bpy.props.EnumProperty(
name="Texture name conflict",
items=(('NEW', "New instance", "Create a new instance"),
('CURRENT', "Use current", "Use current"),),
description="Define how to process texture name conflict",
default='CURRENT',
)
material_conflict_strategy: bpy.props.EnumProperty(
name="Material name conflict",
items=(('RENAME', "Rename", "Rename the new one"),
('CURRENT', "Use current", "Use current"),),
description="Define how to process material name conflict",
default='RENAME',
)
mesh_conflict_strategy: bpy.props.EnumProperty(
name="Mesh name conflict",
items=(('RENAME', "Rename", "Rename the new one"),
('CURRENT', "Use current", "Use current"),),
description="Define how to process mesh name conflict",
default='RENAME',
)
object_conflict_strategy: bpy.props.EnumProperty(
name="Object name conflict",
items=(('RENAME', "Rename", "Rename the new one"),
('CURRENT', "Use current", "Use current"),),
description="Define how to process object name conflict",
default='RENAME',
)
@classmethod
def poll(self, context):
prefs = bpy.context.preferences.addons[__package__].preferences
return (os.path.isdir(prefs.temp_texture_folder) and os.path.isdir(prefs.external_folder))
def execute(self, context):
prefs = bpy.context.preferences.addons[__package__].preferences
import_bm(context, self.filepath,
prefs.no_component_collection, prefs.external_folder, prefs.temp_texture_folder,
self.texture_conflict_strategy, self.material_conflict_strategy,
self.mesh_conflict_strategy, self.object_conflict_strategy)
return {'FINISHED'}
def import_bm(context, bmx_filepath, prefs_fncg, prefs_externalTexture, prefs_tempTextureFolder, opts_texture, opts_material, opts_mesh, opts_object):
# ============================================
# alloc a temp folder for decompress
utils_tempFolderObj = tempfile.TemporaryDirectory()
utils_tempFolder = utils_tempFolderObj.name
utils_tempTextureFolder = os.path.join(utils_tempFolder, "Texture")
# decompress
UTILS_zip_helper.decompress(utils_tempFolder, bmx_filepath)
# ============================================
# read bmx file officially
# index.bm
objectList = []
meshList = []
materialList = []
textureList = []
with open(os.path.join(utils_tempFolder, "index.bm"), "rb") as findex:
# check version first
index_gottenVersion = UTILS_file_io.read_uint32(findex)
if (index_gottenVersion != UTILS_constants.bmfile_currentVersion):
# clean temp folder, output error
UTILS_functions.show_message_box(
("Unsupported BM spec. Expect: {} Gotten: {}".format(UTILS_constants.bmfile_currentVersion, index_gottenVersion), ),
"Unsupported BM spec", 'ERROR')
findex.close()
utils_tempFolderObj.cleanup()
return
# collect block header data
while len(UTILS_file_io.peek_stream(findex)) != 0:
# read
index_name = UTILS_file_io.read_string(findex)
index_type = UTILS_file_io.read_uint8(findex)
index_offset = UTILS_file_io.read_uint64(findex)
index_blockCache = _InfoBlockHelper(index_name, index_offset)
# grouping into list
if index_type == UTILS_constants.BmfileInfoType.OBJECT:
objectList.append(index_blockCache)
elif index_type == UTILS_constants.BmfileInfoType.MESH:
meshList.append(index_blockCache)
elif index_type == UTILS_constants.BmfileInfoType.MATERIAL:
materialList.append(index_blockCache)
elif index_type == UTILS_constants.BmfileInfoType.TEXTURE:
textureList.append(index_blockCache)
else:
pass
# texture.bm
with open(os.path.join(utils_tempFolder, "texture.bm"), "rb") as ftexture:
for item in textureList:
# seek to block
ftexture.seek(item.offset, os.SEEK_SET)
# read data
texture_filename = UTILS_file_io.read_string(ftexture)
texture_isExternal = UTILS_file_io.read_bool(ftexture)
if texture_isExternal:
(texture_target, skip_init) = UTILS_functions.create_instance_with_option(
UTILS_constants.BmfileInfoType.TEXTURE, item.name, opts_texture,
extra_texture_filename= texture_filename, extra_texture_path= prefs_externalTexture)
else:
# not external. copy temp file into blender temp. then use it.
# try copy. if fail, don't need to do more
try:
shutil.copy(os.path.join(utils_tempTextureFolder, texture_filename),
os.path.join(prefs_tempTextureFolder, texture_filename))
except:
pass
(texture_target, skip_init) = UTILS_functions.create_instance_with_option(
UTILS_constants.BmfileInfoType.TEXTURE, item.name, opts_texture,
extra_texture_filename= texture_filename, extra_texture_path= prefs_tempTextureFolder)
# setup name and blender data for header
item.blender_data = texture_target
# material.bm
# WARNING: this code is shared with add_floor - create_or_get_material()
with open(os.path.join(utils_tempFolder, "material.bm"), "rb") as fmaterial:
for item in materialList:
# seek to block
fmaterial.seek(item.offset, os.SEEK_SET)
# read data
material_colAmbient = UTILS_file_io.read_3vector(fmaterial)
material_colDiffuse = UTILS_file_io.read_3vector(fmaterial)
material_colSpecular = UTILS_file_io.read_3vector(fmaterial)
material_colEmissive = UTILS_file_io.read_3vector(fmaterial)
material_specularPower = UTILS_file_io.read_float(fmaterial)
material_alphaTest = UTILS_file_io.read_bool(fmaterial)
material_alphaBlend = UTILS_file_io.read_bool(fmaterial)
material_zBuffer = UTILS_file_io.read_bool(fmaterial)
material_twoSided = UTILS_file_io.read_bool(fmaterial)
material_useTexture = UTILS_file_io.read_bool(fmaterial)
material_texture = UTILS_file_io.read_uint32(fmaterial)
# alloc basic material
(material_target, skip_init) = UTILS_functions.create_instance_with_option(
UTILS_constants.BmfileInfoType.MATERIAL, item.name, opts_material)
item.blender_data = material_target
if skip_init:
continue
# try create material nodes
UTILS_functions.create_blender_material(material_target,
(True,
material_colAmbient, material_colDiffuse, material_colSpecular, material_colEmissive, material_specularPower,
material_alphaTest, material_alphaBlend, material_zBuffer, material_twoSided,
textureList[material_texture].blender_data if material_useTexture else None)
)
# mesh.bm
# WARNING: this code is shared with add_floor
with open(os.path.join(utils_tempFolder, "mesh.bm"), "rb") as fmesh:
mesh_vList=[]
mesh_vtList=[]
mesh_vnList=[]
mesh_faceList=[]
mesh_materialSolt = []
for item in meshList:
fmesh.seek(item.offset, os.SEEK_SET)
# create real mesh
(mesh_target, skip_init) = UTILS_functions.create_instance_with_option(
UTILS_constants.BmfileInfoType.MESH, item.name, opts_mesh)
item.blender_data = mesh_target
if skip_init:
continue
mesh_vList.clear()
mesh_vtList.clear()
mesh_vnList.clear()
mesh_faceList.clear()
mesh_materialSolt.clear()
# in first read, store all data into list
mesh_listCount = UTILS_file_io.read_uint32(fmesh)
for i in range(mesh_listCount):
cache = UTILS_file_io.read_3vector(fmesh)
# switch yz
mesh_vList.append((cache[0], cache[2], cache[1]))
mesh_listCount = UTILS_file_io.read_uint32(fmesh)
for i in range(mesh_listCount):
cache = UTILS_file_io.read_2vector(fmesh)
# reverse v
mesh_vtList.append((cache[0], -cache[1]))
mesh_listCount = UTILS_file_io.read_uint32(fmesh)
for i in range(mesh_listCount):
cache = UTILS_file_io.read_3vector(fmesh)
# switch yz
mesh_vnList.append((cache[0], cache[2], cache[1]))
mesh_listCount = UTILS_file_io.read_uint32(fmesh)
for i in range(mesh_listCount):
mesh_faceData = UTILS_file_io.read_face(fmesh)
mesh_useMaterial = UTILS_file_io.read_bool(fmesh)
mesh_materialIndex = UTILS_file_io.read_uint32(fmesh)
if mesh_useMaterial:
mesh_neededMaterial = materialList[mesh_materialIndex].blender_data
if mesh_neededMaterial in mesh_materialSolt:
mesh_blenderMtlIndex = mesh_materialSolt.index(mesh_neededMaterial)
else:
mesh_blenderMtlIndex = len(mesh_materialSolt)
mesh_materialSolt.append(mesh_neededMaterial)
else:
mesh_blenderMtlIndex = -1
# we need invert triangle sort
mesh_faceList.append((
mesh_faceData[6], mesh_faceData[7], mesh_faceData[8],
mesh_faceData[3], mesh_faceData[4], mesh_faceData[5],
mesh_faceData[0], mesh_faceData[1], mesh_faceData[2],
mesh_blenderMtlIndex
))
# and then we need add material solt for this mesh
for mat in mesh_materialSolt:
mesh_target.materials.append(mat)
# then, we need add correspond count for vertices
mesh_target.vertices.add(len(mesh_vList))
mesh_target.loops.add(len(mesh_faceList)*3) # triangle face confirm
mesh_target.polygons.add(len(mesh_faceList))
mesh_target.uv_layers.new(do_init=False)
mesh_target.create_normals_split()
# add vertices data
mesh_target.vertices.foreach_set("co", unpack_list(mesh_vList))
mesh_target.loops.foreach_set("vertex_index", unpack_list(_flat_vertices_index(mesh_faceList)))
mesh_target.loops.foreach_set("normal", unpack_list(_flat_vertices_normal(mesh_faceList, mesh_vnList)))
mesh_target.uv_layers[0].data.foreach_set("uv", unpack_list(_flat_vertices_uv(mesh_faceList, mesh_vtList)))
for i in range(len(mesh_faceList)):
mesh_target.polygons[i].loop_start = i * 3
mesh_target.polygons[i].loop_total = 3
if mesh_faceList[i][9] != -1:
mesh_target.polygons[i].material_index = mesh_faceList[i][9]
mesh_target.polygons[i].use_smooth = True
mesh_target.validate(clean_customdata=False)
mesh_target.update(calc_edges=False, calc_edges_loose=False)
# object
with open(os.path.join(utils_tempFolder, "object.bm"), "rb") as fobject:
# we need get needed collection first
blender_viewLayer = context.view_layer
blender_collection = blender_viewLayer.active_layer_collection.collection
if prefs_fncg == "":
# fncg stands with Forced Non-Component Group
object_fncgCollection = None
else:
try:
# try get collection
object_fncgCollection = bpy.data.collections[prefs_fncg]
except:
# fail to get, create new one under active collection instead
object_fncgCollection = bpy.data.collections.new(prefs_fncg)
blender_collection.children.link(object_fncgCollection)
# start process it
object_groupList = []
for item in objectList:
fobject.seek(item.offset, os.SEEK_SET)
# read data
object_isComponent = UTILS_file_io.read_bool(fobject)
#object_isForcedNoComponent = UTILS_file_io.read_bool(fobject)
object_isHidden = UTILS_file_io.read_bool(fobject)
object_worldMatrix = UTILS_file_io.read_world_materix(fobject)
object_groupListCount = UTILS_file_io.read_uint32(fobject)
object_groupList.clear()
for i in range(object_groupListCount):
object_groupList.append(UTILS_file_io.read_string(fobject))
object_meshIndex = UTILS_file_io.read_uint32(fobject)
# got mesh first
if object_isComponent:
object_neededMesh = UTILS_functions.load_component(object_meshIndex)
else:
object_neededMesh = meshList[object_meshIndex].blender_data
# create real object
(object_target, skip_init) = UTILS_functions.create_instance_with_option(
UTILS_constants.BmfileInfoType.OBJECT, item.name, opts_object,
extra_mesh=object_neededMesh)
if skip_init:
continue
# link to correct collection
if (object_fncgCollection is not None) and (not object_isComponent) and UTILS_functions.is_component(item.name):
# a object should be grouped into fncg should check following requirements
# fncg is not null
# this object is a normal object
# but its name match component format
object_fncgCollection.objects.link(object_target)
else:
# otherwise, group it into normal collection
blender_collection.objects.link(object_target)
object_target.matrix_world = object_worldMatrix
object_target.hide_set(object_isHidden)
# write custom property
if len(object_groupList) != 0:
UTILS_virtools_prop.fill_virtools_group_data(object_target, tuple(object_groupList))
else:
UTILS_virtools_prop.fill_virtools_group_data(object_target, None)
# update view layer after all objects has been imported
blender_viewLayer.update()
# release temp folder
utils_tempFolderObj.cleanup()
# ==========================================
# blender related functions
class _InfoBlockHelper():
def __init__(self, name, offset):
self.name = name
self.offset = offset
self.blender_data = None
def _flat_vertices_index(faceList):
for item in faceList:
yield (item[0], )
yield (item[3], )
yield (item[6], )
def _flat_vertices_normal(faceList, vnList):
for item in faceList:
yield vnList[item[2]]
yield vnList[item[5]]
yield vnList[item[8]]
def _flat_vertices_uv(faceList, vtList):
for item in faceList:
yield vtList[item[1]]
yield vtList[item[4]]
yield vtList[item[7]]

View File

@ -0,0 +1,128 @@
import bpy, mathutils
from . import UTILS_functions
class BALLANCE_OT_super_align(bpy.types.Operator):
"""Align object with 3ds Max style"""
bl_idname = "ballance.super_align"
bl_label = "3ds Max Align"
bl_options = {'UNDO'}
align_x: bpy.props.BoolProperty(name="X position")
align_y: bpy.props.BoolProperty(name="Y position")
align_z: bpy.props.BoolProperty(name="Z position")
current_references: bpy.props.EnumProperty(
name="Reference (Active Object)",
items=(('MIN', "Min", ""),
('CENTER', "Center (bound box)", ""),
('POINT', "Center (axis)", ""),
('MAX', "Max", "")
),
)
target_references: bpy.props.EnumProperty(
name="Target (Other Objects)",
items=(('MIN', "Min", ""),
('CENTER', "Center (bound box)", ""),
('POINT', "Center (axis)", ""),
('MAX', "Max", "")
),
)
@classmethod
def poll(self, context):
return _check_align_target()
def execute(self, context):
_align_object(self.align_x, self.align_y, self.align_z, self.current_references, self.target_references)
return {'FINISHED'}
def invoke(self, context, event):
wm = context.window_manager
return wm.invoke_props_dialog(self)
def draw(self, context):
layout = self.layout
col = layout.column()
col.label(text="Align axis")
row = col.row()
row.prop(self, "align_x")
row.prop(self, "align_y")
row.prop(self, "align_z")
col.prop(self, "current_references")
col.prop(self, "target_references")
# ============================== method
def _check_align_target():
if bpy.context.active_object is None:
return False
selected = bpy.context.selected_objects[:]
length = len(selected)
if bpy.context.active_object in selected:
length -= 1
if length == 0:
return False
return True
def _align_object(use_x, use_y, use_z, currentMode, targetMode):
if not (use_x or use_y or use_z):
return
# calc active object data
currentObj = bpy.context.active_object
currentObjBbox = [currentObj.matrix_world @ mathutils.Vector(corner) for corner in currentObj.bound_box]
currentObjRef = _provide_obj_reference_point(currentObj, currentObjBbox, currentMode)
# calc target
targetObjList = bpy.context.selected_objects[:]
if currentObj in targetObjList:
targetObjList.remove(currentObj)
# process each obj
for targetObj in targetObjList:
targetObjBbox = [targetObj.matrix_world @ mathutils.Vector(corner) for corner in targetObj.bound_box]
targetObjRef = _provide_obj_reference_point(targetObj, targetObjBbox, targetMode)
if use_x:
targetObj.location.x += currentObjRef.x - targetObjRef.x
if use_y:
targetObj.location.y += currentObjRef.y - targetObjRef.y
if use_z:
targetObj.location.z += currentObjRef.z - targetObjRef.z
def _provide_obj_reference_point(obj, vecList, mode):
refPoint = mathutils.Vector((0, 0, 0))
if (mode == 'MIN'):
refPoint.x = min([vec.x for vec in vecList])
refPoint.y = min([vec.y for vec in vecList])
refPoint.z = min([vec.z for vec in vecList])
elif (mode == 'MAX'):
refPoint.x = max([vec.x for vec in vecList])
refPoint.y = max([vec.y for vec in vecList])
refPoint.z = max([vec.z for vec in vecList])
elif (mode == 'CENTER'):
maxVecCache = mathutils.Vector((0, 0, 0))
minVecCache = mathutils.Vector((0, 0, 0))
minVecCache.x = min([vec.x for vec in vecList])
minVecCache.y = min([vec.y for vec in vecList])
minVecCache.z = min([vec.z for vec in vecList])
maxVecCache.x = max([vec.x for vec in vecList])
maxVecCache.y = max([vec.y for vec in vecList])
maxVecCache.z = max([vec.z for vec in vecList])
refPoint.x = (maxVecCache.x + minVecCache.x) / 2
refPoint.y = (maxVecCache.y + minVecCache.y) / 2
refPoint.z = (maxVecCache.z + minVecCache.z) / 2
else:
refPoint.x = obj.location.x
refPoint.y = obj.location.y
refPoint.z = obj.location.z
return refPoint

View File

@ -0,0 +1,120 @@
import bpy,mathutils
import bmesh
from . import UTILS_functions
class BALLANCE_OT_flatten_uv(bpy.types.Operator):
"""Flatten selected face UV. Only works for convex face"""
bl_idname = "ballance.flatten_uv"
bl_label = "Flatten UV"
bl_options = {'UNDO'}
reference_edge : bpy.props.IntProperty(
name="Reference edge",
description="The references edge of UV. It will be placed in V axis.",
min=0,
soft_min=0,
soft_max=3,
default=0,
)
@classmethod
def poll(self, context):
obj = bpy.context.active_object
if obj == None:
return False
if obj.type != 'MESH':
return False
if obj.mode != 'EDIT':
return False
return True
def invoke(self, context, event):
wm = context.window_manager
return wm.invoke_props_dialog(self)
def execute(self, context):
no_processed_count = _real_flatten_uv(bpy.context.active_object.data, self.reference_edge)
if no_processed_count != 0:
UTILS_functions.show_message_box(
("{} faces may not be processed correctly because they have problem.".format(no_processed_count), ),
"Warning", 'ERROR'
)
return {'FINISHED'}
def draw(self, context):
layout = self.layout
layout.prop(self, "reference_edge")
def _real_flatten_uv(mesh, reference_edge):
no_processed_count = 0
if mesh.uv_layers.active is None:
# if no uv, create it
mesh.uv_layers.new(do_init=False)
bm = bmesh.from_edit_mesh(mesh)
uv_lay = bm.loops.layers.uv.active
for face in bm.faces:
if not face.select:
continue
allPoint = len(face.loops)
if allPoint <= reference_edge:
no_processed_count+=1
continue
# get correct new corrdinate system
p1Relative = reference_edge
p2Relative = reference_edge + 1
p3Relative = reference_edge + 2
if p2Relative >= allPoint:
p2Relative -= allPoint
if p3Relative >= allPoint:
p3Relative -= allPoint
p1=mathutils.Vector(tuple(face.loops[p1Relative].vert.co[x] for x in range(3)))
p2=mathutils.Vector(tuple(face.loops[p2Relative].vert.co[x] for x in range(3)))
p3=mathutils.Vector(tuple(face.loops[p3Relative].vert.co[x] for x in range(3)))
new_y_axis = p2 - p1
new_y_axis.normalize()
vec1 = p3 - p2
vec1.normalize()
new_z_axis = new_y_axis.cross(vec1)
new_z_axis.normalize()
new_x_axis = new_y_axis.cross(new_z_axis)
new_x_axis.normalize()
# construct rebase matrix
origin_base = mathutils.Matrix((
(1.0, 0, 0),
(0, 1.0, 0),
(0, 0, 1.0)
))
origin_base.invert()
new_base = mathutils.Matrix((
(new_x_axis.x, new_y_axis.x, new_z_axis.x),
(new_x_axis.y, new_y_axis.y, new_z_axis.y),
(new_x_axis.z, new_y_axis.z, new_z_axis.z)
))
transition_matrix = origin_base @ new_base
transition_matrix.invert()
# process each face
for loop_index in range(allPoint):
pp = mathutils.Vector(tuple(face.loops[loop_index].vert.co[x] for x in range(3)))
vec = pp-p1
new_vec = transition_matrix @ vec
face.loops[loop_index][uv_lay].uv = (
(new_vec.x if new_vec.x >=0 else -new_vec.x) / 5,
(new_vec.y) / 5
)
# Show the updates in the viewport
bmesh.update_edit_mesh(mesh)
return no_processed_count

View File

@ -0,0 +1,191 @@
import bpy,bmesh
import mathutils
import bpy.types
from . import UTILS_functions
class BALLANCE_OT_rail_uv(bpy.types.Operator):
"""Create a UV for rail"""
bl_idname = "ballance.rail_uv"
bl_label = "Create Rail UV"
bl_options = {'UNDO'}
uv_type: bpy.props.EnumProperty(
name="Type",
description="Define how to create UV",
items=(
("POINT", "Point", "All UV will be created in a specific point"),
("UNIFORM", "Uniform", "All UV will be created within 1x1"),
("SCALE", "Scale", "Give a scale number to scale UV"),
("TT", "TT_ReflectionMapping", "The real internal process of Ballance rail")
),
)
projection_axis: bpy.props.EnumProperty(
name="Projection axis",
description="Projection axis",
items=(
("X", "X axis", "X axis"),
("Y", "Y axis", "Y axis"),
("Z", "Z axis", "Z axis")
),
)
uv_scale : bpy.props.FloatProperty(
name="Scale",
description="The scale of UV",
min=0.0,
default=1.0,
)
@classmethod
def poll(self, context):
return _check_rail_target()
def invoke(self, context, event):
wm = context.window_manager
return wm.invoke_props_dialog(self)
def execute(self, context):
if context.scene.BallanceBlenderPluginProperty.material_picker == None:
UTILS_functions.show_message_box(("No specific material", ), "Lost parameter", 'ERROR')
else:
_create_rail_uv(self.uv_type, context.scene.BallanceBlenderPluginProperty.material_picker, self.uv_scale, self.projection_axis)
return {'FINISHED'}
def draw(self, context):
layout = self.layout
layout.prop(self, "uv_type")
layout.prop(context.scene.BallanceBlenderPluginProperty, "material_picker")
if self.uv_type == 'SCALE' or self.uv_type == 'UNIFORM':
layout.prop(self, "projection_axis")
if self.uv_type == 'SCALE':
layout.prop(self, "uv_scale")
# ====================== method
def _check_rail_target():
for obj in bpy.context.selected_objects:
if obj.type != 'MESH':
continue
if obj.mode != 'OBJECT':
continue
return True
return False
def _get_distance(iterator):
is_first_min = True
is_first_max = True
max_value = 0.0
min_value = 0.0
for item in iterator:
if is_first_max:
is_first_max = False
max_value = item
else:
if item > max_value:
max_value = item
if is_first_min:
is_first_min = False
min_value = item
else:
if item < min_value:
min_value = item
return max_value - min_value
def _create_rail_uv(rail_type, material_pointer, scale_size, projection_axis):
objList = []
ignoredObj = []
for obj in bpy.context.selected_objects:
if obj.type != 'MESH':
ignoredObj.append(obj.name)
continue
if obj.mode != 'OBJECT':
ignoredObj.append(obj.name)
continue
if obj.data.uv_layers.active is None:
# create a empty uv for it.
obj.data.uv_layers.new(do_init=False)
objList.append(obj)
for obj in objList:
mesh = obj.data
# clean it material and set rail first
obj.data.materials.clear()
obj.data.materials.append(material_pointer)
# copy mesh vec for scale or uniform mode
vecList = mesh.vertices[:]
real_scale = 1.0
if rail_type == 'SCALE':
real_scale = scale_size
elif rail_type == 'UNIFORM':
# calc proper scale
if projection_axis == 'X':
maxLength = max(
_get_distance(vec.co[1] for vec in vecList),
_get_distance(vec.co[2] for vec in vecList)
)
elif projection_axis == 'Y':
maxLength = max(
_get_distance(vec.co[0] for vec in vecList),
_get_distance(vec.co[2] for vec in vecList)
)
elif projection_axis == 'Z':
maxLength = max(
_get_distance(vec.co[0] for vec in vecList),
_get_distance(vec.co[1] for vec in vecList)
)
real_scale = 1.0 / maxLength
uv_layer = mesh.uv_layers.active.data
for poly in mesh.polygons:
for loop_index in range(poly.loop_start, poly.loop_start + poly.loop_total):
# get correspond vec index
index = mesh.loops[loop_index].vertex_index
if rail_type == 'POINT':
# set to 1 point
uv_layer[loop_index].uv[0] = 0
uv_layer[loop_index].uv[1] = 1
elif rail_type == 'SCALE' or rail_type == 'UNIFORM':
# following xy -> uv scale
#
# use Z axis: X->U Y->V
# use X axis: Y->U Z->V
# use Y axis: X->U Z->V
if projection_axis == 'X':
uv_layer[loop_index].uv[0] = vecList[index].co[1] * real_scale
uv_layer[loop_index].uv[1] = vecList[index].co[2] * real_scale
elif projection_axis == 'Y':
uv_layer[loop_index].uv[0] = vecList[index].co[0] * real_scale
uv_layer[loop_index].uv[1] = vecList[index].co[2] * real_scale
elif projection_axis == 'Z':
uv_layer[loop_index].uv[0] = vecList[index].co[0] * real_scale
uv_layer[loop_index].uv[1] = vecList[index].co[1] * real_scale
elif rail_type == 'TT':
(uv_layer[loop_index].uv[0], uv_layer[loop_index].uv[1]) = _tt_reflection_mapping_compute(
vecList[index].co,
mesh.loops[loop_index].normal,
(0.0, 0.0, 0.0)
)
if len(ignoredObj) != 0:
UTILS_functions.show_message_box(
("Following objects are not processed due to they are not suit for this function now: ", ) + tuple(ignoredObj),
"Execution result", 'INFO'
)
def _tt_reflection_mapping_compute(_point, _n, _refobj):
# switch blender coord to virtools coord for convenient calc
point = mathutils.Vector((_point[0], _point[2], _point[1]))
n = mathutils.Vector((_n[0], _n[2], _n[1])).normalized()
refobj = mathutils.Vector((_refobj[0], _refobj[2], _refobj[1]))
p = (refobj - point).normalized()
b=(((2*(p*n))*n)-p).normalized()
# convert back to blender coord
return ((b.x + 1.0) / 2.0, -(b.z + 1.0) / 2.0)

View File

@ -0,0 +1,496 @@
import bpy
from . import UTILS_constants, UTILS_functions, UTILS_virtools_prop
class rename_system_props(bpy.types.Operator):
name_standard: bpy.props.EnumProperty(
name="Name Standard",
description="Choose your name standard",
items=(
("YYC", "YYC Tools Chains", "YYC Tools Chains name standard."),
("IMENGYU", "Imengyu Ballance", "Auto grouping name standard for Imengyu/Ballance")
),
)
def invoke(self, context, event):
wm = context.window_manager
return wm.invoke_props_dialog(self)
def draw(self, context):
layout = self.layout
layout.prop(self, "name_standard")
class BALLANCE_OT_rename_by_group(rename_system_props):
"""Rename object by Virtools groups"""
bl_idname = "ballance.rename_by_group"
bl_label = "Rename by Group"
bl_options = {'UNDO'}
def execute(self, context):
_rename_core(_NameStandard.CKGROUP, _NameStandard.cvt_std_from_str_to_int(self.name_standard))
return {'FINISHED'}
class BALLANCE_OT_convert_name(rename_system_props):
"""Convert name from one name standard to another one."""
bl_idname = "ballance.convert_name"
bl_label = "Convert Name"
bl_options = {'UNDO'}
dest_name_standard: bpy.props.EnumProperty(
name="Destination Name Standard",
description="Choose your name standard",
items=(
("YYC", "YYC Tools Chains", "YYC Tools Chains name standard."),
("IMENGYU", "Imengyu Ballance", "Auto grouping name standard for Imengyu/Ballance")
),
)
def execute(self, context):
_rename_core(
_NameStandard.cvt_std_from_str_to_int(self.name_standard),
_NameStandard.cvt_std_from_str_to_int(self.dest_name_standard))
return {'FINISHED'}
# rewrite draw func
def draw(self, context):
layout = self.layout
layout.prop(self, "name_standard")
layout.prop(self, "dest_name_standard")
class BALLANCE_OT_auto_grouping(rename_system_props):
"""Auto Grouping object according to specific name standard."""
bl_idname = "ballance.auto_grouping"
bl_label = "Auto Grouping"
bl_options = {'UNDO'}
def execute(self, context):
_rename_core(_NameStandard.cvt_std_from_str_to_int(self.name_standard), _NameStandard.CKGROUP)
return {'FINISHED'}
# ==========================================
# rename misc funcs
class _ObjectBasicType():
COMPONENT = 0
FLOOR = 1
RAIL = 2
WOOD = 3
STOPPER = 4
DEPTH_CUBE = 5
DECORATION = 6
LEVEL_START = 7
LEVEL_END = 8
CHECKPOINT = 9
RESETPOINT = 10
class _NameStandard():
CKGROUP = 0
YYC = 1
IMENGYU = 2
@staticmethod
def cvt_std_from_str_to_int(std_str):
if std_str == "YYC":
return _NameStandard.YYC
elif std_str == "IMENGYU":
return _NameStandard.IMENGYU
else:
raise Exception("Unknow name standard.")
class _NameInfoHelper():
def __init__(self, _basic_type):
self.basic_type = _basic_type
# extra field notes:
# COMPONENT:
# component_type(string)
# sector(int)
# CHECKPOINT, RESETPOINT:
# sector(int)(following Ballance index, checkpoint starts with 1)
def _get_selected_objects():
return bpy.context.view_layer.active_layer_collection.collection.objects
def _get_sector_from_ckgroup(group_set):
# this counter is served for stupid
# multi-sector-grouping accident.
counter = 0
last_matched_sector = ''
for i in group_set:
regex_result = UTILS_constants.rename_regexCKGroupSector.match(i)
if regex_result is not None:
last_matched_sector = regex_result.group(1)
counter += 1
if counter != 1:
return None
else:
return last_matched_sector
# ==========================================
# rename core funcs
# NOTE: the implement of this function are copied from
# BallanceVirtoolsHelper/bvh/features/mapping/grouping.cpp
# ---
# YYC Tools Chains name standard is Ballance-compatible name standard.
# So this functions also serving for `_get_name_info_from_group` function
# to help get sector field from PC/PR elements. In ordinary call(external call)
# The final error output should be outputed nromally. But in the call from
# `_get_name_info_from_group`, this function should not output any error.
# So parameter `call_internal` is served for this work. In common it is False
# to let function output error str normally. But only set it to True in
# the call from `_get_name_info_from_group` to disable error output.
def _get_name_info_from_yyc_name(obj_name, call_internal = False):
# check component first
regex_result = UTILS_constants.rename_regexYYCComponent.match(obj_name)
if regex_result is not None:
data = _NameInfoHelper(_ObjectBasicType.COMPONENT)
data.component_type = regex_result.group(1)
data.sector = int(regex_result.group(2))
return data
# check PC PR elements
regex_result = UTILS_constants.rename_regexYYCPC.match(obj_name)
if regex_result is not None:
data = _NameInfoHelper(_ObjectBasicType.CHECKPOINT)
data.sector = int(regex_result.group(1))
return data
regex_result = UTILS_constants.rename_regexYYCPR.match(obj_name)
if regex_result is not None:
data = _NameInfoHelper(_ObjectBasicType.RESETPOINT)
data.sector = int(regex_result.group(1))
return data
# check other unique elements
if obj_name == "PS_FourFlames_01":
return _NameInfoHelper(_ObjectBasicType.LEVEL_START)
if obj_name == "PE_Balloon_01":
return _NameInfoHelper(_ObjectBasicType.LEVEL_END)
# process floors
if obj_name.startswith("A_Floor"):
return _NameInfoHelper(_ObjectBasicType.FLOOR)
if obj_name.startswith("A_Wood"):
return _NameInfoHelper(_ObjectBasicType.WOOD)
if obj_name.startswith("A_Rail"):
return _NameInfoHelper(_ObjectBasicType.RAIL)
if obj_name.startswith("A_Stopper"):
return _NameInfoHelper(_ObjectBasicType.STOPPER)
# process others
if obj_name.startswith("DepthCubes"):
return _NameInfoHelper(_ObjectBasicType.DEPTH_CUBE)
if obj_name.startswith("D_"):
return _NameInfoHelper(_ObjectBasicType.DECORATION)
# only output in external calling
if not call_internal:
print("[ERROR]\t{}:\tName match lost.".format(obj_name))
return None
def _get_name_info_from_imengyu_name(obj_name):
# check component first
regex_result = UTILS_constants.rename_regexImengyuComponent.match(obj_name)
if regex_result is not None:
data = _NameInfoHelper(_ObjectBasicType.COMPONENT)
data.component_type = regex_result.group(1)
data.sector = int(regex_result.group(2))
return data
# check PC PR elements
regex_result = UTILS_constants.rename_regexImengyuPCRComp.match(obj_name)
if regex_result is not None:
eles_name = regex_result.group(1)
if eles_name == 'PC_CheckPoint':
data = _NameInfoHelper(_ObjectBasicType.CHECKPOINT)
elif eles_name == 'PR_ResetPoint':
data = _NameInfoHelper(_ObjectBasicType.RESETPOINT)
data.sector = int(regex_result.group(2))
return data
# check other unique elements
if obj_name == "PS_LevelStart":
return _NameInfoHelper(_ObjectBasicType.LEVEL_START)
if obj_name == "PE_LevelEnd":
return _NameInfoHelper(_ObjectBasicType.LEVEL_END)
# process floors
if obj_name.startswith("S_Floors"):
return _NameInfoHelper(_ObjectBasicType.FLOOR)
if obj_name.startswith("S_FloorWoods"):
return _NameInfoHelper(_ObjectBasicType.WOOD)
if obj_name.startswith("S_FloorRails"):
return _NameInfoHelper(_ObjectBasicType.RAIL)
if obj_name.startswith("S_FloorStopper"):
return _NameInfoHelper(_ObjectBasicType.STOPPER)
# process others
if obj_name.startswith("DepthTestCubes"):
return _NameInfoHelper(_ObjectBasicType.DEPTH_CUBE)
if obj_name.startswith("O_"):
return _NameInfoHelper(_ObjectBasicType.DECORATION)
print("[ERROR]\t{}:\tName match lost.".format(obj_name))
return None
def _get_name_info_from_group(obj):
group_list = UTILS_virtools_prop.get_virtools_group_data(obj)
if len(group_list) == 0:
# name it as a decoration
return _NameInfoHelper(_ObjectBasicType.DECORATION)
group_set = set(group_list)
# try to filter unique elements first
set_result = UTILS_constants.rename_uniqueComponentsGroupName.intersection(group_set)
if len(set_result) == 1:
# get it
gotten_group_name = (list(set_result))[0]
if gotten_group_name == 'PS_Levelstart':
return _NameInfoHelper(_ObjectBasicType.LEVEL_START)
elif gotten_group_name == 'PE_Levelende':
return _NameInfoHelper(_ObjectBasicType.LEVEL_END)
elif gotten_group_name == 'PC_Checkpoints' or gotten_group_name == 'PR_Resetpoints':
# these type's data should be gotten from its name
# use _get_name_info_from_yyc_name to get it
# _get_name_info_from_yyc_name is Ballance-compatible name standard
data = _get_name_info_from_yyc_name(obj.name, call_internal=True)
if data is None:
print("[ERROR]\t{}:\tPC_Checkpoints or PR_Resetpoints detected. But couldn't get sector from name.".format(obj.name))
return None
if data.basic_type != _ObjectBasicType.CHECKPOINT and data.basic_type != _ObjectBasicType.RESETPOINT:
# check whether it is checkpoint or resetpoint
# if not, it mean that we got error data from name
# return None instead
print("[ERROR]\t{}:\tPC_Checkpoints or PR_Resetpoints detected. But name is illegal.".format(obj.name))
return None
# otherwise return data
return data
else:
print("[ERROR]\t{}:\tThe match of Unique Component lost.".format(obj.name))
return None
elif len(set_result) != 0:
# must be a weird grouping, report it
print("[ERROR]\t{}:\tA Multi-grouping Unique Component.".format(obj.name))
return None
# distinguish normal elements
set_result = UTILS_constants.rename_normalComponentsGroupName.intersection(group_set)
if len(set_result) == 1:
# get it
# now try get its sector
gotten_elements = (tuple(set_result))[0]
gotten_sector = _get_sector_from_ckgroup(group_set)
if gotten_sector is None:
# fail to get sector
print("[ERROR]\t{}:\tComponent detected. But couldn't get sector from CKGroup data.".format(obj.name))
return None
data = _NameInfoHelper(_ObjectBasicType.COMPONENT)
data.component_type = gotten_elements
data.sector = int(gotten_sector)
return data
elif len(set_result) != 0:
# must be a weird grouping, report it
print("[ERROR]\t{}:\tA Multi-grouping Component.".format(obj.name))
return None
# distinguish road
if 'Phys_FloorRails' in group_set:
# rail
return _NameInfoHelper(_ObjectBasicType.RAIL)
elif 'Phys_Floors' in group_set:
# distinguish it between Floor and Wood
floor_result =UTILS_constants.rename_floorGroupTester.intersection(group_set)
rail_result = UTILS_constants.rename_woodGroupTester.intersection(group_set)
if len(floor_result) > 0 and len(rail_result) == 0:
return _NameInfoHelper(_ObjectBasicType.FLOOR)
elif len(floor_result) == 0 and len(rail_result) > 0:
return _NameInfoHelper(_ObjectBasicType.WOOD)
else:
print("[WARNING]\t{}:\tCan't distinguish between Floors and Rails. Suppose it is Floors".format(obj.name))
return _NameInfoHelper(_ObjectBasicType.FLOOR)
elif 'Phys_FloorStopper' in group_set:
return _NameInfoHelper(_ObjectBasicType.STOPPER)
elif 'DepthTestCubes' in group_set:
return _NameInfoHelper(_ObjectBasicType.DEPTH_CUBE)
# no matched
print("[ERROR]\t{}:\tGroup match lost.".format(obj.name))
return None
def _set_for_yyc_name(obj, name_info):
basic_type = name_info.basic_type
if basic_type == _ObjectBasicType.DECORATION:
obj.name = "D_"
elif basic_type == _ObjectBasicType.LEVEL_START:
obj.name = "PS_FourFlames_01"
elif basic_type == _ObjectBasicType.LEVEL_END:
obj.name = "PE_Balloon_01"
elif basic_type == _ObjectBasicType.RESETPOINT:
obj.name = "PR_Resetpoint_{:0>2d}".format(name_info.sector)
elif basic_type == _ObjectBasicType.CHECKPOINT:
obj.name = "PC_TwoFlames_{:0>2d}".format(name_info.sector)
elif basic_type == _ObjectBasicType.DEPTH_CUBE:
obj.name = "DepthCubes_"
elif basic_type == _ObjectBasicType.FLOOR:
obj.name = "A_Floor_"
elif basic_type == _ObjectBasicType.WOOD:
obj.name = "A_Wood_"
elif basic_type == _ObjectBasicType.RAIL:
obj.name = "A_Rail_"
elif basic_type == _ObjectBasicType.STOPPER:
obj.name = "A_Stopper_"
elif basic_type == _ObjectBasicType.COMPONENT:
obj.name = "{}_{:0>2d}_".format(name_info.component_type, name_info.sector)
def _set_for_imengyu_name(obj, name_info):
basic_type = name_info.basic_type
if basic_type == _ObjectBasicType.DECORATION:
obj.name = "O_"
elif basic_type == _ObjectBasicType.LEVEL_START:
obj.name = "PS_LevelStart"
elif basic_type == _ObjectBasicType.LEVEL_END:
obj.name = "PE_LevelEnd"
elif basic_type == _ObjectBasicType.RESETPOINT:
obj.name = "PR_ResetPoint:{:d}".format(name_info.sector)
elif basic_type == _ObjectBasicType.CHECKPOINT:
obj.name = "PC_CheckPoint:{:d}".format(name_info.sector + 1)
elif basic_type == _ObjectBasicType.DEPTH_CUBE:
obj.name = "DepthTestCubes"
elif basic_type == _ObjectBasicType.FLOOR:
obj.name = "S_Floors"
elif basic_type == _ObjectBasicType.WOOD:
obj.name = "S_FloorWoods"
elif basic_type == _ObjectBasicType.RAIL:
obj.name = "S_FloorRails"
elif basic_type == _ObjectBasicType.STOPPER:
obj.name = "S_FloorStopper"
elif basic_type == _ObjectBasicType.COMPONENT:
obj.name = "{}:{}:{:d}".format(name_info.component_type, obj.name.replace(":", "_"), name_info.sector)
# NOTE: the implement of this function are copied from
# BallanceVirtoolsHelper/bvh/features/mapping/grouping.cpp
def _set_for_group(obj, name_info):
gps = []
basic_type = name_info.basic_type
if basic_type == _ObjectBasicType.DECORATION:
# decoration do not need grouping
pass
elif basic_type == _ObjectBasicType.LEVEL_START:
gps.append("PS_Levelstart")
elif basic_type == _ObjectBasicType.LEVEL_END:
gps.append("PE_Levelende")
elif basic_type == _ObjectBasicType.RESETPOINT:
gps.append("PC_Checkpoints")
elif basic_type == _ObjectBasicType.CHECKPOINT:
gps.append("PR_Resetpoints")
elif basic_type == _ObjectBasicType.DEPTH_CUBE:
gps.append("DepthTestCubes")
elif basic_type == _ObjectBasicType.FLOOR:
gps.append("Phys_Floors")
gps.append("Sound_HitID_01")
gps.append("Sound_RollID_01")
gps.append("Shadow")
elif basic_type == _ObjectBasicType.WOOD:
gps.append("Phys_FloorRails")
gps.append("Sound_HitID_03")
gps.append("Sound_RollID_03")
elif basic_type == _ObjectBasicType.RAIL:
gps.append("Phys_Floors")
gps.append("Sound_HitID_02")
gps.append("Sound_RollID_02")
gps.append("Shadow")
elif basic_type == _ObjectBasicType.STOPPER:
gps.append("Phys_FloorStopper")
elif basic_type == _ObjectBasicType.COMPONENT:
gps.append(name_info.component_type)
# set compabitility for 999 sector loader
if (name_info.sector == 9):
gps.append("Sector_9")
else:
gps.append("Sector_{:0>2d}".format(name_info.sector))
# apply to custom property
UTILS_virtools_prop.fill_virtools_group_data(obj, tuple(gps))
# ==========================================
# assemble funcs
def _get_data(obj, standard):
if standard == _NameStandard.YYC:
return _get_name_info_from_yyc_name(obj.name)
elif standard == _NameStandard.IMENGYU:
return _get_name_info_from_imengyu_name(obj.name)
elif standard == _NameStandard.CKGROUP:
return _get_name_info_from_group(obj)
else:
raise Exception("Unknow standard")
def _set_data(obj, name_info, standard):
if standard == _NameStandard.YYC:
return _set_for_yyc_name(obj, name_info)
elif standard == _NameStandard.IMENGYU:
return _set_for_imengyu_name(obj, name_info)
elif standard == _NameStandard.CKGROUP:
return _set_for_group(obj, name_info)
else:
raise Exception("Unknow standard")
def _rename_core(source_std, dest_std):
if source_std == dest_std:
# if source == dest
# we do not to do anything
return
failed_obj_counter = 0
all_obj_counter = 0
print('============')
print('Rename system report')
print('------------')
for obj in _get_selected_objects():
all_obj_counter += 1
info = _get_data(obj, source_std)
if info is None:
failed_obj_counter += 1
continue
_set_data(obj, info, dest_std)
print('------------')
print('All/failed - {}/{}'.format(all_obj_counter, failed_obj_counter))
print('============')
UTILS_functions.show_message_box(
('Rename system report',
'View console to get more detail',
'All: {}'.format(all_obj_counter),
'Failed: {}'.format(failed_obj_counter)),
"Info",
"INFO"
)

View File

@ -0,0 +1,56 @@
import bpy, mathutils
from . import UTILS_constants, UTILS_functions
# ================================================= actual add
class BALLANCE_OT_add_components(bpy.types.Operator):
"""Add sector related elements"""
bl_idname = "ballance.add_components"
bl_label = "Add elements"
bl_options = {'UNDO'}
elements_type: bpy.props.EnumProperty(
name="Type",
description="This element type",
items=tuple(map(lambda x: (x, x, ""), UTILS_constants.bmfile_componentList)),
)
attentionElements = ["PC_TwoFlames", "PR_Resetpoint"]
uniqueElements = ["PS_FourFlames", "PE_Balloon"]
elements_sector: bpy.props.IntProperty(
name="Sector",
description="Define which sector the object will be grouped in",
min=1,
max=8,
default=1,
)
def execute(self, context):
# get name
if self.elements_type in self.uniqueElements:
finalObjectName = self.elements_type + "_01"
elif self.elements_type in self.attentionElements:
finalObjectName = self.elements_type + "_0" + str(self.elements_sector)
else:
finalObjectName = self.elements_type + "_0" + str(self.elements_sector) + "_"
# create object
loadedMesh = UTILS_functions.load_component(
UTILS_constants.bmfile_componentList.index(self.elements_type))
obj = bpy.data.objects.new(finalObjectName, loadedMesh)
UTILS_functions.add_into_scene_and_move_to_cursor(obj)
return {'FINISHED'}
def invoke(self, context, event):
wm = context.window_manager
return wm.invoke_props_dialog(self)
def draw(self, context):
layout = self.layout
layout.prop(self, "elements_type")
if self.elements_type not in self.uniqueElements:
layout.prop(self, "elements_sector")
if self.elements_type in self.attentionElements:
layout.label(text="Please note that sector is suffix.")

View File

@ -0,0 +1,502 @@
import bpy,mathutils
import os, math
import ast
from bpy_extras import io_utils,node_shader_utils
# from bpy_extras.io_utils import unpack_list
from bpy_extras.image_utils import load_image
from . import UTILS_constants, UTILS_functions, UTILS_safe_eval
class BALLANCE_OT_add_floors(bpy.types.Operator):
"""Add Ballance floor"""
bl_idname = "ballance.add_floors"
bl_label = "Add floor"
bl_options = {'UNDO'}
floor_type: bpy.props.EnumProperty(
name="Type",
description="Floor type",
items=tuple((x, x, "") for x in UTILS_constants.floor_blockDict.keys()),
)
expand_length_1 : bpy.props.IntProperty(
name="D1 length",
description="The length of expand direction 1",
min=0,
default=0,
)
expand_length_2 : bpy.props.IntProperty(
name="D2 length",
description="The length of expand direction 2",
min=0,
default=0,
)
height_multiplier : bpy.props.FloatProperty(
name="Height",
description="The multiplier for height. Default height is 5",
min=0.0,
default=1.0,
)
use_2d_top : bpy.props.BoolProperty(
name="Top edge"
)
use_2d_right : bpy.props.BoolProperty(
name="Right edge"
)
use_2d_bottom : bpy.props.BoolProperty(
name="Bottom edge"
)
use_2d_left : bpy.props.BoolProperty(
name="Left edge"
)
use_3d_top : bpy.props.BoolProperty(
name="Top face"
)
use_3d_bottom : bpy.props.BoolProperty(
name="Bottom face"
)
previous_floor_type = ''
@classmethod
def poll(self, context):
prefs = bpy.context.preferences.addons[__package__].preferences
return os.path.isdir(prefs.external_folder)
def execute(self, context):
# get prefs
prefs = bpy.context.preferences.addons[__package__].preferences
prefs_externalTexture = prefs.external_folder
# load mesh
objmesh = bpy.data.meshes.new('done_')
if self.floor_type in UTILS_constants.floor_basicBlockList:
_load_basic_floor(
objmesh,
self.floor_type,
'R0',
self.height_multiplier,
self.expand_length_1,
self.expand_length_2,
(self.use_2d_top,
self.use_2d_right,
self.use_2d_bottom,
self.use_2d_left,
self.use_3d_top,
self.use_3d_bottom),
(0.0, 0.0, 0.0),
prefs_externalTexture)
elif self.floor_type in UTILS_constants.floor_derivedBlockList:
_load_derived_floor(
objmesh,
self.floor_type,
self.height_multiplier,
self.expand_length_1,
self.expand_length_2,
(self.use_2d_top,
self.use_2d_right,
self.use_2d_bottom,
self.use_2d_left,
self.use_3d_top,
self.use_3d_bottom),
prefs_externalTexture)
else:
raise Exception("Fatal error: unknow floor type.")
# normalization mesh
objmesh.validate(clean_customdata=False)
objmesh.update(calc_edges=False, calc_edges_loose=False)
# create object and link it
obj=bpy.data.objects.new('A_Floor_BMERevenge_', objmesh)
UTILS_functions.add_into_scene_and_move_to_cursor(obj)
return {'FINISHED'}
def invoke(self, context, event):
wm = context.window_manager
return wm.invoke_props_dialog(self)
def draw(self, context):
# get floor prototype
floor_prototype = UTILS_constants.floor_blockDict[self.floor_type]
# try sync default value
if self.previous_floor_type != self.floor_type:
self.previous_floor_type = self.floor_type
default_sides = floor_prototype['DefaultSideConfig']
self.use_2d_top = default_sides['UseTwoDTop']
self.use_2d_right = default_sides['UseTwoDRight']
self.use_2d_bottom = default_sides['UseTwoDBottom']
self.use_2d_left = default_sides['UseTwoDLeft']
self.use_3d_top = default_sides['UseThreeDTop']
self.use_3d_bottom = default_sides['UseThreeDBottom']
# show property
layout = self.layout
col = layout.column()
col.label(text="Basic param")
col.prop(self, "floor_type")
col.prop(self, "height_multiplier")
col.separator()
col.label(text="Expand")
if floor_prototype['ExpandType'] == 'Column' or floor_prototype['ExpandType'] == 'Freedom':
col.prop(self, "expand_length_1")
if floor_prototype['ExpandType'] == 'Freedom':
col.prop(self, "expand_length_2")
col.label(text="Unit size: " + floor_prototype['UnitSize'])
col.label(text="Expand mode: " + floor_prototype['ExpandType'])
grids = col.grid_flow(row_major=True, columns=3)
grids.separator()
grids.label(text=UTILS_constants.floor_expandDirectionMap[floor_prototype['InitColumnDirection']][floor_prototype['ExpandType']][0])
grids.separator()
grids.label(text=UTILS_constants.floor_expandDirectionMap[floor_prototype['InitColumnDirection']][floor_prototype['ExpandType']][3])
grids.template_icon(icon_value = UTILS_constants.icons_floorDict[self.floor_type])
grids.label(text=UTILS_constants.floor_expandDirectionMap[floor_prototype['InitColumnDirection']][floor_prototype['ExpandType']][1])
grids.separator()
grids.label(text=UTILS_constants.floor_expandDirectionMap[floor_prototype['InitColumnDirection']][floor_prototype['ExpandType']][2])
grids.separator()
col.separator()
col.label(text="Faces")
row = col.row()
row.prop(self, "use_3d_top")
row.prop(self, "use_3d_bottom")
col.separator()
col.label(text="Sides")
grids = col.grid_flow(row_major=True, columns=3)
grids.separator()
grids.prop(self, "use_2d_top")
grids.separator()
grids.prop(self, "use_2d_left")
grids.template_icon(icon_value = UTILS_constants.icons_floorDict[self.floor_type])
grids.prop(self, "use_2d_right")
grids.separator()
grids.prop(self, "use_2d_bottom")
grids.separator()
def _face_fallback(normal_face, expand_face, height):
if expand_face == None:
return normal_face
if height <= 1.0:
return normal_face
else:
return expand_face
def _create_or_get_material(material_name, prefs_externalTexture):
# WARNING: this code is shared with bm_import_export
deconflict_mtl_name = "BMERevenge_" + material_name
# create or get material
(mtl, skip_init) = UTILS_functions.create_instance_with_option(
UTILS_constants.BmfileInfoType.MATERIAL,
deconflict_mtl_name, 'CURRENT'
)
if skip_init:
return mtl
# initialize material parameter
# load texture first
texture_filename = UTILS_constants.floor_textureReflactionMap[material_name]
deconflict_texture_name = "BMERevenge_" + texture_filename
(texture, skip_init) = UTILS_functions.create_instance_with_option(
UTILS_constants.BmfileInfoType.TEXTURE,
deconflict_texture_name, 'CURRENT',
extra_texture_path = prefs_externalTexture, extra_texture_filename = texture_filename
)
# iterate material statistic to get corresponding mtl data
for try_item in UTILS_constants.floor_materialStatistic:
if material_name in try_item['member']:
# got it
# set material data
# all floor do not have any transparency props, so we provide 4 False value.
UTILS_functions.create_blender_material(mtl,
(True,
try_item['data']['ambient'], try_item['data']['diffuse'],
try_item['data']['specular'], try_item['data']['emissive'],
try_item['data']['power'],
False, False, False, False,
texture)
)
break
# return mtl
return mtl
def _solve_vec_data(str_data, d1, d2, d3, unit):
(cmd_x, cmd_y, cmd_z) = map(lambda x: x.strip(), str_data.split(','))
# calc raw expand data
raw_d1 = d1 * unit
raw_d2 = d2 * unit
raw_d3 = (d3 - 1) * 5.0 # the 3d heigh unit of ballance floor is always 5.0
# do safe eval
return (
UTILS_safe_eval.do_vec_calc(cmd_x, raw_d1, raw_d2, raw_d3),
UTILS_safe_eval.do_vec_calc(cmd_y, raw_d1, raw_d2, raw_d3),
UTILS_safe_eval.do_vec_calc(cmd_z, raw_d1, raw_d2, raw_d3)
)
def _rotate_translate_vec(_vec, rotation, unit, extra_translate):
vec = (
_vec[0] - unit / 2,
_vec[1] - unit / 2,
_vec[2]
)
if rotation == 'R0':
coso=1
sino=0
elif rotation == 'R90':
coso=0
sino=1
elif rotation == 'R180':
coso=-1
sino=0
elif rotation == 'R270':
coso=0
sino=-1
return (
coso * vec[0] - sino * vec[1] + unit / 2 + extra_translate[0],
sino * vec[0] + coso * vec[1] + unit / 2 + extra_translate[1],
vec[2] + extra_translate[2]
)
def _solve_uv_data(str_data, d1, d2, d3, unit):
(cmd_u, cmd_v) = map(lambda x: x.strip(), str_data.split(','))
# calc raw expand data
raw_d1 = d1 * unit
raw_d2 = d2 * unit
raw_d3 = float(d3 - 1) # the uv heigh unit of ballance floor is always 1.0
# do safe eval
return (
UTILS_safe_eval.do_vec_calc(cmd_u, raw_d1, raw_d2, raw_d3),
UTILS_safe_eval.do_vec_calc(cmd_v, raw_d1, raw_d2, raw_d3)
)
def _solve_normal_data(point1, point2, point3):
p1 = mathutils.Vector(point1)
p2 = mathutils.Vector(point2)
p3 = mathutils.Vector(point3)
vector1 = p2 - p1
vector2 = p3 - p2
# do vector x mutiply
# vector1 x vector2
corss_mul = vector1.cross(vector2)
# do a normalization
corss_mul.normalize()
return (corss_mul[0], corss_mul[1], corss_mul[2])
def _solve_expand_param(str_data, d1, d2):
(cmd_d1, cmd_d2) = map(lambda x: x.strip(), str_data.split(','))
# do safe eval
return (
UTILS_safe_eval.do_expand_calc(cmd_d1, d1, d2),
UTILS_safe_eval.do_expand_calc(cmd_d2, d1, d2)
)
def _virtual_foreach_set(collection, field, base_num, data):
counter = 0
for i in data:
exec("a[j]." + field + "=q", {}, {
'a': collection,
'j': counter + base_num,
'q': i
})
counter+=1
'''
sides_struct should be a tuple and it always have 6 bool items
(use_2d_top, use_2d_right, use_2d_bottom, use_2d_left, use_3d_top, use_3d_bottom)
WARNING: this code is shared with bm import export
'''
def _load_basic_floor(mesh, floor_type, rotation, height_multiplier, d1, d2, sides_struct, extra_translate, prefs_externalTexture):
floor_prototype = UTILS_constants.floor_blockDict[floor_type]
# set some unit
if floor_prototype['UnitSize'] == 'Small':
block_3dworld_unit = 2.5
block_uvworld_unit = 0.5
elif floor_prototype['UnitSize'] == 'Large':
block_3dworld_unit = 5.0
block_uvworld_unit = 1.0
# got all needed faces
needCreatedFaces = []
if sides_struct[0]:
needCreatedFaces.append(_face_fallback(floor_prototype['TwoDTopSide'], floor_prototype['TwoDTopSideExpand'], height_multiplier))
if sides_struct[1]:
needCreatedFaces.append(_face_fallback(floor_prototype['TwoDRightSide'], floor_prototype['TwoDRightSideExpand'], height_multiplier))
if sides_struct[2]:
needCreatedFaces.append(_face_fallback(floor_prototype['TwoDBottomSide'], floor_prototype['TwoDBottomSideExpand'], height_multiplier))
if sides_struct[3]:
needCreatedFaces.append(_face_fallback(floor_prototype['TwoDLeftSide'], floor_prototype['TwoDLeftSideExpand'], height_multiplier))
if sides_struct[4]:
needCreatedFaces.append(floor_prototype['ThreeDTopFace'])
if sides_struct[5]:
needCreatedFaces.append(floor_prototype['ThreeDBottomFace'])
# resolve face
# solve material first
materialDict = {}
allmat = mesh.materials[:]
counter = len(allmat)
for face_define in needCreatedFaces:
for face in face_define['Faces']:
new_texture = face['Textures']
if new_texture not in materialDict.keys():
# try get from existed solt
pending_material = _create_or_get_material(new_texture, prefs_externalTexture)
if pending_material not in allmat:
# no matched. add it
mesh.materials.append(pending_material)
materialDict[new_texture] = counter
counter += 1
else:
# use existed index
materialDict[new_texture] = allmat.index(pending_material)
# now, we can process real mesh
# load existed base count
global_offset_vec = len(mesh.vertices)
global_offset_polygons = len(mesh.polygons)
global_offset_loops = len(mesh.loops)
vecList = []
uvList = []
normalList = []
faceList = []
faceIndList = []
faceMatList = []
for face_define in needCreatedFaces:
base_indices = len(vecList)
for vec in face_define['Vertices']:
vecList.append(_rotate_translate_vec(
_solve_vec_data(vec, d1, d2, height_multiplier, block_3dworld_unit),
rotation, block_3dworld_unit, extra_translate))
for face in face_define['Faces']:
if face['Type'] == 'RECTANGLE':
# rectangle
vec_indices = tuple(map(lambda x: x + base_indices, face['Indices']))
indCount = 4
elif face['Type'] == 'TRIANGLE':
# triangle
vec_indices = tuple(map(lambda x: x + base_indices, face['Indices']))
indCount = 3
# we need calc normal and push it into list
point_normal = _solve_normal_data(vecList[vec_indices[0]], vecList[vec_indices[1]], vecList[vec_indices[2]])
for i in range(indCount):
normalList.append(point_normal)
# push indices into list
for i in range(indCount):
faceList.append(vec_indices[i] + global_offset_vec)
# push uvs into list
for i in range(indCount):
uvList.append(_solve_uv_data(face['UVs'][i], d1, d2, height_multiplier, block_uvworld_unit))
# push material into list
faceMatList.append(materialDict[face['Textures']])
# push face vec count into list
faceIndList.append(indCount)
# push data into blender struct
mesh.vertices.add(len(vecList))
mesh.loops.add(len(faceList))
mesh.polygons.add(len(faceMatList))
mesh.create_normals_split()
if mesh.uv_layers.active is None:
# if no uv, create it
mesh.uv_layers.new(do_init=False)
_virtual_foreach_set(mesh.vertices, "co", global_offset_vec, vecList)
_virtual_foreach_set(mesh.loops, "vertex_index", global_offset_loops, faceList)
_virtual_foreach_set(mesh.loops, "normal", global_offset_loops, normalList)
_virtual_foreach_set(mesh.uv_layers[0].data, "uv", global_offset_loops, uvList)
cache_counter = 0
for i in range(len(faceMatList)):
indCount = faceIndList[i]
mesh.polygons[i + global_offset_polygons].loop_start = global_offset_loops + cache_counter
mesh.polygons[i + global_offset_polygons].loop_total = indCount
mesh.polygons[i + global_offset_polygons].material_index = faceMatList[i]
mesh.polygons[i + global_offset_polygons].use_smooth = True
cache_counter += indCount
def _load_derived_floor(mesh, floor_type, height_multiplier, d1, d2, sides_struct, prefs_externalTexture):
floor_prototype = UTILS_constants.floor_blockDict[floor_type]
# set some unit
if floor_prototype['UnitSize'] == 'Small':
block_3dworld_unit = 2.5
elif floor_prototype['UnitSize'] == 'Large':
block_3dworld_unit = 5.0
# construct face dict
sides_dict = {
'True': True,
'False': False,
'2dTop': sides_struct[0],
'2dRight': sides_struct[1],
'2dBottom': sides_struct[2],
'2dLeft': sides_struct[3],
'3dTop': sides_struct[4],
'3dBottom': sides_struct[5]
}
# iterate smahsed blocks
for blk in floor_prototype['SmashedBlocks']:
# calc basic parameter
start_pos = _solve_vec_data(blk['StartPosition'], d1, d2, height_multiplier, block_3dworld_unit)
expand_param = _solve_expand_param(blk['ExpandParam'], d1, d2)
# re-calc height multiply for this block.
# if the translation of Z is not zero
if start_pos[2] != 0.0:
raw_height = (height_multiplier - 1) * 5.0 + start_pos[2] # test height
blk_height = max(0, (raw_height / 5.0) + 1) # prevent minus height
else:
blk_height = height_multiplier
# get face data
sides_data = tuple(sides_dict[x] for x in blk['SideSync'].split(';'))
# call basic floor creator
_load_basic_floor(
mesh,
blk['Type'],
blk['Rotation'],
blk_height,
expand_param[0],
expand_param[1],
sides_data,
start_pos,
prefs_externalTexture
)

View File

@ -0,0 +1,77 @@
import bpy, mathutils
from . import UTILS_functions
class BALLANCE_OT_add_rails(bpy.types.Operator):
"""Add rail"""
bl_idname = "ballance.add_rails"
bl_label = "Add rail section"
bl_options = {'UNDO'}
rail_type: bpy.props.EnumProperty(
name="Type",
description="Rail type",
items=(('MONO', "Monorail", ""),
('DOUBLE', "Rail", ""),
),
)
rail_radius: bpy.props.FloatProperty(
name="Rail radius",
description="Define rail section radius",
default=0.375,
)
rail_span: bpy.props.FloatProperty(
name="Rail span",
description="Define rail span",
default=3.75,
)
def execute(self, context):
bpy.ops.object.select_all(action='DESELECT')
# create one first
bpy.ops.mesh.primitive_circle_add(vertices=8,
radius=self.rail_radius,
fill_type='NOTHING',
calc_uvs=False,
enter_editmode=False,
align='WORLD',
location=(0.0, 0.0, 0.0))
firstObj = bpy.context.selected_objects[0]
# for double rail
if self.rail_type == 'DOUBLE':
bpy.ops.object.select_all(action='DESELECT')
bpy.ops.mesh.primitive_circle_add(vertices=8,
radius=self.rail_radius,
fill_type='NOTHING',
calc_uvs=False,
enter_editmode=False,
align='WORLD',
location=(self.rail_span, 0.0, 0.0))
secondObj = bpy.context.selected_objects[0]
# merge
bpy.ops.object.select_all(action='DESELECT')
bpy.context.view_layer.objects.active = firstObj
firstObj.select_set(True)
secondObj.select_set(True)
bpy.ops.object.join()
# apply 3d cursor
UTILS_functions.move_to_cursor(firstObj)
return {'FINISHED'}
def invoke(self, context, event):
wm = context.window_manager
return wm.invoke_props_dialog(self)
def draw(self, context):
layout = self.layout
layout.prop(self, "rail_type")
layout.prop(self, "rail_radius")
if self.rail_type == 'DOUBLE':
layout.prop(self, "rail_span")

View File

@ -0,0 +1,221 @@
import bpy
from . import UTILS_constants, UTILS_functions, UTILS_virtools_prop
class common_group_name_props(bpy.types.Operator):
use_custom_name: bpy.props.BoolProperty(
name="Use Custom Name",
description="Whether use user defined group name.",
default=False,
)
group_name: bpy.props.EnumProperty(
name="Group Name",
description="Pick vanilla Ballance group name.",
items=tuple((x, x, "") for x in UTILS_constants.propsVtGroups_availableGroups),
)
custom_group_name: bpy.props.StringProperty(
name="Custom Group Name",
description="Input your custom group name.",
default="",
)
def get_group_name_string(self):
return str(self.custom_group_name if self.use_custom_name else self.group_name)
def invoke(self, context, event):
wm = context.window_manager
return wm.invoke_props_dialog(self)
class BALLANCE_OT_select_virtools_group(common_group_name_props):
"""Select objects by Virtools Group."""
bl_idname = "ballance.select_virtools_group"
bl_label = "Select by Virtools Group"
bl_options = {'UNDO'}
merge_selection: bpy.props.BoolProperty(
name="Merge Selection",
description="Merge selection, rather than re-select them.",
default=False,
)
ignore_hide: bpy.props.BoolProperty(
name="Ignore Hide Property",
description="Select objects without considering visibility.",
default=False,
)
def execute(self, context):
# iterate object
for obj in bpy.context.scene.objects:
# ignore hidden objects
if (not self.ignore_hide) and obj.hide_get() == True:
continue
# check group
if UTILS_virtools_prop.check_virtools_group_data(obj, self.get_group_name_string()):
# select object
obj.select_set(True)
else:
# if not in merge mode, deselect them
if not self.merge_selection:
obj.select_set(False)
return {'FINISHED'}
def draw(self, context):
layout = self.layout
row = layout.row()
row.prop(self, 'ignore_hide')
row.prop(self, 'merge_selection')
layout.separator()
layout.prop(self, 'use_custom_name')
if (self.use_custom_name):
layout.prop(self, 'custom_group_name')
else:
layout.prop(self, 'group_name')
class BALLANCE_OT_filter_virtools_group(common_group_name_props):
"""Filter objects by Virtools Group."""
bl_idname = "ballance.filter_virtools_group"
bl_label = "Filter by Virtools Group"
bl_options = {'UNDO'}
reverse_selection: bpy.props.BoolProperty(
name="Reverse",
description="Reverse operation. Remove matched objects.",
default=False,
)
ignore_hide: bpy.props.BoolProperty(
name="Ignore Hide Property",
description="Select objects without considering visibility.",
default=False,
)
def execute(self, context):
# make a copy for all objects, to ensure it is not viotile
# becuase we need deselect some objects in for statement
selected = bpy.context.selected_objects[:]
# iterate object
for obj in selected:
# ignore hidden objects
if (not self.ignore_hide) and obj.hide_get() == True:
continue
# check group and decide select
is_selected = UTILS_virtools_prop.check_virtools_group_data(obj, self.get_group_name_string())
if self.reverse_selection:
is_selected = not is_selected
# select object
obj.select_set(is_selected)
return {'FINISHED'}
def draw(self, context):
layout = self.layout
row = layout.row()
row.prop(self, 'ignore_hide')
row.prop(self, 'reverse_selection')
layout.separator()
layout.prop(self, 'use_custom_name')
if (self.use_custom_name):
layout.prop(self, 'custom_group_name')
else:
layout.prop(self, 'group_name')
class BALLANCE_OT_ctx_set_group(common_group_name_props):
"""Grouping selected objects"""
bl_idname = "ballance.ctx_set_group"
bl_label = "Grouping Objects"
bl_options = {'UNDO'}
@classmethod
def poll(self, context):
return len(bpy.context.selected_objects) != 0
def execute(self, context):
has_duplicated = False
# iterate object
for obj in bpy.context.selected_objects:
# try setting
if not UTILS_virtools_prop.add_virtools_group_data(obj, self.get_group_name_string()):
has_duplicated = True
# throw a warning if some objects have duplicated group
if has_duplicated:
UTILS_functions.show_message_box(("Some objects have duplicated group name.", "These objects have been omitted.", ), "Duplicated Group", 'ERROR')
return {'FINISHED'}
def invoke(self, context, event):
wm = context.window_manager
return wm.invoke_props_dialog(self)
def draw(self, context):
layout = self.layout
layout.prop(self, 'use_custom_name')
if (self.use_custom_name):
layout.prop(self, 'custom_group_name')
else:
layout.prop(self, 'group_name')
class BALLANCE_OT_ctx_unset_group(common_group_name_props):
"""Ungrouping selected objects"""
bl_idname = "ballance.ctx_unset_group"
bl_label = "Ungrouping Objects"
bl_options = {'UNDO'}
@classmethod
def poll(self, context):
return len(bpy.context.selected_objects) != 0
def execute(self, context):
lack_group = False
# iterate object
for obj in bpy.context.selected_objects:
# try unsetting
if not UTILS_virtools_prop.remove_virtools_group_data(obj, self.get_group_name_string()):
lack_group = True
# throw a warning if some objects have duplicated group
if lack_group:
UTILS_functions.show_message_box(("Some objects lack specified group name.", "These objects have been omitted.", ), "Lack Group", 'ERROR')
return {'FINISHED'}
def draw(self, context):
layout = self.layout
layout.prop(self, 'use_custom_name')
if (self.use_custom_name):
layout.prop(self, 'custom_group_name')
else:
layout.prop(self, 'group_name')
class BALLANCE_OT_ctx_clear_group(bpy.types.Operator):
"""Clear Virtools Groups for selected objects"""
bl_idname = "ballance.ctx_clear_group"
bl_label = "Clear Grouping"
bl_options = {'UNDO'}
@classmethod
def poll(self, context):
return len(bpy.context.selected_objects) != 0
def execute(self, context):
# iterate object
for obj in bpy.context.selected_objects:
UTILS_virtools_prop.clear_virtools_group_data(obj)
return {'FINISHED'}

View File

@ -0,0 +1,102 @@
import bpy
from . import UTILS_constants, UTILS_functions, UTILS_virtools_prop
class BALLANCE_OT_add_virtools_group(bpy.types.Operator):
"""Add a Virtools Group for Active Object."""
bl_idname = "ballance.add_virtools_group"
bl_label = "Add Virtools Group"
bl_options = {'UNDO'}
use_custom_name: bpy.props.BoolProperty(
name="Use Custom Name",
description="Whether use user defined group name.",
default=False,
)
group_name: bpy.props.EnumProperty(
name="Group Name",
description="Pick vanilla Ballance group name.",
items=tuple((x, x, "") for x in UTILS_constants.propsVtGroups_availableGroups),
)
custom_group_name: bpy.props.StringProperty(
name="Custom Group Name",
description="Input your custom group name.",
default="",
)
@classmethod
def poll(self, context):
return context.object is not None
def execute(self, context):
# get name first
gotten_group_name = str(self.custom_group_name if self.use_custom_name else self.group_name)
# try adding
obj = context.object
if not UTILS_virtools_prop.add_virtools_group_data(obj, gotten_group_name):
UTILS_functions.show_message_box(("Group name is duplicated!", ), "Duplicated Name", 'ERROR')
return {'FINISHED'}
def invoke(self, context, event):
wm = context.window_manager
return wm.invoke_props_dialog(self)
def draw(self, context):
self.layout.prop(self, 'use_custom_name')
if (self.use_custom_name):
self.layout.prop(self, 'custom_group_name')
else:
self.layout.prop(self, 'group_name')
class BALLANCE_OT_rm_virtools_group(bpy.types.Operator):
"""Remove a Virtools Group for Active Object."""
bl_idname = "ballance.rm_virtools_group"
bl_label = "Remove Virtools Group"
bl_options = {'UNDO'}
@classmethod
def poll(self, context):
if context.object is None:
return False
obj = context.object
gp = UTILS_virtools_prop.get_virtools_group(obj)
active_gp = UTILS_virtools_prop.get_active_virtools_group(obj)
return int(active_gp) >= 0 and int(active_gp) < len(gp)
def execute(self, context):
obj = context.object
UTILS_virtools_prop.remove_virtools_group_data_by_index(obj, int(UTILS_virtools_prop.get_active_virtools_group(obj)))
return {'FINISHED'}
class BALLANCE_UL_virtools_group(bpy.types.UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
layout.prop(item, 'group_name', icon='GROUP', emboss=False, text="")
class BALLANCE_PT_virtools_group(bpy.types.Panel):
"""Show Virtools Group Properties."""
bl_label = "Virtools Group"
bl_idname = "BALLANCE_PT_virtools_group"
bl_space_type = 'PROPERTIES'
bl_region_type = 'WINDOW'
bl_context = "object"
@classmethod
def poll(cls, context):
return context.object is not None
def draw(self, context):
layout = self.layout
target = bpy.context.active_object
row = layout.row()
row.template_list("BALLANCE_UL_virtools_group", "", target, "virtools_group",
target, "active_virtools_group")
col = row.column(align=True)
col.operator(BALLANCE_OT_add_virtools_group.bl_idname, icon='ADD', text="")
col.operator(BALLANCE_OT_rm_virtools_group.bl_idname, icon='REMOVE', text="")

View File

@ -0,0 +1,91 @@
import bpy
from . import UTILS_constants, UTILS_functions, UTILS_virtools_prop
class BALLANCE_OT_apply_virtools_material(bpy.types.Operator):
"""Apply Virtools Material to Blender Material."""
bl_idname = "ballance.apply_virtools_material"
bl_label = "Apply Virtools Material"
bl_options = {'UNDO'}
@classmethod
def poll(cls, context):
return context.material is not None
def execute(self, context):
mtl = context.material
mtl_data = UTILS_virtools_prop.get_virtools_material_data(mtl)
# check enable, [0] is enable_virtools_material
if mtl_data[0]:
UTILS_functions.create_material_nodes(mtl, mtl_data)
else:
UTILS_functions.show_message_box(("Virtools Material is not enabled.", ), "Apply Failed", 'ERROR')
return {'FINISHED'}
class BALLANCE_OT_parse_virtools_material(bpy.types.Operator):
"""Apply Virtools Material to Blender Material."""
bl_idname = "ballance.parse_virtools_material"
bl_label = "Parse from Blender Principled BSDF"
bl_options = {'UNDO'}
@classmethod
def poll(cls, context):
return context.material is not None
def execute(self, context):
mtl = context.material
mtl_data = UTILS_functions.parse_material_nodes(mtl)
if mtl_data is None:
UTILS_functions.show_message_box(("Fail to parse Principled BSDF.", ), "Parsing Failed", 'ERROR')
else:
UTILS_virtools_prop.set_virtools_material_data(mtl, mtl_data)
return {'FINISHED'}
class BALLANCE_PT_virtools_material(bpy.types.Panel):
"""Show Virtools Material Properties."""
bl_label = "Virtools Material"
bl_idname = "BALLANCE_PT_virtools_material"
bl_space_type = 'PROPERTIES'
bl_region_type = 'WINDOW'
bl_context = "material"
@classmethod
def poll(cls, context):
return context.material is not None
def draw_header(self, context):
# draw a checkbox in header
target = UTILS_virtools_prop.get_virtools_material(context.material)
self.layout.prop(target, "enable_virtools_material", text="")
def draw(self, context):
# get layout and target
layout = self.layout
target = UTILS_virtools_prop.get_virtools_material(context.material)
# decide visible
layout.enabled = target.enable_virtools_material
# draw layout
layout.label(text="Basic Parameters")
layout.prop(target, 'ambient')
layout.prop(target, 'diffuse')
layout.prop(target, 'specular')
layout.prop(target, 'emissive')
layout.prop(target, 'specular_power')
layout.prop(target, 'texture', emboss=True)
layout.separator()
layout.label(text="Advanced Parameters")
layout.prop(target, 'alpha_test')
layout.prop(target, 'alpha_blend')
layout.prop(target, 'z_buffer')
layout.prop(target, 'two_sided')
layout.separator()
layout.label(text="Operations")
layout.operator("ballance.apply_virtools_material", icon="NODETREE")
layout.operator("ballance.parse_virtools_material", icon="HIDE_OFF")

View File

@ -0,0 +1,359 @@
import json
import os
import re
bmfile_currentVersion = 14
bmfile_flagUnicode = 0x800
bmfile_flagDeflatedMaximum = 0x2
bmfile_globalComment = 'Use BM Spec 1.4'.encode('utf-8')
class BmfileInfoType():
OBJECT = 0
MESH = 1
MATERIAL = 2
TEXTURE = 3
bmfile_externalTextureSet = set([
"atari.avi",
"atari.bmp",
"Ball_LightningSphere1.bmp",
"Ball_LightningSphere2.bmp",
"Ball_LightningSphere3.bmp",
"Ball_Paper.bmp",
"Ball_Stone.bmp",
"Ball_Wood.bmp",
"Brick.bmp",
"Button01_deselect.tga",
"Button01_select.tga",
"Button01_special.tga",
"Column_beige.bmp",
"Column_beige_fade.tga",
"Column_blue.bmp",
"Cursor.tga",
"Dome.bmp",
"DomeEnvironment.bmp",
"DomeShadow.tga",
"ExtraBall.bmp",
"ExtraParticle.bmp",
"E_Holzbeschlag.bmp",
"FloorGlow.bmp",
"Floor_Side.bmp",
"Floor_Top_Border.bmp",
"Floor_Top_Borderless.bmp",
"Floor_Top_Checkpoint.bmp",
"Floor_Top_Flat.bmp",
"Floor_Top_Profil.bmp",
"Floor_Top_ProfilFlat.bmp",
"Font_1.tga",
"Gravitylogo_intro.bmp",
"HardShadow.bmp",
"Laterne_Glas.bmp",
"Laterne_Schatten.tga",
"Laterne_Verlauf.tga",
"Logo.bmp",
"Metal_stained.bmp",
"Misc_Ufo.bmp",
"Misc_UFO_Flash.bmp",
"Modul03_Floor.bmp",
"Modul03_Wall.bmp",
"Modul11_13_Wood.bmp",
"Modul11_Wood.bmp",
"Modul15.bmp",
"Modul16.bmp",
"Modul18.bmp",
"Modul18_Gitter.tga",
"Modul30_d_Seiten.bmp",
"Particle_Flames.bmp",
"Particle_Smoke.bmp",
"PE_Bal_balloons.bmp",
"PE_Bal_platform.bmp",
"PE_Ufo_env.bmp",
"Pfeil.tga",
"P_Extra_Life_Oil.bmp",
"P_Extra_Life_Particle.bmp",
"P_Extra_Life_Shadow.bmp",
"Rail_Environment.bmp",
"sandsack.bmp",
"SkyLayer.bmp",
"Sky_Vortex.bmp",
"Stick_Bottom.tga",
"Stick_Stripes.bmp",
"Target.bmp",
"Tower_Roof.bmp",
"Trafo_Environment.bmp",
"Trafo_FlashField.bmp",
"Trafo_Shadow_Big.tga",
"Tut_Pfeil01.tga",
"Tut_Pfeil_Hoch.tga",
"Wolken_intro.tga",
"Wood_Metal.bmp",
"Wood_MetalStripes.bmp",
"Wood_Misc.bmp",
"Wood_Nailed.bmp",
"Wood_Old.bmp",
"Wood_Panel.bmp",
"Wood_Plain.bmp",
"Wood_Plain2.bmp",
"Wood_Raft.bmp"
])
bmfile_componentList = [
"P_Extra_Life",
"P_Extra_Point",
"P_Trafo_Paper",
"P_Trafo_Stone",
"P_Trafo_Wood",
"P_Ball_Paper",
"P_Ball_Stone",
"P_Ball_Wood",
"P_Box",
"P_Dome",
"P_Modul_01",
"P_Modul_03",
"P_Modul_08",
"P_Modul_17",
"P_Modul_18",
"P_Modul_19",
"P_Modul_25",
"P_Modul_26",
"P_Modul_29",
"P_Modul_30",
"P_Modul_34",
"P_Modul_37",
"P_Modul_41",
"PC_TwoFlames",
"PE_Balloon",
"PR_Resetpoint",
"PS_FourFlames"
]
'''
format: key is diection, value is a dict
dict's key is expand mode, value is a tuple
tuple always have 4 items, it means (TOP_STR, RIGHT_STR, BOTTOM_STR, LEFT_STR)
'''
floor_expandDirectionMap = {
"PositiveX": {
"Static": ("X", "X", "X", "X"),
"Column": ("X", "X", "D1", "X"),
"Freedom": ("X", "X", "D1", "D2"),
},
"NegativeX": {
"Static": ("X", "X", "X", "X"),
"Column": ("D1", "X", "X", "X"),
"Freedom": ("D1", "D2", "X", "X"),
},
"PositiveY": {
"Static": ("X", "X", "X", "X"),
"Column": ("X", "D1", "X", "X"),
"Freedom": ("X", "D1", "D2", "X"),
},
"NegativeY": {
"Static": ("X", "X", "X", "X"),
"Column": ("X", "X", "X", "D1"),
"Freedom": ("D2", "X", "X", "D1"),
}
}
floor_textureReflactionMap = {
"FloorSide": "Floor_Side.bmp",
"FloorTopBorder": "Floor_Top_Border.bmp",
"FloorTopBorder_ForSide": "Floor_Top_Border.bmp",
"FloorTopBorderless": "Floor_Top_Borderless.bmp",
"FloorTopBorderless_ForSide": "Floor_Top_Borderless.bmp",
"FloorTopFlat": "Floor_Top_Flat.bmp",
"FloorTopProfil": "Floor_Top_Profil.bmp",
"FloorTopProfilFlat": "Floor_Top_ProfilFlat.bmp",
"BallWood": "Ball_Wood.bmp",
"BallPaper": "Ball_Paper.bmp",
"BallStone": "Ball_Stone.bmp"
}
# WARNING: this data is shared with `BallanceVirtoolsPlugin/bvh/features/mapping/fix_texture.cpp`
floor_materialStatistic = [
{
"member": [
"FloorSide",
"FloorTopBorder_ForSide",
"FloorTopBorderless_ForSide"
],
"data": {
"ambient": (0, 0, 0),
"diffuse": (122 / 255.0, 122 / 255.0, 122 / 255.0),
"specular": (0.0, 0.0, 0.0),
"emissive": (104 / 255.0, 104 / 255.0, 104 / 255.0),
"power": 0
}
},
{
"member": [
"FloorTopBorder",
"FloorTopBorderless",
"FloorTopFlat",
"FloorTopProfil",
"FloorTopProfilFlat"
],
"data": {
"ambient": (0, 0, 0),
"diffuse": (1.0, 1.0, 1.0),
"specular": (80 / 255.0, 80 / 255.0, 80 / 255.0),
"emissive": (0.0, 0.0, 0.0),
"power": 100
}
},
{
"member": [
"BallPaper"
],
"data": {
"ambient": (25 / 255.0, 25 / 255.0, 25 / 255.0),
"diffuse": (1.0, 1.0, 1.0),
"specular": (0.0, 0.0, 0.0),
"emissive": (100 / 255.0, 100 / 255.0, 100 / 255.0),
"power": 0
}
},
{
"member": [
"BallStone",
"BallWood"
],
"data": {
"ambient": (25 / 255.0, 25 / 255.0, 25 / 255.0),
"diffuse": (1.0, 1.0, 1.0),
"specular": (229 / 255.0, 229 / 255.0, 229 / 255.0),
"emissive": (60 / 255.0, 60 / 255.0, 60 / 255.0),
"power": 0
}
}
]
floor_blockDict = {}
floor_basicBlockList = []
floor_derivedBlockList = []
# read from json
for walk_root, walk_dirs, walk_files in os.walk(os.path.join(os.path.dirname(__file__), "json", "basic_blocks")):
for relfile in walk_files:
if not relfile.endswith('.json'): continue
with open(os.path.join(walk_root, relfile)) as fp:
for item in json.load(fp):
floor_basicBlockList.append(item["Type"])
floor_blockDict[item["Type"]] = item
for walk_root, walk_dirs, walk_files in os.walk(os.path.join(os.path.dirname(__file__), "json", "derived_blocks")):
for relfile in walk_files:
if not relfile.endswith('.json'): continue
with open(os.path.join(walk_root, relfile)) as fp:
for item in json.load(fp):
floor_derivedBlockList.append(item["Type"])
floor_blockDict[item["Type"]] = item
icons_floor = None
icons_floorDict = {}
# blenderIcon_elements = None
# blenderIcon_elements_dict = {}
rename_normalComponentsGroupName = set([
"P_Extra_Life",
"P_Extra_Point",
"P_Trafo_Paper",
"P_Trafo_Stone",
"P_Trafo_Wood",
"P_Ball_Paper",
"P_Ball_Stone",
"P_Ball_Wood",
"P_Box",
"P_Dome",
"P_Modul_01",
"P_Modul_03",
"P_Modul_08",
"P_Modul_17",
"P_Modul_18",
"P_Modul_19",
"P_Modul_25",
"P_Modul_26",
"P_Modul_29",
"P_Modul_30",
"P_Modul_34",
"P_Modul_37",
"P_Modul_41"
])
rename_uniqueComponentsGroupName = set([
"PS_Levelstart",
"PE_Levelende",
"PC_Checkpoints",
"PR_Resetpoints"
])
rename_floorGroupTester = set([
"Sound_HitID_01",
"Sound_RollID_01"
])
rename_woodGroupTester = set([
"Sound_HitID_02",
"Sound_RollID_02"
])
# 61 mark: Sector_(0[1-8]|[1-9][0-9]{1,2}|9) may also work
rename_regexCKGroupSector = re.compile('^Sector_([123456789]{1}[0123456789]{1}[0123456789]{1}|[123456789]{1}[0123456789]{1}|0[12345678]{1}|9)$')
rename_regexYYCComponent = re.compile('^(' + '|'.join(rename_normalComponentsGroupName) + ')_(0[1-9]|[1-9][0-9])_.*$')
rename_regexYYCPC = re.compile('^PC_TwoFlames_(0[1-7])$')
rename_regexYYCPR = re.compile('^PR_Resetpoint_(0[1-8])$')
rename_regexImengyuComponent = re.compile('^(' + '|'.join(rename_normalComponentsGroupName) + '):[^:]*:([1-9]|[1-9][0-9])$')
rename_regexImengyuPCRComp = re.compile('^(PC_CheckPoint|PR_ResetPoint):([0-9]+)$')
propsVtGroups_availableGroups = (
"Sector_01",
"Sector_02",
"Sector_03",
"Sector_04",
"Sector_05",
"Sector_06",
"Sector_07",
"Sector_08",
"P_Extra_Life",
"P_Extra_Point",
"P_Trafo_Paper",
"P_Trafo_Stone",
"P_Trafo_Wood",
"P_Ball_Paper",
"P_Ball_Stone",
"P_Ball_Wood",
"P_Box",
"P_Dome",
"P_Modul_01",
"P_Modul_03",
"P_Modul_08",
"P_Modul_17",
"P_Modul_18",
"P_Modul_19",
"P_Modul_25",
"P_Modul_26",
"P_Modul_29",
"P_Modul_30",
"P_Modul_34",
"P_Modul_37",
"P_Modul_41",
"PS_Levelstart",
"PE_Levelende",
"PC_Checkpoints",
"PR_Resetpoints",
"Sound_HitID_01",
"Sound_RollID_01",
"Sound_HitID_02",
"Sound_RollID_02",
"Sound_HitID_03",
"Sound_RollID_03",
"DepthTestCubes",
"Phys_Floors",
"Phys_FloorRails",
"Phys_FloorStopper"
)

View File

@ -0,0 +1,96 @@
import bpy, bmesh, bpy_extras, mathutils
import struct, shutil, os
# writer
def write_string(fs,str):
count=len(str)
write_uint32(fs,count)
fs.write(str.encode("utf_32_le"))
def write_uint8(fs,num):
fs.write(struct.pack("B", num))
def write_uint32(fs,num):
fs.write(struct.pack("I", num))
def write_uint64(fs,num):
fs.write(struct.pack("Q", num))
def write_bool(fs,boolean):
if boolean:
write_uint8(fs, 1)
else:
write_uint8(fs, 0)
def write_float(fs,fl):
fs.write(struct.pack("f", fl))
def write_world_matrix(fs, matt):
mat = matt.transposed()
fs.write(struct.pack("ffffffffffffffff",
mat[0][0],mat[0][2], mat[0][1], mat[0][3],
mat[2][0],mat[2][2], mat[2][1], mat[2][3],
mat[1][0],mat[1][2], mat[1][1], mat[1][3],
mat[3][0],mat[3][2], mat[3][1], mat[3][3]))
def write_3vector(fs, x, y ,z):
fs.write(struct.pack("fff", x, y ,z))
def write_color(fs, colors):
write_3vector(fs, colors[0], colors[1], colors[2])
def write_2vector(fs, u, v):
fs.write(struct.pack("ff", u, v))
def write_face(fs, v1, vt1, vn1, v2, vt2, vn2, v3, vt3, vn3):
fs.write(struct.pack("IIIIIIIII", v1, vt1, vn1, v2, vt2, vn2, v3, vt3, vn3))
# reader
def peek_stream(fs):
res = fs.read(1)
fs.seek(-1, os.SEEK_CUR)
return res
def read_float(fs):
return struct.unpack("f", fs.read(4))[0]
def read_uint8(fs):
return struct.unpack("B", fs.read(1))[0]
def read_uint32(fs):
return struct.unpack("I", fs.read(4))[0]
def read_uint64(fs):
return struct.unpack("Q", fs.read(8))[0]
def read_string(fs):
count = read_uint32(fs)
return fs.read(count*4).decode("utf_32_le")
def read_bool(fs):
return read_uint8(fs) != 0
def read_world_materix(fs):
p = struct.unpack("ffffffffffffffff", fs.read(4*4*4))
res = mathutils.Matrix((
(p[0], p[2], p[1], p[3]),
(p[8], p[10], p[9], p[11]),
(p[4], p[6], p[5], p[7]),
(p[12], p[14], p[13], p[15])))
return res.transposed()
def read_3vector(fs):
return struct.unpack("fff", fs.read(3*4))
def read_2vector(fs):
return struct.unpack("ff", fs.read(2*4))
def read_face(fs):
return struct.unpack("IIIIIIIII", fs.read(4*9))
def read_component_face(fs):
return struct.unpack("IIIIII", fs.read(4*6))

View File

@ -0,0 +1,265 @@
import bpy, bmesh, bpy_extras, mathutils
import struct, shutil, os
from bpy_extras.io_utils import unpack_list
from bpy_extras.image_utils import load_image
from bpy_extras import io_utils, node_shader_utils
from . import UTILS_file_io, UTILS_constants, UTILS_virtools_prop
# =================================
# scene operation
def show_message_box(message, title, icon):
def draw(self, context):
layout = self.layout
for item in message:
layout.label(text=item, translate=False)
bpy.context.window_manager.popup_menu(draw, title = title, icon = icon)
def add_into_scene_and_move_to_cursor(obj):
move_to_cursor(obj)
view_layer = bpy.context.view_layer
collection = view_layer.active_layer_collection.collection
collection.objects.link(obj)
def move_to_cursor(obj):
obj.location = bpy.context.scene.cursor.location
# =================================
# is compoent
def is_component(name):
return get_component_id(name) != -1
def get_component_id(name):
for ind, comp in enumerate(UTILS_constants.bmfile_componentList):
if name.startswith(comp):
return ind
return -1
# =================================
# create / parse material
def create_blender_material(input_mtl, packed_data):
# adding material nodes
create_material_nodes(input_mtl, packed_data)
# write custom property
UTILS_virtools_prop.set_virtools_material_data(input_mtl, packed_data)
def create_material_nodes(input_mtl, packed_data):
(enable_virtools_material,
ambient, diffuse, specular, emissive, specular_power,
alpha_test, alpha_blend, z_buffer, two_sided,
texture) = packed_data
# enable nodes mode
input_mtl.use_nodes=True
# delete all existed nodes
for node in input_mtl.node_tree.nodes:
input_mtl.node_tree.nodes.remove(node)
# create ballance-style blender material
bnode = input_mtl.node_tree.nodes.new(type="ShaderNodeBsdfPrincipled")
mnode = input_mtl.node_tree.nodes.new(type="ShaderNodeOutputMaterial")
input_mtl.node_tree.links.new(bnode.outputs[0],mnode.inputs[0])
input_mtl.metallic = sum(ambient) / 3
input_mtl.diffuse_color = [i for i in diffuse] + [1]
input_mtl.specular_color = specular
input_mtl.specular_intensity = specular_power
# adding a texture
if texture is not None:
inode = input_mtl.node_tree.nodes.new(type="ShaderNodeTexImage")
inode.image = texture
input_mtl.node_tree.links.new(inode.outputs[0], bnode.inputs[0])
# return None if fail to parse
def parse_material_nodes(mtl):
# get node
mat_wrap = node_shader_utils.PrincipledBSDFWrapper(mtl)
# check existence of Principled BSDF
if mat_wrap:
# we trying get texture data from Principled BSDF
use_mirror = mat_wrap.metallic != 0.0
if use_mirror:
mtl_ambient = (mat_wrap.metallic, mat_wrap.metallic, mat_wrap.metallic)
else:
mtl_ambient = (1.0, 1.0, 1.0)
mtl_diffuse = (mat_wrap.base_color[0], mat_wrap.base_color[1], mat_wrap.base_color[2])
mtl_specular = (mat_wrap.specular, mat_wrap.specular, mat_wrap.specular)
mtl_emissive = mat_wrap.emission_color[:3]
mtl_specularPower = 0.0
# confirm texture
mtl_texture = None
tex_wrap = getattr(mat_wrap, "base_color_texture", None)
if tex_wrap:
image = tex_wrap.image
if image:
mtl_texture = image
# return value
return (True,
mtl_ambient, mtl_diffuse, mtl_specular, mtl_emissive, mtl_specularPower,
False, False, False, False,
mtl_texture
)
else:
return None
# =================================
# load component
def load_component(component_id):
# get file first
component_name = UTILS_constants.bmfile_componentList[component_id]
selected_file = os.path.join(
os.path.dirname(__file__),
'meshes',
component_name + '.bin'
)
# read file. please note this sector is sync with import_bm's mesh's code. when something change, please change each other.
fmesh = open(selected_file, 'rb')
# create real mesh, we don't need to consider name. blender will solve duplicated name
mesh = bpy.data.meshes.new('mesh_' + component_name)
vList = []
vnList = []
faceList = []
# in first read, store all data into list
listCount = UTILS_file_io.read_uint32(fmesh)
for i in range(listCount):
cache = UTILS_file_io.read_3vector(fmesh)
# switch yz
vList.append((cache[0], cache[2], cache[1]))
listCount = UTILS_file_io.read_uint32(fmesh)
for i in range(listCount):
cache = UTILS_file_io.read_3vector(fmesh)
# switch yz
vnList.append((cache[0], cache[2], cache[1]))
listCount = UTILS_file_io.read_uint32(fmesh)
for i in range(listCount):
faceData = UTILS_file_io.read_component_face(fmesh)
# we need invert triangle sort
faceList.append((
faceData[4], faceData[5],
faceData[2], faceData[3],
faceData[0], faceData[1]
))
# then, we need add correspond count for vertices
mesh.vertices.add(len(vList))
mesh.loops.add(len(faceList)*3) # triangle face confirmed
mesh.polygons.add(len(faceList))
mesh.create_normals_split()
# add vertices data
mesh.vertices.foreach_set("co", unpack_list(vList))
mesh.loops.foreach_set("vertex_index", unpack_list(_flat_component_vertices_index(faceList)))
mesh.loops.foreach_set("normal", unpack_list(_flat_component_vertices_normal(faceList, vnList)))
for i in range(len(faceList)):
mesh.polygons[i].loop_start = i * 3
mesh.polygons[i].loop_total = 3
mesh.polygons[i].use_smooth = True
mesh.validate(clean_customdata=False)
mesh.update(calc_edges=False, calc_edges_loose=False)
fmesh.close()
return mesh
def _flat_component_vertices_index(faceList):
for item in faceList:
yield (item[0], )
yield (item[2], )
yield (item[4], )
def _flat_component_vertices_normal(faceList, vnList):
for item in faceList:
yield vnList[item[1]]
yield vnList[item[3]]
yield vnList[item[5]]
# =================================
# create instance with option
def create_instance_with_option(instance_type, instance_name, instance_opt,
extra_mesh = None, extra_texture_path = None, extra_texture_filename = None):
"""
Create instance with opetions
`instance_type`, `instance_name`, `instance_opt` are essential for each type instances.
For object, you should provide `extra_mesh`.
For texture, you should provide `extra_texture_path` and `extra_texture_filename`.
"""
def get_instance():
try:
if instance_type == UTILS_constants.BmfileInfoType.OBJECT:
temp_instance = bpy.data.objects[instance_name]
elif instance_type == UTILS_constants.BmfileInfoType.MESH:
temp_instance = bpy.data.meshes[instance_name]
elif instance_type == UTILS_constants.BmfileInfoType.MATERIAL:
temp_instance = bpy.data.materials[instance_name]
elif instance_type == UTILS_constants.BmfileInfoType.TEXTURE:
temp_instance = bpy.data.images[instance_name]
temp_is_existed = True
except:
temp_instance = None
temp_is_existed = False
return (temp_instance, temp_is_existed)
def create_instance():
if instance_type == UTILS_constants.BmfileInfoType.OBJECT:
instance_obj = bpy.data.objects.new(instance_name, extra_mesh)
#instance_obj.name = instance_name
elif instance_type == UTILS_constants.BmfileInfoType.MESH:
instance_obj = bpy.data.meshes.new(instance_name)
#instance_obj.name = instance_name
elif instance_type == UTILS_constants.BmfileInfoType.MATERIAL:
instance_obj = bpy.data.materials.new(instance_name)
#instance_obj.name = instance_name
elif instance_type == UTILS_constants.BmfileInfoType.TEXTURE:
# this command will also check current available texture
# because `get_instance()` only check texture name
# but this strategy is not based on texture filepath, so this create method will
# correct this problem
instance_obj = load_image(extra_texture_filename, extra_texture_path, check_existing=(instance_opt == 'CURRENT'))
instance_obj.name = instance_name
return instance_obj
# analyze options
if (not isinstance(instance_opt, str)) or instance_opt == 'RENAME':
# create new instance
# or always create new instance if opts is not string
temp_instance = create_instance()
temp_skip_init = False
elif instance_opt == 'CURRENT':
# try get instance
(temp_instance, temp_is_existed) = get_instance()
# if got instance successfully, we do not create new one, otherwise, we should
# create new instance
if not temp_is_existed:
temp_instance = create_instance()
temp_skip_init = False
else:
temp_skip_init = True
return (temp_instance, temp_skip_init)

View File

@ -0,0 +1,49 @@
import bpy
import bpy.types
class MyPropertyGroup(bpy.types.PropertyGroup):
material_picker : bpy.props.PointerProperty(
type=bpy.types.Material,
name="Material",
description="The material used for rail"
)
collection_picker : bpy.props.PointerProperty(
type=bpy.types.Collection,
name="Collection",
description="The collection which will be exported"
)
object_picker : bpy.props.PointerProperty(
type=bpy.types.Object,
name="Object",
description="The object which will be exported"
)
class BallanceBlenderPluginPreferences(bpy.types.AddonPreferences):
bl_idname = __package__
external_folder: bpy.props.StringProperty(
name="External texture folder",
description="The Ballance texture folder which will be used by this plugin to get external texture.",
)
no_component_collection: bpy.props.StringProperty(
name="No component collection",
description="(Import) The object which stored in this collectiion will not be saved as component. (Export) All forced no component objects will be stored in this collection",
)
temp_texture_folder: bpy.props.StringProperty(
name="Temp texture folder",
description="The folder which will temporarily store the textures which are extracted from bm. Due to system temp folder will be deleted after decoding of bm, so this path should not be blank.",
)
def draw(self, context):
layout = self.layout
row = layout.row()
col = row.column()
col.prop(self, "external_folder")
col.prop(self, "no_component_collection")
col.prop(self, "temp_texture_folder")

View File

@ -0,0 +1,68 @@
import ast
class SimpleCalcEnsurance(ast.NodeVisitor):
def __init__(self):
self.is_illegal_syntax: bool = False
self.allow_float: bool = True
self.param_name: tuple = tuple()
def wrapper_visit(self, node: ast.AST, allow_float: bool, param_name: tuple) -> bool:
self.is_illegal_syntax = False
self.allow_float = allow_float
self.param_name = param_name
self.visit(node)
return self.is_illegal_syntax
def generic_visit(node):
self.is_illegal_syntax = True
def visit_Expression(self, node: ast.Expression):
self.visit(node.body)
def visit_BinOp(self, node: ast.BinOp):
if isinstance(node.op, ast.Add) or isinstance(node.op, ast.Sub) or isinstance(node.op, ast.Mult) or isinstance(node.op, ast.Div):
self.visit(node.left)
self.visit(node.right)
else:
self.is_illegal_syntax = True
def visit_UnaryOp(self, node: ast.UnaryOp):
if isinstance(node.op, ast.USub):
self.visit(node.operand)
else:
self.is_illegal_syntax = True
def visit_Constant(self, node: ast.Constant):
if (self.allow_float and isinstance(node.value, float)) or isinstance(node.value, int):
pass
else:
self.is_illegal_syntax = True
def visit_Name(self, node: ast.Name):
if node.id in self.param_name and isinstance(node.ctx, ast.Load):
pass
else:
self.is_illegal_syntax = True
def _do_calc(szEval: str, allow_float: bool, d: dict):
ast_tree = ast.parse(szEval, mode='eval')
walker = SimpleCalcEnsurance()
if walker.wrapper_visit(ast_tree, allow_float, d.keys()):
raise Exception("Illegal AST Tree. Tree contain illegal syntax. Please check BMERevenge.")
return eval(compile(ast_tree, '', mode='eval'), {}, d)
def do_vec_calc(szEval: str, raw_d1: float, raw_d2: float, raw_d3: float) -> float:
return float(_do_calc(szEval, True, {
"d1": raw_d1,
"d2": raw_d2,
"d3": raw_d3
}))
def do_expand_calc(szEval: str, d1: int, d2: int) -> int:
return int(_do_calc(szEval, False, {
"d1": d1,
"d2": d2
}))

View File

@ -0,0 +1,194 @@
import bpy
from . import UTILS_constants, UTILS_functions
class BALLANCE_PG_virtools_material(bpy.types.PropertyGroup):
enable_virtools_material: bpy.props.BoolProperty(
name="Enable Virtools Material",
default=False,
)
ambient: bpy.props.FloatVectorProperty(name="Ambient",
subtype='COLOR',
min=0.0,
max=1.0,
default=[0.0,0.0,0.0])
diffuse: bpy.props.FloatVectorProperty(name="Diffuse",
subtype='COLOR',
min=0.0,
max=1.0,
default=[0.0,0.0,0.0])
specular: bpy.props.FloatVectorProperty(name="Specular",
subtype='COLOR',
min=0.0,
max=1.0,
default=[0.0,0.0,0.0])
emissive: bpy.props.FloatVectorProperty(name="Emissive",
subtype='COLOR',
min=0.0,
max=1.0,
default=[0.0,0.0,0.0])
specular_power: bpy.props.FloatProperty(
name="Specular Power",
min=0.0,
max=100.0,
default=0.0,
)
alpha_test: bpy.props.BoolProperty(
name="Alpha Test",
description="Alpha Func: VXCMP_GREATER. Alpha Ref: 1.",
default=False,
)
alpha_blend: bpy.props.BoolProperty(
name="Alpha Blend",
description="Source Blend: VXBLEND_SRCALPHA. Dest Blend: VXBLEND_INVSRCALPHA.",
default=False,
)
z_buffer: bpy.props.BoolProperty(
name="Z Buffer",
description="ZFunc: VXCMP_LESSEQUAL.",
default=False,
)
two_sided: bpy.props.BoolProperty(
name="Two Sided",
default=False,
)
texture: bpy.props.PointerProperty(
type=bpy.types.Image,
name="Texture",
description="The texture used for Virtools material"
)
class BALLANCE_PG_virtools_group(bpy.types.PropertyGroup):
group_name: bpy.props.StringProperty(
name="Group Name",
default=""
)
def get_virtools_material(mtl):
return mtl.virtools_material
def get_virtools_material_data(mtl):
data = get_virtools_material(mtl)
return (
data.enable_virtools_material,
data.ambient,
data.diffuse,
data.specular,
data.emissive,
data.specular_power,
data.alpha_test,
data.alpha_blend,
data.z_buffer,
data.two_sided,
data.texture
)
def set_virtools_material_data(mtl, packed_data):
data = get_virtools_material(mtl)
# packed_data have the same order with the return value of `get_virtools_material_data`
(data.enable_virtools_material,
data.ambient, data.diffuse, data.specular, data.emissive, data.specular_power,
data.alpha_test, data.alpha_blend, data.z_buffer, data.two_sided,
data.texture) = packed_data
def get_active_virtools_group(obj):
return obj.active_virtools_group
def get_virtools_group(obj):
return obj.virtools_group
def check_virtools_group_data(obj, probe):
for item in get_virtools_group(obj):
if probe == str(item.group_name):
return True
return False
def add_virtools_group_data(obj, new_data):
# check exist
if check_virtools_group_data(obj, new_data):
# existed, give up
return False
# "add" do not need operate active_virtools_group
data = get_virtools_group(obj)
it = data.add()
it.name = ""
it.group_name = new_data
return True
def remove_virtools_group_data(obj, rm_data):
gp = get_virtools_group(obj)
active_gp = get_active_virtools_group(obj)
for idx, item in enumerate(gp):
if rm_data == str(item.group_name):
# decrease active group if removed item is ahead of active group
if idx <= active_gp:
active_gp -= 1
# remove
gp.remove(idx)
# indicate success
return True
return False
def remove_virtools_group_data_by_index(obj, rm_idx):
gp = get_virtools_group(obj)
active_gp = get_active_virtools_group(obj)
# report error
if rm_idx >= len(gp):
return False
# remove
if rm_idx <= active_gp:
active_gp -= 1
gp.remove(rm_idx)
return True
def clear_virtools_group_data(obj):
gp = get_virtools_group(obj)
active_gp = get_active_virtools_group(obj)
gp.clear()
active_gp = 0
def fill_virtools_group_data(obj, data_list):
# clear first
clear_virtools_group_data(obj)
# if no data to add, return
if data_list is None:
return
# add one by one after check duplication
data = get_virtools_group(obj)
for item in set(data_list):
it = data.add()
it.name = ""
it.group_name = item
def get_virtools_group_data(obj):
return tuple(str(item.group_name) for item in get_virtools_group(obj))
def register_props():
bpy.types.Object.virtools_group = bpy.props.CollectionProperty(type=BALLANCE_PG_virtools_group)
bpy.types.Object.active_virtools_group = bpy.props.IntProperty()
bpy.types.Material.virtools_material = bpy.props.PointerProperty(type=BALLANCE_PG_virtools_material)
def unregister_props():
del bpy.types.Material.virtools_material
del bpy.types.Object.virtools_group
del bpy.types.Object.active_virtools_group

View File

@ -0,0 +1,48 @@
import pathlib, zipfile, os, shutil
from . import UTILS_constants
def compress(folder, zip_file):
# remove target file first
if os.path.isfile(zip_file):
os.remove(zip_file)
# compress data
with zipfile.ZipFile(zip_file, mode= 'w', compression= zipfile.ZIP_DEFLATED, compresslevel= 9, allowZip64= False) as zip_obj:
# set global comment
zip_obj.comment = UTILS_constants.bmfile_globalComment
# iterate folder and add files
for folder_name, subfolders, filenames in os.walk(folder):
for filename in filenames:
# construct zip_entry
abstract_filepath = os.path.join(folder_name, filename)
relative_filepath = os.path.relpath(abstract_filepath, folder)
zip_entry = zipfile.ZipInfo.from_file(abstract_filepath, arcname= relative_filepath)
zip_entry.compress_type = zipfile.ZIP_DEFLATED
# compress file
with open(abstract_filepath, 'rb') as fs_in:
with zip_obj.open(zip_entry, mode= 'w') as zip_in:
# References
# https://stackoverflow.com/questions/53254622/zipfile-header-language-encoding-bit-set-differently-between-python2-and-python3
# set unicode flag after opening internal file.
# for the shit implement of python module zipfile, we need set Deflated:Maximum manually
zip_entry.flag_bits |= UTILS_constants.bmfile_flagUnicode
zip_entry.flag_bits |= UTILS_constants.bmfile_flagDeflatedMaximum
# copy file
shutil.copyfileobj(fs_in, zip_in)
def decompress(folder, zip_file):
with zipfile.ZipFile(zip_file, mode= 'r', compression= zipfile.ZIP_DEFLATED, compresslevel= 9, allowZip64= False) as zip_obj:
for zip_entry in zip_obj.infolist():
if (zip_entry.flag_bits & UTILS_constants.bmfile_flagUnicode) == 0:
# lost unicode flag, throw error
raise Exception("Zip Entry lost UNICODE flag.")
# decompress file
zip_obj.extract(zip_entry, path= folder)

View File

@ -2,133 +2,248 @@ bl_info={
"name":"Ballance Blender Plugin",
"description":"Ballance mapping tools for Blender",
"author":"yyc12345",
"version":(0,1),
"version":(3,0),
"blender":(2,83,0),
"category":"Object",
"support":"TESTING"
"support":"TESTING",
"warning": "Please read document before using this plugin.",
"wiki_url": "https://github.com/yyc12345/BallanceBlenderHelper",
"tracker_url": "https://github.com/yyc12345/BallanceBlenderHelper/issues"
}
# ============================================= import system
import bpy,bpy_extras
# =============================================
# import system
import bpy, bpy_extras
import bpy.utils.previews
import os
# import my code (with reload)
if "bpy" in locals():
import importlib
if "bm_import_export" in locals():
importlib.reload(bm_import_export)
if "floor_rail_uv" in locals():
importlib.reload(floor_rail_uv)
if "utils" in locals():
importlib.reload(utils)
if "config" in locals():
importlib.reload(config)
from . import config, utils, bm_import_export, floor_rail_uv
if "UTILS_constants" in locals():
importlib.reload(UTILS_constants)
if "UTILS_functions" in locals():
importlib.reload(UTILS_functions)
if "UTILS_preferences" in locals():
importlib.reload(UTILS_preferences)
if "UTILS_file_io" in locals():
importlib.reload(UTILS_file_io)
if "UTILS_zip_helper" in locals():
importlib.reload(UTILS_zip_helper)
if "UTILS_virtools_prop" in locals():
importlib.reload(UTILS_virtools_prop)
if "UTILS_safe_eval" in locals():
importlib.reload(UTILS_safe_eval)
# ============================================= func block
if "BMFILE_export" in locals():
importlib.reload(BMFILE_export)
if "BMFILE_import" in locals():
importlib.reload(BMFILE_import)
class ImportBM(bpy.types.Operator, bpy_extras.io_utils.ImportHelper):
"""Load a Ballance Map File"""
bl_idname = "import_scene.bm"
bl_label = "Import BM"
bl_options = {'PRESET', 'UNDO'}
filename_ext = ".bm"
if "MODS_rail_uv" in locals():
importlib.reload(MODS_rail_uv)
if "MODS_3dsmax_align" in locals():
importlib.reload(MODS_3dsmax_align)
if "MODS_flatten_uv" in locals():
importlib.reload(MODS_flatten_uv)
def execute(self, context):
bm_import_export.import_bm(context, self.filepath)
return {'FINISHED'}
class ExportBM(bpy.types.Operator, bpy_extras.io_utils.ExportHelper):
"""Save a Ballance Map File"""
bl_idname = "export_scene.bm"
bl_label = 'Export BM'
bl_options = {'PRESET'}
filename_ext = ".bm"
export_mode: bpy.props.EnumProperty(
name="Export mode",
items=(('COLLECTION', "Selected collection", "Export the selected collection"),
('OBJECT', "Selected objects", "Export the selected objects"),
),
)
export_target: bpy.props.StringProperty(
name="Export target",
description="Which one will be exported",
)
no_component_suffix: bpy.props.StringProperty(
name="No component suffix",
description="The object which have this suffix will not be saved as component.",
)
def execute(self, context):
bm_import_export.export_bm(context, self.filepath, self.export_mode, self.export_target, self.no_component_suffix)
return {'FINISHED'}
if "OBJS_add_components" in locals():
importlib.reload(OBJS_add_components)
if "OBJS_add_floors" in locals():
importlib.reload(OBJS_add_floors)
if "OBJS_add_rails" in locals():
importlib.reload(OBJS_add_rails)
if "OBJS_group_opers" in locals():
importlib.reload(OBJS_group_opers)
# ============================================= menu system
if "NAMES_rename_system" in locals():
importlib.reload(NAMES_rename_system)
class RailUVOperator(bpy.types.Operator):
"""Create a UV for rail"""
bl_idname = "ballance.rail_uv"
bl_label = "Create Rail UV"
bl_options = {'UNDO'}
if "PROPS_virtools_group" in locals():
importlib.reload(PROPS_virtools_group)
if "PROPS_virtools_material" in locals():
importlib.reload(PROPS_virtools_material)
def execute(self, context):
floor_rail_uv.create_rail_uv()
return {'FINISHED'}
from . import UTILS_constants, UTILS_functions, UTILS_preferences, UTILS_virtools_prop, UTILS_safe_eval
from . import BMFILE_export, BMFILE_import
from . import MODS_3dsmax_align, MODS_flatten_uv, MODS_rail_uv
from . import OBJS_add_components, OBJS_add_floors, OBJS_add_rails, OBJS_group_opers
from . import NAMES_rename_system
from . import PROPS_virtools_group, PROPS_virtools_material
class FloorUVOperator(bpy.types.Operator):
"""Virtoolize the UV of floor"""
bl_idname = "ballance.floor_uv"
bl_label = "Virtoolize floor UV"
bl_options = {'UNDO'}
# =============================================
# menu system
def execute(self, context):
floor_rail_uv.virtoolize_floor_uv()
return {'FINISHED'}
class ThreeDViewerMenu(bpy.types.Menu):
bl_label = "Ballance 3D"
bl_idname = "OBJECT_MT_ballance3d_menu"
class BALLANCE_MT_ThreeDViewerMenu(bpy.types.Menu):
"""Ballance 3D operators"""
bl_idname = "BALLANCE_MT_ThreeDViewerMenu"
bl_label = "Ballance"
def draw(self, context):
layout = self.layout
layout.operator("ballance.rail_uv")
layout.operator("ballance.floor_uv")
layout.operator(MODS_3dsmax_align.BALLANCE_OT_super_align.bl_idname)
layout.operator(MODS_rail_uv.BALLANCE_OT_rail_uv.bl_idname)
layout.operator(MODS_flatten_uv.BALLANCE_OT_flatten_uv.bl_idname)
# ============================================= blender call system
class BALLANCE_MT_OutlinerMenu(bpy.types.Menu):
"""Ballance rename operators"""
bl_idname = "BALLANCE_MT_OutlinerMenu"
bl_label = "Ballance"
def draw(self, context):
layout = self.layout
oprt = layout.operator(NAMES_rename_system.BALLANCE_OT_rename_by_group.bl_idname)
oprt = layout.operator(NAMES_rename_system.BALLANCE_OT_convert_name.bl_idname)
oprt = layout.operator(NAMES_rename_system.BALLANCE_OT_auto_grouping.bl_idname)
class BALLANCE_MT_AddFloorMenu(bpy.types.Menu):
"""Add Ballance floor"""
bl_idname = "BALLANCE_MT_AddFloorMenu"
bl_label = "Floors"
def draw(self, context):
layout = self.layout
layout.label(text="Basic floor")
for item in UTILS_constants.floor_basicBlockList:
cop = layout.operator(
OBJS_add_floors.BALLANCE_OT_add_floors.bl_idname,
text=item, icon_value = UTILS_constants.icons_floorDict[item])
cop.floor_type = item
layout.separator()
layout.label(text="Derived floor")
for item in UTILS_constants.floor_derivedBlockList:
cop = layout.operator(
OBJS_add_floors.BALLANCE_OT_add_floors.bl_idname,
text=item, icon_value = UTILS_constants.icons_floorDict[item])
cop.floor_type = item
# =============================================
# blender call system
classes = (
ImportBM,
ExportBM,
RailUVOperator,
FloorUVOperator,
ThreeDViewerMenu
UTILS_preferences.BallanceBlenderPluginPreferences,
UTILS_preferences.MyPropertyGroup,
BMFILE_import.BALLANCE_OT_import_bm,
BMFILE_export.BALLANCE_OT_export_bm,
MODS_rail_uv.BALLANCE_OT_rail_uv,
MODS_3dsmax_align.BALLANCE_OT_super_align,
MODS_flatten_uv.BALLANCE_OT_flatten_uv,
BALLANCE_MT_ThreeDViewerMenu,
OBJS_add_components.BALLANCE_OT_add_components,
OBJS_add_rails.BALLANCE_OT_add_rails,
OBJS_add_floors.BALLANCE_OT_add_floors,
BALLANCE_MT_AddFloorMenu,
NAMES_rename_system.BALLANCE_OT_rename_by_group,
NAMES_rename_system.BALLANCE_OT_convert_name,
NAMES_rename_system.BALLANCE_OT_auto_grouping,
BALLANCE_MT_OutlinerMenu,
UTILS_virtools_prop.BALLANCE_PG_virtools_material,
UTILS_virtools_prop.BALLANCE_PG_virtools_group,
PROPS_virtools_group.BALLANCE_OT_add_virtools_group,
PROPS_virtools_group.BALLANCE_OT_rm_virtools_group,
PROPS_virtools_group.BALLANCE_UL_virtools_group,
PROPS_virtools_group.BALLANCE_PT_virtools_group,
PROPS_virtools_material.BALLANCE_OT_apply_virtools_material,
PROPS_virtools_material.BALLANCE_OT_parse_virtools_material,
PROPS_virtools_material.BALLANCE_PT_virtools_material,
OBJS_group_opers.BALLANCE_OT_select_virtools_group,
OBJS_group_opers.BALLANCE_OT_filter_virtools_group,
OBJS_group_opers.BALLANCE_OT_ctx_set_group,
OBJS_group_opers.BALLANCE_OT_ctx_unset_group,
OBJS_group_opers.BALLANCE_OT_ctx_clear_group,
)
def menu_func_bm_import(self, context):
self.layout.operator(ImportBM.bl_idname, text="Ballance Map (.bm)")
self.layout.operator(BMFILE_import.BALLANCE_OT_import_bm.bl_idname, text="Ballance Map (.bmx)")
def menu_func_bm_export(self, context):
self.layout.operator(ExportBM.bl_idname, text="Ballance Map (.bm)")
self.layout.operator(BMFILE_export.BALLANCE_OT_export_bm.bl_idname, text="Ballance Map (.bmx)")
def menu_func_ballance_3d(self, context):
layout = self.layout
layout.menu(ThreeDViewerMenu.bl_idname)
layout.menu(BALLANCE_MT_ThreeDViewerMenu.bl_idname)
def menu_func_ballance_add(self, context):
layout = self.layout
layout.separator()
layout.label(text="Ballance")
layout.operator_menu_enum(
OBJS_add_components.BALLANCE_OT_add_components.bl_idname,
"elements_type", icon='MESH_ICOSPHERE', text="Elements")
layout.operator(OBJS_add_rails.BALLANCE_OT_add_rails.bl_idname, icon='MESH_CIRCLE', text="Rail section")
layout.menu(BALLANCE_MT_AddFloorMenu.bl_idname, icon='MESH_CUBE')
def menu_func_ballance_rename(self, context):
layout = self.layout
layout.menu(BALLANCE_MT_OutlinerMenu.bl_idname)
def menu_func_ballance_select(self, context):
layout = self.layout
layout.separator()
layout.label(text="Ballance")
layout.operator(OBJS_group_opers.BALLANCE_OT_select_virtools_group.bl_idname, icon='SELECT_SET')
layout.operator(OBJS_group_opers.BALLANCE_OT_filter_virtools_group.bl_idname, icon='FILTER')
def menu_func_ballance_grouping(self, context):
layout = self.layout
layout.separator()
col = layout.column()
col.operator_context = 'INVOKE_DEFAULT'
col.label(text="Ballance")
col.operator(OBJS_group_opers.BALLANCE_OT_ctx_set_group.bl_idname, icon='ADD', text="Group into...")
col.operator(OBJS_group_opers.BALLANCE_OT_ctx_unset_group.bl_idname, icon='REMOVE', text="Ungroup from...")
col.operator(OBJS_group_opers.BALLANCE_OT_ctx_clear_group.bl_idname, icon='TRASH', text="Clear Grouping")
def register():
# we need init all icon first
icon_path = os.path.join(os.path.dirname(__file__), "icons")
UTILS_constants.icons_floor = bpy.utils.previews.new()
for key, value in UTILS_constants.floor_blockDict.items():
blockIconName = "Ballance_FloorIcon_" + key
UTILS_constants.icons_floor.load(blockIconName, os.path.join(icon_path, "floor", value["BindingDisplayTexture"]), 'IMAGE')
UTILS_constants.icons_floorDict[key] = UTILS_constants.icons_floor[blockIconName].icon_id
for cls in classes:
bpy.utils.register_class(cls)
bpy.types.Scene.BallanceBlenderPluginProperty = bpy.props.PointerProperty(type=UTILS_preferences.MyPropertyGroup)
UTILS_virtools_prop.register_props()
bpy.types.TOPBAR_MT_file_import.append(menu_func_bm_import)
bpy.types.TOPBAR_MT_file_export.append(menu_func_bm_export)
bpy.types.VIEW3D_HT_header.append(menu_func_ballance_3d)
bpy.types.VIEW3D_MT_editor_menus.prepend(menu_func_ballance_3d)
bpy.types.VIEW3D_MT_add.append(menu_func_ballance_add)
bpy.types.OUTLINER_HT_header.append(menu_func_ballance_rename)
bpy.types.VIEW3D_MT_select_object.append(menu_func_ballance_select)
bpy.types.VIEW3D_MT_object_context_menu.append(menu_func_ballance_grouping)
def unregister():
bpy.types.TOPBAR_MT_file_import.remove(menu_func_bm_import)
bpy.types.TOPBAR_MT_file_export.remove(menu_func_bm_export)
bpy.types.VIEW3D_HT_header.remove(menu_func_ballance_3d)
bpy.types.VIEW3D_MT_editor_menus.remove(menu_func_ballance_3d)
bpy.types.VIEW3D_MT_add.remove(menu_func_ballance_add)
bpy.types.OUTLINER_HT_header.remove(menu_func_ballance_rename)
bpy.types.VIEW3D_MT_select_object.remove(menu_func_ballance_select)
bpy.types.VIEW3D_MT_object_context_menu.remove(menu_func_ballance_grouping)
UTILS_virtools_prop.unregister_props()
del bpy.types.Scene.BallanceBlenderPluginProperty
for cls in classes:
bpy.utils.unregister_class(cls)
# we need uninstall all icon after all classes unregister
bpy.utils.previews.remove(UTILS_constants.icons_floor)
if __name__=="__main__":
register()

View File

@ -1,328 +0,0 @@
import bpy,bmesh,bpy_extras,mathutils
import pathlib,zipfile,time,os,tempfile,math
import struct,shutil
from bpy_extras import io_utils,node_shader_utils
from . import utils, config
bm_current_version = 10
def import_bm(context,filepath):
# todo: finish this
pass
def export_bm(context,filepath,export_mode, export_target, no_component_suffix):
# ============================================ alloc a temp folder
tempFolderObj = tempfile.TemporaryDirectory()
tempFolder = tempFolderObj.name
# debug
# tempFolder = "G:\\ziptest"
tempTextureFolder = os.path.join(tempFolder, "Texture")
os.makedirs(tempTextureFolder)
# ============================================ find export target
if export_mode== "COLLECTION":
objectList = bpy.data.collections[export_target].objects
else:
objectList = [bpy.data.objects[export_target]]
needSuffixChecker = no_component_suffix != ""
componentObj = set()
for obj in objectList:
if needSuffixChecker and obj.name.endwith(no_component_suffix):
pass # meshObjList.add(obj)
else:
if is_component(obj.name):
componentObj.add(obj)
else:
pass # meshObjList.add(obj)
# ============================================ export
finfo = open(os.path.join(tempFolder, "index.bm"), "wb")
finfo.write(struct.pack("I", bm_current_version))
# ====================== export object
fobject = open(os.path.join(tempFolder, "object.bm"), "wb")
meshSet = set()
meshList = []
meshCount = 0
for obj in objectList:
# only export mesh object
if obj.type != 'MESH':
continue
varis_component = obj in componentObj
# clean no mesh object
currentMesh = obj.data
if currentMesh == None:
continue
# triangle first and then group
if not varis_component:
if currentMesh not in meshSet:
mesh_triangulate(currentMesh)
meshSet.add(currentMesh)
meshList.append(currentMesh)
meshId = meshCount
meshCount += 1
else:
meshId = meshList.index(currentMesh)
# write finfo first
write_string(finfo, obj.name)
write_int(finfo, info_bm_type.OBJECT)
write_long(finfo, fobject.tell())
# write fobject
write_int(fobject, 1 if varis_component else 0)
write_worldMatrix(fobject, obj.matrix_world)
if varis_component:
write_int(fobject, get_component_id(obj.name))
else:
write_int(fobject, meshId)
fobject.close()
# ====================== export mesh
fmesh = open(os.path.join(tempFolder, "mesh.bm"), "wb")
materialSet = set()
materialList = []
for mesh in meshList:
mesh.calc_normals_split()
# write finfo first
write_string(finfo, mesh.name)
write_int(finfo, info_bm_type.MESH)
write_long(finfo, fmesh.tell())
# write fmesh
# vertices
vecList = mesh.vertices[:]
write_int(fmesh, len(vecList))
for vec in vecList:
#swap yz
write_3vector(fmesh,vec.co[0],vec.co[2],vec.co[1])
# uv
face_index_pairs = [(face, index) for index, face in enumerate(mesh.polygons)]
uv_layer = mesh.uv_layers.active.data[:]
write_int(fmesh, len(face_index_pairs) * 3)
for f, f_index in face_index_pairs:
# it should be triangle face, otherwise throw a error
if (f.loop_total != 3):
raise Exception("Not a triangle", f.poly.loop_total)
for loop_index in range(f.loop_start, f.loop_start + f.loop_total):
uv = uv_layer[loop_index].uv
# reverse v
write_2vector(fmesh, uv[0], -uv[1])
# normals
write_int(fmesh, len(face_index_pairs) * 3)
for f, f_index in face_index_pairs:
# no need to check triangle again
for loop_index in range(f.loop_start, f.loop_start + f.loop_total):
nml = mesh.loops[loop_index].normal
# swap yz
write_3vector(fmesh, nml[0], nml[2], nml[1])
# face
# get material first
currentMat = mesh.materials[:]
noMaterial = len(currentMat) == 0
for mat in currentMat:
if mat not in materialSet:
materialSet.add(mat)
materialList.append(mat)
write_int(fmesh, len(face_index_pairs))
vtIndex = []
vnIndex = []
vIndex = []
for f, f_index in face_index_pairs:
# confirm material use
if noMaterial:
usedMat = 0
else:
usedMat = materialList.index(currentMat[f.material_index])
# export face
vtIndex.clear()
vnIndex.clear()
vIndex.clear()
counter = 0
for loop_index in range(f.loop_start, f.loop_start + f.loop_total):
vIndex.append(mesh.loops[loop_index].vertex_index)
vnIndex.append(f_index * 3 + counter)
vtIndex.append(f_index * 3 + counter)
counter += 1
# reverse vertices sort
write_face(fmesh,
vIndex[2], vtIndex[2], vnIndex[2],
vIndex[1], vtIndex[1], vnIndex[1],
vIndex[0], vtIndex[0], vnIndex[0])
# set used material
write_int(fmesh, 0 if noMaterial else 1)
write_int(fmesh, usedMat)
mesh.free_normals_split()
fmesh.close()
# ====================== export material
fmaterial = open(os.path.join(tempFolder, "material.bm"), "wb")
textureSet = set()
textureList = []
textureCount = 0
for material in materialList:
# write finfo first
write_string(finfo, material.name)
write_int(finfo, info_bm_type.MATERIAL)
write_long(finfo, fmaterial.tell())
# write basic color
mat_wrap = node_shader_utils.PrincipledBSDFWrapper(material)
if mat_wrap:
use_mirror = mat_wrap.metallic != 0.0
if use_mirror:
write_3vector(fmaterial, mat_wrap.metallic, mat_wrap.metallic, mat_wrap.metallic)
else:
write_3vector(fmaterial, 1, 1, 1)
write_3vector(fmaterial, mat_wrap.base_color[0], mat_wrap.base_color[1], mat_wrap.base_color[2])
write_3vector(fmaterial, mat_wrap.specular, mat_wrap.specular, mat_wrap.specular)
# confirm texture
tex_wrap = getattr(mat_wrap, "base_color_texture", None)
if tex_wrap:
image = tex_wrap.image
if image:
# add into texture list
if image not in textureSet:
textureSet.add(image)
textureList.append(image)
currentTexture = textureCount
textureCount += 1
else:
currentTexture = textureList.index(image)
write_int(fmaterial, 1)
write_int(fmaterial, currentTexture)
else:
# no texture
write_int(fmaterial, 0)
write_int(fmaterial, 0)
else:
# no texture
write_int(fmaterial, 0)
write_int(fmaterial, 0)
else:
# no Principled BSDF. write garbage
write_3vector(fmaterial, 0.8, 0.8, 0.8)
write_3vector(fmaterial, 0.8, 0.8, 0.8)
write_3vector(fmaterial, 0.8, 0.8, 0.8)
write_int(fmaterial, 0)
write_int(fmaterial, 0)
fmaterial.close()
# ====================== export texture
ftexture = open(os.path.join(tempFolder, "texture.bm"), "wb")
source_dir = os.path.dirname(bpy.data.filepath)
existed_texture = set()
for texture in textureList:
# write finfo first
write_string(finfo, texture.name)
write_int(finfo, info_bm_type.TEXTURE)
write_long(finfo, ftexture.tell())
# confirm internal
texture_filepath = io_utils.path_reference(texture.filepath, source_dir, tempTextureFolder,
'ABSOLUTE', "", None, texture.library)
filename = os.path.basename(texture_filepath)
write_string(ftexture, filename)
if (is_external_texture(filename)):
write_int(ftexture, 1)
else:
# copy internal texture, if this file is copied, do not copy it again
write_int(ftexture, 0)
if filename not in existed_texture:
shutil.copy(texture_filepath, os.path.join(tempTextureFolder, filename))
existed_texture.add(filename)
ftexture.close()
# close info fs
finfo.close()
# ============================================ save zip and clean up folder
if os.path.isfile(filepath):
os.remove(filepath)
with zipfile.ZipFile(filepath, 'w', zipfile.ZIP_DEFLATED, 9) as zipObj:
for folderName, subfolders, filenames in os.walk(tempFolder):
for filename in filenames:
filePath = os.path.join(folderName, filename)
arcname=os.path.relpath(filePath, tempFolder)
zipObj.write(filePath, arcname)
tempFolderObj.cleanup()
# ======================================================================================= export / import assistant
class info_bm_type():
OBJECT = 0
MESH = 1
MATERIAL = 2
TEXTURE = 3
def is_component(name):
return get_component_id(name) != -1
def get_component_id(name):
return -1 # todo: finish this, -1 mean not a component
def is_external_texture(name):
if name in config.external_texture_list:
return True
else:
return False
def mesh_triangulate(me):
bm = bmesh.new()
bm.from_mesh(me)
bmesh.ops.triangulate(bm, faces=bm.faces)
bm.to_mesh(me)
bm.free()
# ======================================================================================= file io assistant
def write_string(fs,str):
count=len(str)
write_int(fs,count)
fs.write(str.encode("utf_32_le"))
def write_int(fs,num):
fs.write(struct.pack("I", num))
def write_long(fs,num):
fs.write(struct.pack("Q", num))
def write_worldMatrix(fs, matt):
mat = matt.transposed()
fs.write(struct.pack("ffffffffffffffff",
mat[0][0],mat[0][2], mat[0][1], mat[0][3],
mat[2][0],mat[2][2], mat[2][1], mat[2][3],
mat[1][0],mat[1][2], mat[1][1], mat[1][3],
mat[3][0],mat[3][2], mat[3][1], mat[3][3]))
def write_3vector(fs, x, y ,z):
fs.write(struct.pack("fff", x, y ,z))
def write_2vector(fs, u, v):
fs.write(struct.pack("ff", u, v))
def write_face(fs, v1, vn1, vt1, v2, vn2, vt2, v3, vn3, vt3):
fs.write(struct.pack("IIIIIIIII", v1, vn1, vt1, v2, vn2, vt2, v3, vn3, vt3))

View File

@ -1,85 +0,0 @@
external_texture_list = set([
'Ball_LightningSphere1.bmp',
'Ball_LightningSphere2.bmp',
'Ball_LightningSphere3.bmp',
'Ball_Paper.bmp',
'Ball_Stone.bmp',
'Ball_Wood.bmp',
'Brick.bmp',
'Button01_deselect.tga',
'Button01_select.tga',
'Button01_special.tga',
'Column_beige.bmp',
'Column_beige_fade.tga',
'Column_blue.bmp',
'Cursor.tga',
'Dome.bmp',
'DomeEnvironment.bmp',
'DomeShadow.tga',
'ExtraBall.bmp',
'ExtraParticle.bmp',
'E_Holzbeschlag.bmp',
'FloorGlow.bmp',
'Floor_Side.bmp',
'Floor_Top_Border.bmp',
'Floor_Top_Borderless.bmp',
'Floor_Top_Checkpoint.bmp',
'Floor_Top_Flat.bmp',
'Floor_Top_Profil.bmp',
'Floor_Top_ProfilFlat.bmp',
'Font_1.tga',
'Gravitylogo_intro.bmp',
'HardShadow.bmp',
'Laterne_Glas.bmp',
'Laterne_Schatten.tga',
'Laterne_Verlauf.tga',
'Logo.bmp',
'Metal_stained.bmp',
'Misc_Ufo.bmp',
'Misc_UFO_Flash.bmp',
'Modul03_Floor.bmp',
'Modul03_Wall.bmp',
'Modul11_13_Wood.bmp',
'Modul11_Wood.bmp',
'Modul15.bmp',
'Modul16.bmp',
'Modul18.bmp',
'Modul18_Gitter.tga',
'Modul30_d_Seiten.bmp',
'Particle_Flames.bmp',
'Particle_Smoke.bmp',
'PE_Bal_balloons.bmp',
'PE_Bal_platform.bmp',
'PE_Ufo_env.bmp',
'Pfeil.tga',
'P_Extra_Life_Oil.bmp',
'P_Extra_Life_Particle.bmp',
'P_Extra_Life_Shadow.bmp',
'Rail_Environment.bmp',
'sandsack.bmp',
'SkyLayer.bmp',
'Sky_Vortex.bmp',
'Stick_Bottom.tga',
'Stick_Stripes.bmp',
'Target.bmp',
'Tower_Roof.bmp',
'Trafo_Environment.bmp',
'Trafo_FlashField.bmp',
'Trafo_Shadow_Big.tga',
'Tut_Pfeil01.tga',
'Tut_Pfeil_Hoch.tga',
'Wolken_intro.tga',
'Wood_Metal.bmp',
'Wood_MetalStripes.bmp',
'Wood_Misc.bmp',
'Wood_Nailed.bmp',
'Wood_Old.bmp',
'Wood_Panel.bmp',
'Wood_Plain.bmp',
'Wood_Plain2.bmp',
'Wood_Raft.bmp'
])
component_list = [
]

View File

@ -1,34 +0,0 @@
import bpy,bmesh
from . import utils
def create_rail_uv():
meshList = []
for obj in bpy.context.selected_objects:
if obj.type != 'MESH':
continue
if obj.data.uv_layers.active.data == None:
utils.ShowMessageBox("You should create a UV layer for this object firstly. Then execute this operator.", "No UV layer", 'ERROR')
return
meshList.append(obj.data)
for mesh in meshList:
# vecList = mesh.vertices[:]
uv_layer = mesh.uv_layers.active.data
for poly in mesh.polygons:
for loop_index in range(poly.loop_start, poly.loop_start + poly.loop_total):
# index = mesh.loops[loop_index].vertex_index
uv_layer[loop_index].uv[0] = 0 # vecList[index].co[0]
uv_layer[loop_index].uv[1] = 1 # vecList[index].co[1]
def virtoolize_floor_uv():
pass
def mesh_triangulate(me):
bm = bmesh.new()
bm.from_mesh(me)
bmesh.ops.triangulate(bm, faces=bm.faces)
bm.to_mesh(me)
bm.free()

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

View File

@ -0,0 +1,483 @@
[
{
"Type": "Flat",
"BindingDisplayTexture": "Flat.png",
"UnitSize": "Small",
"ExpandType": "Freedom",
"InitColumnDirection": "PositiveY",
"DefaultSideConfig": {
"UseTwoDTop": false,
"UseTwoDRight": false,
"UseTwoDBottom": false,
"UseTwoDLeft": false,
"UseThreeDTop": true,
"UseThreeDBottom": false
},
"ThreeDTopFace": {
"Vertices": [
"0, 0, 0",
"2.5+d2, 0, 0",
"2.5+d2, 2.5+d1, 0",
"0, 2.5+d1, 0"
],
"Faces": [
{
"Type": "RECTANGLE",
"Textures": "FloorTopBorderless",
"Indices": [
0,
1,
2,
3
],
"UVs": [
"0, 0.5",
"0, -d2",
"0.5+d1, -d2",
"0.5+d1, 0.5"
]
}
]
},
"ThreeDBottomFace": {
"Vertices": [
"0, 0, -5-d3",
"2.5+d2, 0, -5-d3",
"2.5+d2, 2.5+d1, -5-d3",
"0, 2.5+d1, -5-d3"
],
"Faces": [
{
"Type": "RECTANGLE",
"Textures": "FloorTopBorderless_ForSide",
"Indices": [
3,
2,
1,
0
],
"UVs": [
"0, 0.5",
"0, -d2",
"0.5+d1, -d2",
"0.5+d1, 0.5"
]
}
]
},
"TwoDTopSide": {
"Vertices": [
"0, 0, 0",
"0, 2.5+d1, 0",
"0, 2.5+d1, -5-d3",
"0, 0, -5-d3"
],
"Faces": [
{
"Type": "RECTANGLE",
"Textures": "FloorTopBorderless_ForSide",
"Indices": [
0,
1,
2,
3
],
"UVs": [
"0, 0",
"0, 0.5+d1",
"1+d3, 0.5+d1",
"1+d3, 0"
]
}
]
},
"TwoDRightSide": {
"Vertices": [
"2.5+d2, 2.5+d1, 0",
"2.5+d2, 2.5+d1, -5-d3",
"0, 2.5+d1, -5-d3",
"0, 2.5+d1, 0"
],
"Faces": [
{
"Type": "RECTANGLE",
"Textures": "FloorTopBorderless_ForSide",
"Indices": [
0,
1,
2,
3
],
"UVs": [
"0, -d2",
"1+d3, -d2",
"1+d3, 0.5",
"0, 0.5"
]
}
]
},
"TwoDBottomSide": {
"Vertices": [
"2.5+d2, 2.5+d1, 0",
"2.5+d2, 0, 0",
"2.5+d2, 0, -5-d3",
"2.5+d2, 2.5+d1, -5-d3"
],
"Faces": [
{
"Type": "RECTANGLE",
"Textures": "FloorTopBorderless_ForSide",
"Indices": [
0,
1,
2,
3
],
"UVs": [
"0, 0.5+d1",
"0, 0",
"1+d3, 0",
"1+d3, 0.5+d1"
]
}
]
},
"TwoDLeftSide": {
"Vertices": [
"0, 0, 0",
"0, 0, -5-d3",
"2.5+d2, 0, -5-d3",
"2.5+d2, 0, 0"
],
"Faces": [
{
"Type": "RECTANGLE",
"Textures": "FloorTopBorderless_ForSide",
"Indices": [
0,
1,
2,
3
],
"UVs": [
"0, 0.5",
"1+d3, 0.5",
"1+d3, -d2",
"0, -d2"
]
}
]
},
"TwoDTopSideExpand": null,
"TwoDRightSideExpand": null,
"TwoDBottomSideExpand": null,
"TwoDLeftSideExpand": null
},
{
"Type": "NormalSinkTransition",
"BindingDisplayTexture": "NormalSinkTransition.png",
"UnitSize": "Large",
"ExpandType": "Static",
"InitColumnDirection": "PositiveX",
"DefaultSideConfig": {
"UseTwoDTop": false,
"UseTwoDRight": true,
"UseTwoDBottom": false,
"UseTwoDLeft": true,
"UseThreeDTop": true,
"UseThreeDBottom": false
},
"ThreeDTopFace": {
"Vertices": [
"0, 0, 0",
"5, 0, 0",
"0, 2.5, -0.7",
"5, 5, 0",
"0, 5, 0"
],
"Faces": [
{
"Type": "TRIANGLE",
"Textures": "FloorTopProfilFlat",
"Indices": [
0,
1,
2
],
"UVs": [
"0, 1",
"0, 0",
"0.5, 1"
]
},
{
"Type": "TRIANGLE",
"Textures": "FloorTopProfilFlat",
"Indices": [
3,
2,
1
],
"UVs": [
"1, 0",
"0.5, 1",
"0, 0"
]
},
{
"Type": "TRIANGLE",
"Textures": "FloorTopProfilFlat",
"Indices": [
2,
3,
4
],
"UVs": [
"0.5, 1",
"1, 0",
"1, 1"
]
}
]
},
"ThreeDBottomFace": {
"Vertices": [
"5, 0, -5-d3",
"5, 5, -5-d3",
"0, 5, -5-d3",
"0, 0, -5-d3"
],
"Faces": [
{
"Type": "RECTANGLE",
"Textures": "FloorTopBorderless_ForSide",
"Indices": [
3,
2,
1,
0
],
"UVs": [
"0, 1",
"1, 1",
"1, 0",
"0, 0"
]
}
]
},
"TwoDTopSide": {
"Vertices": [
"0, 0, -5-d3",
"0, 0, 0",
"0, 2.5, -0.7",
"0, 2.5, -5-d3",
"0, 5, -5-d3",
"0, 5, 0"
],
"Faces": [
{
"Type": "RECTANGLE",
"Textures": "FloorTopBorderless_ForSide",
"Indices": [
0,
1,
2,
3
],
"UVs": [
"0, -d3",
"0, 1",
"0.5, 0.86",
"0.5, -d3"
]
},
{
"Type": "RECTANGLE",
"Textures": "FloorTopBorderless_ForSide",
"Indices": [
5,
4,
3,
2
],
"UVs": [
"1, 1",
"1, -d3",
"0.5, -d3",
"0.5, 0.86"
]
}
]
},
"TwoDRightSide": {
"Vertices": [
"5, 5, 0",
"5, 5, -5-d3",
"0, 5, -5-d3",
"0, 5, 0"
],
"Faces": [
{
"Type": "RECTANGLE",
"Textures": "FloorSide",
"Indices": [
0,
1,
2,
3
],
"UVs": [
"0, 0",
"1+d3, 0",
"1+d3, 1",
"0, 1"
]
}
]
},
"TwoDBottomSide": {
"Vertices": [
"5, 5, 0",
"5, 0, 0",
"5, 0, -5-d3",
"5, 5, -5-d3"
],
"Faces": [
{
"Type": "RECTANGLE",
"Textures": "FloorTopBorderless_ForSide",
"Indices": [
0,
1,
2,
3
],
"UVs": [
"0, 1",
"0, 0",
"1+d3, 0",
"1+d3, 1"
]
}
]
},
"TwoDLeftSide": {
"Vertices": [
"5, 0, 0",
"5, 0, -5-d3",
"0, 0, -5-d3",
"0, 0, 0"
],
"Faces": [
{
"Type": "RECTANGLE",
"Textures": "FloorSide",
"Indices": [
3,
2,
1,
0
],
"UVs": [
"0, 1",
"1+d3, 1",
"1+d3, 0",
"0, 0"
]
}
]
},
"TwoDTopSideExpand": null,
"TwoDRightSideExpand": {
"Vertices": [
"0, 5, 0",
"5, 5, 0",
"5, 5, -2.5",
"0, 5, -2.5",
"0, 5, -5-d3",
"5, 5, -5-d3"
],
"Faces": [
{
"Type": "RECTANGLE",
"Textures": "FloorTopBorder_ForSide",
"Indices": [
0,
1,
2,
3
],
"UVs": [
"0, 1",
"0, 0",
"0.5, 0",
"0.5, 1"
]
},
{
"Type": "RECTANGLE",
"Textures": "FloorTopBorderless_ForSide",
"Indices": [
5,
4,
3,
2
],
"UVs": [
"0.5+d3, 0",
"0.5+d3, 1",
"0, 1",
"0, 0"
]
}
]
},
"TwoDBottomSideExpand": null,
"TwoDLeftSideExpand": {
"Vertices": [
"0, 0, 0",
"5, 0, 0",
"5, 0, -2.5",
"0, 0, -2.5",
"0, 0, -5-d3",
"5, 0, -5-d3"
],
"Faces": [
{
"Type": "RECTANGLE",
"Textures": "FloorTopBorder_ForSide",
"Indices": [
3,
2,
1,
0
],
"UVs": [
"0.5, 1",
"0.5, 0",
"0, 0",
"0, 1"
]
},
{
"Type": "RECTANGLE",
"Textures": "FloorTopBorderless_ForSide",
"Indices": [
2,
3,
4,
5
],
"UVs": [
"0, 0",
"0, 1",
"0.5+d3, 1",
"0.5+d3, 0"
]
}
]
}
}
]

View File

@ -0,0 +1,680 @@
[
{
"Type": "NormalBorder",
"BindingDisplayTexture": "NormalBorder.png",
"UnitSize": "Small",
"ExpandType": "Column",
"InitColumnDirection": "PositiveX",
"DefaultSideConfig": {
"UseTwoDTop": false,
"UseTwoDRight": false,
"UseTwoDBottom": false,
"UseTwoDLeft": true,
"UseThreeDTop": true,
"UseThreeDBottom": false
},
"ThreeDTopFace": {
"Vertices": [
"0, 0, 0",
"2.5+d1, 0, 0",
"2.5+d1, 2.5, 0",
"0, 2.5, 0"
],
"Faces": [
{
"Type": "RECTANGLE",
"Textures": "FloorTopFlat",
"Indices": [
0,
1,
2,
3
],
"UVs": [
"0, 0.5",
"0, -d1",
"0.5, -d1",
"0.5, 0.5"
]
}
]
},
"ThreeDBottomFace": {
"Vertices": [
"2.5+d1, 0, -5-d3",
"2.5+d1, 2.5, -5-d3",
"0, 2.5, -5-d3",
"0, 0, -5-d3"
],
"Faces": [
{
"Type": "RECTANGLE",
"Textures": "FloorTopBorderless_ForSide",
"Indices": [
3,
2,
1,
0
],
"UVs": [
"0, 0.5",
"0.5, 0.5",
"0.5, -d1",
"0, -d1"
]
}
]
},
"TwoDTopSide": {
"Vertices": [
"0, 0, -5-d3",
"0, 2.5, -5-d3",
"0, 2.5, 0",
"0, 0, 0"
],
"Faces": [
{
"Type": "RECTANGLE",
"Textures": "FloorTopBorderless_ForSide",
"Indices": [
3,
2,
1,
0
],
"UVs": [
"0, 0.5",
"0, 0",
"1+d3, 0",
"1+d3, 0.5"
]
}
]
},
"TwoDRightSide": {
"Vertices": [
"2.5+d1, 2.5, 0",
"2.5+d1, 2.5, -5-d3",
"0, 2.5, -5-d3",
"0, 2.5, 0"
],
"Faces": [
{
"Type": "RECTANGLE",
"Textures": "FloorTopBorderless_ForSide",
"Indices": [
0,
1,
2,
3
],
"UVs": [
"0, -d1",
"1+d3, -d1",
"1+d3, 0.5",
"0, 0.5"
]
}
]
},
"TwoDBottomSide": {
"Vertices": [
"2.5+d1, 0, -5-d3",
"2.5+d1, 2.5, -5-d3",
"2.5+d1, 2.5, 0",
"2.5+d1, 0, 0"
],
"Faces": [
{
"Type": "RECTANGLE",
"Textures": "FloorTopBorderless_ForSide",
"Indices": [
0,
1,
2,
3
],
"UVs": [
"1+d3, 0.5",
"1+d3, 0",
"0, 0",
"0, 0.5"
]
}
]
},
"TwoDLeftSide": {
"Vertices": [
"2.5+d1, 0, 0",
"2.5+d1, 0, -5-d3",
"0, 0, -5-d3",
"0, 0, 0"
],
"Faces": [
{
"Type": "RECTANGLE",
"Textures": "FloorSide",
"Indices": [
3,
2,
1,
0
],
"UVs": [
"0, 0.5",
"1+d3, 0.5",
"1+d3, -d1",
"0, -d1"
]
}
]
},
"TwoDTopSideExpand": null,
"TwoDRightSideExpand": null,
"TwoDBottomSideExpand": null,
"TwoDLeftSideExpand": {
"Vertices": [
"2.5+d1, 0, -5-d3",
"0, 0, -5-d3",
"0, 0, -2.5",
"2.5+d1, 0, -2.5",
"2.5+d1, 0, 0",
"0, 0, 0"
],
"Faces": [
{
"Type": "RECTANGLE",
"Textures": "FloorTopBorder_ForSide",
"Indices": [
2,
3,
4,
5
],
"UVs": [
"0.5, 0.5",
"0.5, -d1",
"0, -d1",
"0, 0.5"
]
},
{
"Type": "RECTANGLE",
"Textures": "FloorTopBorderless_ForSide",
"Indices": [
3,
2,
1,
0
],
"UVs": [
"0, -d1",
"0, 0.5",
"0.5+d3, 0.5",
"0.5+d3, -d1"
]
}
]
}
},
{
"Type": "NormalOutterCorner",
"BindingDisplayTexture": "NormalOutterCorner.png",
"UnitSize": "Small",
"ExpandType": "Static",
"InitColumnDirection": "PositiveX",
"DefaultSideConfig": {
"UseTwoDTop": true,
"UseTwoDRight": false,
"UseTwoDBottom": false,
"UseTwoDLeft": true,
"UseThreeDTop": true,
"UseThreeDBottom": false
},
"ThreeDTopFace": {
"Vertices": [
"0, 0, 0",
"2.5, 0, 0",
"2.5, 2.5, 0",
"0, 2.5, 0"
],
"Faces": [
{
"Type": "TRIANGLE",
"Textures": "FloorTopFlat",
"Indices": [
0,
1,
2
],
"UVs": [
"0, 0.5",
"0, 1",
"0.5, 1"
]
},
{
"Type": "TRIANGLE",
"Textures": "FloorTopFlat",
"Indices": [
0,
2,
3
],
"UVs": [
"1, 0.5",
"0.5, 1",
"1, 1"
]
}
]
},
"ThreeDBottomFace": {
"Vertices": [
"2.5, 0, -5-d3",
"2.5, 2.5, -5-d3",
"0, 2.5, -5-d3",
"0, 0, -5-d3"
],
"Faces": [
{
"Type": "RECTANGLE",
"Textures": "FloorTopBorderless_ForSide",
"Indices": [
3,
2,
1,
0
],
"UVs": [
"0, 0.5",
"0.5, 0.5",
"0.5, 0",
"0, 0"
]
}
]
},
"TwoDTopSide": {
"Vertices": [
"0, 0, -5-d3",
"0, 0, 0",
"0, 2.5, 0",
"0, 2.5, -5-d3"
],
"Faces": [
{
"Type": "RECTANGLE",
"Textures": "FloorSide",
"Indices": [
0,
1,
2,
3
],
"UVs": [
"1+d3, 0",
"0, 0",
"0, 0.5",
"1+d3, 0.5"
]
}
]
},
"TwoDRightSide": {
"Vertices": [
"2.5, 2.5, -5-d3",
"0, 2.5, -5-d3",
"0, 2.5, 0",
"2.5, 2.5, 0"
],
"Faces": [
{
"Type": "RECTANGLE",
"Textures": "FloorTopBorderless_ForSide",
"Indices": [
0,
1,
2,
3
],
"UVs": [
"1+d3, 0",
"1+d3, 0.5",
"0, 0.5",
"0, 0"
]
}
]
},
"TwoDBottomSide": {
"Vertices": [
"2.5, 0, -5-d3",
"2.5, 2.5, -5-d3",
"2.5, 2.5, 0",
"2.5, 0, 0"
],
"Faces": [
{
"Type": "RECTANGLE",
"Textures": "FloorTopBorderless_ForSide",
"Indices": [
0,
1,
2,
3
],
"UVs": [
"1+d3, 0",
"1+d3, 0.5",
"0, 0.5",
"0, 0"
]
}
]
},
"TwoDLeftSide": {
"Vertices": [
"2.5, 0, 0",
"2.5, 0, -5-d3",
"0, 0, -5-d3",
"0, 0, 0"
],
"Faces": [
{
"Type": "RECTANGLE",
"Textures": "FloorSide",
"Indices": [
3,
2,
1,
0
],
"UVs": [
"0, 0.5",
"1+d3, 0.5",
"1+d3, 0",
"0, 0"
]
}
]
},
"TwoDTopSideExpand": {
"Vertices": [
"0, 0, -5-d3",
"0, 2.5, -5-d3",
"0, 2.5, -2.5",
"0, 0, -2.5",
"0, 0, 0",
"0, 2.5, 0"
],
"Faces": [
{
"Type": "RECTANGLE",
"Textures": "FloorTopBorder_ForSide",
"Indices": [
2,
3,
4,
5
],
"UVs": [
"0.5, 0.5",
"0.5, 0",
"0, 0",
"0, 0.5"
]
},
{
"Type": "RECTANGLE",
"Textures": "FloorTopBorderless_ForSide",
"Indices": [
3,
2,
1,
0
],
"UVs": [
"0, 0",
"0, 0.5",
"0.5+d3, 0.5",
"0.5+d3, 0"
]
}
]
},
"TwoDRightSideExpand": null,
"TwoDBottomSideExpand": null,
"TwoDLeftSideExpand": {
"Vertices": [
"2.5, 0, -5-d3",
"0, 0, -5-d3",
"0, 0, -2.5",
"2.5, 0, -2.5",
"2.5, 0, 0",
"0, 0, 0"
],
"Faces": [
{
"Type": "RECTANGLE",
"Textures": "FloorTopBorder_ForSide",
"Indices": [
2,
3,
4,
5
],
"UVs": [
"0.5, 0.5",
"0.5, 0",
"0, 0",
"0, 0.5"
]
},
{
"Type": "RECTANGLE",
"Textures": "FloorTopBorderless_ForSide",
"Indices": [
3,
2,
1,
0
],
"UVs": [
"0, 0",
"0, 0.5",
"0.5+d3, 0.5",
"0.5+d3, 0"
]
}
]
}
},
{
"Type": "NormalInnerCorner",
"BindingDisplayTexture": "NormalInnerCorner.png",
"UnitSize": "Small",
"ExpandType": "Static",
"InitColumnDirection": "PositiveX",
"DefaultSideConfig": {
"UseTwoDTop": false,
"UseTwoDRight": false,
"UseTwoDBottom": false,
"UseTwoDLeft": false,
"UseThreeDTop": true,
"UseThreeDBottom": false
},
"ThreeDTopFace": {
"Vertices": [
"2.5, 0, 0",
"2.5, 2.5, 0",
"0, 0, 0",
"0, 2.5, 0"
],
"Faces": [
{
"Type": "TRIANGLE",
"Textures": "FloorTopFlat",
"Indices": [
0,
1,
2
],
"UVs": [
"0.5, 1",
"0, 1",
"0.5, 0.5"
]
},
{
"Type": "TRIANGLE",
"Textures": "FloorTopFlat",
"Indices": [
3,
2,
1
],
"UVs": [
"0.5, 1",
"0.5, 0.5",
"1, 1"
]
}
]
},
"ThreeDBottomFace": {
"Vertices": [
"2.5, 0, -5-d3",
"2.5, 2.5, -5-d3",
"0, 2.5, -5-d3",
"0, 0, -5-d3"
],
"Faces": [
{
"Type": "RECTANGLE",
"Textures": "FloorTopBorderless_ForSide",
"Indices": [
3,
2,
1,
0
],
"UVs": [
"0, 0.5",
"0.5, 0.5",
"0.5, 0",
"0, 0"
]
}
]
},
"TwoDTopSide": {
"Vertices": [
"0, 0, 0",
"0, 2.5, 0",
"0, 2.5, -5-d3",
"0, 0, -5-d3"
],
"Faces": [
{
"Type": "RECTANGLE",
"Textures": "FloorTopBorderless_ForSide",
"Indices": [
0,
1,
2,
3
],
"UVs": [
"0, 0",
"0, 0.5",
"1+d3, 0.5",
"1+d3, 0"
]
}
]
},
"TwoDRightSide": {
"Vertices": [
"2.5, 2.5, 0",
"2.5, 2.5, -5-d3",
"0, 2.5, -5-d3",
"0, 2.5, 0"
],
"Faces": [
{
"Type": "RECTANGLE",
"Textures": "FloorTopBorderless_ForSide",
"Indices": [
0,
1,
2,
3
],
"UVs": [
"0, 0",
"1+d3, 0",
"1+d3, 0.5",
"0, 0.5"
]
}
]
},
"TwoDBottomSide": {
"Vertices": [
"2.5, 2.5, 0",
"2.5, 0, 0",
"2.5, 0, -5-d3",
"2.5, 2.5, -5-d3"
],
"Faces": [
{
"Type": "RECTANGLE",
"Textures": "FloorTopBorderless_ForSide",
"Indices": [
0,
1,
2,
3
],
"UVs": [
"0, 0.5",
"0, 0",
"1+d3, 0",
"1+d3, 0.5"
]
}
]
},
"TwoDLeftSide": {
"Vertices": [
"0, 0, 0",
"0, 0, -5-d3",
"2.5, 0, -5-d3",
"2.5, 0, 0"
],
"Faces": [
{
"Type": "RECTANGLE",
"Textures": "FloorTopBorderless_ForSide",
"Indices": [
0,
1,
2,
3
],
"UVs": [
"0, 0.5",
"1+d3, 0.5",
"1+d3, 0",
"0, 0"
]
}
]
},
"TwoDTopSideExpand": null,
"TwoDRightSideExpand": null,
"TwoDBottomSideExpand": null,
"TwoDLeftSideExpand": null
}
]

View File

@ -0,0 +1,493 @@
[
{
"Type": "RibbonBorder",
"BindingDisplayTexture": "RibbonBorder.png",
"UnitSize": "Small",
"ExpandType": "Column",
"InitColumnDirection": "PositiveX",
"DefaultSideConfig": {
"UseTwoDTop": false,
"UseTwoDRight": false,
"UseTwoDBottom": false,
"UseTwoDLeft": true,
"UseThreeDTop": true,
"UseThreeDBottom": false
},
"ThreeDTopFace": {
"Vertices": [
"2.5+d1, 0, 0",
"2.5+d1, 2.5, -0.7",
"0, 2.5, -0.7",
"0, 0, 0"
],
"Faces": [
{
"Type": "RECTANGLE",
"Textures": "FloorTopFlat",
"Indices": [
0,
1,
2,
3
],
"UVs": [
"0, -d1",
"1, -d1",
"1, 0.5",
"0, 0.5"
]
}
]
},
"ThreeDBottomFace": {
"Vertices": [
"2.5+d1, 0, -5-d3",
"2.5+d1, 2.5, -5-d3",
"0, 2.5, -5-d3",
"0, 0, -5-d3"
],
"Faces": [
{
"Type": "RECTANGLE",
"Textures": "FloorTopBorderless_ForSide",
"Indices": [
3,
2,
1,
0
],
"UVs": [
"0, 0.5",
"0.5, 0.5",
"0.5, -d1",
"0, -d1"
]
}
]
},
"TwoDTopSide": {
"Vertices": [
"0, 0, -5-d3",
"0, 0, 0",
"0, 2.5, -0.7",
"0, 2.5, -5-d3"
],
"Faces": [
{
"Type": "RECTANGLE",
"Textures": "FloorTopBorderless_ForSide",
"Indices": [
0,
1,
2,
3
],
"UVs": [
"1+d3, 0",
"0, 0",
"0.14, 0.5",
"1+d3, 0.5"
]
}
]
},
"TwoDRightSide": {
"Vertices": [
"2.5+d1, 2.5, -0.7",
"2.5+d1, 2.5, -5-d3",
"0, 2.5, -5-d3",
"0, 2.5, -0.7"
],
"Faces": [
{
"Type": "RECTANGLE",
"Textures": "FloorTopBorderless_ForSide",
"Indices": [
0,
1,
2,
3
],
"UVs": [
"0.14, -d1",
"1+d3, -d1",
"1+d3, 0.5",
"0.14, 0.5"
]
}
]
},
"TwoDBottomSide": {
"Vertices": [
"2.5+d1, 0, 0",
"2.5+d1, 0, -5-d3",
"2.5+d1, 2.5, -5-d3",
"2.5+d1, 2.5, -0.7"
],
"Faces": [
{
"Type": "RECTANGLE",
"Textures": "FloorTopBorderless_ForSide",
"Indices": [
0,
1,
2,
3
],
"UVs": [
"0, 0",
"1+d3, 0",
"1+d3, 0.5",
"0.14, 0.5"
]
}
]
},
"TwoDLeftSide": {
"Vertices": [
"2.5+d1, 0, 0",
"2.5+d1, 0, -5-d3",
"0, 0, -5-d3",
"0, 0, 0"
],
"Faces": [
{
"Type": "RECTANGLE",
"Textures": "FloorSide",
"Indices": [
3,
2,
1,
0
],
"UVs": [
"0, 0.5",
"1+d3, 0.5",
"1+d3, -d1",
"0, -d1"
]
}
]
},
"TwoDTopSideExpand": null,
"TwoDRightSideExpand": null,
"TwoDBottomSideExpand": null,
"TwoDLeftSideExpand": {
"Vertices": [
"2.5+d1, 0, -5-d3",
"0, 0, -5-d3",
"0, 0, -2.5",
"2.5+d1, 0, -2.5",
"2.5+d1, 0, 0",
"0, 0, 0"
],
"Faces": [
{
"Type": "RECTANGLE",
"Textures": "FloorTopBorder_ForSide",
"Indices": [
2,
3,
4,
5
],
"UVs": [
"0.5, 0.5",
"0.5, -d1",
"0, -d1",
"0, 0.5"
]
},
{
"Type": "RECTANGLE",
"Textures": "FloorTopBorderless_ForSide",
"Indices": [
3,
2,
1,
0
],
"UVs": [
"0, -d1",
"0, 0.5",
"0.5+d3, 0.5",
"0.5+d3, -d1"
]
}
]
}
},
{
"Type": "RibbonOutterCorner",
"BindingDisplayTexture": "RibbonOutterCorner.png",
"UnitSize": "Small",
"ExpandType": "Static",
"InitColumnDirection": "PositiveX",
"DefaultSideConfig": {
"UseTwoDTop": true,
"UseTwoDRight": false,
"UseTwoDBottom": false,
"UseTwoDLeft": true,
"UseThreeDTop": true,
"UseThreeDBottom": false
},
"ThreeDTopFace": {
"Vertices": [
"0, 0, 0",
"2.5, 0, 0",
"2.5, 2.5, -0.7",
"0, 2.5, 0"
],
"Faces": [
{
"Type": "TRIANGLE",
"Textures": "FloorTopFlat",
"Indices": [
0,
1,
2
],
"UVs": [
"0, 0",
"0, 1",
"1, 1"
]
},
{
"Type": "TRIANGLE",
"Textures": "FloorTopFlat",
"Indices": [
0,
2,
3
],
"UVs": [
"1, 0",
"0, 1",
"1, 1"
]
}
]
},
"ThreeDBottomFace": {
"Vertices": [
"2.5, 0, -5-d3",
"2.5, 2.5, -5-d3",
"0, 2.5, -5-d3",
"0, 0, -5-d3"
],
"Faces": [
{
"Type": "RECTANGLE",
"Textures": "FloorTopBorderless_ForSide",
"Indices": [
3,
2,
1,
0
],
"UVs": [
"0, 0.5",
"0.5, 0.5",
"0.5, 0",
"0, 0"
]
}
]
},
"TwoDTopSide": {
"Vertices": [
"0, 0, -5-d3",
"0, 0, 0",
"0, 2.5, 0",
"0, 2.5, -5-d3"
],
"Faces": [
{
"Type": "RECTANGLE",
"Textures": "FloorSide",
"Indices": [
0,
1,
2,
3
],
"UVs": [
"1+d3, 0",
"0, 0",
"0, 0.5",
"1+d3, 0.5"
]
}
]
},
"TwoDRightSide": {
"Vertices": [
"2.5, 2.5, -5-d3",
"0, 2.5, -5-d3",
"0, 2.5, 0",
"2.5, 2.5, -0.7"
],
"Faces": [
{
"Type": "RECTANGLE",
"Textures": "FloorTopBorderless_ForSide",
"Indices": [
0,
1,
2,
3
],
"UVs": [
"1+d3, 0",
"1+d3, 0.5",
"0, 0.5",
"0.14, 0"
]
}
]
},
"TwoDBottomSide": {
"Vertices": [
"2.5, 0, -5-d3",
"2.5, 2.5, -5-d3",
"2.5, 2.5, -0.7",
"2.5, 0, 0"
],
"Faces": [
{
"Type": "RECTANGLE",
"Textures": "FloorTopBorderless_ForSide",
"Indices": [
0,
1,
2,
3
],
"UVs": [
"1+d3, 0",
"1+d3, 0.5",
"0.14, 0.5",
"0, 0"
]
}
]
},
"TwoDLeftSide": {
"Vertices": [
"2.5, 0, 0",
"2.5, 0, -5-d3",
"0, 0, -5-d3",
"0, 0, 0"
],
"Faces": [
{
"Type": "RECTANGLE",
"Textures": "FloorSide",
"Indices": [
3,
2,
1,
0
],
"UVs": [
"0, 0.5",
"1+d3, 0.5",
"1+d3, 0",
"0, 0"
]
}
]
},
"TwoDTopSideExpand": {
"Vertices": [
"0, 0, -5-d3",
"0, 2.5, -5-d3",
"0, 2.5, -2.5",
"0, 0, -2.5",
"0, 0, 0",
"0, 2.5, 0"
],
"Faces": [
{
"Type": "RECTANGLE",
"Textures": "FloorTopBorder_ForSide",
"Indices": [
2,
3,
4,
5
],
"UVs": [
"0.5, 0.5",
"0.5, 0",
"0, 0",
"0, 0.5"
]
},
{
"Type": "RECTANGLE",
"Textures": "FloorTopBorderless_ForSide",
"Indices": [
3,
2,
1,
0
],
"UVs": [
"0, 0",
"0, 0.5",
"0.5+d3, 0.5",
"0.5+d3, 0"
]
}
]
},
"TwoDRightSideExpand": null,
"TwoDBottomSideExpand": null,
"TwoDLeftSideExpand": {
"Vertices": [
"2.5, 0, -5-d3",
"0, 0, -5-d3",
"0, 0, -2.5",
"2.5, 0, -2.5",
"2.5, 0, 0",
"0, 0, 0"
],
"Faces": [
{
"Type": "RECTANGLE",
"Textures": "FloorTopBorder_ForSide",
"Indices": [
2,
3,
4,
5
],
"UVs": [
"0.5, 0.5",
"0.5, 0",
"0, 0",
"0, 0.5"
]
},
{
"Type": "RECTANGLE",
"Textures": "FloorTopBorderless_ForSide",
"Indices": [
3,
2,
1,
0
],
"UVs": [
"0, 0",
"0, 0.5",
"0.5+d3, 0.5",
"0.5+d3, 0"
]
}
]
}
}
]

View File

@ -0,0 +1,680 @@
[
{
"Type": "SinkBorder",
"BindingDisplayTexture": "SinkBorder.png",
"UnitSize": "Small",
"ExpandType": "Column",
"InitColumnDirection": "PositiveX",
"DefaultSideConfig": {
"UseTwoDTop": false,
"UseTwoDRight": false,
"UseTwoDBottom": false,
"UseTwoDLeft": true,
"UseThreeDTop": true,
"UseThreeDBottom": false
},
"ThreeDTopFace": {
"Vertices": [
"2.5+d1, 0, 0",
"2.5+d1, 2.5, -0.7",
"0, 2.5, -0.7",
"0, 0, 0"
],
"Faces": [
{
"Type": "RECTANGLE",
"Textures": "FloorTopProfil",
"Indices": [
0,
1,
2,
3
],
"UVs": [
"0, -d1",
"0.5, -d1",
"0.5, 0.5",
"0, 0.5"
]
}
]
},
"ThreeDBottomFace": {
"Vertices": [
"2.5+d1, 0, -5-d3",
"2.5+d1, 2.5, -5-d3",
"0, 2.5, -5-d3",
"0, 0, -5-d3"
],
"Faces": [
{
"Type": "RECTANGLE",
"Textures": "FloorTopBorderless_ForSide",
"Indices": [
3,
2,
1,
0
],
"UVs": [
"0, 0.5",
"0.5, 0.5",
"0.5, -d1",
"0, -d1"
]
}
]
},
"TwoDTopSide": {
"Vertices": [
"0, 0, -5-d3",
"0, 0, 0",
"0, 2.5, -0.7",
"0, 2.5, -5-d3"
],
"Faces": [
{
"Type": "RECTANGLE",
"Textures": "FloorTopBorderless_ForSide",
"Indices": [
0,
1,
2,
3
],
"UVs": [
"1+d3, 0",
"0, 0",
"0.14, 0.5",
"1+d3, 0.5"
]
}
]
},
"TwoDRightSide": {
"Vertices": [
"2.5+d1, 2.5, -0.7",
"2.5+d1, 2.5, -5-d3",
"0, 2.5, -5-d3",
"0, 2.5, -0.7"
],
"Faces": [
{
"Type": "RECTANGLE",
"Textures": "FloorTopBorderless_ForSide",
"Indices": [
0,
1,
2,
3
],
"UVs": [
"0.14, -d1",
"1+d3, -d1",
"1+d3, 0.5",
"0.14, 0.5"
]
}
]
},
"TwoDBottomSide": {
"Vertices": [
"2.5+d1, 0, 0",
"2.5+d1, 0, -5-d3",
"2.5+d1, 2.5, -5-d3",
"2.5+d1, 2.5, -0.7"
],
"Faces": [
{
"Type": "RECTANGLE",
"Textures": "FloorTopBorderless_ForSide",
"Indices": [
0,
1,
2,
3
],
"UVs": [
"0, 0",
"1+d3, 0",
"1+d3, 0.5",
"0.14, 0.5"
]
}
]
},
"TwoDLeftSide": {
"Vertices": [
"2.5+d1, 0, 0",
"2.5+d1, 0, -5-d3",
"0, 0, -5-d3",
"0, 0, 0"
],
"Faces": [
{
"Type": "RECTANGLE",
"Textures": "FloorSide",
"Indices": [
3,
2,
1,
0
],
"UVs": [
"0, 0.5",
"1+d3, 0.5",
"1+d3, -d1",
"0, -d1"
]
}
]
},
"TwoDTopSideExpand": null,
"TwoDRightSideExpand": null,
"TwoDBottomSideExpand": null,
"TwoDLeftSideExpand": {
"Vertices": [
"2.5+d1, 0, -5-d3",
"0, 0, -5-d3",
"0, 0, -2.5",
"2.5+d1, 0, -2.5",
"2.5+d1, 0, 0",
"0, 0, 0"
],
"Faces": [
{
"Type": "RECTANGLE",
"Textures": "FloorTopBorder_ForSide",
"Indices": [
2,
3,
4,
5
],
"UVs": [
"0.5, 0.5",
"0.5, -d1",
"0, -d1",
"0, 0.5"
]
},
{
"Type": "RECTANGLE",
"Textures": "FloorTopBorderless_ForSide",
"Indices": [
3,
2,
1,
0
],
"UVs": [
"0, -d1",
"0, 0.5",
"0.5+d3, 0.5",
"0.5+d3, -d1"
]
}
]
}
},
{
"Type": "SinkOutterCorner",
"BindingDisplayTexture": "SinkOutterCorner.png",
"UnitSize": "Small",
"ExpandType": "Static",
"InitColumnDirection": "PositiveX",
"DefaultSideConfig": {
"UseTwoDTop": true,
"UseTwoDRight": false,
"UseTwoDBottom": false,
"UseTwoDLeft": true,
"UseThreeDTop": true,
"UseThreeDBottom": false
},
"ThreeDTopFace": {
"Vertices": [
"0, 0, 0",
"2.5, 0, 0",
"2.5, 2.5, -0.7",
"0, 2.5, 0"
],
"Faces": [
{
"Type": "TRIANGLE",
"Textures": "FloorTopProfil",
"Indices": [
0,
1,
2
],
"UVs": [
"0, 0.5",
"0, 1",
"0.5, 1"
]
},
{
"Type": "TRIANGLE",
"Textures": "FloorTopProfil",
"Indices": [
0,
2,
3
],
"UVs": [
"1, 0.5",
"0.5, 1",
"1, 1"
]
}
]
},
"ThreeDBottomFace": {
"Vertices": [
"2.5, 0, -5-d3",
"2.5, 2.5, -5-d3",
"0, 2.5, -5-d3",
"0, 0, -5-d3"
],
"Faces": [
{
"Type": "RECTANGLE",
"Textures": "FloorTopBorderless_ForSide",
"Indices": [
3,
2,
1,
0
],
"UVs": [
"0, 0.5",
"0.5, 0.5",
"0.5, 0",
"0, 0"
]
}
]
},
"TwoDTopSide": {
"Vertices": [
"0, 0, -5-d3",
"0, 0, 0",
"0, 2.5, 0",
"0, 2.5, -5-d3"
],
"Faces": [
{
"Type": "RECTANGLE",
"Textures": "FloorSide",
"Indices": [
0,
1,
2,
3
],
"UVs": [
"1+d3, 0",
"0, 0",
"0, 0.5",
"1+d3, 0.5"
]
}
]
},
"TwoDRightSide": {
"Vertices": [
"2.5, 2.5, -5-d3",
"0, 2.5, -5-d3",
"0, 2.5, 0",
"2.5, 2.5, -0.7"
],
"Faces": [
{
"Type": "RECTANGLE",
"Textures": "FloorTopBorderless_ForSide",
"Indices": [
0,
1,
2,
3
],
"UVs": [
"1+d3, 0",
"1+d3, 0.5",
"0, 0.5",
"0.14, 0"
]
}
]
},
"TwoDBottomSide": {
"Vertices": [
"2.5, 0, -5-d3",
"2.5, 2.5, -5-d3",
"2.5, 2.5, -0.7",
"2.5, 0, 0"
],
"Faces": [
{
"Type": "RECTANGLE",
"Textures": "FloorTopBorderless_ForSide",
"Indices": [
0,
1,
2,
3
],
"UVs": [
"1+d3, 0",
"1+d3, 0.5",
"0.14, 0.5",
"0, 0"
]
}
]
},
"TwoDLeftSide": {
"Vertices": [
"2.5, 0, 0",
"2.5, 0, -5-d3",
"0, 0, -5-d3",
"0, 0, 0"
],
"Faces": [
{
"Type": "RECTANGLE",
"Textures": "FloorSide",
"Indices": [
3,
2,
1,
0
],
"UVs": [
"0, 0.5",
"1+d3, 0.5",
"1+d3, 0",
"0, 0"
]
}
]
},
"TwoDTopSideExpand": {
"Vertices": [
"0, 0, -5-d3",
"0, 2.5, -5-d3",
"0, 2.5, -2.5",
"0, 0, -2.5",
"0, 0, 0",
"0, 2.5, 0"
],
"Faces": [
{
"Type": "RECTANGLE",
"Textures": "FloorTopBorder_ForSide",
"Indices": [
2,
3,
4,
5
],
"UVs": [
"0.5, 0.5",
"0.5, 0",
"0, 0",
"0, 0.5"
]
},
{
"Type": "RECTANGLE",
"Textures": "FloorTopBorderless_ForSide",
"Indices": [
3,
2,
1,
0
],
"UVs": [
"0, 0",
"0, 0.5",
"0.5+d3, 0.5",
"0.5+d3, 0"
]
}
]
},
"TwoDRightSideExpand": null,
"TwoDBottomSideExpand": null,
"TwoDLeftSideExpand": {
"Vertices": [
"2.5, 0, -5-d3",
"0, 0, -5-d3",
"0, 0, -2.5",
"2.5, 0, -2.5",
"2.5, 0, 0",
"0, 0, 0"
],
"Faces": [
{
"Type": "RECTANGLE",
"Textures": "FloorTopBorder_ForSide",
"Indices": [
2,
3,
4,
5
],
"UVs": [
"0.5, 0.5",
"0.5, 0",
"0, 0",
"0, 0.5"
]
},
{
"Type": "RECTANGLE",
"Textures": "FloorTopBorderless_ForSide",
"Indices": [
3,
2,
1,
0
],
"UVs": [
"0, 0",
"0, 0.5",
"0.5+d3, 0.5",
"0.5+d3, 0"
]
}
]
}
},
{
"Type": "SinkInnerCorner",
"BindingDisplayTexture": "SinkInnerCorner.png",
"UnitSize": "Small",
"ExpandType": "Static",
"InitColumnDirection": "PositiveX",
"DefaultSideConfig": {
"UseTwoDTop": false,
"UseTwoDRight": false,
"UseTwoDBottom": false,
"UseTwoDLeft": false,
"UseThreeDTop": true,
"UseThreeDBottom": false
},
"ThreeDTopFace": {
"Vertices": [
"2.5, 0, -0.7",
"2.5, 2.5, 0",
"0, 0, -0.7",
"0, 2.5, -0.7"
],
"Faces": [
{
"Type": "TRIANGLE",
"Textures": "FloorTopProfil",
"Indices": [
0,
1,
2
],
"UVs": [
"0.5, 1",
"0, 1",
"0.5, 0.5"
]
},
{
"Type": "TRIANGLE",
"Textures": "FloorTopProfil",
"Indices": [
3,
2,
1
],
"UVs": [
"0.5, 1",
"0.5, 0.5",
"1, 1"
]
}
]
},
"ThreeDBottomFace": {
"Vertices": [
"2.5, 0, -5-d3",
"2.5, 2.5, -5-d3",
"0, 2.5, -5-d3",
"0, 0, -5-d3"
],
"Faces": [
{
"Type": "RECTANGLE",
"Textures": "FloorTopBorderless_ForSide",
"Indices": [
3,
2,
1,
0
],
"UVs": [
"0, 0.5",
"0.5, 0.5",
"0.5, 0",
"0, 0"
]
}
]
},
"TwoDTopSide": {
"Vertices": [
"0, 0, -0.7",
"0, 2.5, -0.7",
"0, 2.5, -5-d3",
"0, 0, -5-d3"
],
"Faces": [
{
"Type": "RECTANGLE",
"Textures": "FloorTopBorderless_ForSide",
"Indices": [
0,
1,
2,
3
],
"UVs": [
"0.14, 0",
"0.14, 0.5",
"1+d3, 0.5",
"1+d3, 0"
]
}
]
},
"TwoDRightSide": {
"Vertices": [
"2.5, 2.5, 0",
"2.5, 2.5, -5-d3",
"0, 2.5, -5-d3",
"0, 2.5, -0.7"
],
"Faces": [
{
"Type": "RECTANGLE",
"Textures": "FloorTopBorderless_ForSide",
"Indices": [
0,
1,
2,
3
],
"UVs": [
"0, 0",
"1+d3, 0",
"1+d3, 0.5",
"0.14, 0.5"
]
}
]
},
"TwoDBottomSide": {
"Vertices": [
"2.5, 2.5, 0",
"2.5, 0, -0.7",
"2.5, 0, -5-d3",
"2.5, 2.5, -5-d3"
],
"Faces": [
{
"Type": "RECTANGLE",
"Textures": "FloorTopBorderless_ForSide",
"Indices": [
0,
1,
2,
3
],
"UVs": [
"0, 0.5",
"0.14, 0",
"1+d3, 0",
"1+d3, 0.5"
]
}
]
},
"TwoDLeftSide": {
"Vertices": [
"0, 0, -0.7",
"0, 0, -5-d3",
"2.5, 0, -5-d3",
"2.5, 0, -0.7"
],
"Faces": [
{
"Type": "RECTANGLE",
"Textures": "FloorTopBorderless_ForSide",
"Indices": [
0,
1,
2,
3
],
"UVs": [
"0.14, 0.5",
"1+d3, 0.5",
"1+d3, 0",
"0.14, 0"
]
}
]
},
"TwoDTopSideExpand": null,
"TwoDRightSideExpand": null,
"TwoDBottomSideExpand": null,
"TwoDLeftSideExpand": null
}
]

View File

@ -0,0 +1,827 @@
[
{
"Type": "WoodTrafo",
"BindingDisplayTexture": "WoodTrafo.png",
"UnitSize": "Large",
"ExpandType": "Static",
"InitColumnDirection": "PositiveX",
"DefaultSideConfig": {
"UseTwoDTop": true,
"UseTwoDRight": true,
"UseTwoDBottom": true,
"UseTwoDLeft": true,
"UseThreeDTop": true,
"UseThreeDBottom": true
},
"ThreeDTopFace": {
"Vertices": [
"0, 5, 0",
"0, 0, 0",
"5, 0, 0",
"5, 5, 0"
],
"Faces": [
{
"Type": "RECTANGLE",
"Textures": "BallWood",
"Indices": [
0,
1,
2,
3
],
"UVs": [
"0, 0",
"1, 0",
"1, 1",
"0, 1"
]
}
]
},
"ThreeDBottomFace": {
"Vertices": [
"0, 0, -5-d3",
"0, 5, -5-d3",
"5, 5, -5-d3",
"5, 0, -5-d3"
],
"Faces": [
{
"Type": "RECTANGLE",
"Textures": "BallWood",
"Indices": [
0,
1,
2,
3
],
"UVs": [
"0, 0",
"1, 0",
"1, 1",
"0, 1"
]
}
]
},
"TwoDTopSide": {
"Vertices": [
"0, 5, -5-d3",
"0, 0, -5-d3",
"0, 0, 0",
"0, 5, 0"
],
"Faces": [
{
"Type": "RECTANGLE",
"Textures": "BallWood",
"Indices": [
0,
1,
2,
3
],
"UVs": [
"0, -d3",
"1, -d3",
"1, 1",
"0, 1"
]
}
]
},
"TwoDRightSide": {
"Vertices": [
"5, 5, -5-d3",
"0, 5, -5-d3",
"0, 5, 0",
"5, 5, 0"
],
"Faces": [
{
"Type": "RECTANGLE",
"Textures": "BallWood",
"Indices": [
0,
1,
2,
3
],
"UVs": [
"0, -d3",
"1, -d3",
"1, 1",
"0, 1"
]
}
]
},
"TwoDBottomSide": {
"Vertices": [
"5, 0, -5-d3",
"5, 5, -5-d3",
"5, 5, 0",
"5, 0, 0"
],
"Faces": [
{
"Type": "RECTANGLE",
"Textures": "BallWood",
"Indices": [
0,
1,
2,
3
],
"UVs": [
"0, -d3",
"1, -d3",
"1, 1",
"0, 1"
]
}
]
},
"TwoDLeftSide": {
"Vertices": [
"0, 0, -5-d3",
"5, 0, -5-d3",
"5, 0, 0",
"0, 0, 0"
],
"Faces": [
{
"Type": "RECTANGLE",
"Textures": "BallWood",
"Indices": [
0,
1,
2,
3
],
"UVs": [
"0, -d3",
"1, -d3",
"1, 1",
"0, 1"
]
}
]
},
"TwoDTopSideExpand": {
"Vertices": [
"0, 5, -5-d3",
"0, 0, -5-d3",
"0, 0, 0",
"0, 5, 0"
],
"Faces": [
{
"Type": "RECTANGLE",
"Textures": "BallWood",
"Indices": [
0,
1,
2,
3
],
"UVs": [
"1+d3, 0",
"1+d3, 1",
"0, 1",
"0, 0"
]
}
]
},
"TwoDRightSideExpand": {
"Vertices": [
"5, 5, -5-d3",
"0, 5, -5-d3",
"0, 5, 0",
"5, 5, 0"
],
"Faces": [
{
"Type": "RECTANGLE",
"Textures": "BallWood",
"Indices": [
0,
1,
2,
3
],
"UVs": [
"1+d3, 0",
"1+d3, 1",
"0, 1",
"0, 0"
]
}
]
},
"TwoDBottomSideExpand": {
"Vertices": [
"5, 0, -5-d3",
"5, 5, -5-d3",
"5, 5, 0",
"5, 0, 0"
],
"Faces": [
{
"Type": "RECTANGLE",
"Textures": "BallWood",
"Indices": [
0,
1,
2,
3
],
"UVs": [
"1+d3, 0",
"1+d3, 1",
"0, 1",
"0, 0"
]
}
]
},
"TwoDLeftSideExpand": {
"Vertices": [
"0, 0, -5-d3",
"5, 0, -5-d3",
"5, 0, 0",
"0, 0, 0"
],
"Faces": [
{
"Type": "RECTANGLE",
"Textures": "BallWood",
"Indices": [
0,
1,
2,
3
],
"UVs": [
"1+d3, 0",
"1+d3, 1",
"0, 1",
"0, 0"
]
}
]
}
},
{
"Type": "StoneTrafo",
"BindingDisplayTexture": "StoneTrafo.png",
"UnitSize": "Large",
"ExpandType": "Static",
"InitColumnDirection": "PositiveX",
"DefaultSideConfig": {
"UseTwoDTop": true,
"UseTwoDRight": true,
"UseTwoDBottom": true,
"UseTwoDLeft": true,
"UseThreeDTop": true,
"UseThreeDBottom": true
},
"ThreeDTopFace": {
"Vertices": [
"0, 5, 0",
"0, 0, 0",
"5, 0, 0",
"5, 5, 0"
],
"Faces": [
{
"Type": "RECTANGLE",
"Textures": "BallStone",
"Indices": [
0,
1,
2,
3
],
"UVs": [
"0, 0",
"1, 0",
"1, 1",
"0, 1"
]
}
]
},
"ThreeDBottomFace": {
"Vertices": [
"0, 0, -5-d3",
"0, 5, -5-d3",
"5, 5, -5-d3",
"5, 0, -5-d3"
],
"Faces": [
{
"Type": "RECTANGLE",
"Textures": "BallStone",
"Indices": [
0,
1,
2,
3
],
"UVs": [
"0, 0",
"1, 0",
"1, 1",
"0, 1"
]
}
]
},
"TwoDTopSide": {
"Vertices": [
"0, 5, -5-d3",
"0, 0, -5-d3",
"0, 0, 0",
"0, 5, 0"
],
"Faces": [
{
"Type": "RECTANGLE",
"Textures": "BallStone",
"Indices": [
0,
1,
2,
3
],
"UVs": [
"0, -d3",
"1, -d3",
"1, 1",
"0, 1"
]
}
]
},
"TwoDRightSide": {
"Vertices": [
"5, 5, -5-d3",
"0, 5, -5-d3",
"0, 5, 0",
"5, 5, 0"
],
"Faces": [
{
"Type": "RECTANGLE",
"Textures": "BallStone",
"Indices": [
0,
1,
2,
3
],
"UVs": [
"0, -d3",
"1, -d3",
"1, 1",
"0, 1"
]
}
]
},
"TwoDBottomSide": {
"Vertices": [
"5, 0, -5-d3",
"5, 5, -5-d3",
"5, 5, 0",
"5, 0, 0"
],
"Faces": [
{
"Type": "RECTANGLE",
"Textures": "BallStone",
"Indices": [
0,
1,
2,
3
],
"UVs": [
"0, -d3",
"1, -d3",
"1, 1",
"0, 1"
]
}
]
},
"TwoDLeftSide": {
"Vertices": [
"0, 0, -5-d3",
"5, 0, -5-d3",
"5, 0, 0",
"0, 0, 0"
],
"Faces": [
{
"Type": "RECTANGLE",
"Textures": "BallStone",
"Indices": [
0,
1,
2,
3
],
"UVs": [
"0, -d3",
"1, -d3",
"1, 1",
"0, 1"
]
}
]
},
"TwoDTopSideExpand": {
"Vertices": [
"0, 5, -5-d3",
"0, 0, -5-d3",
"0, 0, 0",
"0, 5, 0"
],
"Faces": [
{
"Type": "RECTANGLE",
"Textures": "BallStone",
"Indices": [
0,
1,
2,
3
],
"UVs": [
"1+d3, 0",
"1+d3, 1",
"0, 1",
"0, 0"
]
}
]
},
"TwoDRightSideExpand": {
"Vertices": [
"5, 5, -5-d3",
"0, 5, -5-d3",
"0, 5, 0",
"5, 5, 0"
],
"Faces": [
{
"Type": "RECTANGLE",
"Textures": "BallStone",
"Indices": [
0,
1,
2,
3
],
"UVs": [
"1+d3, 0",
"1+d3, 1",
"0, 1",
"0, 0"
]
}
]
},
"TwoDBottomSideExpand": {
"Vertices": [
"5, 0, -5-d3",
"5, 5, -5-d3",
"5, 5, 0",
"5, 0, 0"
],
"Faces": [
{
"Type": "RECTANGLE",
"Textures": "BallStone",
"Indices": [
0,
1,
2,
3
],
"UVs": [
"1+d3, 0",
"1+d3, 1",
"0, 1",
"0, 0"
]
}
]
},
"TwoDLeftSideExpand": {
"Vertices": [
"0, 0, -5-d3",
"5, 0, -5-d3",
"5, 0, 0",
"0, 0, 0"
],
"Faces": [
{
"Type": "RECTANGLE",
"Textures": "BallStone",
"Indices": [
0,
1,
2,
3
],
"UVs": [
"1+d3, 0",
"1+d3, 1",
"0, 1",
"0, 0"
]
}
]
}
},
{
"Type": "PaperTrafo",
"BindingDisplayTexture": "PaperTrafo.png",
"UnitSize": "Large",
"ExpandType": "Static",
"InitColumnDirection": "PositiveX",
"DefaultSideConfig": {
"UseTwoDTop": true,
"UseTwoDRight": true,
"UseTwoDBottom": true,
"UseTwoDLeft": true,
"UseThreeDTop": true,
"UseThreeDBottom": true
},
"ThreeDTopFace": {
"Vertices": [
"0, 5, 0",
"0, 0, 0",
"5, 0, 0",
"5, 5, 0"
],
"Faces": [
{
"Type": "RECTANGLE",
"Textures": "BallPaper",
"Indices": [
0,
1,
2,
3
],
"UVs": [
"0, 0",
"1, 0",
"1, 1",
"0, 1"
]
}
]
},
"ThreeDBottomFace": {
"Vertices": [
"0, 0, -5-d3",
"0, 5, -5-d3",
"5, 5, -5-d3",
"5, 0, -5-d3"
],
"Faces": [
{
"Type": "RECTANGLE",
"Textures": "BallPaper",
"Indices": [
0,
1,
2,
3
],
"UVs": [
"0, 0",
"1, 0",
"1, 1",
"0, 1"
]
}
]
},
"TwoDTopSide": {
"Vertices": [
"0, 5, -5-d3",
"0, 0, -5-d3",
"0, 0, 0",
"0, 5, 0"
],
"Faces": [
{
"Type": "RECTANGLE",
"Textures": "BallPaper",
"Indices": [
0,
1,
2,
3
],
"UVs": [
"0, -d3",
"1, -d3",
"1, 1",
"0, 1"
]
}
]
},
"TwoDRightSide": {
"Vertices": [
"5, 5, -5-d3",
"0, 5, -5-d3",
"0, 5, 0",
"5, 5, 0"
],
"Faces": [
{
"Type": "RECTANGLE",
"Textures": "BallPaper",
"Indices": [
0,
1,
2,
3
],
"UVs": [
"0, -d3",
"1, -d3",
"1, 1",
"0, 1"
]
}
]
},
"TwoDBottomSide": {
"Vertices": [
"5, 0, -5-d3",
"5, 5, -5-d3",
"5, 5, 0",
"5, 0, 0"
],
"Faces": [
{
"Type": "RECTANGLE",
"Textures": "BallPaper",
"Indices": [
0,
1,
2,
3
],
"UVs": [
"0, -d3",
"1, -d3",
"1, 1",
"0, 1"
]
}
]
},
"TwoDLeftSide": {
"Vertices": [
"0, 0, -5-d3",
"5, 0, -5-d3",
"5, 0, 0",
"0, 0, 0"
],
"Faces": [
{
"Type": "RECTANGLE",
"Textures": "BallPaper",
"Indices": [
0,
1,
2,
3
],
"UVs": [
"0, -d3",
"1, -d3",
"1, 1",
"0, 1"
]
}
]
},
"TwoDTopSideExpand": {
"Vertices": [
"0, 5, -5-d3",
"0, 0, -5-d3",
"0, 0, 0",
"0, 5, 0"
],
"Faces": [
{
"Type": "RECTANGLE",
"Textures": "BallPaper",
"Indices": [
0,
1,
2,
3
],
"UVs": [
"1+d3, 0",
"1+d3, 1",
"0, 1",
"0, 0"
]
}
]
},
"TwoDRightSideExpand": {
"Vertices": [
"5, 5, -5-d3",
"0, 5, -5-d3",
"0, 5, 0",
"5, 5, 0"
],
"Faces": [
{
"Type": "RECTANGLE",
"Textures": "BallPaper",
"Indices": [
0,
1,
2,
3
],
"UVs": [
"1+d3, 0",
"1+d3, 1",
"0, 1",
"0, 0"
]
}
]
},
"TwoDBottomSideExpand": {
"Vertices": [
"5, 0, -5-d3",
"5, 5, -5-d3",
"5, 5, 0",
"5, 0, 0"
],
"Faces": [
{
"Type": "RECTANGLE",
"Textures": "BallPaper",
"Indices": [
0,
1,
2,
3
],
"UVs": [
"1+d3, 0",
"1+d3, 1",
"0, 1",
"0, 0"
]
}
]
},
"TwoDLeftSideExpand": {
"Vertices": [
"0, 0, -5-d3",
"5, 0, -5-d3",
"5, 0, 0",
"0, 0, 0"
],
"Faces": [
{
"Type": "RECTANGLE",
"Textures": "BallPaper",
"Indices": [
0,
1,
2,
3
],
"UVs": [
"1+d3, 0",
"1+d3, 1",
"0, 1",
"0, 0"
]
}
]
}
}
]

View File

@ -0,0 +1,317 @@
[
{
"Type": "NormalFloor",
"BindingDisplayTexture": "NormalFloor.png",
"UnitSize": "Small",
"ExpandType": "Column",
"InitColumnDirection": "PositiveX",
"DefaultSideConfig": {
"UseTwoDTop": false,
"UseTwoDRight": true,
"UseTwoDBottom": false,
"UseTwoDLeft": true,
"UseThreeDTop": true,
"UseThreeDBottom": false
},
"SmashedBlocks": [
{
"Type": "NormalBorder",
"Rotation": "R0",
"SideSync": "2dTop;False;2dBottom;2dLeft;3dTop;3dBottom",
"StartPosition": "0, 0, 0",
"ExpandParam": "d1, 0"
},
{
"Type": "NormalBorder",
"Rotation": "R180",
"SideSync": "2dBottom;False;2dTop;2dRight;3dTop;3dBottom",
"StartPosition": "d1, (2.5*1), 0",
"ExpandParam": "d1, 0"
}
]
},
{
"Type": "NormalFloorTerminal",
"BindingDisplayTexture": "NormalFloorTerminal.png",
"UnitSize": "Small",
"ExpandType": "Static",
"InitColumnDirection": "PositiveX",
"DefaultSideConfig": {
"UseTwoDTop": true,
"UseTwoDRight": true,
"UseTwoDBottom": false,
"UseTwoDLeft": true,
"UseThreeDTop": true,
"UseThreeDBottom": false
},
"SmashedBlocks": [
{
"Type": "NormalOutterCorner",
"Rotation": "R0",
"SideSync": "2dTop;False;2dBottom;2dLeft;3dTop;3dBottom",
"StartPosition": "0, 0, 0",
"ExpandParam": "0, 0"
},
{
"Type": "NormalOutterCorner",
"Rotation": "R270",
"SideSync": "2dRight;2dBottom;False;2dTop;3dTop;3dBottom",
"StartPosition": "0, (2.5*1), 0",
"ExpandParam": "0, 0"
}
]
},
{
"Type": "Normal1x1",
"BindingDisplayTexture": "Normal1x1.png",
"UnitSize": "Small",
"ExpandType": "Static",
"InitColumnDirection": "PositiveX",
"DefaultSideConfig": {
"UseTwoDTop": true,
"UseTwoDRight": true,
"UseTwoDBottom": true,
"UseTwoDLeft": true,
"UseThreeDTop": true,
"UseThreeDBottom": false
},
"SmashedBlocks": [
{
"Type": "NormalOutterCorner",
"Rotation": "R0",
"SideSync": "2dTop;False;False;2dLeft;3dTop;3dBottom",
"StartPosition": "0, 0, 0",
"ExpandParam": "0, 0"
},
{
"Type": "NormalOutterCorner",
"Rotation": "R90",
"SideSync": "2dLeft;False;False;2dBottom;3dTop;3dBottom",
"StartPosition": "(2.5*1), 0, 0",
"ExpandParam": "0, 0"
},
{
"Type": "NormalOutterCorner",
"Rotation": "R180",
"SideSync": "2dBottom;False;False;2dRight;3dTop;3dBottom",
"StartPosition": "(2.5*1), (2.5*1), 0",
"ExpandParam": "0, 0"
},
{
"Type": "NormalOutterCorner",
"Rotation": "R270",
"SideSync": "2dRight;False;False;2dTop;3dTop;3dBottom",
"StartPosition": "0, (2.5*1), 0",
"ExpandParam": "0, 0"
}
]
},
{
"Type": "NormalPlatform",
"BindingDisplayTexture": "NormalPlatform.png",
"UnitSize": "Small",
"ExpandType": "Freedom",
"InitColumnDirection": "PositiveY",
"DefaultSideConfig": {
"UseTwoDTop": true,
"UseTwoDRight": true,
"UseTwoDBottom": true,
"UseTwoDLeft": true,
"UseThreeDTop": true,
"UseThreeDBottom": false
},
"SmashedBlocks": [
{
"Type": "Flat",
"Rotation": "R0",
"SideSync": "False;False;False;False;3dTop;3dBottom",
"StartPosition": "(2.5*1), (2.5*1), 0",
"ExpandParam": "d1, d2"
},
{
"Type": "NormalBorder",
"Rotation": "R0",
"SideSync": "False;False;False;2dLeft;3dTop;3dBottom",
"StartPosition": "(2.5*1), 0, 0",
"ExpandParam": "d2, 0"
},
{
"Type": "NormalBorder",
"Rotation": "R90",
"SideSync": "False;False;False;2dBottom;3dTop;3dBottom",
"StartPosition": "(2.5*2) +d2, (2.5*1), 0",
"ExpandParam": "d1, 0"
},
{
"Type": "NormalBorder",
"Rotation": "R180",
"SideSync": "False;False;False;2dRight;3dTop;3dBottom",
"StartPosition": "(2.5*1) +d2, (2.5*2) +d1, 0",
"ExpandParam": "d2, 0"
},
{
"Type": "NormalBorder",
"Rotation": "R270",
"SideSync": "False;False;False;2dTop;3dTop;3dBottom",
"StartPosition": "0, (2.5*1) +d1, 0",
"ExpandParam": "d1, 0"
},
{
"Type": "NormalOutterCorner",
"Rotation": "R0",
"SideSync": "2dTop;False;False;2dLeft;3dTop;3dBottom",
"StartPosition": "0, 0, 0",
"ExpandParam": "0, 0"
},
{
"Type": "NormalOutterCorner",
"Rotation": "R90",
"SideSync": "2dLeft;False;False;2dBottom;3dTop;3dBottom",
"StartPosition": "(2.5*2) +d2, 0, 0",
"ExpandParam": "0, 0"
},
{
"Type": "NormalOutterCorner",
"Rotation": "R180",
"SideSync": "2dBottom;False;False;2dRight;3dTop;3dBottom",
"StartPosition": "(2.5*2) +d2, (2.5*2) +d1, 0",
"ExpandParam": "0, 0"
},
{
"Type": "NormalOutterCorner",
"Rotation": "R270",
"SideSync": "2dRight;False;False;2dTop;3dTop;3dBottom",
"StartPosition": "0, (2.5*2) +d1, 0",
"ExpandParam": "0, 0"
}
]
},
{
"Type": "NormalLConnector",
"BindingDisplayTexture": "NormalLConnector.png",
"UnitSize": "Small",
"ExpandType": "Static",
"InitColumnDirection": "PositiveX",
"DefaultSideConfig": {
"UseTwoDTop": true,
"UseTwoDRight": false,
"UseTwoDBottom": false,
"UseTwoDLeft": true,
"UseThreeDTop": true,
"UseThreeDBottom": false
},
"SmashedBlocks": [
{
"Type": "NormalOutterCorner",
"Rotation": "R0",
"SideSync": "2dTop;False;False;2dLeft;3dTop;3dBottom",
"StartPosition": "0, 0, 0",
"ExpandParam": "0, 0"
},
{
"Type": "NormalBorder",
"Rotation": "R0",
"SideSync": "False;False;2dBottom;2dLeft;3dTop;3dBottom",
"StartPosition": "(2.5*1), 0, 0",
"ExpandParam": "0, 0"
},
{
"Type": "NormalInnerCorner",
"Rotation": "R0",
"SideSync": "False;2dRight;2dBottom;False;3dTop;3dBottom",
"StartPosition": "(2.5*1), (2.5*1), 0",
"ExpandParam": "0, 0"
},
{
"Type": "NormalBorder",
"Rotation": "R270",
"SideSync": "2dRight;False;False;2dTop;3dTop;3dBottom",
"StartPosition": "0, (2.5*1), 0",
"ExpandParam": "0, 0"
}
]
},
{
"Type": "NormalTConnector",
"BindingDisplayTexture": "NormalTConnector.png",
"UnitSize": "Small",
"ExpandType": "Static",
"InitColumnDirection": "PositiveX",
"DefaultSideConfig": {
"UseTwoDTop": true,
"UseTwoDRight": false,
"UseTwoDBottom": false,
"UseTwoDLeft": false,
"UseThreeDTop": true,
"UseThreeDBottom": false
},
"SmashedBlocks": [
{
"Type": "NormalInnerCorner",
"Rotation": "R270",
"SideSync": "False;2dBottom;2dLeft;False;3dTop;3dBottom",
"StartPosition": "(2.5*1), 0, 0",
"ExpandParam": "0, 0"
},
{
"Type": "NormalInnerCorner",
"Rotation": "R0",
"SideSync": "False;2dRight;2dBottom;False;3dTop;3dBottom",
"StartPosition": "(2.5*1), (2.5*1), 0",
"ExpandParam": "0, 0"
},
{
"Type": "NormalBorder",
"Rotation": "R270",
"SideSync": "2dRight;False;2dLeft;2dTop;3dTop;3dBottom",
"StartPosition": "0, (2.5*1), 0",
"ExpandParam": "1, 0"
}
]
},
{
"Type": "NormalCrossroad",
"BindingDisplayTexture": "NormalCrossroad.png",
"UnitSize": "Small",
"ExpandType": "Static",
"InitColumnDirection": "PositiveX",
"DefaultSideConfig": {
"UseTwoDTop": false,
"UseTwoDRight": false,
"UseTwoDBottom": false,
"UseTwoDLeft": false,
"UseThreeDTop": true,
"UseThreeDBottom": false
},
"SmashedBlocks": [
{
"Type": "NormalInnerCorner",
"Rotation": "R0",
"SideSync": "False;2dRight;2dBottom;False;3dTop;3dBottom",
"StartPosition": "(2.5*1), (2.5*1), 0",
"ExpandParam": "0, 0"
},
{
"Type": "NormalInnerCorner",
"Rotation": "R90",
"SideSync": "False;2dTop;2dRight;False;3dTop;3dBottom",
"StartPosition": "0, (2.5*1), 0",
"ExpandParam": "0, 0"
},
{
"Type": "NormalInnerCorner",
"Rotation": "R180",
"SideSync": "False;2dLeft;2dTop;False;3dTop;3dBottom",
"StartPosition": "0, 0, 0",
"ExpandParam": "0, 0"
},
{
"Type": "NormalInnerCorner",
"Rotation": "R270",
"SideSync": "False;2dBottom;2dLeft;False;3dTop;3dBottom",
"StartPosition": "(2.5*1), 0, 0",
"ExpandParam": "0, 0"
}
]
}
]

View File

@ -0,0 +1,397 @@
[
{
"Type": "SinkFloor",
"BindingDisplayTexture": "SinkFloor.png",
"UnitSize": "Small",
"ExpandType": "Column",
"InitColumnDirection": "PositiveX",
"DefaultSideConfig": {
"UseTwoDTop": false,
"UseTwoDRight": true,
"UseTwoDBottom": false,
"UseTwoDLeft": true,
"UseThreeDTop": true,
"UseThreeDBottom": false
},
"SmashedBlocks": [
{
"Type": "SinkBorder",
"Rotation": "R0",
"SideSync": "2dTop;False;2dBottom;2dLeft;3dTop;3dBottom",
"StartPosition": "0, 0, 0",
"ExpandParam": "d1, 0"
},
{
"Type": "SinkBorder",
"Rotation": "R180",
"SideSync": "2dBottom;False;2dTop;2dRight;3dTop;3dBottom",
"StartPosition": "d1, (2.5*1), 0",
"ExpandParam": "d1, 0"
}
]
},
{
"Type": "SinkFloorTerminal",
"BindingDisplayTexture": "SinkFloorTerminal.png",
"UnitSize": "Small",
"ExpandType": "Static",
"InitColumnDirection": "PositiveX",
"DefaultSideConfig": {
"UseTwoDTop": true,
"UseTwoDRight": true,
"UseTwoDBottom": false,
"UseTwoDLeft": true,
"UseThreeDTop": true,
"UseThreeDBottom": false
},
"SmashedBlocks": [
{
"Type": "SinkOutterCorner",
"Rotation": "R0",
"SideSync": "2dTop;False;2dBottom;2dLeft;3dTop;3dBottom",
"StartPosition": "0, 0, 0",
"ExpandParam": "0, 0"
},
{
"Type": "SinkOutterCorner",
"Rotation": "R270",
"SideSync": "2dRight;2dBottom;False;2dTop;3dTop;3dBottom",
"StartPosition": "0, (2.5*1), 0",
"ExpandParam": "0, 0"
}
]
},
{
"Type": "Sink1x1",
"BindingDisplayTexture": "Sink1x1.png",
"UnitSize": "Small",
"ExpandType": "Static",
"InitColumnDirection": "PositiveX",
"DefaultSideConfig": {
"UseTwoDTop": true,
"UseTwoDRight": true,
"UseTwoDBottom": true,
"UseTwoDLeft": true,
"UseThreeDTop": true,
"UseThreeDBottom": false
},
"SmashedBlocks": [
{
"Type": "SinkOutterCorner",
"Rotation": "R0",
"SideSync": "2dTop;False;False;2dLeft;3dTop;3dBottom",
"StartPosition": "0, 0, 0",
"ExpandParam": "0, 0"
},
{
"Type": "SinkOutterCorner",
"Rotation": "R90",
"SideSync": "2dLeft;False;False;2dBottom;3dTop;3dBottom",
"StartPosition": "(2.5*1), 0, 0",
"ExpandParam": "0, 0"
},
{
"Type": "SinkOutterCorner",
"Rotation": "R180",
"SideSync": "2dBottom;False;False;2dRight;3dTop;3dBottom",
"StartPosition": "(2.5*1), (2.5*1), 0",
"ExpandParam": "0, 0"
},
{
"Type": "SinkOutterCorner",
"Rotation": "R270",
"SideSync": "2dRight;False;False;2dTop;3dTop;3dBottom",
"StartPosition": "0, (2.5*1), 0",
"ExpandParam": "0, 0"
}
]
},
{
"Type": "SinkPlatform",
"BindingDisplayTexture": "SinkPlatform.png",
"UnitSize": "Small",
"ExpandType": "Freedom",
"InitColumnDirection": "PositiveY",
"DefaultSideConfig": {
"UseTwoDTop": true,
"UseTwoDRight": true,
"UseTwoDBottom": true,
"UseTwoDLeft": true,
"UseThreeDTop": true,
"UseThreeDBottom": false
},
"SmashedBlocks": [
{
"Type": "Flat",
"Rotation": "R0",
"SideSync": "False;False;False;False;3dTop;3dBottom",
"StartPosition": "(2.5*1), (2.5*1), -0.7",
"ExpandParam": "d1, d2"
},
{
"Type": "SinkBorder",
"Rotation": "R0",
"SideSync": "False;False;False;2dLeft;3dTop;3dBottom",
"StartPosition": "(2.5*1), 0, 0",
"ExpandParam": "d2, 0"
},
{
"Type": "SinkBorder",
"Rotation": "R90",
"SideSync": "False;False;False;2dBottom;3dTop;3dBottom",
"StartPosition": "(2.5*2) +d2, (2.5*1), 0",
"ExpandParam": "d1, 0"
},
{
"Type": "SinkBorder",
"Rotation": "R180",
"SideSync": "False;False;False;2dRight;3dTop;3dBottom",
"StartPosition": "(2.5*1) +d2, (2.5*2) +d1, 0",
"ExpandParam": "d2, 0"
},
{
"Type": "SinkBorder",
"Rotation": "R270",
"SideSync": "False;False;False;2dTop;3dTop;3dBottom",
"StartPosition": "0, (2.5*1) +d1, 0",
"ExpandParam": "d1, 0"
},
{
"Type": "SinkOutterCorner",
"Rotation": "R0",
"SideSync": "2dTop;False;False;2dLeft;3dTop;3dBottom",
"StartPosition": "0, 0, 0",
"ExpandParam": "0, 0"
},
{
"Type": "SinkOutterCorner",
"Rotation": "R90",
"SideSync": "2dLeft;False;False;2dBottom;3dTop;3dBottom",
"StartPosition": "(2.5*2) +d2, 0, 0",
"ExpandParam": "0, 0"
},
{
"Type": "SinkOutterCorner",
"Rotation": "R180",
"SideSync": "2dBottom;False;False;2dRight;3dTop;3dBottom",
"StartPosition": "(2.5*2) +d2, (2.5*2) +d1, 0",
"ExpandParam": "0, 0"
},
{
"Type": "SinkOutterCorner",
"Rotation": "R270",
"SideSync": "2dRight;False;False;2dTop;3dTop;3dBottom",
"StartPosition": "0, (2.5*2) +d1, 0",
"ExpandParam": "0, 0"
}
]
},
{
"Type": "RibbonPlatform",
"BindingDisplayTexture": "RibbonPlatform.png",
"UnitSize": "Small",
"ExpandType": "Freedom",
"InitColumnDirection": "PositiveY",
"DefaultSideConfig": {
"UseTwoDTop": true,
"UseTwoDRight": true,
"UseTwoDBottom": true,
"UseTwoDLeft": true,
"UseThreeDTop": true,
"UseThreeDBottom": false
},
"SmashedBlocks": [
{
"Type": "Flat",
"Rotation": "R0",
"SideSync": "False;False;False;False;3dTop;3dBottom",
"StartPosition": "(2.5*1), (2.5*1), -0.7",
"ExpandParam": "d1, d2"
},
{
"Type": "RibbonBorder",
"Rotation": "R0",
"SideSync": "False;False;False;2dLeft;3dTop;3dBottom",
"StartPosition": "(2.5*1), 0, 0",
"ExpandParam": "d2, 0"
},
{
"Type": "RibbonBorder",
"Rotation": "R90",
"SideSync": "False;False;False;2dBottom;3dTop;3dBottom",
"StartPosition": "(2.5*2) +d2, (2.5*1), 0",
"ExpandParam": "d1, 0"
},
{
"Type": "RibbonBorder",
"Rotation": "R180",
"SideSync": "False;False;False;2dRight;3dTop;3dBottom",
"StartPosition": "(2.5*1) +d2, (2.5*2) +d1, 0",
"ExpandParam": "d2, 0"
},
{
"Type": "RibbonBorder",
"Rotation": "R270",
"SideSync": "False;False;False;2dTop;3dTop;3dBottom",
"StartPosition": "0, (2.5*1) +d1, 0",
"ExpandParam": "d1, 0"
},
{
"Type": "RibbonOutterCorner",
"Rotation": "R0",
"SideSync": "2dTop;False;False;2dLeft;3dTop;3dBottom",
"StartPosition": "0, 0, 0",
"ExpandParam": "0, 0"
},
{
"Type": "RibbonOutterCorner",
"Rotation": "R90",
"SideSync": "2dLeft;False;False;2dBottom;3dTop;3dBottom",
"StartPosition": "(2.5*2) +d2, 0, 0",
"ExpandParam": "0, 0"
},
{
"Type": "RibbonOutterCorner",
"Rotation": "R180",
"SideSync": "2dBottom;False;False;2dRight;3dTop;3dBottom",
"StartPosition": "(2.5*2) +d2, (2.5*2) +d1, 0",
"ExpandParam": "0, 0"
},
{
"Type": "RibbonOutterCorner",
"Rotation": "R270",
"SideSync": "2dRight;False;False;2dTop;3dTop;3dBottom",
"StartPosition": "0, (2.5*2) +d1, 0",
"ExpandParam": "0, 0"
}
]
},
{
"Type": "SinkLConnector",
"BindingDisplayTexture": "SinkLConnector.png",
"UnitSize": "Small",
"ExpandType": "Static",
"InitColumnDirection": "PositiveX",
"DefaultSideConfig": {
"UseTwoDTop": true,
"UseTwoDRight": false,
"UseTwoDBottom": false,
"UseTwoDLeft": true,
"UseThreeDTop": true,
"UseThreeDBottom": false
},
"SmashedBlocks": [
{
"Type": "SinkOutterCorner",
"Rotation": "R0",
"SideSync": "2dTop;False;False;2dLeft;3dTop;3dBottom",
"StartPosition": "0, 0, 0",
"ExpandParam": "0, 0"
},
{
"Type": "SinkBorder",
"Rotation": "R0",
"SideSync": "False;False;2dBottom;2dLeft;3dTop;3dBottom",
"StartPosition": "(2.5*1), 0, 0",
"ExpandParam": "0, 0"
},
{
"Type": "SinkInnerCorner",
"Rotation": "R0",
"SideSync": "False;2dRight;2dBottom;False;3dTop;3dBottom",
"StartPosition": "(2.5*1), (2.5*1), 0",
"ExpandParam": "0, 0"
},
{
"Type": "SinkBorder",
"Rotation": "R270",
"SideSync": "2dRight;False;False;2dTop;3dTop;3dBottom",
"StartPosition": "0, (2.5*1), 0",
"ExpandParam": "0, 0"
}
]
},
{
"Type": "SinkTConnector",
"BindingDisplayTexture": "SinkTConnector.png",
"UnitSize": "Small",
"ExpandType": "Static",
"InitColumnDirection": "PositiveX",
"DefaultSideConfig": {
"UseTwoDTop": true,
"UseTwoDRight": false,
"UseTwoDBottom": false,
"UseTwoDLeft": false,
"UseThreeDTop": true,
"UseThreeDBottom": false
},
"SmashedBlocks": [
{
"Type": "SinkInnerCorner",
"Rotation": "R270",
"SideSync": "False;2dBottom;2dLeft;False;3dTop;3dBottom",
"StartPosition": "(2.5*1), 0, 0",
"ExpandParam": "0, 0"
},
{
"Type": "SinkInnerCorner",
"Rotation": "R0",
"SideSync": "False;2dRight;2dBottom;False;3dTop;3dBottom",
"StartPosition": "(2.5*1), (2.5*1), 0",
"ExpandParam": "0, 0"
},
{
"Type": "SinkBorder",
"Rotation": "R270",
"SideSync": "2dRight;False;2dLeft;2dTop;3dTop;3dBottom",
"StartPosition": "0, (2.5*1), 0",
"ExpandParam": "1, 0"
}
]
},
{
"Type": "SinkCrossroad",
"BindingDisplayTexture": "SinkCrossroad.png",
"UnitSize": "Small",
"ExpandType": "Static",
"InitColumnDirection": "PositiveX",
"DefaultSideConfig": {
"UseTwoDTop": false,
"UseTwoDRight": false,
"UseTwoDBottom": false,
"UseTwoDLeft": false,
"UseThreeDTop": true,
"UseThreeDBottom": false
},
"SmashedBlocks": [
{
"Type": "SinkInnerCorner",
"Rotation": "R0",
"SideSync": "False;2dRight;2dBottom;False;3dTop;3dBottom",
"StartPosition": "(2.5*1), (2.5*1), 0",
"ExpandParam": "0, 0"
},
{
"Type": "SinkInnerCorner",
"Rotation": "R90",
"SideSync": "False;2dTop;2dRight;False;3dTop;3dBottom",
"StartPosition": "0, (2.5*1), 0",
"ExpandParam": "0, 0"
},
{
"Type": "SinkInnerCorner",
"Rotation": "R180",
"SideSync": "False;2dLeft;2dTop;False;3dTop;3dBottom",
"StartPosition": "0, 0, 0",
"ExpandParam": "0, 0"
},
{
"Type": "SinkInnerCorner",
"Rotation": "R270",
"SideSync": "False;2dBottom;2dLeft;False;3dTop;3dBottom",
"StartPosition": "(2.5*1), 0, 0",
"ExpandParam": "0, 0"
}
]
}
]

View File

@ -0,0 +1,262 @@
[
{
"Type": "WideFloor",
"BindingDisplayTexture": "WideFloor.png",
"UnitSize": "Small",
"ExpandType": "Freedom",
"InitColumnDirection": "PositiveY",
"DefaultSideConfig": {
"UseTwoDTop": false,
"UseTwoDRight": true,
"UseTwoDBottom": false,
"UseTwoDLeft": true,
"UseThreeDTop": true,
"UseThreeDBottom": false
},
"SmashedBlocks": [
{
"Type": "SinkBorder",
"Rotation": "R0",
"SideSync": "2dTop;False;2dBottom;2dLeft;3dTop;3dBottom",
"StartPosition": "0, 0, 0",
"ExpandParam": "d2, 0"
},
{
"Type": "SinkBorder",
"Rotation": "R180",
"SideSync": "2dBottom;False;2dTop;2dRight;3dTop;3dBottom",
"StartPosition": "d2, (2.5*2)+d1, 0",
"ExpandParam": "d2, 0"
},
{
"Type": "Flat",
"Rotation": "R0",
"SideSync": "2dTop;False;2dBottom;False;3dTop;3dBottom",
"StartPosition": "0, (2.5*1), -0.7",
"ExpandParam": "d1, d2"
}
]
},
{
"Type": "WideFloorTerminal",
"BindingDisplayTexture": "WideFloorTerminal.png",
"UnitSize": "Small",
"ExpandType": "Column",
"InitColumnDirection": "PositiveY",
"DefaultSideConfig": {
"UseTwoDTop": true,
"UseTwoDRight": true,
"UseTwoDBottom": false,
"UseTwoDLeft": true,
"UseThreeDTop": true,
"UseThreeDBottom": false
},
"SmashedBlocks": [
{
"Type": "SinkOutterCorner",
"Rotation": "R0",
"SideSync": "2dTop;False;2dBottom;2dLeft;3dTop;3dBottom",
"StartPosition": "0, 0, 0",
"ExpandParam": "0, 0"
},
{
"Type": "SinkOutterCorner",
"Rotation": "R270",
"SideSync": "2dRight;2dBottom;False;2dTop;3dTop;3dBottom",
"StartPosition": "0, (2.5*2)+d1, 0",
"ExpandParam": "0, 0"
},
{
"Type": "SinkBorder",
"Rotation": "R270",
"SideSync": "False;2dBottom;False;2dTop;3dTop;3dBottom",
"StartPosition": "0, (2.5*1) +d1, 0",
"ExpandParam": "d1, 0"
}
]
},
{
"Type": "WideLConnector",
"BindingDisplayTexture": "WideLConnector.png",
"UnitSize": "Small",
"ExpandType": "Column",
"InitColumnDirection": "PositiveY",
"DefaultSideConfig": {
"UseTwoDTop": true,
"UseTwoDRight": false,
"UseTwoDBottom": false,
"UseTwoDLeft": true,
"UseThreeDTop": true,
"UseThreeDBottom": false
},
"SmashedBlocks": [
{
"Type": "SinkOutterCorner",
"Rotation": "R0",
"SideSync": "2dTop;False;False;2dLeft;3dTop;3dBottom",
"StartPosition": "0, 0, 0",
"ExpandParam": "0, 0"
},
{
"Type": "SinkBorder",
"Rotation": "R0",
"SideSync": "False;False;2dBottom;2dLeft;3dTop;3dBottom",
"StartPosition": "(2.5*1), 0, 0",
"ExpandParam": "d1 + 1, 0"
},
{
"Type": "SinkInnerCorner",
"Rotation": "R0",
"SideSync": "False;2dRight;2dBottom;False;3dTop;3dBottom",
"StartPosition": "(2.5*2) + d1, (2.5*2) + d1, 0",
"ExpandParam": "0, 0"
},
{
"Type": "SinkBorder",
"Rotation": "R270",
"SideSync": "2dRight;False;False;2dTop;3dTop;3dBottom",
"StartPosition": "0, (2.5*2)+d1, 0",
"ExpandParam": "d1 + 1, 0"
},
{
"Type": "Flat",
"Rotation": "R0",
"SideSync": "False;False;2dBottom;False;3dTop;3dBottom",
"StartPosition": "2.5, 2.5, -0.7",
"ExpandParam": "d1, d1 + 1"
},
{
"Type": "Flat",
"Rotation": "R0",
"SideSync": "False;2dRight;False;False;3dTop;3dBottom",
"StartPosition": "2.5, (2.5 * 2) + d1, -0.7",
"ExpandParam": "0, d1"
}
]
},
{
"Type": "WideTConnector",
"BindingDisplayTexture": "WideTConnector.png",
"UnitSize": "Small",
"ExpandType": "Column",
"InitColumnDirection": "PositiveY",
"DefaultSideConfig": {
"UseTwoDTop": true,
"UseTwoDRight": false,
"UseTwoDBottom": false,
"UseTwoDLeft": false,
"UseThreeDTop": true,
"UseThreeDBottom": false
},
"SmashedBlocks": [
{
"Type": "SinkInnerCorner",
"Rotation": "R270",
"SideSync": "False;2dBottom;2dLeft;False;3dTop;3dBottom",
"StartPosition": "(2.5*2) + d1, 0, 0",
"ExpandParam": "0, 0"
},
{
"Type": "SinkInnerCorner",
"Rotation": "R0",
"SideSync": "False;2dRight;2dBottom;False;3dTop;3dBottom",
"StartPosition": "(2.5*2) + d1, (2.5*2) + d1, 0",
"ExpandParam": "0, 0"
},
{
"Type": "SinkBorder",
"Rotation": "R270",
"SideSync": "2dRight;False;2dLeft;2dTop;3dTop;3dBottom",
"StartPosition": "0, (2.5*2) + d1, 0",
"ExpandParam": "d1 + 2, 0"
},
{
"Type": "Flat",
"Rotation": "R0",
"SideSync": "False;False;2dBottom;False;3dTop;3dBottom",
"StartPosition": "2.5, 2.5, -0.7",
"ExpandParam": "d1, d1+1"
},
{
"Type": "Flat",
"Rotation": "R0",
"SideSync": "False;False;False;2dLeft;3dTop;3dBottom",
"StartPosition": "2.5, 0, -0.7",
"ExpandParam": "0, d1"
},
{
"Type": "Flat",
"Rotation": "R0",
"SideSync": "False;2dRight;False;False;3dTop;3dBottom",
"StartPosition": "2.5, (2.5*2)+d1, -0.7",
"ExpandParam": "0, d1"
}
]
},
{
"Type": "WideCrossroad",
"BindingDisplayTexture": "WideCrossroad.png",
"UnitSize": "Small",
"ExpandType": "Column",
"InitColumnDirection": "PositiveY",
"DefaultSideConfig": {
"UseTwoDTop": false,
"UseTwoDRight": false,
"UseTwoDBottom": false,
"UseTwoDLeft": false,
"UseThreeDTop": true,
"UseThreeDBottom": false
},
"SmashedBlocks": [
{
"Type": "SinkInnerCorner",
"Rotation": "R0",
"SideSync": "False;2dRight;2dBottom;False;3dTop;3dBottom",
"StartPosition": "(2.5*2)+d1, (2.5*2)+d1, 0",
"ExpandParam": "0, 0"
},
{
"Type": "SinkInnerCorner",
"Rotation": "R90",
"SideSync": "False;2dTop;2dRight;False;3dTop;3dBottom",
"StartPosition": "0, (2.5*2)+d1, 0",
"ExpandParam": "0, 0"
},
{
"Type": "SinkInnerCorner",
"Rotation": "R180",
"SideSync": "False;2dLeft;2dTop;False;3dTop;3dBottom",
"StartPosition": "0, 0, 0",
"ExpandParam": "0, 0"
},
{
"Type": "SinkInnerCorner",
"Rotation": "R270",
"SideSync": "False;2dBottom;2dLeft;False;3dTop;3dBottom",
"StartPosition": "(2.5*2)+d1, 0, 0",
"ExpandParam": "0, 0"
},
{
"Type": "Flat",
"Rotation": "R0",
"SideSync": "2dTop;False;2dBottom;False;3dTop;3dBottom",
"StartPosition": "0, 2.5, -0.7",
"ExpandParam": "d1, d1+2"
},
{
"Type": "Flat",
"Rotation": "R0",
"SideSync": "False;False;False;2dLeft;3dTop;3dBottom",
"StartPosition": "2.5, 0, -0.7",
"ExpandParam": "0, d1"
},
{
"Type": "Flat",
"Rotation": "R0",
"SideSync": "False;2dRight;False;False;3dTop;3dBottom",
"StartPosition": "2.5, (2.5*2)+d1, -0.7",
"ExpandParam": "0, d1"
}
]
}
]

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,8 +0,0 @@
import bpy
def ShowMessageBox(message = "", title = "Message Box", icon = 'INFO'):
def draw(self, context):
self.layout.label(text=message)
bpy.context.window_manager.popup_menu(draw, title = title, icon = icon)