Compare commits
14 Commits
7f33e4ad92
...
v4.3-alpha
Author | SHA1 | Date | |
---|---|---|---|
2b2b18cfa4 | |||
b19800e37f | |||
e14729500c | |||
48bfc54830 | |||
7e74e42bd7 | |||
96a81b165b | |||
0681f0d240 | |||
d700f1276a | |||
3bea3d67b9 | |||
ec41b7553a | |||
9e2539499e | |||
3a5cd1c937 | |||
1afa5f9893 | |||
1383e87104 |
Binary file not shown.
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 30 KiB |
Binary file not shown.
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 31 KiB |
Binary file not shown.
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 42 KiB |
@ -84,6 +84,7 @@
|
|||||||
"identifier": "floor_normal_1x1",
|
"identifier": "floor_normal_1x1",
|
||||||
"showcase": {
|
"showcase": {
|
||||||
"title": "Normal 1x1",
|
"title": "Normal 1x1",
|
||||||
|
"category": "1x1 Blocks",
|
||||||
"icon": "Normal1x1",
|
"icon": "Normal1x1",
|
||||||
"type": "floor",
|
"type": "floor",
|
||||||
"cfgs": [
|
"cfgs": [
|
||||||
@ -134,6 +135,7 @@
|
|||||||
"identifier": "floor_sink_1x1",
|
"identifier": "floor_sink_1x1",
|
||||||
"showcase": {
|
"showcase": {
|
||||||
"title": "Sink 1x1",
|
"title": "Sink 1x1",
|
||||||
|
"category": "1x1 Blocks",
|
||||||
"icon": "Sink1x1",
|
"icon": "Sink1x1",
|
||||||
"type": "floor",
|
"type": "floor",
|
||||||
"cfgs": [
|
"cfgs": [
|
||||||
|
@ -49,6 +49,7 @@
|
|||||||
"identifier": "floor_normal_border",
|
"identifier": "floor_normal_border",
|
||||||
"showcase": {
|
"showcase": {
|
||||||
"title": "Normal Border",
|
"title": "Normal Border",
|
||||||
|
"category": "Borders",
|
||||||
"icon": "NormalBorder",
|
"icon": "NormalBorder",
|
||||||
"type": "floor",
|
"type": "floor",
|
||||||
"cfgs": [
|
"cfgs": [
|
||||||
@ -112,6 +113,7 @@
|
|||||||
"identifier": "floor_sink_border",
|
"identifier": "floor_sink_border",
|
||||||
"showcase": {
|
"showcase": {
|
||||||
"title": "Sink Border",
|
"title": "Sink Border",
|
||||||
|
"category": "Borders",
|
||||||
"icon": "SinkBorder",
|
"icon": "SinkBorder",
|
||||||
"type": "floor",
|
"type": "floor",
|
||||||
"cfgs": [
|
"cfgs": [
|
||||||
@ -175,6 +177,7 @@
|
|||||||
"identifier": "floor_ribbon_border",
|
"identifier": "floor_ribbon_border",
|
||||||
"showcase": {
|
"showcase": {
|
||||||
"title": "Ribbon Border",
|
"title": "Ribbon Border",
|
||||||
|
"category": "Borders",
|
||||||
"icon": "RibbonBorder",
|
"icon": "RibbonBorder",
|
||||||
"type": "floor",
|
"type": "floor",
|
||||||
"cfgs": [
|
"cfgs": [
|
||||||
|
@ -1,4 +1,12 @@
|
|||||||
[
|
[
|
||||||
|
// One of Chris suggested more vanilla prototypes.
|
||||||
|
// This prototype represent a half of a normal, sink or double ribbon border,
|
||||||
|
// which looks like trapezoid from top to bottom.
|
||||||
|
//
|
||||||
|
// The bottom edge of trapezoid is from origin to +X with `long_edge_length` length.
|
||||||
|
// The length of top edge is `short_edge_length` and it just like moving bottom edge to +Y direction.
|
||||||
|
// The offset between top edge and bottom edge is always 2.5.
|
||||||
|
// The distance from the closest point of top edge, to Y axis is `short_edge_offset`.
|
||||||
{
|
{
|
||||||
"identifier": "cv_trapezoid_side",
|
"identifier": "cv_trapezoid_side",
|
||||||
"showcase": null,
|
"showcase": null,
|
||||||
@ -153,6 +161,11 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
// Same as previous one, but looks like triangle from top to bottom.
|
||||||
|
//
|
||||||
|
// The bottom edge is from origin to +X with `edge_length` length.
|
||||||
|
// The tip is going to +Y.
|
||||||
|
// The height of this triangle is always 2.5 and the offset between tip and Y axis is `tip_offset`.
|
||||||
{
|
{
|
||||||
"identifier": "cv_triangle_side",
|
"identifier": "cv_triangle_side",
|
||||||
"showcase": null,
|
"showcase": null,
|
||||||
|
@ -149,6 +149,7 @@
|
|||||||
"identifier": "floor_normal_inner_corner",
|
"identifier": "floor_normal_inner_corner",
|
||||||
"showcase": {
|
"showcase": {
|
||||||
"title": "Normal Inner Corner",
|
"title": "Normal Inner Corner",
|
||||||
|
"category": "Half Block Corners",
|
||||||
"icon": "NormalInnerCorner",
|
"icon": "NormalInnerCorner",
|
||||||
"type": "floor",
|
"type": "floor",
|
||||||
"cfgs": [
|
"cfgs": [
|
||||||
@ -201,6 +202,7 @@
|
|||||||
"identifier": "floor_sink_inner_corner",
|
"identifier": "floor_sink_inner_corner",
|
||||||
"showcase": {
|
"showcase": {
|
||||||
"title": "Sink Inner Corner",
|
"title": "Sink Inner Corner",
|
||||||
|
"category": "Half Block Corners",
|
||||||
"icon": "SinkInnerCorner",
|
"icon": "SinkInnerCorner",
|
||||||
"type": "floor",
|
"type": "floor",
|
||||||
"cfgs": [
|
"cfgs": [
|
||||||
@ -253,6 +255,7 @@
|
|||||||
"identifier": "floor_ribbon_inner_corner",
|
"identifier": "floor_ribbon_inner_corner",
|
||||||
"showcase": {
|
"showcase": {
|
||||||
"title": "Ribbon Inner Corner",
|
"title": "Ribbon Inner Corner",
|
||||||
|
"category": "Half Block Corners",
|
||||||
"icon": "RibbonInnerCorner",
|
"icon": "RibbonInnerCorner",
|
||||||
"type": "floor",
|
"type": "floor",
|
||||||
"cfgs": [
|
"cfgs": [
|
||||||
@ -305,6 +308,7 @@
|
|||||||
"identifier": "floor_normal_outter_corner",
|
"identifier": "floor_normal_outter_corner",
|
||||||
"showcase": {
|
"showcase": {
|
||||||
"title": "Normal Outter Corner",
|
"title": "Normal Outter Corner",
|
||||||
|
"category": "Half Block Corners",
|
||||||
"icon": "NormalOutterCorner",
|
"icon": "NormalOutterCorner",
|
||||||
"type": "floor",
|
"type": "floor",
|
||||||
"cfgs": [
|
"cfgs": [
|
||||||
@ -357,6 +361,7 @@
|
|||||||
"identifier": "floor_sink_outter_corner",
|
"identifier": "floor_sink_outter_corner",
|
||||||
"showcase": {
|
"showcase": {
|
||||||
"title": "Sink Outter Corner",
|
"title": "Sink Outter Corner",
|
||||||
|
"category": "Half Block Corners",
|
||||||
"icon": "SinkOutterCorner",
|
"icon": "SinkOutterCorner",
|
||||||
"type": "floor",
|
"type": "floor",
|
||||||
"cfgs": [
|
"cfgs": [
|
||||||
@ -409,6 +414,7 @@
|
|||||||
"identifier": "floor_ribbon_outter_corner",
|
"identifier": "floor_ribbon_outter_corner",
|
||||||
"showcase": {
|
"showcase": {
|
||||||
"title": "Ribbon Outter Corner",
|
"title": "Ribbon Outter Corner",
|
||||||
|
"category": "Half Block Corners",
|
||||||
"icon": "RibbonOutterCorner",
|
"icon": "RibbonOutterCorner",
|
||||||
"type": "floor",
|
"type": "floor",
|
||||||
"cfgs": [
|
"cfgs": [
|
||||||
|
@ -228,6 +228,7 @@
|
|||||||
"identifier": "floor_normal_l_crossing",
|
"identifier": "floor_normal_l_crossing",
|
||||||
"showcase": {
|
"showcase": {
|
||||||
"title": "Normal L Crossing",
|
"title": "Normal L Crossing",
|
||||||
|
"category": "Floor Crossings",
|
||||||
"icon": "NormalLCrossing",
|
"icon": "NormalLCrossing",
|
||||||
"type": "floor",
|
"type": "floor",
|
||||||
"cfgs": [
|
"cfgs": [
|
||||||
@ -278,6 +279,7 @@
|
|||||||
"identifier": "floor_sink_l_crossing",
|
"identifier": "floor_sink_l_crossing",
|
||||||
"showcase": {
|
"showcase": {
|
||||||
"title": "Sink L Crossing",
|
"title": "Sink L Crossing",
|
||||||
|
"category": "Floor Crossings",
|
||||||
"icon": "SinkLCrossing",
|
"icon": "SinkLCrossing",
|
||||||
"type": "floor",
|
"type": "floor",
|
||||||
"cfgs": [
|
"cfgs": [
|
||||||
@ -328,6 +330,7 @@
|
|||||||
"identifier": "floor_normal_t_crossing",
|
"identifier": "floor_normal_t_crossing",
|
||||||
"showcase": {
|
"showcase": {
|
||||||
"title": "Normal T Crossing",
|
"title": "Normal T Crossing",
|
||||||
|
"category": "Floor Crossings",
|
||||||
"icon": "NormalTCrossing",
|
"icon": "NormalTCrossing",
|
||||||
"type": "floor",
|
"type": "floor",
|
||||||
"cfgs": [
|
"cfgs": [
|
||||||
@ -378,6 +381,7 @@
|
|||||||
"identifier": "floor_sink_t_crossing",
|
"identifier": "floor_sink_t_crossing",
|
||||||
"showcase": {
|
"showcase": {
|
||||||
"title": "Sink T Crossing",
|
"title": "Sink T Crossing",
|
||||||
|
"category": "Floor Crossings",
|
||||||
"icon": "SinkTCrossing",
|
"icon": "SinkTCrossing",
|
||||||
"type": "floor",
|
"type": "floor",
|
||||||
"cfgs": [
|
"cfgs": [
|
||||||
@ -428,6 +432,7 @@
|
|||||||
"identifier": "floor_normal_x_crossing",
|
"identifier": "floor_normal_x_crossing",
|
||||||
"showcase": {
|
"showcase": {
|
||||||
"title": "Normal X Crossing",
|
"title": "Normal X Crossing",
|
||||||
|
"category": "Floor Crossings",
|
||||||
"icon": "NormalXCrossing",
|
"icon": "NormalXCrossing",
|
||||||
"type": "floor",
|
"type": "floor",
|
||||||
"cfgs": [
|
"cfgs": [
|
||||||
@ -478,6 +483,7 @@
|
|||||||
"identifier": "floor_sink_x_crossing",
|
"identifier": "floor_sink_x_crossing",
|
||||||
"showcase": {
|
"showcase": {
|
||||||
"title": "Sink X Crossing",
|
"title": "Sink X Crossing",
|
||||||
|
"category": "Floor Crossings",
|
||||||
"icon": "SinkXCrossing",
|
"icon": "SinkXCrossing",
|
||||||
"type": "floor",
|
"type": "floor",
|
||||||
"cfgs": [
|
"cfgs": [
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
"identifier": "floor_flat",
|
"identifier": "floor_flat",
|
||||||
"showcase": {
|
"showcase": {
|
||||||
"title": "Flat",
|
"title": "Flat",
|
||||||
|
"category": "Miscellaneous",
|
||||||
"icon": "Flat",
|
"icon": "Flat",
|
||||||
"type": "floor",
|
"type": "floor",
|
||||||
"cfgs": [
|
"cfgs": [
|
||||||
@ -36,7 +37,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"field": "is_sink_",
|
"field": "is_sink_",
|
||||||
"type": "float",
|
"type": "bool",
|
||||||
"title": "Is Sink",
|
"title": "Is Sink",
|
||||||
"desc": "Whether this flat floor is used for sink floor.",
|
"desc": "Whether this flat floor is used for sink floor.",
|
||||||
"default": "False"
|
"default": "False"
|
||||||
|
@ -116,6 +116,7 @@
|
|||||||
"identifier": "floor_normal_platform",
|
"identifier": "floor_normal_platform",
|
||||||
"showcase": {
|
"showcase": {
|
||||||
"title": "Normal Platform",
|
"title": "Normal Platform",
|
||||||
|
"category": "Platforms",
|
||||||
"icon": "NormalPlatform",
|
"icon": "NormalPlatform",
|
||||||
"type": "floor",
|
"type": "floor",
|
||||||
"cfgs": [
|
"cfgs": [
|
||||||
@ -191,6 +192,7 @@
|
|||||||
"identifier": "floor_sink_platform",
|
"identifier": "floor_sink_platform",
|
||||||
"showcase": {
|
"showcase": {
|
||||||
"title": "Sink Platform",
|
"title": "Sink Platform",
|
||||||
|
"category": "Platforms",
|
||||||
"icon": "SinkPlatform",
|
"icon": "SinkPlatform",
|
||||||
"type": "floor",
|
"type": "floor",
|
||||||
"cfgs": [
|
"cfgs": [
|
||||||
@ -266,6 +268,7 @@
|
|||||||
"identifier": "floor_ribbon_platform",
|
"identifier": "floor_ribbon_platform",
|
||||||
"showcase": {
|
"showcase": {
|
||||||
"title": "Ribbon Platform",
|
"title": "Ribbon Platform",
|
||||||
|
"category": "Platforms",
|
||||||
"icon": "RibbonPlatform",
|
"icon": "RibbonPlatform",
|
||||||
"type": "floor",
|
"type": "floor",
|
||||||
"cfgs": [
|
"cfgs": [
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
"identifier": "floor_normal_straight",
|
"identifier": "floor_normal_straight",
|
||||||
"showcase": {
|
"showcase": {
|
||||||
"title": "Normal Floor",
|
"title": "Normal Floor",
|
||||||
|
"category": "Floors",
|
||||||
"icon": "NormalFloor",
|
"icon": "NormalFloor",
|
||||||
"type": "floor",
|
"type": "floor",
|
||||||
"cfgs": [
|
"cfgs": [
|
||||||
@ -142,6 +143,7 @@
|
|||||||
"identifier": "floor_sink_straight",
|
"identifier": "floor_sink_straight",
|
||||||
"showcase": {
|
"showcase": {
|
||||||
"title": "Sink Floor",
|
"title": "Sink Floor",
|
||||||
|
"category": "Floors",
|
||||||
"icon": "SinkFloor",
|
"icon": "SinkFloor",
|
||||||
"type": "floor",
|
"type": "floor",
|
||||||
"cfgs": [
|
"cfgs": [
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
[
|
[
|
||||||
|
// The shared template prototype used by all floor terminals.
|
||||||
{
|
{
|
||||||
"identifier": "raw_floor_terminal",
|
"identifier": "raw_floor_terminal",
|
||||||
"showcase": null,
|
"showcase": null,
|
||||||
@ -22,26 +23,30 @@
|
|||||||
"faces": [],
|
"faces": [],
|
||||||
"instances": [
|
"instances": [
|
||||||
{
|
{
|
||||||
"identifier": "cv_triangle_side",
|
"identifier": "cv_trapezoid_side",
|
||||||
"skip": "False",
|
"skip": "False",
|
||||||
"params": {
|
"params": {
|
||||||
"edge_length": "2.5",
|
"long_edge_length": "5.0",
|
||||||
"tip_offset": "2.5",
|
"short_edge_offset": "2.5",
|
||||||
|
"short_edge_length": "2.5",
|
||||||
"height": "height",
|
"height": "height",
|
||||||
"face": "(face[0], False, False, face[3], face[4], None)",
|
"face": "(face[0], False, False, face[3], face[4], False)",
|
||||||
"is_sink": "is_sink"
|
"is_sink": "is_sink",
|
||||||
|
"is_ribbon": "False"
|
||||||
},
|
},
|
||||||
"transform": "ident()"
|
"transform": "ident()"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"identifier": "cv_triangle_side",
|
"identifier": "cv_trapezoid_side",
|
||||||
"skip": "False",
|
"skip": "False",
|
||||||
"params": {
|
"params": {
|
||||||
"edge_length": "2.5",
|
"long_edge_length": "5.0",
|
||||||
"tip_offset": "2.5",
|
"short_edge_offset": "2.5",
|
||||||
|
"short_edge_length": "2.5",
|
||||||
"height": "height",
|
"height": "height",
|
||||||
"face": "(face[0], False, False, face[3], face[5], None)",
|
"face": "(face[0], False, False, face[3], face[5], False)",
|
||||||
"is_sink": "is_sink"
|
"is_sink": "is_sink",
|
||||||
|
"is_ribbon": "False"
|
||||||
},
|
},
|
||||||
"transform": "move(0, 5, 0) @ scale(1, -1, 1)"
|
"transform": "move(0, 5, 0) @ scale(1, -1, 1)"
|
||||||
},
|
},
|
||||||
@ -61,7 +66,7 @@
|
|||||||
"identifier": "floor_rectangle_bottom",
|
"identifier": "floor_rectangle_bottom",
|
||||||
"skip": "not face[1]",
|
"skip": "not face[1]",
|
||||||
"params": {
|
"params": {
|
||||||
"length": "2.5",
|
"length": "5",
|
||||||
"width": "5"
|
"width": "5"
|
||||||
},
|
},
|
||||||
"transform": "move(0, 0, -height)"
|
"transform": "move(0, 0, -height)"
|
||||||
@ -72,6 +77,7 @@
|
|||||||
"identifier": "floor_normal_terminal",
|
"identifier": "floor_normal_terminal",
|
||||||
"showcase": {
|
"showcase": {
|
||||||
"title": "Normal Floor Terminal",
|
"title": "Normal Floor Terminal",
|
||||||
|
"category": "Floors",
|
||||||
"icon": "NormalFloorTerminal",
|
"icon": "NormalFloorTerminal",
|
||||||
"type": "floor",
|
"type": "floor",
|
||||||
"cfgs": [
|
"cfgs": [
|
||||||
@ -122,6 +128,7 @@
|
|||||||
"identifier": "floor_sink_terminal",
|
"identifier": "floor_sink_terminal",
|
||||||
"showcase": {
|
"showcase": {
|
||||||
"title": "Sink Floor Terminal",
|
"title": "Sink Floor Terminal",
|
||||||
|
"category": "Floors",
|
||||||
"icon": "SinkFloorTerminal",
|
"icon": "SinkFloorTerminal",
|
||||||
"type": "floor",
|
"type": "floor",
|
||||||
"cfgs": [
|
"cfgs": [
|
||||||
|
@ -137,6 +137,7 @@
|
|||||||
"identifier": "wood_trafo",
|
"identifier": "wood_trafo",
|
||||||
"showcase": {
|
"showcase": {
|
||||||
"title": "Wood Trafo",
|
"title": "Wood Trafo",
|
||||||
|
"category": "Trafo",
|
||||||
"icon": "WoodTrafo",
|
"icon": "WoodTrafo",
|
||||||
"type": "floor",
|
"type": "floor",
|
||||||
"cfgs": [
|
"cfgs": [
|
||||||
@ -187,6 +188,7 @@
|
|||||||
"identifier": "stone_trafo",
|
"identifier": "stone_trafo",
|
||||||
"showcase": {
|
"showcase": {
|
||||||
"title": "Stone Trafo",
|
"title": "Stone Trafo",
|
||||||
|
"category": "Trafo",
|
||||||
"icon": "StoneTrafo",
|
"icon": "StoneTrafo",
|
||||||
"type": "floor",
|
"type": "floor",
|
||||||
"cfgs": [
|
"cfgs": [
|
||||||
@ -237,6 +239,7 @@
|
|||||||
"identifier": "paper_trafo",
|
"identifier": "paper_trafo",
|
||||||
"showcase": {
|
"showcase": {
|
||||||
"title": "Paper Trafo",
|
"title": "Paper Trafo",
|
||||||
|
"category": "Trafo",
|
||||||
"icon": "PaperTrafo",
|
"icon": "PaperTrafo",
|
||||||
"type": "floor",
|
"type": "floor",
|
||||||
"cfgs": [
|
"cfgs": [
|
||||||
|
@ -111,6 +111,7 @@
|
|||||||
"identifier": "floor_transition",
|
"identifier": "floor_transition",
|
||||||
"showcase": {
|
"showcase": {
|
||||||
"title": "Transition",
|
"title": "Transition",
|
||||||
|
"category": "Miscellaneous",
|
||||||
"icon": "Transition",
|
"icon": "Transition",
|
||||||
"type": "floor",
|
"type": "floor",
|
||||||
"cfgs": [
|
"cfgs": [
|
||||||
@ -191,6 +192,7 @@
|
|||||||
"identifier": "floor_narrow_transition",
|
"identifier": "floor_narrow_transition",
|
||||||
"showcase": {
|
"showcase": {
|
||||||
"title": "Narrow Transition",
|
"title": "Narrow Transition",
|
||||||
|
"category": "Miscellaneous",
|
||||||
"icon": "NarrowTransition",
|
"icon": "NarrowTransition",
|
||||||
"type": "floor",
|
"type": "floor",
|
||||||
"cfgs": [
|
"cfgs": [
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
"identifier": "floor_wide_straight",
|
"identifier": "floor_wide_straight",
|
||||||
"showcase": {
|
"showcase": {
|
||||||
"title": "Wide Floor",
|
"title": "Wide Floor",
|
||||||
|
"category": "Wide Floors",
|
||||||
"icon": "WideFloor",
|
"icon": "WideFloor",
|
||||||
"type": "floor",
|
"type": "floor",
|
||||||
"cfgs": [
|
"cfgs": [
|
||||||
@ -106,6 +107,7 @@
|
|||||||
"identifier": "floor_wide_terminal",
|
"identifier": "floor_wide_terminal",
|
||||||
"showcase": {
|
"showcase": {
|
||||||
"title": "Wide Floor Terminal",
|
"title": "Wide Floor Terminal",
|
||||||
|
"category": "Wide Floors",
|
||||||
"icon": "WideFloorTerminal",
|
"icon": "WideFloorTerminal",
|
||||||
"type": "floor",
|
"type": "floor",
|
||||||
"cfgs": [
|
"cfgs": [
|
||||||
@ -166,34 +168,50 @@
|
|||||||
"transform": "rot(0, 0, 90) @ scale(1, -1, 1)"
|
"transform": "rot(0, 0, 90) @ scale(1, -1, 1)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"identifier": "cv_triangle_side",
|
"identifier": "cv_trapezoid_side",
|
||||||
"skip": "False",
|
"skip": "False",
|
||||||
"params": {
|
"params": {
|
||||||
"edge_length": "2.5",
|
"long_edge_length": "5.0",
|
||||||
"tip_offset": "2.5",
|
"short_edge_offset": "2.5",
|
||||||
|
"short_edge_length": "2.5",
|
||||||
"height": "height",
|
"height": "height",
|
||||||
"face": "(face[0], False, False, face[3], face[4], None)",
|
"face": "(face[0], False, False, face[3], face[4], False)",
|
||||||
"is_sink": "True"
|
"is_sink": "True",
|
||||||
|
"is_ribbon": "False"
|
||||||
},
|
},
|
||||||
"transform": "ident()"
|
"transform": "ident()"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"identifier": "cv_triangle_side",
|
"identifier": "cv_trapezoid_side",
|
||||||
"skip": "False",
|
"skip": "False",
|
||||||
"params": {
|
"params": {
|
||||||
"edge_length": "2.5",
|
"long_edge_length": "5.0",
|
||||||
"tip_offset": "2.5",
|
"short_edge_offset": "2.5",
|
||||||
|
"short_edge_length": "2.5",
|
||||||
"height": "height",
|
"height": "height",
|
||||||
"face": "(face[0], False, False, face[3], face[5], None)",
|
"face": "(face[0], False, False, face[3], face[5], False)",
|
||||||
"is_sink": "True"
|
"is_sink": "True",
|
||||||
|
"is_ribbon": "False"
|
||||||
},
|
},
|
||||||
"transform": "move(0, width + 5, 0) @ scale(1, -1, 1)"
|
"transform": "move(0, width + 5, 0) @ scale(1, -1, 1)"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"identifier": "floor_flat",
|
||||||
|
"skip": "False",
|
||||||
|
"params": {
|
||||||
|
"height": "height",
|
||||||
|
"length": "2.5",
|
||||||
|
"width": "width",
|
||||||
|
"face": "(face[0], False, False, face[3], False, False)",
|
||||||
|
"is_sink": "True"
|
||||||
|
},
|
||||||
|
"transform": "move(2.5, 2.5, 0)"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"identifier": "floor_rectangle_bottom",
|
"identifier": "floor_rectangle_bottom",
|
||||||
"skip": "not face[1]",
|
"skip": "not face[1]",
|
||||||
"params": {
|
"params": {
|
||||||
"length": "2.5",
|
"length": "5",
|
||||||
"width": "5 + width"
|
"width": "5 + width"
|
||||||
},
|
},
|
||||||
"transform": "move(0, 0, -height)"
|
"transform": "move(0, 0, -height)"
|
||||||
@ -204,6 +222,7 @@
|
|||||||
"identifier": "floor_wide_l_crossing",
|
"identifier": "floor_wide_l_crossing",
|
||||||
"showcase": {
|
"showcase": {
|
||||||
"title": "Wide Floor L Crossing",
|
"title": "Wide Floor L Crossing",
|
||||||
|
"category": "Wide Floors",
|
||||||
"icon": "WideLCrossing",
|
"icon": "WideLCrossing",
|
||||||
"type": "floor",
|
"type": "floor",
|
||||||
"cfgs": [
|
"cfgs": [
|
||||||
@ -336,6 +355,7 @@
|
|||||||
"identifier": "floor_wide_t_crossing",
|
"identifier": "floor_wide_t_crossing",
|
||||||
"showcase": {
|
"showcase": {
|
||||||
"title": "Wide Floor T Crossing",
|
"title": "Wide Floor T Crossing",
|
||||||
|
"category": "Wide Floors",
|
||||||
"icon": "WideTCrossing",
|
"icon": "WideTCrossing",
|
||||||
"type": "floor",
|
"type": "floor",
|
||||||
"cfgs": [
|
"cfgs": [
|
||||||
@ -459,6 +479,7 @@
|
|||||||
"identifier": "floor_wide_x_crossing",
|
"identifier": "floor_wide_x_crossing",
|
||||||
"showcase": {
|
"showcase": {
|
||||||
"title": "Wide Floor X Crossing",
|
"title": "Wide Floor X Crossing",
|
||||||
|
"category": "Wide Floors",
|
||||||
"icon": "WideXCrossing",
|
"icon": "WideXCrossing",
|
||||||
"type": "floor",
|
"type": "floor",
|
||||||
"cfgs": [
|
"cfgs": [
|
||||||
|
@ -250,7 +250,7 @@ class BBP_OT_add_bme_struct(bpy.types.Operator):
|
|||||||
case UTIL_bme.PrototypeShowcaseCfgsTypes.Float:
|
case UTIL_bme.PrototypeShowcaseCfgsTypes.Float:
|
||||||
box_layout.prop(op_cfgs_visitor[cfg_index], 'prop_float', text='')
|
box_layout.prop(op_cfgs_visitor[cfg_index], 'prop_float', text='')
|
||||||
case UTIL_bme.PrototypeShowcaseCfgsTypes.Boolean:
|
case UTIL_bme.PrototypeShowcaseCfgsTypes.Boolean:
|
||||||
box_layout.prop(op_cfgs_visitor[cfg_index], 'prop_bool', text='')
|
box_layout.prop(op_cfgs_visitor[cfg_index], 'prop_bool', toggle=1, text='Yes', text_ctxt='BBP_OT_add_bme_struct/draw')
|
||||||
case UTIL_bme.PrototypeShowcaseCfgsTypes.Face:
|
case UTIL_bme.PrototypeShowcaseCfgsTypes.Face:
|
||||||
# face will show a special layout (grid view)
|
# face will show a special layout (grid view)
|
||||||
grids = box_layout.grid_flow(
|
grids = box_layout.grid_flow(
|
||||||
@ -280,16 +280,24 @@ class BBP_OT_add_bme_struct(bpy.types.Operator):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def draw_blc_menu(cls, layout: bpy.types.UILayout):
|
def draw_blc_menu(cls, layout: bpy.types.UILayout):
|
||||||
for ident in _g_EnumHelper_BmeStructType.get_bme_identifiers():
|
for category, idents in _g_EnumHelper_BmeStructType.get_bme_categories().items():
|
||||||
# draw operator
|
# draw category label
|
||||||
cop = layout.operator(
|
layout.label(text=category, text_ctxt=UTIL_translation.build_prototype_showcase_category_context())
|
||||||
cls.bl_idname,
|
|
||||||
text = _g_EnumHelper_BmeStructType.get_bme_showcase_title(ident),
|
# draw prototypes list
|
||||||
icon_value = _g_EnumHelper_BmeStructType.get_bme_showcase_icon(ident),
|
for ident in idents:
|
||||||
text_ctxt = UTIL_translation.build_prototype_showcase_context(ident),
|
# draw operator
|
||||||
)
|
cop = layout.operator(
|
||||||
# and assign its init type value
|
cls.bl_idname,
|
||||||
cop.bme_struct_type = _g_EnumHelper_BmeStructType.to_selection(ident)
|
text = _g_EnumHelper_BmeStructType.get_bme_showcase_title(ident),
|
||||||
|
icon_value = _g_EnumHelper_BmeStructType.get_bme_showcase_icon(ident),
|
||||||
|
text_ctxt = UTIL_translation.build_prototype_showcase_title_context(ident),
|
||||||
|
)
|
||||||
|
# and assign its init type value
|
||||||
|
cop.bme_struct_type = _g_EnumHelper_BmeStructType.to_selection(ident)
|
||||||
|
|
||||||
|
# draw separator
|
||||||
|
layout.separator()
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import bpy, mathutils, math
|
import bpy, mathutils, math
|
||||||
import typing
|
import typing
|
||||||
from . import UTIL_rail_creator
|
from . import UTIL_rail_creator, PROP_preferences
|
||||||
|
|
||||||
## Const Value Hint:
|
## Const Value Hint:
|
||||||
# Default Rail Radius: 0.35 (in measure)
|
# Default Rail Radius: 0.35 (in measure)
|
||||||
@ -233,6 +233,10 @@ class BBP_OT_add_rail_section(SharedRailSectionInputProperty, bpy.types.Operator
|
|||||||
bl_options = {'REGISTER', 'UNDO'}
|
bl_options = {'REGISTER', 'UNDO'}
|
||||||
bl_translation_context = 'BBP_OT_add_rail_section'
|
bl_translation_context = 'BBP_OT_add_rail_section'
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def poll(cls, context):
|
||||||
|
return PROP_preferences.get_raw_preferences().has_valid_blc_tex_folder()
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
UTIL_rail_creator.rail_creator_wrapper(
|
UTIL_rail_creator.rail_creator_wrapper(
|
||||||
lambda bm: UTIL_rail_creator.create_rail_section(
|
lambda bm: UTIL_rail_creator.create_rail_section(
|
||||||
@ -254,6 +258,10 @@ class BBP_OT_add_transition_section(bpy.types.Operator):
|
|||||||
bl_options = {'REGISTER', 'UNDO'}
|
bl_options = {'REGISTER', 'UNDO'}
|
||||||
bl_translation_context = 'BBP_OT_add_transition_section'
|
bl_translation_context = 'BBP_OT_add_transition_section'
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def poll(cls, context):
|
||||||
|
return PROP_preferences.get_raw_preferences().has_valid_blc_tex_folder()
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
UTIL_rail_creator.rail_creator_wrapper(
|
UTIL_rail_creator.rail_creator_wrapper(
|
||||||
lambda bm: UTIL_rail_creator.create_transition_section(bm, c_DefaultRailRadius, c_DefaultRailSpan),
|
lambda bm: UTIL_rail_creator.create_transition_section(bm, c_DefaultRailRadius, c_DefaultRailSpan),
|
||||||
@ -272,6 +280,10 @@ class BBP_OT_add_straight_rail(SharedExtraTransform, SharedRailSectionInputPrope
|
|||||||
bl_options = {'REGISTER', 'UNDO'}
|
bl_options = {'REGISTER', 'UNDO'}
|
||||||
bl_translation_context = 'BBP_OT_add_straight_rail'
|
bl_translation_context = 'BBP_OT_add_straight_rail'
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def poll(cls, context):
|
||||||
|
return PROP_preferences.get_raw_preferences().has_valid_blc_tex_folder()
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
UTIL_rail_creator.rail_creator_wrapper(
|
UTIL_rail_creator.rail_creator_wrapper(
|
||||||
lambda bm: UTIL_rail_creator.create_straight_rail(
|
lambda bm: UTIL_rail_creator.create_straight_rail(
|
||||||
@ -301,6 +313,10 @@ class BBP_OT_add_transition_rail(SharedExtraTransform, SharedRailCapInputPropert
|
|||||||
bl_options = {'REGISTER', 'UNDO'}
|
bl_options = {'REGISTER', 'UNDO'}
|
||||||
bl_translation_context = 'BBP_OT_add_transition_rail'
|
bl_translation_context = 'BBP_OT_add_transition_rail'
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def poll(cls, context):
|
||||||
|
return PROP_preferences.get_raw_preferences().has_valid_blc_tex_folder()
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
UTIL_rail_creator.rail_creator_wrapper(
|
UTIL_rail_creator.rail_creator_wrapper(
|
||||||
lambda bm: UTIL_rail_creator.create_transition_rail(
|
lambda bm: UTIL_rail_creator.create_transition_rail(
|
||||||
@ -340,6 +356,10 @@ class BBP_OT_add_side_rail(SharedExtraTransform, SharedRailCapInputProperty, Sha
|
|||||||
translation_context = 'BBP_OT_add_side_rail/property'
|
translation_context = 'BBP_OT_add_side_rail/property'
|
||||||
) # type: ignore
|
) # type: ignore
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def poll(cls, context):
|
||||||
|
return PROP_preferences.get_raw_preferences().has_valid_blc_tex_folder()
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
UTIL_rail_creator.rail_creator_wrapper(
|
UTIL_rail_creator.rail_creator_wrapper(
|
||||||
lambda bm: UTIL_rail_creator.create_straight_rail(
|
lambda bm: UTIL_rail_creator.create_straight_rail(
|
||||||
@ -379,6 +399,10 @@ class BBP_OT_add_arc_rail(SharedExtraTransform, SharedRailSectionInputProperty,
|
|||||||
translation_context = 'BBP_OT_add_arc_rail/property'
|
translation_context = 'BBP_OT_add_arc_rail/property'
|
||||||
) # type: ignore
|
) # type: ignore
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def poll(cls, context):
|
||||||
|
return PROP_preferences.get_raw_preferences().has_valid_blc_tex_folder()
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
UTIL_rail_creator.rail_creator_wrapper(
|
UTIL_rail_creator.rail_creator_wrapper(
|
||||||
lambda bm: UTIL_rail_creator.create_screw_rail(
|
lambda bm: UTIL_rail_creator.create_screw_rail(
|
||||||
@ -430,6 +454,10 @@ class BBP_OT_add_spiral_rail(SharedExtraTransform, SharedRailCapInputProperty, S
|
|||||||
translation_context = 'BBP_OT_add_spiral_rail/property'
|
translation_context = 'BBP_OT_add_spiral_rail/property'
|
||||||
) # type: ignore
|
) # type: ignore
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def poll(cls, context):
|
||||||
|
return PROP_preferences.get_raw_preferences().has_valid_blc_tex_folder()
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
UTIL_rail_creator.rail_creator_wrapper(
|
UTIL_rail_creator.rail_creator_wrapper(
|
||||||
lambda bm: UTIL_rail_creator.create_screw_rail(
|
lambda bm: UTIL_rail_creator.create_screw_rail(
|
||||||
@ -474,6 +502,10 @@ class BBP_OT_add_side_spiral_rail(SharedExtraTransform, SharedRailSectionInputPr
|
|||||||
translation_context = 'BBP_OT_add_side_spiral_rail/property'
|
translation_context = 'BBP_OT_add_side_spiral_rail/property'
|
||||||
) # type: ignore
|
) # type: ignore
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def poll(cls, context):
|
||||||
|
return PROP_preferences.get_raw_preferences().has_valid_blc_tex_folder()
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
UTIL_rail_creator.rail_creator_wrapper(
|
UTIL_rail_creator.rail_creator_wrapper(
|
||||||
lambda bm: UTIL_rail_creator.create_screw_rail(
|
lambda bm: UTIL_rail_creator.create_screw_rail(
|
||||||
|
@ -18,10 +18,16 @@ class BBP_OT_export_virtools(bpy.types.Operator, UTIL_file_browser.ExportVirtool
|
|||||||
return (
|
return (
|
||||||
PROP_preferences.get_raw_preferences().has_valid_blc_tex_folder()
|
PROP_preferences.get_raw_preferences().has_valid_blc_tex_folder()
|
||||||
and bmap.is_bmap_available())
|
and bmap.is_bmap_available())
|
||||||
|
|
||||||
|
def invoke(self, context, event):
|
||||||
|
# preset virtools encoding if possible
|
||||||
|
self.preset_vt_encodings_if_possible(context)
|
||||||
|
# call parent invoke function (same reason written in IMPORT module)
|
||||||
|
return super().invoke(context, event)
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
# check selecting first
|
# check selecting first
|
||||||
objls: tuple[bpy.types.Object] | None = self.general_get_export_objects(context)
|
objls: tuple[bpy.types.Object, ...] | None = self.general_get_export_objects(context)
|
||||||
if objls is None:
|
if objls is None:
|
||||||
self.report({'ERROR'}, 'No selected target!')
|
self.report({'ERROR'}, 'No selected target!')
|
||||||
return {'CANCELLED'}
|
return {'CANCELLED'}
|
||||||
@ -38,10 +44,16 @@ class BBP_OT_export_virtools(bpy.types.Operator, UTIL_file_browser.ExportVirtool
|
|||||||
self.report({'ERROR'}, 'You must specify at least one encoding for file saving (e.g. cp1252, gbk)!')
|
self.report({'ERROR'}, 'You must specify at least one encoding for file saving (e.g. cp1252, gbk)!')
|
||||||
return {'CANCELLED'}
|
return {'CANCELLED'}
|
||||||
|
|
||||||
|
# check file name
|
||||||
|
filename = self.general_get_filename()
|
||||||
|
if not os.path.isfile(filename):
|
||||||
|
self.report({'ERROR'}, 'No file was selected!')
|
||||||
|
return {'CANCELLED'}
|
||||||
|
|
||||||
# start exporting
|
# start exporting
|
||||||
with UTIL_ioport_shared.ExportEditModeBackup() as editmode_guard:
|
with UTIL_ioport_shared.ExportEditModeBackup() as editmode_guard:
|
||||||
_export_virtools(
|
_export_virtools(
|
||||||
self.general_get_filename(),
|
filename,
|
||||||
encodings,
|
encodings,
|
||||||
texture_save_opt,
|
texture_save_opt,
|
||||||
self.general_get_use_compress(),
|
self.general_get_use_compress(),
|
||||||
@ -68,7 +80,7 @@ _TTexturePair = tuple[bpy.types.Image, bmap.BMTexture]
|
|||||||
|
|
||||||
def _export_virtools(
|
def _export_virtools(
|
||||||
file_name_: str,
|
file_name_: str,
|
||||||
encodings_: tuple[str],
|
encodings_: tuple[str, ...],
|
||||||
texture_save_opt_: UTIL_virtools_types.CK_TEXTURE_SAVEOPTIONS,
|
texture_save_opt_: UTIL_virtools_types.CK_TEXTURE_SAVEOPTIONS,
|
||||||
use_compress_: bool,
|
use_compress_: bool,
|
||||||
compress_level_: int,
|
compress_level_: int,
|
||||||
|
@ -18,6 +18,12 @@ class BBP_OT_import_virtools(bpy.types.Operator, UTIL_file_browser.ImportVirtool
|
|||||||
return (
|
return (
|
||||||
PROP_preferences.get_raw_preferences().has_valid_blc_tex_folder()
|
PROP_preferences.get_raw_preferences().has_valid_blc_tex_folder()
|
||||||
and bmap.is_bmap_available())
|
and bmap.is_bmap_available())
|
||||||
|
|
||||||
|
def invoke(self, context, event):
|
||||||
|
# preset virtools encoding if possible
|
||||||
|
self.preset_vt_encodings_if_possible(context)
|
||||||
|
# call parent invoke function (do no call self "execute", because we need show a modal window)
|
||||||
|
return super().invoke(context, event)
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
# check whether encoding list is empty to avoid real stupid user.
|
# check whether encoding list is empty to avoid real stupid user.
|
||||||
@ -26,8 +32,14 @@ class BBP_OT_import_virtools(bpy.types.Operator, UTIL_file_browser.ImportVirtool
|
|||||||
self.report({'ERROR'}, 'You must specify at least one encoding for file loading (e.g. cp1252, gbk)!')
|
self.report({'ERROR'}, 'You must specify at least one encoding for file loading (e.g. cp1252, gbk)!')
|
||||||
return {'CANCELLED'}
|
return {'CANCELLED'}
|
||||||
|
|
||||||
|
# check file name
|
||||||
|
filename = self.general_get_filename()
|
||||||
|
if not os.path.isfile(filename):
|
||||||
|
self.report({'ERROR'}, 'No file was selected!')
|
||||||
|
return {'CANCELLED'}
|
||||||
|
|
||||||
_import_virtools(
|
_import_virtools(
|
||||||
self.general_get_filename(),
|
filename,
|
||||||
encodings,
|
encodings,
|
||||||
self.general_get_conflict_resolver()
|
self.general_get_conflict_resolver()
|
||||||
)
|
)
|
||||||
@ -40,7 +52,7 @@ class BBP_OT_import_virtools(bpy.types.Operator, UTIL_file_browser.ImportVirtool
|
|||||||
self.draw_virtools_params(context, layout, True)
|
self.draw_virtools_params(context, layout, True)
|
||||||
self.draw_ballance_params(layout, True)
|
self.draw_ballance_params(layout, True)
|
||||||
|
|
||||||
def _import_virtools(file_name_: str, encodings_: tuple[str], resolver: UTIL_ioport_shared.ConflictResolver) -> None:
|
def _import_virtools(file_name_: str, encodings_: tuple[str, ...], resolver: UTIL_ioport_shared.ConflictResolver) -> None:
|
||||||
# create temp folder
|
# create temp folder
|
||||||
with tempfile.TemporaryDirectory() as vt_temp_folder:
|
with tempfile.TemporaryDirectory() as vt_temp_folder:
|
||||||
tr_text: str = bpy.app.translations.pgettext_rpt(
|
tr_text: str = bpy.app.translations.pgettext_rpt(
|
||||||
|
399
bbp_ng/OP_OBJECT_game_view.py
Normal file
399
bbp_ng/OP_OBJECT_game_view.py
Normal file
@ -0,0 +1,399 @@
|
|||||||
|
import bpy, mathutils
|
||||||
|
import typing, enum, math
|
||||||
|
from . import UTIL_functions
|
||||||
|
|
||||||
|
# TODO:
|
||||||
|
# This file should have fully refactor after we finish Virtools Camera import and export,
|
||||||
|
# because this module is highly rely on it. Current implementation is a compromise.
|
||||||
|
# There is a list of things to be done:
|
||||||
|
# - Remove BBP_OT_game_resolution operator, because Virtools Camera will have similar function in panel.
|
||||||
|
# - Update BBP_OT_game_cameraoperator with Virtools Camera.
|
||||||
|
|
||||||
|
#region Game Resolution
|
||||||
|
|
||||||
|
class ResolutionKind(enum.IntEnum):
|
||||||
|
Normal = enum.auto()
|
||||||
|
Extended = enum.auto()
|
||||||
|
Widescreen = enum.auto()
|
||||||
|
Panoramic = enum.auto()
|
||||||
|
|
||||||
|
def to_resolution(self) -> tuple[int, int]:
|
||||||
|
match self:
|
||||||
|
case ResolutionKind.Normal: return (1024, 768)
|
||||||
|
case ResolutionKind.Extended: return (1280, 720)
|
||||||
|
case ResolutionKind.Widescreen: return (1400, 600)
|
||||||
|
case ResolutionKind.Panoramic: return (2000, 700)
|
||||||
|
|
||||||
|
_g_ResolutionKindDesc: dict[ResolutionKind, tuple[str, str]] = {
|
||||||
|
ResolutionKind.Normal: ("Normal", "Aspect ratio: 4:3."),
|
||||||
|
ResolutionKind.Extended: ("Extended", "Aspect ratio: 16:9."),
|
||||||
|
ResolutionKind.Widescreen: ("Widescreen", "Aspect ratio: 7:3."),
|
||||||
|
ResolutionKind.Panoramic: ("Panoramic", "Aspect ratio: 20:7."),
|
||||||
|
}
|
||||||
|
_g_EnumHelper_ResolutionKind = UTIL_functions.EnumPropHelper(
|
||||||
|
ResolutionKind,
|
||||||
|
lambda x: str(x.value),
|
||||||
|
lambda x: ResolutionKind(int(x)),
|
||||||
|
lambda x: _g_ResolutionKindDesc[x][0],
|
||||||
|
lambda x: _g_ResolutionKindDesc[x][1],
|
||||||
|
lambda _: ""
|
||||||
|
)
|
||||||
|
|
||||||
|
class BBP_OT_game_resolution(bpy.types.Operator):
|
||||||
|
"""Set Blender render resolution to Ballance game"""
|
||||||
|
bl_idname = "bbp.game_resolution"
|
||||||
|
bl_label = "Game Resolution"
|
||||||
|
bl_options = {'REGISTER', 'UNDO'}
|
||||||
|
bl_translation_context = 'BBP_OT_game_resolution'
|
||||||
|
|
||||||
|
resolution_kind: bpy.props.EnumProperty(
|
||||||
|
name = "Resolution Kind",
|
||||||
|
description = "The type of preset resolution.",
|
||||||
|
items = _g_EnumHelper_ResolutionKind.generate_items(),
|
||||||
|
default = _g_EnumHelper_ResolutionKind.to_selection(ResolutionKind.Normal)
|
||||||
|
) # type: ignore
|
||||||
|
|
||||||
|
def invoke(self, context, event):
|
||||||
|
return self.execute(context)
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
layout = self.layout
|
||||||
|
layout.use_property_split = True
|
||||||
|
layout.prop(self, 'resolution_kind')
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
# fetch resolution
|
||||||
|
resolution_kind = _g_EnumHelper_ResolutionKind.get_selection(self.resolution_kind)
|
||||||
|
resolution = resolution_kind.to_resolution()
|
||||||
|
# setup resolution
|
||||||
|
render_settings = bpy.context.scene.render
|
||||||
|
render_settings.resolution_x = resolution[0]
|
||||||
|
render_settings.resolution_y = resolution[1]
|
||||||
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Game Camera
|
||||||
|
|
||||||
|
#region Enum Defines
|
||||||
|
|
||||||
|
class TargetKind(enum.IntEnum):
|
||||||
|
Cursor = enum.auto()
|
||||||
|
ActiveObject = enum.auto()
|
||||||
|
_g_TargetKindDesc: dict[TargetKind, tuple[str, str, str]] = {
|
||||||
|
TargetKind.Cursor: ("3D Cursor", "3D cursor is player ball.", "CURSOR"),
|
||||||
|
TargetKind.ActiveObject: ("Active Object", "The origin point of active object is player ball.", "OBJECT_DATA"),
|
||||||
|
}
|
||||||
|
_g_EnumHelper_TargetKind = UTIL_functions.EnumPropHelper(
|
||||||
|
TargetKind,
|
||||||
|
lambda x: str(x.value),
|
||||||
|
lambda x: TargetKind(int(x)),
|
||||||
|
lambda x: _g_TargetKindDesc[x][0],
|
||||||
|
lambda x: _g_TargetKindDesc[x][1],
|
||||||
|
lambda x: _g_TargetKindDesc[x][2],
|
||||||
|
)
|
||||||
|
|
||||||
|
class RotationKind(enum.IntEnum):
|
||||||
|
Preset = enum.auto()
|
||||||
|
Custom = enum.auto()
|
||||||
|
_g_RotationKindDesc: dict[RotationKind, tuple[str, str]] = {
|
||||||
|
RotationKind.Preset: ("Preset", "8 preset rotation angles usually used in game."),
|
||||||
|
RotationKind.Custom: ("Custom", "User manually input rotation angle.")
|
||||||
|
}
|
||||||
|
_g_EnumHelper_RotationKind = UTIL_functions.EnumPropHelper(
|
||||||
|
RotationKind,
|
||||||
|
lambda x: str(x.value),
|
||||||
|
lambda x: RotationKind(int(x)),
|
||||||
|
lambda x: _g_RotationKindDesc[x][0],
|
||||||
|
lambda x: _g_RotationKindDesc[x][1],
|
||||||
|
lambda _: ""
|
||||||
|
)
|
||||||
|
|
||||||
|
class RotationAngle(enum.IntEnum):
|
||||||
|
Deg0 = enum.auto()
|
||||||
|
Deg45 = enum.auto()
|
||||||
|
Deg90 = enum.auto()
|
||||||
|
Deg135 = enum.auto()
|
||||||
|
Deg180 = enum.auto()
|
||||||
|
Deg225 = enum.auto()
|
||||||
|
Deg270 = enum.auto()
|
||||||
|
Deg315 = enum.auto()
|
||||||
|
|
||||||
|
def to_degree(self) -> float:
|
||||||
|
match self:
|
||||||
|
case RotationAngle.Deg0: return 0
|
||||||
|
case RotationAngle.Deg45: return 45
|
||||||
|
case RotationAngle.Deg90: return 90
|
||||||
|
case RotationAngle.Deg135: return 135
|
||||||
|
case RotationAngle.Deg180: return 180
|
||||||
|
case RotationAngle.Deg225: return 225
|
||||||
|
case RotationAngle.Deg270: return 270
|
||||||
|
case RotationAngle.Deg315: return 315
|
||||||
|
|
||||||
|
def to_radians(self) -> float:
|
||||||
|
return math.radians(self.to_degree())
|
||||||
|
|
||||||
|
_g_RotationAngleDesc: dict[RotationAngle, tuple[str, str]] = {
|
||||||
|
# TODO: Add axis direction in description after we add Camera support when importing
|
||||||
|
# (because we only can confirm game camera behavior after that).
|
||||||
|
RotationAngle.Deg0: ("0 Degree", "0 degree"),
|
||||||
|
RotationAngle.Deg45: ("45 Degree", "45 degree"),
|
||||||
|
RotationAngle.Deg90: ("90 Degree", "90 degree"),
|
||||||
|
RotationAngle.Deg135: ("135 Degree", "135 degree"),
|
||||||
|
RotationAngle.Deg180: ("180 Degree", "180 degree"),
|
||||||
|
RotationAngle.Deg225: ("225 Degree", "225 degree"),
|
||||||
|
RotationAngle.Deg270: ("270 Degree", "270 degree"),
|
||||||
|
RotationAngle.Deg315: ("315 Degree", "315 degree"),
|
||||||
|
}
|
||||||
|
_g_EnumHelper_RotationAngle = UTIL_functions.EnumPropHelper(
|
||||||
|
RotationAngle,
|
||||||
|
lambda x: str(x.value),
|
||||||
|
lambda x: RotationAngle(int(x)),
|
||||||
|
lambda x: _g_RotationAngleDesc[x][0],
|
||||||
|
lambda x: _g_RotationAngleDesc[x][1],
|
||||||
|
lambda _: ""
|
||||||
|
)
|
||||||
|
|
||||||
|
class PerspectiveKind(enum.IntEnum):
|
||||||
|
Ordinary = enum.auto()
|
||||||
|
Lift = enum.auto()
|
||||||
|
EasterEgg = enum.auto()
|
||||||
|
_g_PerspectiveKindDesc: dict[PerspectiveKind, tuple[str, str]] = {
|
||||||
|
PerspectiveKind.Ordinary: ("Ordinary", "The default perspective for game camera."),
|
||||||
|
PerspectiveKind.Lift: ("Lift", "Lifted camera in game for downcast level."),
|
||||||
|
PerspectiveKind.EasterEgg: ("Easter Egg", "A very close view to player ball in game."),
|
||||||
|
}
|
||||||
|
_g_EnumHelper_PerspectiveKind = UTIL_functions.EnumPropHelper(
|
||||||
|
PerspectiveKind,
|
||||||
|
lambda x: str(x.value),
|
||||||
|
lambda x: PerspectiveKind(int(x)),
|
||||||
|
lambda x: _g_PerspectiveKindDesc[x][0],
|
||||||
|
lambda x: _g_PerspectiveKindDesc[x][1],
|
||||||
|
lambda _: ""
|
||||||
|
)
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
class BBP_OT_game_camera(bpy.types.Operator):
|
||||||
|
"""Order active camera look at target like Ballance does"""
|
||||||
|
bl_idname = "bbp.game_camera"
|
||||||
|
bl_label = "Game Camera"
|
||||||
|
bl_options = {'REGISTER', 'UNDO'}
|
||||||
|
bl_translation_context = 'BBP_OT_game_camera'
|
||||||
|
|
||||||
|
target_kind: bpy.props.EnumProperty(
|
||||||
|
name = "Target Kind",
|
||||||
|
description = "",
|
||||||
|
items = _g_EnumHelper_TargetKind.generate_items(),
|
||||||
|
default = _g_EnumHelper_TargetKind.to_selection(TargetKind.Cursor)
|
||||||
|
) # type: ignore
|
||||||
|
|
||||||
|
rotation_kind: bpy.props.EnumProperty(
|
||||||
|
name = "Rotation Angle Kind",
|
||||||
|
description = "",
|
||||||
|
items = _g_EnumHelper_RotationKind.generate_items(),
|
||||||
|
default = _g_EnumHelper_RotationKind.to_selection(RotationKind.Preset)
|
||||||
|
) # type: ignore
|
||||||
|
preset_rotation_angle: bpy.props.EnumProperty(
|
||||||
|
name = "Preset Rotation Angle",
|
||||||
|
description = "",
|
||||||
|
items = _g_EnumHelper_RotationAngle.generate_items(),
|
||||||
|
default = _g_EnumHelper_RotationAngle.to_selection(RotationAngle.Deg0)
|
||||||
|
) # type: ignore
|
||||||
|
custom_rotation_angle: bpy.props.FloatProperty(
|
||||||
|
name = "Custom Rotation Angle",
|
||||||
|
description = "The rotation angle of camera relative to 3D Cursor",
|
||||||
|
subtype = 'ANGLE',
|
||||||
|
min = 0, max = math.radians(360),
|
||||||
|
step = 100,
|
||||||
|
# MARK: What the fuck of the precision?
|
||||||
|
# I set it to 2 but it doesn't work so I forcely set it to 100.
|
||||||
|
precision = 100,
|
||||||
|
) # type: ignore
|
||||||
|
|
||||||
|
perspective_kind: bpy.props.EnumProperty(
|
||||||
|
name = "Rotation Angle Kind",
|
||||||
|
description = "",
|
||||||
|
items = _g_EnumHelper_PerspectiveKind.generate_items(),
|
||||||
|
default = _g_EnumHelper_PerspectiveKind.to_selection(PerspectiveKind.Ordinary)
|
||||||
|
) # type: ignore
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def poll(cls, context):
|
||||||
|
# find camera object
|
||||||
|
camera_obj = _find_camera_obj()
|
||||||
|
if camera_obj is None: return False
|
||||||
|
# find active object
|
||||||
|
active_obj = bpy.context.active_object
|
||||||
|
if active_obj is None: return False
|
||||||
|
# camera object should not be active object
|
||||||
|
return camera_obj != active_obj
|
||||||
|
|
||||||
|
def invoke(self, context, event):
|
||||||
|
# order user enter camera view
|
||||||
|
_enter_camera_view()
|
||||||
|
# then execute following code
|
||||||
|
return self.execute(context)
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
layout = self.layout
|
||||||
|
|
||||||
|
# Show target picker
|
||||||
|
layout.label(text='Target', text_ctxt='BBP_OT_game_camera/draw')
|
||||||
|
layout.row().prop(self, 'target_kind', expand=True)
|
||||||
|
|
||||||
|
# Show rotation angle according to different types.
|
||||||
|
layout.separator()
|
||||||
|
layout.label(text='Rotation', text_ctxt='BBP_OT_game_camera/draw')
|
||||||
|
layout.row().prop(self, 'rotation_kind', expand=True)
|
||||||
|
rot_kind = _g_EnumHelper_RotationKind.get_selection(self.rotation_kind)
|
||||||
|
match rot_kind:
|
||||||
|
case RotationKind.Preset:
|
||||||
|
layout.prop(self, 'preset_rotation_angle', text='')
|
||||||
|
case RotationKind.Custom:
|
||||||
|
layout.prop(self, 'custom_rotation_angle', text='')
|
||||||
|
|
||||||
|
# Show perspective kind
|
||||||
|
layout.separator()
|
||||||
|
layout.label(text='Perspective', text_ctxt='BBP_OT_game_camera/draw')
|
||||||
|
layout.row().prop(self, 'perspective_kind', expand=True)
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
# fetch angle
|
||||||
|
angle: float
|
||||||
|
rot_kind = _g_EnumHelper_RotationKind.get_selection(self.rotation_kind)
|
||||||
|
match rot_kind:
|
||||||
|
case RotationKind.Preset:
|
||||||
|
rot_angle = _g_EnumHelper_RotationAngle.get_selection(self.preset_rotation_angle)
|
||||||
|
angle = rot_angle.to_radians()
|
||||||
|
case RotationKind.Custom:
|
||||||
|
angle = float(self.custom_rotation_angle)
|
||||||
|
# fetch others
|
||||||
|
camera_obj = typing.cast(bpy.types.Object, _find_camera_obj())
|
||||||
|
target_kind = _g_EnumHelper_TargetKind.get_selection(self.target_kind)
|
||||||
|
perspective_kind = _g_EnumHelper_PerspectiveKind.get_selection(self.perspective_kind)
|
||||||
|
|
||||||
|
# setup its transform and properties
|
||||||
|
glob_trans = _fetch_glob_translation(camera_obj, target_kind)
|
||||||
|
_setup_camera_transform(camera_obj, angle, perspective_kind, glob_trans)
|
||||||
|
_setup_camera_properties(camera_obj)
|
||||||
|
|
||||||
|
# return
|
||||||
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
def _find_3d_view_space() -> bpy.types.SpaceView3D | None:
|
||||||
|
# get current area
|
||||||
|
area = bpy.context.area
|
||||||
|
if area is None: return None
|
||||||
|
|
||||||
|
# check whether it is 3d view
|
||||||
|
if area.type != 'VIEW_3D': return None
|
||||||
|
|
||||||
|
# get the active space in area
|
||||||
|
space = area.spaces.active
|
||||||
|
if space is None: return None
|
||||||
|
|
||||||
|
# okey. cast its type and return
|
||||||
|
return typing.cast(bpy.types.SpaceView3D, space)
|
||||||
|
|
||||||
|
def _enter_camera_view() -> None:
|
||||||
|
space = _find_3d_view_space()
|
||||||
|
if space is None: return
|
||||||
|
|
||||||
|
region = space.region_3d
|
||||||
|
if region is None: return
|
||||||
|
|
||||||
|
region.view_perspective = 'CAMERA'
|
||||||
|
|
||||||
|
def _find_camera_obj() -> bpy.types.Object | None:
|
||||||
|
space = _find_3d_view_space()
|
||||||
|
if space is None: return None
|
||||||
|
|
||||||
|
return space.camera
|
||||||
|
|
||||||
|
def _fetch_glob_translation(camobj: bpy.types.Object, target_kind: TargetKind) -> mathutils.Vector:
|
||||||
|
# we have checked any bad cases in "poll",
|
||||||
|
# so we can simply return value in there without any check.
|
||||||
|
match target_kind:
|
||||||
|
case TargetKind.Cursor:
|
||||||
|
return bpy.context.scene.cursor.location
|
||||||
|
case TargetKind.ActiveObject:
|
||||||
|
return bpy.context.active_object.location
|
||||||
|
|
||||||
|
def _setup_camera_transform(camobj: bpy.types.Object, angle: float, perspective: PerspectiveKind, glob_trans: mathutils.Vector) -> None:
|
||||||
|
# decide the camera offset with ref point
|
||||||
|
ingamecam_pos: mathutils.Vector
|
||||||
|
match perspective:
|
||||||
|
case PerspectiveKind.Ordinary:
|
||||||
|
ingamecam_pos = mathutils.Vector((22, 0, 35))
|
||||||
|
case PerspectiveKind.Lift:
|
||||||
|
ingamecam_pos = mathutils.Vector((22, 0, 35 + 20))
|
||||||
|
case PerspectiveKind.EasterEgg:
|
||||||
|
ingamecam_pos = mathutils.Vector((22, 0, 3.86))
|
||||||
|
|
||||||
|
# decide the position of ref point
|
||||||
|
refpot_pos: mathutils.Vector
|
||||||
|
match perspective:
|
||||||
|
case PerspectiveKind.EasterEgg:
|
||||||
|
refpot_pos = mathutils.Vector((4.4, 0, 0))
|
||||||
|
case _:
|
||||||
|
refpot_pos = mathutils.Vector((0, 0, 0))
|
||||||
|
|
||||||
|
# perform rotation for both positions
|
||||||
|
player_rot_mat = mathutils.Matrix.Rotation(angle, 4, 'Z')
|
||||||
|
ingamecam_pos = ingamecam_pos @ player_rot_mat
|
||||||
|
refpot_pos = refpot_pos @ player_rot_mat
|
||||||
|
|
||||||
|
# calculate the rotation of camera
|
||||||
|
|
||||||
|
# YYC MARK:
|
||||||
|
# Following code are linear algebra required.
|
||||||
|
#
|
||||||
|
# We can calulate the direction of camera by simply substracting 2 vector.
|
||||||
|
# In default, the direction of camera is -Z, up direction is +Y.
|
||||||
|
# So this computed direction is -Z in new cooredinate system.
|
||||||
|
# Now we can compute +Z axis in this new coordinate system.
|
||||||
|
new_z = (ingamecam_pos - refpot_pos)
|
||||||
|
new_z.normalize()
|
||||||
|
# For ballance camera, all camera is +Z up.
|
||||||
|
# So we can use it to compute +X axis in new coordinate system
|
||||||
|
assistant_y = mathutils.Vector((0, 0, 1))
|
||||||
|
new_x = typing.cast(mathutils.Vector, assistant_y.cross(new_z))
|
||||||
|
new_x.normalize()
|
||||||
|
# now we calc the final axis
|
||||||
|
new_y = typing.cast(mathutils.Vector, new_z.cross(new_x))
|
||||||
|
new_y.normalize()
|
||||||
|
# okey, we conbine them as a matrix
|
||||||
|
rot_mat = mathutils.Matrix((
|
||||||
|
(new_x.x, new_y.x, new_z.x, 0),
|
||||||
|
(new_x.y, new_y.y, new_z.y, 0),
|
||||||
|
(new_x.z, new_y.z, new_z.z, 0),
|
||||||
|
(0, 0, 0, 1)
|
||||||
|
))
|
||||||
|
|
||||||
|
# calc the final transform matrix and apply it
|
||||||
|
trans_mat = mathutils.Matrix.Translation(ingamecam_pos)
|
||||||
|
glob_trans_mat = mathutils.Matrix.Translation(glob_trans)
|
||||||
|
camobj.matrix_world = glob_trans_mat @ trans_mat @ rot_mat
|
||||||
|
|
||||||
|
def _setup_camera_properties(camobj: bpy.types.Object) -> None:
|
||||||
|
# fetch camera
|
||||||
|
camera = typing.cast(bpy.types.Camera, camobj.data)
|
||||||
|
|
||||||
|
# set clipping
|
||||||
|
camera.clip_start = 4
|
||||||
|
camera.clip_end = 1200
|
||||||
|
# set FOV
|
||||||
|
camera.lens_unit = 'FOV'
|
||||||
|
camera.angle = math.radians(58)
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
def register() -> None:
|
||||||
|
bpy.utils.register_class(BBP_OT_game_resolution)
|
||||||
|
bpy.utils.register_class(BBP_OT_game_camera)
|
||||||
|
|
||||||
|
def unregister() -> None:
|
||||||
|
bpy.utils.unregister_class(BBP_OT_game_camera)
|
||||||
|
bpy.utils.unregister_class(BBP_OT_game_resolution)
|
||||||
|
|
@ -9,11 +9,11 @@ class AlignMode(enum.IntEnum):
|
|||||||
BBoxCenter = enum.auto()
|
BBoxCenter = enum.auto()
|
||||||
AxisCenter = enum.auto()
|
AxisCenter = enum.auto()
|
||||||
Max = enum.auto()
|
Max = enum.auto()
|
||||||
_g_AlignModeDesc: dict[AlignMode, tuple[str, str]] = {
|
_g_AlignModeDesc: dict[AlignMode, tuple[str, str, str]] = {
|
||||||
AlignMode.Min: ("Min", "The min value in specified axis."),
|
AlignMode.Min: ("Min", "The min value in specified axis.", "REMOVE"),
|
||||||
AlignMode.BBoxCenter: ("Center (Bounding Box)", "The bounding box center in specified axis."),
|
AlignMode.BBoxCenter: ("Center (Bounding Box)", "The bounding box center in specified axis.", "SHADING_BBOX"),
|
||||||
AlignMode.AxisCenter: ("Center (Axis)", "The object's source point in specified axis."),
|
AlignMode.AxisCenter: ("Center (Axis)", "The object's source point in specified axis.", "OBJECT_ORIGIN"),
|
||||||
AlignMode.Max: ("Max", "The max value in specified axis."),
|
AlignMode.Max: ("Max", "The max value in specified axis.", "ADD"),
|
||||||
}
|
}
|
||||||
_g_EnumHelper_AlignMode = UTIL_functions.EnumPropHelper(
|
_g_EnumHelper_AlignMode = UTIL_functions.EnumPropHelper(
|
||||||
AlignMode,
|
AlignMode,
|
||||||
@ -21,7 +21,23 @@ _g_EnumHelper_AlignMode = UTIL_functions.EnumPropHelper(
|
|||||||
lambda x: AlignMode(int(x)),
|
lambda x: AlignMode(int(x)),
|
||||||
lambda x: _g_AlignModeDesc[x][0],
|
lambda x: _g_AlignModeDesc[x][0],
|
||||||
lambda x: _g_AlignModeDesc[x][1],
|
lambda x: _g_AlignModeDesc[x][1],
|
||||||
lambda _: ''
|
lambda x: _g_AlignModeDesc[x][2]
|
||||||
|
)
|
||||||
|
|
||||||
|
class CurrentInstance(enum.IntEnum):
|
||||||
|
ActiveObject = enum.auto()
|
||||||
|
Cursor = enum.auto()
|
||||||
|
_g_CurrentInstanceDesc: dict[CurrentInstance, tuple[str, str, str]] = {
|
||||||
|
CurrentInstance.ActiveObject: ("Active Object", "Use Active Object as Current Object", "OBJECT_DATA"),
|
||||||
|
CurrentInstance.Cursor: ("3D Cursor", "Use 3D Cursor as Current Object", "CURSOR"),
|
||||||
|
}
|
||||||
|
_g_EnumHelper_CurrentInstance = UTIL_functions.EnumPropHelper(
|
||||||
|
CurrentInstance,
|
||||||
|
lambda x: str(x.value),
|
||||||
|
lambda x: CurrentInstance(int(x)),
|
||||||
|
lambda x: _g_CurrentInstanceDesc[x][0],
|
||||||
|
lambda x: _g_CurrentInstanceDesc[x][1],
|
||||||
|
lambda x: _g_CurrentInstanceDesc[x][2]
|
||||||
)
|
)
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
@ -55,14 +71,23 @@ class BBP_PG_legacy_align_history(bpy.types.PropertyGroup):
|
|||||||
default = False,
|
default = False,
|
||||||
translation_context = 'BBP_PG_legacy_align_history/property'
|
translation_context = 'BBP_PG_legacy_align_history/property'
|
||||||
) # type: ignore
|
) # type: ignore
|
||||||
|
current_instance: bpy.props.EnumProperty(
|
||||||
|
name = "Current Instance",
|
||||||
|
description = "Decide which instance should be used as Current Object",
|
||||||
|
items = _g_EnumHelper_CurrentInstance.generate_items(),
|
||||||
|
default = _g_EnumHelper_CurrentInstance.to_selection(CurrentInstance.ActiveObject),
|
||||||
|
translation_context = 'BBP_PG_legacy_align_history/property'
|
||||||
|
) # type: ignore
|
||||||
current_align_mode: bpy.props.EnumProperty(
|
current_align_mode: bpy.props.EnumProperty(
|
||||||
name = "Current Object (Active Object)",
|
name = "Current Object",
|
||||||
|
description = "The align mode applied to Current Object",
|
||||||
items = _g_EnumHelper_AlignMode.generate_items(),
|
items = _g_EnumHelper_AlignMode.generate_items(),
|
||||||
default = _g_EnumHelper_AlignMode.to_selection(AlignMode.AxisCenter),
|
default = _g_EnumHelper_AlignMode.to_selection(AlignMode.AxisCenter),
|
||||||
translation_context = 'BBP_PG_legacy_align_history/property'
|
translation_context = 'BBP_PG_legacy_align_history/property'
|
||||||
) # type: ignore
|
) # type: ignore
|
||||||
target_align_mode: bpy.props.EnumProperty(
|
target_align_mode: bpy.props.EnumProperty(
|
||||||
name = "Target Objects (Selected Objects)",
|
name = "Target Objects",
|
||||||
|
description = "The align mode applied to Target Objects (selected objects except active object if Current Instance is active object)",
|
||||||
items = _g_EnumHelper_AlignMode.generate_items(),
|
items = _g_EnumHelper_AlignMode.generate_items(),
|
||||||
default = _g_EnumHelper_AlignMode.to_selection(AlignMode.AxisCenter),
|
default = _g_EnumHelper_AlignMode.to_selection(AlignMode.AxisCenter),
|
||||||
translation_context = 'BBP_PG_legacy_align_history/property'
|
translation_context = 'BBP_PG_legacy_align_history/property'
|
||||||
@ -148,7 +173,7 @@ class BBP_OT_legacy_align(bpy.types.Operator):
|
|||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
# get processed objects
|
# get processed objects
|
||||||
(current_obj, target_objs) = _prepare_objects()
|
(current_obj, current_cursor, target_objs) = _prepare_objects()
|
||||||
# YYC MARK:
|
# YYC MARK:
|
||||||
# This statement is VERY IMPORTANT.
|
# This statement is VERY IMPORTANT.
|
||||||
# If this statement is not presented, Blender will return identity matrix
|
# If this statement is not presented, Blender will return identity matrix
|
||||||
@ -162,7 +187,8 @@ class BBP_OT_legacy_align(bpy.types.Operator):
|
|||||||
histories = UTIL_functions.CollectionVisitor(self.align_history)
|
histories = UTIL_functions.CollectionVisitor(self.align_history)
|
||||||
for entry in histories:
|
for entry in histories:
|
||||||
_align_objects(
|
_align_objects(
|
||||||
current_obj, target_objs,
|
_g_EnumHelper_CurrentInstance.get_selection(entry.current_instance),
|
||||||
|
current_obj, current_cursor, target_objs,
|
||||||
entry.align_x, entry.align_y, entry.align_z,
|
entry.align_x, entry.align_y, entry.align_z,
|
||||||
_g_EnumHelper_AlignMode.get_selection(entry.current_align_mode),
|
_g_EnumHelper_AlignMode.get_selection(entry.current_align_mode),
|
||||||
_g_EnumHelper_AlignMode.get_selection(entry.target_align_mode)
|
_g_EnumHelper_AlignMode.get_selection(entry.target_align_mode)
|
||||||
@ -185,11 +211,22 @@ class BBP_OT_legacy_align(bpy.types.Operator):
|
|||||||
row.prop(entry, "align_y", toggle = 1)
|
row.prop(entry, "align_y", toggle = 1)
|
||||||
row.prop(entry, "align_z", toggle = 1)
|
row.prop(entry, "align_z", toggle = 1)
|
||||||
|
|
||||||
# show mode
|
# show current instance
|
||||||
col.separator()
|
col.separator()
|
||||||
col.label(text='Current Object (Active Object)', text_ctxt='BBP_OT_legacy_align/draw')
|
col.label(text='Current Object', text_ctxt='BBP_OT_legacy_align/draw')
|
||||||
col.prop(entry, "current_align_mode", expand = True)
|
# it should be shown in horizon so we create a new sublayout
|
||||||
col.label(text='Target Objects (Selected Objects)', text_ctxt='BBP_OT_legacy_align/draw')
|
row = col.row()
|
||||||
|
row.prop(entry, 'current_instance', expand=True)
|
||||||
|
|
||||||
|
# show instance and mode
|
||||||
|
col.separator()
|
||||||
|
# only show current object mode if current instance is active object,
|
||||||
|
# because there is no mode for 3d cursor.
|
||||||
|
current_instnce = _g_EnumHelper_CurrentInstance.get_selection(entry.current_instance)
|
||||||
|
if current_instnce == CurrentInstance.ActiveObject:
|
||||||
|
col.label(text='Current Object Align Mode', text_ctxt='BBP_OT_legacy_align/draw')
|
||||||
|
col.prop(entry, "current_align_mode", expand = True)
|
||||||
|
col.label(text='Target Objects Align Mode', text_ctxt='BBP_OT_legacy_align/draw')
|
||||||
col.prop(entry, "target_align_mode", expand = True)
|
col.prop(entry, "target_align_mode", expand = True)
|
||||||
|
|
||||||
# show apply button
|
# show apply button
|
||||||
@ -206,44 +243,66 @@ class BBP_OT_legacy_align(bpy.types.Operator):
|
|||||||
#region Core Functions
|
#region Core Functions
|
||||||
|
|
||||||
def _check_align_requirement() -> bool:
|
def _check_align_requirement() -> bool:
|
||||||
# if we are not in object mode, do not do legacy align
|
# If we are not in object mode, do not do legacy align
|
||||||
if not UTIL_functions.is_in_object_mode():
|
if not UTIL_functions.is_in_object_mode():
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# check current obj
|
# YYC MARK:
|
||||||
|
# We still need to check active object (as current object)
|
||||||
|
# although we can choose align with active object or 3d cursor.
|
||||||
|
# Because we can not make any promise that user will
|
||||||
|
# select Active Object or 3D Cursor as current object before executing this operator.
|
||||||
if bpy.context.active_object is None:
|
if bpy.context.active_object is None:
|
||||||
return False
|
return False
|
||||||
# check target obj with filter of current obj
|
|
||||||
length = len(bpy.context.selected_objects)
|
# YYC MARK:
|
||||||
if bpy.context.active_object in bpy.context.selected_objects:
|
# Roughly check selected objects.
|
||||||
length -= 1
|
# We do not need exclude active object from selected objects,
|
||||||
return length != 0
|
# because active object may be moved when 3D Cursor is current object.
|
||||||
|
if len(bpy.context.selected_objects) == 0:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
def _prepare_objects() -> tuple[bpy.types.Object, set[bpy.types.Object]]:
|
def _prepare_objects() -> tuple[bpy.types.Object, mathutils.Vector, list[bpy.types.Object]]:
|
||||||
# get current object
|
# Fetch current object
|
||||||
current_obj: bpy.types.Object = bpy.context.active_object
|
current_obj = typing.cast(bpy.types.Object, bpy.context.active_object)
|
||||||
|
|
||||||
# get target objects
|
# Fetch 3d cursor location
|
||||||
target_objs: set[bpy.types.Object] = set(bpy.context.selected_objects)
|
current_cursor: mathutils.Vector = bpy.context.scene.cursor.location
|
||||||
# remove active one
|
|
||||||
if current_obj in target_objs:
|
# YYC MARK:
|
||||||
target_objs.remove(current_obj)
|
# Fetch target objects and do NOT remove active object from it.
|
||||||
|
# because active object will be moved when current instance is 3D Cursor.
|
||||||
|
target_objs: list[bpy.types.Object] = bpy.context.selected_objects[:]
|
||||||
|
|
||||||
# return value
|
# return value
|
||||||
return (current_obj, target_objs)
|
return (current_obj, current_cursor, target_objs)
|
||||||
|
|
||||||
def _align_objects(
|
def _align_objects(
|
||||||
current_obj: bpy.types.Object, target_objs: set[bpy.types.Object],
|
current_instance: CurrentInstance,
|
||||||
|
current_obj: bpy.types.Object, current_cursor: mathutils.Vector, target_objs: list[bpy.types.Object],
|
||||||
align_x: bool, align_y: bool, align_z: bool, current_mode: AlignMode, target_mode: AlignMode) -> None:
|
align_x: bool, align_y: bool, align_z: bool, current_mode: AlignMode, target_mode: AlignMode) -> None:
|
||||||
# if no align, skip
|
# if no align, skip
|
||||||
if not (align_x or align_y or align_z):
|
if not (align_x or align_y or align_z):
|
||||||
return
|
return
|
||||||
|
|
||||||
# calc current object data
|
# calc current object data
|
||||||
current_obj_ref: mathutils.Vector = _get_object_ref_point(current_obj, current_mode)
|
current_obj_ref: mathutils.Vector
|
||||||
|
match current_instance:
|
||||||
|
case CurrentInstance.ActiveObject:
|
||||||
|
current_obj_ref = _get_object_ref_point(current_obj, current_mode)
|
||||||
|
case CurrentInstance.Cursor:
|
||||||
|
current_obj_ref = current_cursor
|
||||||
|
|
||||||
# process each target obj
|
# process each target obj
|
||||||
for target_obj in target_objs:
|
for target_obj in target_objs:
|
||||||
|
# YYC MARK:
|
||||||
|
# If we use active object as current instance, we need exclude it from target objects,
|
||||||
|
# because there is no pre-exclude considering the scenario that 3D Cursor is current instance.
|
||||||
|
if current_instance == CurrentInstance.ActiveObject and current_obj == target_obj:
|
||||||
|
continue
|
||||||
|
|
||||||
# calc target object data
|
# calc target object data
|
||||||
target_obj_ref: mathutils.Vector = _get_object_ref_point(target_obj, target_mode)
|
target_obj_ref: mathutils.Vector = _get_object_ref_point(target_obj, target_mode)
|
||||||
# build translation transform
|
# build translation transform
|
||||||
@ -256,21 +315,21 @@ def _align_objects(
|
|||||||
# apply translation transform to left side (add into original matrix)
|
# apply translation transform to left side (add into original matrix)
|
||||||
target_obj.matrix_world = target_obj_translation_matrix @ target_obj.matrix_world
|
target_obj.matrix_world = target_obj_translation_matrix @ target_obj.matrix_world
|
||||||
|
|
||||||
bpy.context.scene.update_tag
|
|
||||||
|
|
||||||
def _get_object_ref_point(obj: bpy.types.Object, mode: AlignMode) -> mathutils.Vector:
|
def _get_object_ref_point(obj: bpy.types.Object, mode: AlignMode) -> mathutils.Vector:
|
||||||
ref_pos: mathutils.Vector = mathutils.Vector((0, 0, 0))
|
ref_pos = mathutils.Vector((0, 0, 0))
|
||||||
|
|
||||||
# calc bounding box data
|
# calc bounding box data
|
||||||
corners: tuple[mathutils.Vector] = tuple(obj.matrix_world @ mathutils.Vector(corner) for corner in obj.bound_box)
|
corners: tuple[mathutils.Vector, ...] = tuple(obj.matrix_world @ mathutils.Vector(corner) for corner in obj.bound_box)
|
||||||
bbox_min_corner: mathutils.Vector = mathutils.Vector((0, 0, 0))
|
bbox_min_corner = mathutils.Vector((
|
||||||
bbox_min_corner.x = min((vec.x for vec in corners))
|
min((vec.x for vec in corners)),
|
||||||
bbox_min_corner.y = min((vec.y for vec in corners))
|
min((vec.y for vec in corners)),
|
||||||
bbox_min_corner.z = min((vec.z for vec in corners))
|
min((vec.z for vec in corners)),
|
||||||
bbox_max_corner: mathutils.Vector = mathutils.Vector((0, 0, 0))
|
))
|
||||||
bbox_max_corner.x = max((vec.x for vec in corners))
|
bbox_max_corner = mathutils.Vector((
|
||||||
bbox_max_corner.y = max((vec.y for vec in corners))
|
max((vec.x for vec in corners)),
|
||||||
bbox_max_corner.z = max((vec.z for vec in corners))
|
max((vec.y for vec in corners)),
|
||||||
|
max((vec.z for vec in corners)),
|
||||||
|
))
|
||||||
|
|
||||||
# return value by given align mode
|
# return value by given align mode
|
||||||
match(mode):
|
match(mode):
|
||||||
|
@ -195,6 +195,16 @@ class PropsVisitor():
|
|||||||
def get_ioport_encodings(self) -> tuple[str, ...]:
|
def get_ioport_encodings(self) -> tuple[str, ...]:
|
||||||
encodings = get_ioport_encodings(self.__mAssocScene)
|
encodings = get_ioport_encodings(self.__mAssocScene)
|
||||||
return tuple(i.encoding for i in encodings)
|
return tuple(i.encoding for i in encodings)
|
||||||
|
def preset_ioport_encodings(self) -> None:
|
||||||
|
"""
|
||||||
|
Set IOPort used encodings list as preset encoding list.
|
||||||
|
Please note that all old values will be overwritten.
|
||||||
|
"""
|
||||||
|
encodings = get_ioport_encodings(self.__mAssocScene)
|
||||||
|
encodings.clear()
|
||||||
|
for default_enc in UTIL_virtools_types.g_PyBMapDefaultEncodings:
|
||||||
|
item = encodings.add()
|
||||||
|
item.encoding = default_enc
|
||||||
def draw_ioport_encodings(self, layout: bpy.types.UILayout) -> None:
|
def draw_ioport_encodings(self, layout: bpy.types.UILayout) -> None:
|
||||||
target = get_ptrprop_resolver(self.__mAssocScene)
|
target = get_ptrprop_resolver(self.__mAssocScene)
|
||||||
row = layout.row()
|
row = layout.row()
|
||||||
@ -218,24 +228,11 @@ class PropsVisitor():
|
|||||||
col.separator()
|
col.separator()
|
||||||
col.operator(BBP_OT_clear_ioport_encodings.bl_idname, icon='TRASH', text='')
|
col.operator(BBP_OT_clear_ioport_encodings.bl_idname, icon='TRASH', text='')
|
||||||
|
|
||||||
@bpy.app.handlers.persistent
|
|
||||||
def _ioport_encodings_initializer(file_path: str):
|
|
||||||
# if we can fetch property, and it is empty after loading file
|
|
||||||
# we fill it with default value
|
|
||||||
encodings = get_ioport_encodings(bpy.context.scene)
|
|
||||||
if len(encodings) == 0:
|
|
||||||
for default_enc in UTIL_virtools_types.g_PyBMapDefaultEncodings:
|
|
||||||
item = encodings.add()
|
|
||||||
item.encoding = default_enc
|
|
||||||
|
|
||||||
def register() -> None:
|
def register() -> None:
|
||||||
bpy.utils.register_class(BBP_PG_bmap_encoding)
|
bpy.utils.register_class(BBP_PG_bmap_encoding)
|
||||||
bpy.utils.register_class(BBP_UL_bmap_encoding)
|
bpy.utils.register_class(BBP_UL_bmap_encoding)
|
||||||
bpy.utils.register_class(BBP_PG_ptrprop_resolver)
|
bpy.utils.register_class(BBP_PG_ptrprop_resolver)
|
||||||
|
|
||||||
# register ioport encodings default value
|
|
||||||
bpy.app.handlers.load_post.append(_ioport_encodings_initializer)
|
|
||||||
|
|
||||||
bpy.utils.register_class(BBP_OT_add_ioport_encodings)
|
bpy.utils.register_class(BBP_OT_add_ioport_encodings)
|
||||||
bpy.utils.register_class(BBP_OT_rm_ioport_encodings)
|
bpy.utils.register_class(BBP_OT_rm_ioport_encodings)
|
||||||
bpy.utils.register_class(BBP_OT_up_ioport_encodings)
|
bpy.utils.register_class(BBP_OT_up_ioport_encodings)
|
||||||
@ -253,9 +250,6 @@ def unregister() -> None:
|
|||||||
bpy.utils.unregister_class(BBP_OT_rm_ioport_encodings)
|
bpy.utils.unregister_class(BBP_OT_rm_ioport_encodings)
|
||||||
bpy.utils.unregister_class(BBP_OT_add_ioport_encodings)
|
bpy.utils.unregister_class(BBP_OT_add_ioport_encodings)
|
||||||
|
|
||||||
# unregister ioport encodings default value
|
|
||||||
bpy.app.handlers.load_post.remove(_ioport_encodings_initializer)
|
|
||||||
|
|
||||||
bpy.utils.unregister_class(BBP_PG_ptrprop_resolver)
|
bpy.utils.unregister_class(BBP_PG_ptrprop_resolver)
|
||||||
bpy.utils.unregister_class(BBP_UL_bmap_encoding)
|
bpy.utils.unregister_class(BBP_UL_bmap_encoding)
|
||||||
bpy.utils.unregister_class(BBP_PG_bmap_encoding)
|
bpy.utils.unregister_class(BBP_PG_bmap_encoding)
|
||||||
|
@ -24,6 +24,7 @@ TOKEN_IDENTIFIER: str = 'identifier'
|
|||||||
|
|
||||||
TOKEN_SHOWCASE: str = 'showcase'
|
TOKEN_SHOWCASE: str = 'showcase'
|
||||||
TOKEN_SHOWCASE_TITLE: str = 'title'
|
TOKEN_SHOWCASE_TITLE: str = 'title'
|
||||||
|
TOKEN_SHOWCASE_CATEGORY: str = 'category'
|
||||||
TOKEN_SHOWCASE_ICON: str = 'icon'
|
TOKEN_SHOWCASE_ICON: str = 'icon'
|
||||||
TOKEN_SHOWCASE_TYPE: str = 'type'
|
TOKEN_SHOWCASE_TYPE: str = 'type'
|
||||||
TOKEN_SHOWCASE_CFGS: str = 'cfgs'
|
TOKEN_SHOWCASE_CFGS: str = 'cfgs'
|
||||||
@ -64,10 +65,10 @@ TOKEN_INSTANCES_TRANSFORM: str = 'transform'
|
|||||||
|
|
||||||
#region Prototype Loader
|
#region Prototype Loader
|
||||||
|
|
||||||
## The list storing BME prototype.
|
|
||||||
_g_BMEPrototypes: list[dict[str, typing.Any]] = []
|
_g_BMEPrototypes: list[dict[str, typing.Any]] = []
|
||||||
## The dict. Key is prototype identifier. value is the index of prototype in prototype list.
|
"""The list storing BME prototype."""
|
||||||
_g_BMEPrototypeIndexMap: dict[str, int] = {}
|
_g_BMEPrototypeIndexMap: dict[str, int] = {}
|
||||||
|
"""The dict. Key is prototype identifier. Value is the index of prototype in prototype list."""
|
||||||
|
|
||||||
# the core loader
|
# the core loader
|
||||||
for walk_root, walk_dirs, walk_files in os.walk(os.path.join(os.path.dirname(__file__), 'jsons')):
|
for walk_root, walk_dirs, walk_files in os.walk(os.path.join(os.path.dirname(__file__), 'jsons')):
|
||||||
@ -99,7 +100,7 @@ def _env_fct_angle(x1: float, y1: float, x2: float, y2: float) -> float:
|
|||||||
# second, its direction (clockwise is positive) is opposite with blender rotation direction (counter-clockwise is positive).
|
# second, its direction (clockwise is positive) is opposite with blender rotation direction (counter-clockwise is positive).
|
||||||
diff = mathutils.Vector((x2, y2)) - mathutils.Vector((x1, y1))
|
diff = mathutils.Vector((x2, y2)) - mathutils.Vector((x1, y1))
|
||||||
bld_angle = math.degrees(mathutils.Vector((1,0)).angle_signed(diff, 0))
|
bld_angle = math.degrees(mathutils.Vector((1,0)).angle_signed(diff, 0))
|
||||||
|
|
||||||
# flip it first
|
# flip it first
|
||||||
bld_angle = -bld_angle
|
bld_angle = -bld_angle
|
||||||
# process positove number and negative number respectively
|
# process positove number and negative number respectively
|
||||||
@ -141,7 +142,7 @@ _g_ProgFieldGlobals: dict[str, typing.Any] = {
|
|||||||
'rot': lambda x, y, z: mathutils.Matrix.LocRotScale(None, mathutils.Euler((math.radians(x), math.radians(y), math.radians(z)), 'XYZ'), None),
|
'rot': lambda x, y, z: mathutils.Matrix.LocRotScale(None, mathutils.Euler((math.radians(x), math.radians(y), math.radians(z)), 'XYZ'), None),
|
||||||
'scale': lambda x, y, z: mathutils.Matrix.LocRotScale(None, None, (x, y, z)),
|
'scale': lambda x, y, z: mathutils.Matrix.LocRotScale(None, None, (x, y, z)),
|
||||||
'ident': lambda: mathutils.Matrix.Identity(4),
|
'ident': lambda: mathutils.Matrix.Identity(4),
|
||||||
|
|
||||||
# my misc custom functions
|
# my misc custom functions
|
||||||
'distance': _env_fct_distance,
|
'distance': _env_fct_distance,
|
||||||
'angle': _env_fct_angle,
|
'angle': _env_fct_angle,
|
||||||
@ -191,8 +192,32 @@ class EnumPropHelper(UTIL_functions.EnumPropHelper[str]):
|
|||||||
"""
|
"""
|
||||||
The BME specialized Blender EnumProperty helper.
|
The BME specialized Blender EnumProperty helper.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
showcase_identifiers: tuple[str, ...]
|
||||||
|
showcase_categories: dict[str, tuple[str, ...]]
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
# build cache for showcase identifiers and categories
|
||||||
|
# prepare cache value
|
||||||
|
identifiers: list[str] = []
|
||||||
|
categories: dict[str, list[str]] = {}
|
||||||
|
# iterate showcase prototypes
|
||||||
|
for x in filter(lambda x: x[TOKEN_SHOWCASE] is not None, _g_BMEPrototypes):
|
||||||
|
# fetch identifier and category
|
||||||
|
identifier = typing.cast(str, x[TOKEN_IDENTIFIER])
|
||||||
|
category = typing.cast(str, x[TOKEN_SHOWCASE][TOKEN_SHOWCASE_CATEGORY])
|
||||||
|
# add into identifier list
|
||||||
|
identifiers.append(identifier)
|
||||||
|
# add into categories
|
||||||
|
categories_inner = categories.get(category, None)
|
||||||
|
if categories_inner is None:
|
||||||
|
categories_inner = []
|
||||||
|
categories[category] = categories_inner
|
||||||
|
categories_inner.append(identifier)
|
||||||
|
# tuple the result
|
||||||
|
self.showcase_identifiers = tuple(identifiers)
|
||||||
|
self.showcase_categories = {k: tuple(v) for k, v in categories.items()}
|
||||||
|
|
||||||
# init parent class
|
# init parent class
|
||||||
super().__init__(
|
super().__init__(
|
||||||
self.get_bme_identifiers(),
|
self.get_bme_identifiers(),
|
||||||
@ -202,17 +227,20 @@ class EnumPropHelper(UTIL_functions.EnumPropHelper[str]):
|
|||||||
lambda _: '',
|
lambda _: '',
|
||||||
lambda x: self.get_bme_showcase_icon(x)
|
lambda x: self.get_bme_showcase_icon(x)
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_bme_identifiers(self) -> tuple[str, ...]:
|
def get_bme_identifiers(self) -> tuple[str, ...]:
|
||||||
"""
|
"""
|
||||||
Get the identifier of prototype which need to be exposed to user.
|
Get the identifier of prototype which need to be exposed to user.
|
||||||
Template prototype is not included.
|
In other words, template prototype is not included.
|
||||||
"""
|
"""
|
||||||
return tuple(
|
return self.showcase_identifiers
|
||||||
x[TOKEN_IDENTIFIER] # get identifier
|
|
||||||
for x in filter(lambda x: x[TOKEN_SHOWCASE] is not None, _g_BMEPrototypes) # filter() to filter no showcase template.
|
|
||||||
)
|
|
||||||
|
|
||||||
|
def get_bme_categories(self) -> dict[str, tuple[str, ...]]:
|
||||||
|
"""
|
||||||
|
Get user-oriented identifier list grouped by category.
|
||||||
|
"""
|
||||||
|
return self.showcase_categories
|
||||||
|
|
||||||
def get_bme_showcase_title(self, ident: str) -> str:
|
def get_bme_showcase_title(self, ident: str) -> str:
|
||||||
"""
|
"""
|
||||||
Get BME display title by prototype identifier.
|
Get BME display title by prototype identifier.
|
||||||
@ -326,14 +354,14 @@ def create_bme_struct(
|
|||||||
# create mtl slot remap to help following mesh adding
|
# create mtl slot remap to help following mesh adding
|
||||||
# because mesh writer do not accept string format mtl slot visiting,
|
# because mesh writer do not accept string format mtl slot visiting,
|
||||||
# it only accept int based mtl slot index.
|
# it only accept int based mtl slot index.
|
||||||
#
|
#
|
||||||
# Also we build face used mtl slot index at the same time.
|
# Also we build face used mtl slot index at the same time.
|
||||||
# So we do not analyse texture field again when providing face data.
|
# So we do not analyse texture field again when providing face data.
|
||||||
# The result is in `prebuild_face_mtl_idx` and please note it will store all face's mtl index.
|
# The result is in `prebuild_face_mtl_idx` and please note it will store all face's mtl index.
|
||||||
# For example: if face 0 is skipped and face 1 is used, the first entry in `prebuild_face_mtl_idx`
|
# For example: if face 0 is skipped and face 1 is used, the first entry in `prebuild_face_mtl_idx`
|
||||||
# will be the mtl slot index used by face 0, not 1. And its length is equal to the face count.
|
# will be the mtl slot index used by face 0, not 1. And its length is equal to the face count.
|
||||||
# However, because face 0 is skipped, so the entry is not used and default set to 0.
|
# However, because face 0 is skipped, so the entry is not used and default set to 0.
|
||||||
#
|
#
|
||||||
# NOTE: since Python 3.6, the item of builtin dict is ordered by inserting order.
|
# NOTE: since Python 3.6, the item of builtin dict is ordered by inserting order.
|
||||||
# we rely on this to implement following features.
|
# we rely on this to implement following features.
|
||||||
mtl_remap: dict[str, int] = {}
|
mtl_remap: dict[str, int] = {}
|
||||||
@ -351,7 +379,7 @@ def create_bme_struct(
|
|||||||
# if existing, no need to add into remap
|
# if existing, no need to add into remap
|
||||||
# but we need get its index from remap
|
# but we need get its index from remap
|
||||||
prebuild_face_mtl_idx[face_idx] = mtl_remap.get(mtl_name, 0)
|
prebuild_face_mtl_idx[face_idx] = mtl_remap.get(mtl_name, 0)
|
||||||
|
|
||||||
# pre-compute vertices data because we may need used later.
|
# pre-compute vertices data because we may need used later.
|
||||||
# Because if face normal data is null, it mean that we need to compute it
|
# Because if face normal data is null, it mean that we need to compute it
|
||||||
# by given vertices.
|
# by given vertices.
|
||||||
@ -366,7 +394,7 @@ def create_bme_struct(
|
|||||||
cache_bv = typing.cast(mathutils.Vector, transform @ cache_bv)
|
cache_bv = typing.cast(mathutils.Vector, transform @ cache_bv)
|
||||||
# get result
|
# get result
|
||||||
prebuild_vec_data.append((cache_bv.x, cache_bv.y, cache_bv.z))
|
prebuild_vec_data.append((cache_bv.x, cache_bv.y, cache_bv.z))
|
||||||
|
|
||||||
# Check whether given transform is mirror matrix
|
# Check whether given transform is mirror matrix
|
||||||
# because mirror matrix will reverse triangle indice order.
|
# because mirror matrix will reverse triangle indice order.
|
||||||
# If matrix is mirror matrix, we need reverse it again in following procession,
|
# If matrix is mirror matrix, we need reverse it again in following procession,
|
||||||
|
@ -340,6 +340,14 @@ class VirtoolsParams():
|
|||||||
translation_context = 'BBP/UTIL_ioport_shared.VirtoolsParams/property'
|
translation_context = 'BBP/UTIL_ioport_shared.VirtoolsParams/property'
|
||||||
) # type: ignore
|
) # type: ignore
|
||||||
|
|
||||||
|
def preset_vt_encodings_if_possible(self, context: bpy.types.Context):
|
||||||
|
"""
|
||||||
|
Set preset value for Virtools Encoding list if there is no value inside it.
|
||||||
|
"""
|
||||||
|
ptrprops = PROP_ptrprop_resolver.PropsVisitor(context.scene)
|
||||||
|
if len(ptrprops.get_ioport_encodings()) == 0:
|
||||||
|
ptrprops.preset_ioport_encodings()
|
||||||
|
|
||||||
def draw_virtools_params(self, context: bpy.types.Context, layout: bpy.types.UILayout, is_importer: bool) -> None:
|
def draw_virtools_params(self, context: bpy.types.Context, layout: bpy.types.UILayout, is_importer: bool) -> None:
|
||||||
header: bpy.types.UILayout
|
header: bpy.types.UILayout
|
||||||
body: bpy.types.UILayout
|
body: bpy.types.UILayout
|
||||||
@ -364,7 +372,6 @@ class VirtoolsParams():
|
|||||||
if self.use_compress:
|
if self.use_compress:
|
||||||
body.prop(self, 'compress_level')
|
body.prop(self, 'compress_level')
|
||||||
|
|
||||||
|
|
||||||
def general_get_vt_encodings(self, context: bpy.types.Context) -> tuple[str, ...]:
|
def general_get_vt_encodings(self, context: bpy.types.Context) -> tuple[str, ...]:
|
||||||
# get from ptrprop resolver then filter empty item
|
# get from ptrprop resolver then filter empty item
|
||||||
ptrprops = PROP_ptrprop_resolver.PropsVisitor(context.scene)
|
ptrprops = PROP_ptrprop_resolver.PropsVisitor(context.scene)
|
||||||
|
@ -55,14 +55,22 @@ import bpy
|
|||||||
CTX_BBP: str = 'BBP'
|
CTX_BBP: str = 'BBP'
|
||||||
|
|
||||||
# The universal translation context prefix for BME module in BBP_NG plugin.
|
# The universal translation context prefix for BME module in BBP_NG plugin.
|
||||||
CTX_BBP_BME: str = CTX_BBP + '/BME'
|
CTX_BBP_BME: str = f'{CTX_BBP}/BME'
|
||||||
def build_prototype_showcase_context(identifier: str) -> str:
|
CTX_BBP_BME_CATEGORY: str = f'{CTX_BBP_BME}/Category'
|
||||||
|
CTX_BBP_BME_PROTOTYPE: str = f'{CTX_BBP_BME}/Proto'
|
||||||
|
def build_prototype_showcase_category_context() -> str:
|
||||||
|
"""
|
||||||
|
Build the context for getting the translation for BME prototype showcase category.
|
||||||
|
@return The context for getting translation.
|
||||||
|
"""
|
||||||
|
return CTX_BBP_BME_CATEGORY
|
||||||
|
def build_prototype_showcase_title_context(identifier: str) -> str:
|
||||||
"""
|
"""
|
||||||
Build the context for getting the translation for BME prototype showcase title.
|
Build the context for getting the translation for BME prototype showcase title.
|
||||||
@param[in] identifier The identifier of this prototype.
|
@param[in] identifier The identifier of this prototype.
|
||||||
@return The context for getting translation.
|
@return The context for getting translation.
|
||||||
"""
|
"""
|
||||||
return CTX_BBP_BME + '/' + identifier
|
return f'{CTX_BBP_BME_PROTOTYPE}/{identifier}'
|
||||||
def build_prototype_showcase_cfg_context(identifier: str, cfg_index: int) -> str:
|
def build_prototype_showcase_cfg_context(identifier: str, cfg_index: int) -> str:
|
||||||
"""
|
"""
|
||||||
Build the context for getting the translation for BME prototype showcase configuration title or description.
|
Build the context for getting the translation for BME prototype showcase configuration title or description.
|
||||||
@ -70,7 +78,7 @@ def build_prototype_showcase_cfg_context(identifier: str, cfg_index: int) -> str
|
|||||||
@param[in] cfg_index The index of this configuration in this prototype showcase.
|
@param[in] cfg_index The index of this configuration in this prototype showcase.
|
||||||
@return The context for getting translation.
|
@return The context for getting translation.
|
||||||
"""
|
"""
|
||||||
return CTX_BBP_BME + f'/{identifier}/[{cfg_index}]'
|
return f'{CTX_BBP_BME_PROTOTYPE}/{identifier}/[{cfg_index}]'
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@ from . import OP_IMPORT_bmfile, OP_EXPORT_bmfile, OP_IMPORT_virtools, OP_EXPORT_
|
|||||||
from . import OP_UV_flatten_uv, OP_UV_rail_uv
|
from . import OP_UV_flatten_uv, OP_UV_rail_uv
|
||||||
from . import OP_MTL_fix_materials
|
from . import OP_MTL_fix_materials
|
||||||
from . import OP_ADDS_component, OP_ADDS_bme, OP_ADDS_rail
|
from . import OP_ADDS_component, OP_ADDS_bme, OP_ADDS_rail
|
||||||
from . import OP_OBJECT_legacy_align, OP_OBJECT_virtools_group, OP_OBJECT_snoop_group_then_to_mesh, OP_OBJECT_naming_convention
|
from . import OP_OBJECT_legacy_align, OP_OBJECT_virtools_group, OP_OBJECT_snoop_group_then_to_mesh, OP_OBJECT_naming_convention, OP_OBJECT_game_view
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
@ -170,7 +170,7 @@ class BBP_MT_View3DMenu(bpy.types.Menu):
|
|||||||
bl_translation_context = 'BBP_MT_View3DMenu'
|
bl_translation_context = 'BBP_MT_View3DMenu'
|
||||||
|
|
||||||
def draw(self, context):
|
def draw(self, context):
|
||||||
layout = self.layout
|
layout = typing.cast(bpy.types.UILayout, self.layout)
|
||||||
layout.label(text='UV', icon='UV', text_ctxt='BBP_MT_View3DMenu/draw')
|
layout.label(text='UV', icon='UV', text_ctxt='BBP_MT_View3DMenu/draw')
|
||||||
layout.operator(OP_UV_flatten_uv.BBP_OT_flatten_uv.bl_idname)
|
layout.operator(OP_UV_flatten_uv.BBP_OT_flatten_uv.bl_idname)
|
||||||
layout.operator(OP_UV_rail_uv.BBP_OT_rail_uv.bl_idname)
|
layout.operator(OP_UV_rail_uv.BBP_OT_rail_uv.bl_idname)
|
||||||
@ -178,6 +178,10 @@ class BBP_MT_View3DMenu(bpy.types.Menu):
|
|||||||
layout.label(text='Align', icon='SNAP_ON', text_ctxt='BBP_MT_View3DMenu/draw')
|
layout.label(text='Align', icon='SNAP_ON', text_ctxt='BBP_MT_View3DMenu/draw')
|
||||||
layout.operator(OP_OBJECT_legacy_align.BBP_OT_legacy_align.bl_idname)
|
layout.operator(OP_OBJECT_legacy_align.BBP_OT_legacy_align.bl_idname)
|
||||||
layout.separator()
|
layout.separator()
|
||||||
|
layout.label(text='Camera', icon='CAMERA_DATA', text_ctxt='BBP_MT_View3DMenu/draw')
|
||||||
|
layout.operator(OP_OBJECT_game_view.BBP_OT_game_resolution.bl_idname)
|
||||||
|
layout.operator(OP_OBJECT_game_view.BBP_OT_game_camera.bl_idname)
|
||||||
|
layout.separator()
|
||||||
layout.label(text='Select', icon='SELECT_SET', text_ctxt='BBP_MT_View3DMenu/draw')
|
layout.label(text='Select', icon='SELECT_SET', text_ctxt='BBP_MT_View3DMenu/draw')
|
||||||
layout.operator(OP_OBJECT_virtools_group.BBP_OT_select_object_by_virtools_group.bl_idname)
|
layout.operator(OP_OBJECT_virtools_group.BBP_OT_select_object_by_virtools_group.bl_idname)
|
||||||
layout.separator()
|
layout.separator()
|
||||||
@ -346,6 +350,7 @@ def register() -> None:
|
|||||||
OP_OBJECT_virtools_group.register()
|
OP_OBJECT_virtools_group.register()
|
||||||
OP_OBJECT_snoop_group_then_to_mesh.register()
|
OP_OBJECT_snoop_group_then_to_mesh.register()
|
||||||
OP_OBJECT_naming_convention.register()
|
OP_OBJECT_naming_convention.register()
|
||||||
|
OP_OBJECT_game_view.register()
|
||||||
|
|
||||||
# register other classes
|
# register other classes
|
||||||
for cls in g_BldClasses:
|
for cls in g_BldClasses:
|
||||||
@ -368,6 +373,7 @@ def unregister() -> None:
|
|||||||
bpy.utils.unregister_class(cls)
|
bpy.utils.unregister_class(cls)
|
||||||
|
|
||||||
# unregister modules
|
# unregister modules
|
||||||
|
OP_OBJECT_game_view.unregister()
|
||||||
OP_OBJECT_naming_convention.unregister()
|
OP_OBJECT_naming_convention.unregister()
|
||||||
OP_OBJECT_snoop_group_then_to_mesh.unregister()
|
OP_OBJECT_snoop_group_then_to_mesh.unregister()
|
||||||
OP_OBJECT_virtools_group.unregister()
|
OP_OBJECT_virtools_group.unregister()
|
||||||
|
@ -37,7 +37,7 @@ license = [
|
|||||||
# ]
|
# ]
|
||||||
|
|
||||||
# Optional list of supported platforms. If omitted, the extension will be available in all operating systems.
|
# Optional list of supported platforms. If omitted, the extension will be available in all operating systems.
|
||||||
platforms = ["windows-x64", "linux-x64"]
|
platforms = ["windows-x64", "linux-x64", "macos-arm64"]
|
||||||
# Supported platforms: "windows-x64", "macos-arm64", "linux-x64", "windows-arm64", "macos-x64"
|
# Supported platforms: "windows-x64", "macos-arm64", "linux-x64", "windows-arm64", "macos-x64"
|
||||||
|
|
||||||
# Optional: bundle 3rd party Python modules.
|
# Optional: bundle 3rd party Python modules.
|
||||||
|
442
i18n/blender.pot
442
i18n/blender.pot
File diff suppressed because it is too large
Load Diff
442
i18n/zh_HANS.po
442
i18n/zh_HANS.po
File diff suppressed because it is too large
Load Diff
@ -27,6 +27,7 @@ class ShowcaseCfg(BaseModel):
|
|||||||
|
|
||||||
class Showcase(BaseModel):
|
class Showcase(BaseModel):
|
||||||
title: str = Field(frozen=True, strict=True)
|
title: str = Field(frozen=True, strict=True)
|
||||||
|
category: str = Field(frozen=True, strict=True)
|
||||||
icon: str = Field(frozen=True, strict=True)
|
icon: str = Field(frozen=True, strict=True)
|
||||||
type: ShowcaseType = Field(frozen=True)
|
type: ShowcaseType = Field(frozen=True)
|
||||||
cfgs: list[ShowcaseCfg] = Field(frozen=True, strict=True)
|
cfgs: list[ShowcaseCfg] = Field(frozen=True, strict=True)
|
||||||
|
@ -9,80 +9,96 @@ import pydantic, polib, json5
|
|||||||
# If the context string of translation changed, please synchronize it.
|
# If the context string of translation changed, please synchronize it.
|
||||||
|
|
||||||
CTX_TRANSLATION: str = 'BBP/BME'
|
CTX_TRANSLATION: str = 'BBP/BME'
|
||||||
|
CTX_PROTOTYPE: str = f'{CTX_TRANSLATION}/Proto'
|
||||||
|
CTX_CATEGORY: str = f'{CTX_TRANSLATION}/Category'
|
||||||
|
|
||||||
|
|
||||||
def _extract_prototype(prototype: bme.Prototype) -> typing.Iterator[polib.POEntry]:
|
class JsonsExtractor:
|
||||||
identifier = prototype.identifier
|
|
||||||
showcase = prototype.showcase
|
|
||||||
|
|
||||||
# Show message
|
po: polib.POFile
|
||||||
logging.info(f'Extracting prototype {identifier}')
|
"""Extracted PO file"""
|
||||||
|
categories: set[str]
|
||||||
|
"""Set for removing duplicated category names"""
|
||||||
|
|
||||||
# Extract showcase
|
def __init__(self) -> None:
|
||||||
if showcase is None:
|
# create po file
|
||||||
return
|
self.po = polib.POFile()
|
||||||
|
self.po.metadata = {
|
||||||
|
'Project-Id-Version': '1.0',
|
||||||
|
'Report-Msgid-Bugs-To': 'you@example.com',
|
||||||
|
'POT-Creation-Date': 'YEAR-MO-DA HO:MI+ZONE',
|
||||||
|
'PO-Revision-Date': 'YEAR-MO-DA HO:MI+ZONE',
|
||||||
|
'Last-Translator': 'FULL NAME <EMAIL@ADDRESS>',
|
||||||
|
'Language-Team': 'LANGUAGE <LL@li.org>',
|
||||||
|
'MIME-Version': '1.0',
|
||||||
|
'Content-Type': 'text/plain; charset=utf-8',
|
||||||
|
'Content-Transfer-Encoding': '8bit',
|
||||||
|
'X-Generator': 'polib',
|
||||||
|
}
|
||||||
|
# create category set
|
||||||
|
self.categories = set()
|
||||||
|
|
||||||
# Extract showcase title
|
def __extract_prototype(self, prototype: bme.Prototype) -> None:
|
||||||
yield polib.POEntry(msgid=showcase.title, msgstr='', msgctxt=f'{CTX_TRANSLATION}/{identifier}')
|
identifier = prototype.identifier
|
||||||
# Extract showcase entries
|
showcase = prototype.showcase
|
||||||
for i, cfg in enumerate(showcase.cfgs):
|
|
||||||
# extract title and description
|
|
||||||
yield polib.POEntry(msgid=cfg.title, msgstr='', msgctxt=f'{CTX_TRANSLATION}/{identifier}/[{i}]')
|
|
||||||
yield polib.POEntry(msgid=cfg.desc, msgstr='', msgctxt=f'{CTX_TRANSLATION}/{identifier}/[{i}]')
|
|
||||||
|
|
||||||
|
# Show message
|
||||||
|
logging.info(f'Extracting prototype {identifier}')
|
||||||
|
|
||||||
def _extract_json(json_file: Path) -> typing.Iterator[polib.POEntry]:
|
# Extract showcase
|
||||||
# Show message
|
if showcase is None:
|
||||||
logging.info(f'Extracting file {json_file}')
|
return
|
||||||
|
|
||||||
try:
|
# Extract showcase title
|
||||||
# Read file and convert it into BME struct.
|
self.po.append(polib.POEntry(msgid=showcase.title, msgstr='', msgctxt=f'{CTX_PROTOTYPE}/{identifier}'))
|
||||||
with open(json_file, 'r', encoding='utf-8') as f:
|
# extract showcase category
|
||||||
document = json5.load(f)
|
if showcase.category not in self.categories:
|
||||||
prototypes = bme.Prototypes.model_validate(document)
|
self.po.append(polib.POEntry(msgid=showcase.category, msgstr='', msgctxt=CTX_CATEGORY))
|
||||||
# Extract translation
|
self.categories.add(showcase.category)
|
||||||
return itertools.chain.from_iterable(_extract_prototype(prototype) for prototype in prototypes.root)
|
# Extract showcase entries
|
||||||
except pydantic.ValidationError:
|
for i, cfg in enumerate(showcase.cfgs):
|
||||||
logging.error(f'Can not extract translation from {json_file} due to struct error. Please validate it first.')
|
# extract title and description
|
||||||
except (ValueError, UnicodeDecodeError):
|
self.po.append(polib.POEntry(msgid=cfg.title, msgstr='', msgctxt=f'{CTX_PROTOTYPE}/{identifier}/[{i}]'))
|
||||||
logging.error(f'Can not extract translation from {json_file} due to JSON5 error. Please validate it first.')
|
self.po.append(polib.POEntry(msgid=cfg.desc, msgstr='', msgctxt=f'{CTX_PROTOTYPE}/{identifier}/[{i}]'))
|
||||||
|
|
||||||
# Output nothing
|
def __extract_json(self, json_file: Path) -> None:
|
||||||
return itertools.chain.from_iterable(())
|
# Show message
|
||||||
|
logging.info(f'Extracting file {json_file}')
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Read file and convert it into BME struct.
|
||||||
|
with open(json_file, 'r', encoding='utf-8') as f:
|
||||||
|
document = json5.load(f)
|
||||||
|
prototypes = bme.Prototypes.model_validate(document)
|
||||||
|
# Extract translation
|
||||||
|
for prototype in prototypes.root:
|
||||||
|
self.__extract_prototype(prototype)
|
||||||
|
except pydantic.ValidationError:
|
||||||
|
logging.error(
|
||||||
|
f'Can not extract translation from {json_file} due to struct error. Please validate it first.')
|
||||||
|
except (ValueError, UnicodeDecodeError):
|
||||||
|
logging.error(f'Can not extract translation from {json_file} due to JSON5 error. Please validate it first.')
|
||||||
|
|
||||||
def extract_jsons() -> None:
|
def extract_jsons(self) -> None:
|
||||||
raw_jsons_dir = common.get_raw_assets_folder(AssetKind.Jsons)
|
raw_jsons_dir = common.get_raw_assets_folder(AssetKind.Jsons)
|
||||||
|
|
||||||
# Create POT content
|
# Iterate all prototypes and add into POT
|
||||||
po = polib.POFile()
|
for raw_json_file in raw_jsons_dir.glob('*.json5'):
|
||||||
po.metadata = {
|
# Skip non-file.
|
||||||
'Project-Id-Version': '1.0',
|
if not raw_json_file.is_file():
|
||||||
'Report-Msgid-Bugs-To': 'you@example.com',
|
continue
|
||||||
'POT-Creation-Date': 'YEAR-MO-DA HO:MI+ZONE',
|
# Extract json
|
||||||
'PO-Revision-Date': 'YEAR-MO-DA HO:MI+ZONE',
|
self.__extract_json(raw_json_file)
|
||||||
'Last-Translator': 'FULL NAME <EMAIL@ADDRESS>',
|
|
||||||
'Language-Team': 'LANGUAGE <LL@li.org>',
|
|
||||||
'MIME-Version': '1.0',
|
|
||||||
'Content-Type': 'text/plain; charset=utf-8',
|
|
||||||
'Content-Transfer-Encoding': '8bit',
|
|
||||||
'X-Generator': 'polib',
|
|
||||||
}
|
|
||||||
|
|
||||||
# Iterate all prototypes and add into POT
|
def save(self) -> None:
|
||||||
for raw_json_file in raw_jsons_dir.glob('*.json5'):
|
"""Save extracted POT file into correct path"""
|
||||||
# Skip non-file.
|
pot_file = common.get_root_folder() / 'i18n' / 'bme.pot'
|
||||||
if not raw_json_file.is_file():
|
logging.info(f'Saving POT into {pot_file}')
|
||||||
continue
|
self.po.save(str(pot_file))
|
||||||
# Extract json and append it.
|
|
||||||
po.extend(_extract_json(raw_json_file))
|
|
||||||
|
|
||||||
# Write into POT file
|
|
||||||
pot_file = common.get_root_folder() / 'i18n' / 'bme.pot'
|
|
||||||
logging.info(f'Saving POT into {pot_file}')
|
|
||||||
po.save(str(pot_file))
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
common.setup_logging()
|
common.setup_logging()
|
||||||
extract_jsons()
|
extractor = JsonsExtractor()
|
||||||
|
extractor.extract_jsons()
|
||||||
|
extractor.save()
|
||||||
|
@ -48,6 +48,9 @@ def _validate_showcase(showcase: bme.Showcase, variables: set[str]) -> None:
|
|||||||
# The title of showcase should not be empty
|
# The title of showcase should not be empty
|
||||||
if len(showcase.title) == 0:
|
if len(showcase.title) == 0:
|
||||||
logging.error('The title of showcase should not be empty.')
|
logging.error('The title of showcase should not be empty.')
|
||||||
|
# Category words should not be empty.
|
||||||
|
if len(showcase.category) == 0:
|
||||||
|
logging.error('The category of showcase should not be empty.')
|
||||||
|
|
||||||
# Check icon name
|
# Check icon name
|
||||||
_check_showcase_icon(showcase.icon)
|
_check_showcase_icon(showcase.icon)
|
||||||
|
Reference in New Issue
Block a user