├── .gitignore ├── LICENSE ├── combine_tile.py ├── dds2png.py ├── cgf2fbx.py ├── h322r16.py ├── README.md └── load_geomap.py /.gitignore: -------------------------------------------------------------------------------- 1 | # for projects that use SCons for building: http://http://www.scons.org/ 2 | .sconsign.dblite 3 | io_scene_cgf.zip 4 | __pycache__/ 5 | .ycm_extra_conf.py 6 | .mac_env/ 7 | .mac_venv/ 8 | .venv/ 9 | .win_env/ 10 | .win_venv/ 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Jeremy Chen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /combine_tile.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from __future__ import absolute_import 4 | import os, sys, shutil, optparse, logging 5 | from PIL import Image 6 | 7 | __rootpath__ = os.path.abspath(os.path.expanduser(os.path.dirname(__file__))) 8 | 9 | def process_options(parser = None): 10 | if parser is None: 11 | parser = optparse.OptionParser( 12 | usage = "Usage: %prog [OPTIONS] input files ...", 13 | epilog = "" 14 | ) 15 | parser.add_option('-v', '--verbose', 16 | default = False, 17 | action = 'store_true', 18 | help = 'Verbose logging trace.') 19 | 20 | parser.add_option('-x', 21 | dest = 'tile_size', 22 | default = '10x10', 23 | help = 'Row X Column tiles.') 24 | 25 | parser.add_option('-d', '--directory', 26 | dest = 'working_dir', 27 | help = 'Directory where the tile located.') 28 | 29 | parser.add_option('-p', '--pattern', 30 | default = '%x_%y', 31 | help = 'Tile pattern.') 32 | 33 | parser.add_option('-o', '--output', 34 | dest = 'output_file', 35 | default = 'output.png', 36 | help = 'Specify output path.') 37 | 38 | return parser 39 | 40 | def run(args = None): 41 | if args is None: 42 | args = sys.argv[1:] 43 | 44 | parser = process_options() 45 | keywords, positional = parser.parse_args(args) 46 | 47 | input_files = [] 48 | 49 | if len(positional) > 0: 50 | input_files = input_files + positional 51 | 52 | if len(input_files) == 0: 53 | parser.print_help() 54 | return 55 | 56 | logging.root.setLevel(logging.INFO) 57 | 58 | if keywords.verbose: 59 | logging.root.setLevel(logging.DEBUG) 60 | 61 | tile_size = keywords.tile_size.split('x') 62 | tx = int(tile_size[0]) 63 | ty = int(tile_size[1]) 64 | ta = tx * ty 65 | 66 | logging.debug("Tile size is %d x %d = %d" % (tx, ty, ta)) 67 | 68 | tw = None 69 | th = None 70 | 71 | output_image = None 72 | 73 | xi = yi = 0 74 | 75 | for i in range(0, ta): 76 | tile_filepath = os.path.join(keywords.working_dir, (keywords.pattern % i)) 77 | logging.debug("Reading image tile %d => %s" % (i, tile_filepath)) 78 | im = Image.open(tile_filepath) 79 | if tw is None: 80 | tw = im.width 81 | if th is None: 82 | th = im.height 83 | 84 | 85 | if output_image is None: 86 | output_image = Image.new('RGBA', ( tw * tx, th * ty )) 87 | 88 | output_image.paste(im, (xi * tw, yi * th)) 89 | 90 | logging.debug("Image tile resolution: %d x %d" % (im.width, im.height)) 91 | 92 | im.close() 93 | 94 | yi = yi + 1 95 | if yi == tx: 96 | xi = xi + 1 97 | yi = 0 98 | 99 | 100 | if output_image: 101 | output_image.save(keywords.output_file) 102 | output_image.close() 103 | 104 | if __name__ == '__main__': 105 | run() 106 | 107 | -------------------------------------------------------------------------------- /dds2png.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from __future__ import absolute_import, print_function 4 | import os, sys, optparse, subprocess, re, time, string, logging 5 | from subprocess import PIPE, Popen, STDOUT 6 | from PIL import Image 7 | 8 | 9 | curdir = os.path.abspath(os.path.dirname(__file__)) 10 | 11 | LOG = logging.getLogger('dds2png') 12 | 13 | def run(args=None): 14 | if args is None: 15 | args = sys.argv[1:] 16 | parser = optparse.OptionParser( 17 | usage="usage: %prog [OPTIONS] files ...", 18 | description=("dds2png is a script to convert any DDS file format to PNG."), 19 | epilog='' 20 | ) 21 | 22 | parser.add_option('-d', '--directory', type='string', default=None, 23 | action='store', 24 | help='Searching for DDS.') 25 | 26 | parser.add_option('-v', '--verbose', 27 | default=False, 28 | action='store_true', 29 | help='Verbose logging trace') 30 | 31 | keywords, positional = parser.parse_args(args) 32 | 33 | input_lines = [] 34 | 35 | directory = keywords.directory 36 | 37 | if not directory and len(positional) == 0: 38 | parser.print_help() 39 | exit(0) 40 | elif directory and len(positional) == 0: 41 | for root, dirs, files in os.walk(directory): 42 | # ignore dirs, filtering .dds 43 | for f in files: 44 | if f.endswith('.dds') and not f.startswith('_') and not f.startswith('.'): 45 | input_lines.append(os.path.join(root, f)) 46 | elif positional[0] == '-': 47 | # read from the stdin 48 | input_lines = sys.stdin.readlines() 49 | positional = positional[1:] 50 | 51 | 52 | if keywords.verbose: 53 | LOG.setLevel(logging.DEBUG) 54 | else: 55 | LOG.setLevel(logging.INFO) 56 | 57 | LOGGING_FORMAT = '%(asctime)-15s %(levelname)8s | %(message)s' 58 | logging.basicConfig(format=LOGGING_FORMAT) 59 | 60 | filenames = [] 61 | if len(input_lines) > 0: 62 | filenames.extend(input_lines) 63 | else: 64 | filenames.extend(positional) 65 | 66 | if len(filenames): 67 | filenames = list(map(lambda x: os.path.abspath(x.replace('\r', '').replace('\n', '')), filenames)) 68 | 69 | 70 | error_converts = [] 71 | 72 | for filename in filenames: 73 | (base_filename, filename_ext) = os.path.splitext(filename) 74 | output_filename = base_filename + '.png' 75 | try: 76 | with Image.open(filename) as im: 77 | LOG.debug('%s - %s - %s' % (im.format, im.size, im.mode)) 78 | LOG.info('Converting %s to %s' % (filename, output_filename)) 79 | try: 80 | im.save(output_filename) 81 | except: 82 | error_converts.append(filename) 83 | LOG.error("Can not convert %s to %s" % (filename, output_filename)) 84 | LOG.info('Converted %s' % (output_filename)) 85 | except: 86 | error_converts.append(filename) 87 | pass 88 | 89 | LOG.info('Done') 90 | 91 | if len(error_converts): 92 | LOG.warning('Several convertion error caughts:') 93 | for fn in error_converts: 94 | LOG.warning(fn) 95 | 96 | if __name__ == '__main__': 97 | # Execute the main entrance. 98 | run() 99 | 100 | -------------------------------------------------------------------------------- /cgf2fbx.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os, sys, logging, optparse 4 | import importlib 5 | 6 | bpy_in='bpy' in locals() 7 | 8 | if not bpy_in: 9 | try: 10 | importlib.import_module('bpy') 11 | bpy_in = True 12 | except: 13 | bpy_in = False 14 | 15 | if bpy_in: 16 | import bpy 17 | import bpy_extras 18 | import mathutils 19 | 20 | try: 21 | curdir = os.path.abspath(os.path.dirname(__file__)) 22 | except: 23 | curdir = '' 24 | 25 | def run(argv=None): 26 | if argv is None: 27 | idx = 1 28 | for i, v in enumerate(sys.argv): 29 | if v == '--': 30 | idx = i 31 | break 32 | 33 | argv = sys.argv[idx+1:] 34 | 35 | parser = optparse.OptionParser() 36 | parser.add_option('-a', '--anim', 37 | default=False, 38 | action='store_true') 39 | 40 | parser.add_option('--anim_only', 41 | default=False, 42 | action='store_true') 43 | 44 | parser.add_option('--output-directory', 45 | type='string', action='store', dest='output_directory', 46 | ) 47 | 48 | parser.add_option('-v', '--verbose', 49 | default=False, action='store_true' 50 | ) 51 | 52 | (keywords, positional) = parser.parse_args(argv) 53 | 54 | # Force the render engine to CYCLES 55 | bpy.context.scene.render.engine = 'CYCLES' 56 | 57 | # Clear the scene first. 58 | bpy.ops.object.select_all(action='SELECT') # select all object 59 | bpy.ops.object.delete() # delete all select objects. 60 | 61 | sFilePath = positional[0] if len(positional) > 0 else None 62 | bIncludeAnimations = keywords.anim 63 | 64 | if sFilePath is None: 65 | print('No input files specified.') 66 | return 67 | 68 | bpy.ops.import_scene.cgf(filepath=sFilePath, import_animations=bIncludeAnimations) 69 | 70 | if keywords.output_directory: 71 | fbx_filepath = os.path.join(os.path.abspath(os.path.expanduser(keywords.output_directory)), os.path.splitext(os.path.basename(sFilePath))[0]) 72 | else: 73 | fbx_filepath = os.path.splitext(sFilePath)[0] 74 | 75 | object_types = None 76 | 77 | if keywords.anim_only: 78 | fbx_filepath += '_animations.fbx' 79 | object_types = { 'ARMATURE' } 80 | else: 81 | fbx_filepath += '.fbx' 82 | object_types = { 'ARMATURE', 'MESH' } 83 | 84 | # Imported 85 | # Exported the scene as FBX output. 86 | bpy.ops.export_scene.fbx( 87 | filepath=fbx_filepath, 88 | axis_forward = 'Z', 89 | axis_up = 'Y', 90 | bake_space_transform = True, 91 | object_types = object_types, 92 | use_mesh_modifiers = True, 93 | use_mesh_modifiers_render = True, 94 | add_leaf_bones = False, 95 | bake_anim = bIncludeAnimations, 96 | bake_anim_use_all_bones = False, 97 | bake_anim_use_nla_strips = False, 98 | bake_anim_use_all_actions = True, 99 | bake_anim_force_startend_keying = False, 100 | bake_anim_step = 1, 101 | bake_anim_simplify_factor = 0, 102 | use_anim = True, 103 | use_anim_action_all = True, 104 | use_default_take = True, 105 | use_anim_optimize = True, 106 | anim_optimize_precision = 6, 107 | ) 108 | 109 | if __name__ == '__main__': 110 | run() 111 | 112 | -------------------------------------------------------------------------------- /h322r16.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os, sys, logging, optparse, struct 4 | 5 | default_options={ 6 | 'resolution': '1536x1536' 7 | } 8 | 9 | def process_options(argv=None): 10 | if not argv: 11 | argv = sys.argv[1:] 12 | 13 | parser = optparse.OptionParser( 14 | usage='%prog [OPTIONS] input files ...', 15 | description='A tool for converting .h32 into .r16', 16 | ) 17 | 18 | parser.add_option('-o', '--output', action='store', type='string', 19 | help="Specified the output destination!") 20 | 21 | parser.add_option('-x', '--resolution', action='store', type='string', 22 | help="Specified the width & height.") 23 | 24 | parser.add_option('-v', '--verbose', action='store_true', default=False, 25 | help='Verbose logging trace') 26 | 27 | keywords, positional = parser.parse_args(argv) 28 | 29 | if keywords.verbose: 30 | logging.root.setLevel(logging.DEBUG) 31 | 32 | if not keywords.output: 33 | logging.debug('No output destination specified!') 34 | 35 | if not keywords.resolution: 36 | logging.debug('No resolution specified, use %s by default.' % default_options['resolution']) 37 | 38 | if len(positional) == 0: 39 | logging.debug('No input file specified!') 40 | 41 | for k in default_options.keys(): 42 | if not keywords.__getattribute__(k): 43 | keywords.__setattr__(k, default_options[k]) 44 | 45 | return (parser, keywords, positional) 46 | 47 | def unpack(f, fmt): 48 | return struct.unpack(fmt, f.read(struct.calcsize(fmt))) 49 | 50 | def pack(fmt, **args): 51 | return struct.pack(fmt, **args) 52 | 53 | def read_h32(a_file, width, height): 54 | with open(a_file, 'rb') as f: 55 | f.seek(0, 2) 56 | bufsize = f.tell() 57 | f.seek(0, 0) 58 | 59 | logging.debug('File size: %i' % bufsize) 60 | bufread = 0 61 | data = [] 62 | 63 | for x in range(width): 64 | for y in range(height): 65 | r, g, b = unpack(f, '<3B') 66 | bufread = bufread + 3 67 | 68 | h = g * 1.0 / 255.0 + r * 1.0 / 255.0 / 255.0 + b * 1.0 / 255.0 / 255.0 / 255.0 # GRB => Height(0~1) 69 | h = int(h * 65535.0) # Range to unsigned short 70 | data.append(h) 71 | 72 | assert bufread == bufsize, "There're remaining %i buffer size, incorrect resolution specified?" % (bufsize - bufread) 73 | return data 74 | return None 75 | 76 | def do_convert(input_file, width, height, output_file): 77 | data = read_h32(input_file, width, height) 78 | 79 | if data: 80 | with open(output_file, 'wb') as f: 81 | for x in range(width): 82 | for y in range(height): 83 | f.write(struct.pack(' Due to the python API on Blender 2.8, unsupported for now. 14 | 15 | The kind of scripts was working on `Blender 2.79`, so should get work correctly on `Blender 2.6` too. 16 | 17 | Execute as a command line in terminal, replace the default `python` runtime by the `Blender` binary executable. 18 | 19 | Run in **Background** mode. 20 | 21 | ```bash 22 | {blender_executable} -b --python {script} -- {script_arguments} 23 | ``` 24 | 25 | Or run the `script` in `Blender` 's `Console` directly, e.g. 26 | 27 | ``` 28 | # Blender 2.79b Python Console. 29 | 30 | # defines the filename in the locals() 31 | filename = {script_filename} 32 | # Run the given script by exec mode. 33 | exec(compile(open(filename).read(), filename, 'exec')) 34 | ``` 35 | 36 | Particular scripts has arguments required, so passing the *arguments* as the `variables` in the `locals` context, just define the the argument as the local variable will be work fine. 37 | 38 | ``` 39 | # Blender 2.79b Python Console. 40 | 41 | # defines the arguments 42 | cgf_file='/Volumes/assets/Objects/monster/mosbear/mosbear.cgf' 43 | # defines the filename in the locals() 44 | filename = ~/Projects/io_scene_cgf_extras/cgf2fbx.py 45 | # Run the given script by exec mode. 46 | exec(compile(open(filename).read(), filename, 'exec')) 47 | ``` 48 | 49 | More information about how to execute with `Blender`, see [Tips and Tricks in blender.org](https://docs.blender.org/api/current/info_tips_and_tricks.html#executing-external-scripts) 50 | ## Scripts 51 | 52 | ### cgf2fbx.py 53 | 54 | Convert ___CGF/CAF___ file format to ___fbx___, including the **Meshes**, **Armatures**, **Animations**, **Materials**, etc. 55 | 56 | #### Prerequisite 57 | 58 | `blender` executable runtime enviroment required. 59 | 60 | #### Usage 61 | 62 | A simple commond for converting a given `cgf` file into `fbx`, no animation data exporting, just includes the `meshes` and the `materials`: 63 | 64 | ``` 65 | blender -b --python cgf2fbx.py -- [CGF file] 66 | ``` 67 | 68 | Resolves the animations data and export to fbx, so the final fbx including the meshes, materials, animations, etc. 69 | 70 | ``` 71 | blender -b --python cgf2fbx.py -- [-a|--anim] [CGF file] 72 | ``` 73 | 74 | Resolves the animations data and export to fbx, but doesn't export the meshes, just the animation data only. 75 | 76 | ``` 77 | blender -b --python cgf2fbx.py -- --anim_only [CGF file] 78 | ``` 79 | 80 | ### dds2png.py 81 | 82 | Convert file format `DDS` to `PNG`. 83 | 84 | #### Prerequisite 85 | 86 | `dds2png.py` require module `PIL`. 87 | 88 | Install the `PIL` module via `pip`, following below: 89 | 90 | On `python 2.x`: 91 | 92 | ``` 93 | pip install image 94 | ``` 95 | 96 | On `python 3.x`, choose `pillow` instead of `image`: 97 | 98 | ``` 99 | pip install pillow 100 | ``` 101 | 102 | #### Usage 103 | 104 | ``` 105 | dds2png.py [DDS files...] 106 | ``` 107 | 108 | The output files will generating at the same directory as input files. 109 | 110 | ### combine_tile.py 111 | 112 | Combines `x` * `y` tiles image to a single image. 113 | 114 | #### Prerequisite 115 | 116 | The same as `dds2png.py`, this required `PIL` module as well. See [dds2png.py](#dds2png.py). 117 | 118 | #### Usage 119 | 120 | A simple example run in command-line combining *ZoneMap* from radar display: 121 | 122 | ``` 123 | combine_tile.py -x 256x256 -p radar_df1_%03d.dds -d /Volumes/assets/Textures/ui/zonemap/df1 -o DF1.png 124 | ``` 125 | 126 | ### load_geomap.py 127 | 128 | Load `terrain` data from `GEO` data into `Blender`. 129 | 130 | #### Prerequisite 131 | 132 | the `load_geomap.py` requires `blender` as the executable runtime, see [cgf2fbx.py](#cgf2fbx.py) 133 | 134 | #### Usage 135 | 136 | There're a example command-line for importing `GEO` mapdata into `blender`: 137 | 138 | ``` 139 | blender --python load_geomap.py -- -d /Volumes/Untitled/Resources/geo_data/models /Volumes/Untitled/Resources/geo_data/220010000.bin 140 | ``` 141 | 142 | When finished executing the `load_geomap.py`, will showing the `terrain` in the blender application. 143 | 144 | ### h322r16.py 145 | 146 | A tool for converting `.h32` file format into `.r16` or `.raw`, make working correctly in `Unity`, `UE4`, `WorldMachine`, etc. The type of heightmap converted is setting to __unsigned 16bit little endian__. 147 | 148 | #### Prerequisite 149 | 150 | The same as `dds2png.py`, requires `PIL` module as well. see [dds2png.py](#dds2png.py) 151 | 152 | #### Usage 153 | 154 | A simple usage show at command-line mode: 155 | 156 | ``` 157 | h322r16.py [H32 files ...] 158 | ``` 159 | 160 | Specified `-o` to the `output` destination. 161 | 162 | -------------------------------------------------------------------------------- /load_geomap.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os, sys, optparse, logging, struct 4 | import importlib 5 | 6 | bpy_in='bpy' in locals() 7 | 8 | if not bpy_in: 9 | try: 10 | importlib.import_module('bpy') 11 | bpy_in=True 12 | except: 13 | # Non bpy module, so run this script by standalone, not run in blender. 14 | bpy_in=False 15 | pass 16 | 17 | if bpy_in: 18 | import bpy 19 | import bpy_extras 20 | import mathutils 21 | 22 | try: 23 | __rootpath__ = os.path.abspath(os.path.dirname(__file__)) 24 | except: 25 | __rootpath__ = '' 26 | 27 | def unpack(fio, fmt): 28 | return struct.unpack(fmt, fio.read(struct.calcsize(fmt))) 29 | 30 | def read_map(map_file): 31 | with open(map_file, 'rb') as mf: 32 | mf.seek(0, 2) 33 | bufsize = mf.tell() 34 | mf.seek(0, 0) 35 | 36 | logging.debug('========================================') 37 | logging.debug('Map File: %s' % os.path.basename(map_file)) 38 | logging.debug("File Size: %i" % bufsize) 39 | logging.debug('========================================') 40 | 41 | geo_count, = unpack(mf, ' -1: 232 | argv = sys.argv[idx+1:] 233 | 234 | keywords, positional = parse_arguments(argv) 235 | 236 | if clean: 237 | clear_all_data() 238 | 239 | load_map(positional[0], keywords.geometry_dir) 240 | 241 | def run(argv=None): 242 | keywords, positional = parse_arguments(argv) 243 | 244 | logging.debug('Run script for processing now on ...') 245 | 246 | for i, input_file in enumerate(positional): 247 | if not os.path.exists(os.path.abspath(input_file)): 248 | logging.warning("Non exist input file: %s" % input_file) 249 | continue 250 | 251 | read_map(os.path.abspath(input_file)) 252 | # if keywords.map: 253 | # read_map(os.path.abspath(input_file)) 254 | # elif keywords.geometry: 255 | # read_geometry(os.path.abspath(input_file)) 256 | 257 | logging.debug('Done!') 258 | 259 | if __name__ == '__main__': 260 | if bpy_in: 261 | run_in_blender(clean=True) 262 | else: 263 | run() 264 | 265 | --------------------------------------------------------------------------------