6 Commits

Author SHA1 Message Date
2b2b18cfa4 fix: add error report for invalid file path when importing or exporting virtools file.
- add error report for invalid file path when importing or exporting virtools to avoid BMapException was thrown. Reported by SongRui
2025-08-25 14:09:10 +08:00
b19800e37f fix: fix bug that there is no preset encoding names.
- fix the issue that there is no preset encoding names in list when enable plugin without any extra operations.
2025-08-25 13:53:57 +08:00
e14729500c fix: fix rail adders poll issue
- add Ballance Texture requirement for all rail adders because they need it.
2025-08-25 13:24:15 +08:00
48bfc54830 i18n: modify i18n file batchly.
- update translation context in i18n files batchly due to previous BME prototype changes.
2025-08-25 13:13:42 +08:00
7e74e42bd7 feat: add BME category display in blender.
- add BME category display in blender, including add menu and side menu.
2025-08-25 13:07:55 +08:00
96a81b165b feat: add category fields for BME.
- add category for BME prorotypes.
- update validator and extractor for this change.
2025-08-25 10:30:44 +08:00
24 changed files with 709 additions and 553 deletions

View File

@ -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": [

View File

@ -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": [

View File

@ -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": [

View File

@ -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": [

View File

@ -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": [

View File

@ -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": [

View File

@ -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": [

View File

@ -77,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": [
@ -127,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": [

View File

@ -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": [

View File

@ -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": [

View File

@ -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": [
@ -220,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": [
@ -352,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": [
@ -475,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": [

View File

@ -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

View File

@ -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(

View File

@ -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,

View File

@ -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(

View File

@ -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)

View File

@ -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,

View File

@ -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)

View File

@ -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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -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)

View File

@ -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()

View File

@ -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)