def on_export_clicked(self, w: Gtk.MenuToolButton): dialog = Gtk.FileChooserNative.new(_("Export font in folder..."), MainController.window(), Gtk.FileChooserAction.SELECT_FOLDER, '_Save', None) response = dialog.run() fn = dialog.get_filename() dialog.destroy() if response == Gtk.ResponseType.ACCEPT: xml, tables = self.font.export_to_xml() with open(os.path.join(fn, f'char_tables.xml'), 'w') as f: f.write(prettify(xml)) for i, table in tables.items(): table.save(os.path.join(fn, f'table-{i}.png'))
def on_btn_export_clicked(self, *args): dialog = Gtk.FileChooserNative.new( _("Export dungeon tileset..."), MainController.window(), Gtk.FileChooserAction.SELECT_FOLDER, None, None ) response = dialog.run() fn = dialog.get_filename() dialog.destroy() assert self.dtef is not None if response == Gtk.ResponseType.ACCEPT: try: # Write XML with open(os.path.join(fn, 'tileset.dtef.xml'), 'w') as f: f.write(prettify(self.dtef.get_xml())) # Write Tiles var0, var1, var2, rest = self.dtef.get_tiles() var0fn, var1fn, var2fn, restfn = self.dtef.get_filenames() var0.save(os.path.join(fn, var0fn)) var1.save(os.path.join(fn, var1fn)) var2.save(os.path.join(fn, var2fn)) rest.save(os.path.join(fn, restfn)) shutil.copy(get_template_file(), os.path.join(fn, 'template.png')) md = SkyTempleMessageDialog(MainController.window(), Gtk.DialogFlags.DESTROY_WITH_PARENT, Gtk.MessageType.INFO, Gtk.ButtonsType.OK, _("The tileset was successfully exported."), title=_("Success!"), is_success=True) md.run() md.destroy() except BaseException as e: display_error( sys.exc_info(), str(e), _("Error exporting the tileset.") )
# You should have received a copy of the GNU General Public License # along with SkyTemple. If not, see <https://www.gnu.org/licenses/>. # mypy: ignore-errors import os from ndspy.rom import NintendoDSRom from xml.etree.ElementTree import Element, ElementTree from skytemple_files.common.xml_util import prettify from skytemple_files.common.types.file_types import FileType from skytemple_files.common.util import get_files_from_rom_with_extension, get_ppmdu_config_for_rom from skytemple_files.graphics.fonts.font_sir0.handler import FontSir0Handler base_dir = os.path.join(os.path.dirname(__file__), '..', '..', '..', '..', '..') out_dir = os.path.join(os.path.dirname(__file__), 'dbg_output') os.makedirs(out_dir, exist_ok=True) rom = NintendoDSRom.fromFile(os.path.join(base_dir, 'skyworkcopy_us.nds')) config = get_ppmdu_config_for_rom(rom) for fn in ["FONT/kanji.dat", "FONT/kanji_b.dat", "FONT/unknown.dat"]: font = FontSir0Handler.deserialize(rom.getFileByName(fn)) xml, tables = font.export_to_xml() with open(os.path.join(out_dir, fn.replace('/', '_') + f'.xml'), 'w') as f: f.write(prettify(xml)) for i, table in tables.items(): table.save(os.path.join(out_dir, fn.replace('/', '_') + f'.{i}.png')) assert font == FontSir0Handler.deserialize(FontSir0Handler.serialize(font))
def on_btn_export_clicked(self, *args): dialog: Gtk.Dialog = self.builder.get_object('export_dialog') dialog.resize(640, 560) dialog.set_attached_to(SkyTempleMainController.window()) dialog.set_transient_for(SkyTempleMainController.window()) # Fill Pokémon tree store: Gtk.TreeStore = self.builder.get_object('export_dialog_store') store.clear() monster_entries_by_base_id: Dict[int, List[MdEntry]] = {} for entry in self.module.monster_md.entries: if entry.md_index_base not in monster_entries_by_base_id: monster_entries_by_base_id[entry.md_index_base] = [] monster_entries_by_base_id[entry.md_index_base].append(entry) for baseid, entry_list in monster_entries_by_base_id.items(): name = self.module.project.get_string_provider().get_value(StringType.POKEMON_NAMES, baseid) entry_main_tree = self.module.generate_entry__entity_root(baseid, name) ent_root = store.append(None, [ -1, -1, False, entry_main_tree[0], entry_main_tree[1], False, False ]) for entry in entry_list: entry_main_tree = self.module.generate_entry__entry(entry.md_index, entry.gender) store.append( ent_root, [ entry_main_tree[4], -1, True, entry_main_tree[0], entry_main_tree[1], False, False ] ) names, md_gender1, md_gender2, moveset, moveset2, stats, portraits, portraits2 = self.module.get_export_data(self.entry) we_are_gender1 = md_gender1 == self.entry if md_gender2 is None: sw: Gtk.Switch = self.builder.get_object('export_type_other_gender') sw.set_active(False) sw.set_sensitive(False) if portraits is None: sw: Gtk.Switch = self.builder.get_object('export_type_portraits_current_gender') sw.set_active(False) sw.set_sensitive(False) if portraits2 is None: sw: Gtk.Switch = self.builder.get_object('export_type_portraits_other_gender') sw.set_active(False) sw.set_sensitive(False) if stats is None: sw: Gtk.Switch = self.builder.get_object('export_type_stats') sw.set_active(False) sw.set_sensitive(False) if moveset is None: sw: Gtk.Switch = self.builder.get_object('export_type_moveset1') sw.set_active(False) sw.set_sensitive(False) if moveset2 is None: sw: Gtk.Switch = self.builder.get_object('export_type_moveset2') sw.set_active(False) sw.set_sensitive(False) resp = dialog.run() dialog.hide() if resp == Gtk.ResponseType.APPLY: # Create output XML if not self.builder.get_object('export_type_current_gender').get_active(): if we_are_gender1: md_gender1 = None else: md_gender2 = None if not self.builder.get_object('export_type_other_gender').get_active(): if not we_are_gender1: md_gender1 = None else: md_gender2 = None if not self.builder.get_object('export_type_names').get_active(): names = None if not self.builder.get_object('export_type_stats').get_active(): stats = None if not self.builder.get_object('export_type_moveset1').get_active(): moveset = None if not self.builder.get_object('export_type_moveset2').get_active(): moveset2 = None if not self.builder.get_object('export_type_portraits_current_gender').get_active(): if we_are_gender1: portraits = None else: portraits2 = None if not self.builder.get_object('export_type_portraits_other_gender').get_active(): if not we_are_gender1: portraits = None else: portraits2 = None xml = monster_xml_export( self.module.project.get_rom_module().get_static_data().game_version, md_gender1, md_gender2, names, moveset, moveset2, stats, portraits, portraits2 ) # 1. Export to file if self.builder.get_object('export_file_switch').get_active(): save_diag = Gtk.FileChooserNative.new( _("Export Pokémon as..."), SkyTempleMainController.window(), Gtk.FileChooserAction.SAVE, None, None ) add_dialog_xml_filter(save_diag) response = save_diag.run() fn = save_diag.get_filename() if '.' not in fn: fn += '.xml' save_diag.destroy() if response == Gtk.ResponseType.ACCEPT: with open(fn, 'w') as f: f.write(prettify(xml)) else: md = SkyTempleMessageDialog(SkyTempleMainController.window(), Gtk.DialogFlags.DESTROY_WITH_PARENT, Gtk.MessageType.WARNING, Gtk.ButtonsType.OK, "Export was canceled.") md.set_position(Gtk.WindowPosition.CENTER) md.run() md.destroy() return # 2. Import to selected Pokémon selected_monsters: List[int] = [] def collect_monsters_recurse(titer: Optional[Gtk.TreeIter]): for i in range(store.iter_n_children(titer)): child = store.iter_nth_child(titer, i) if store[child][2] and store[child][5]: # is floor and is selected selected_monsters.append(store[child][0]) collect_monsters_recurse(child) collect_monsters_recurse(None) self.module.import_from_xml(selected_monsters, xml)
def write_xml(xml, fn): with open(os.path.join(output_dir, fn), 'w') as f: f.write(prettify(xml))
def ExportSheets(outDir, sdwImg, effectData, anim_name_map): if not os.path.isdir(outDir): os.makedirs(outDir) if DEBUG_PRINT: if not os.path.isdir(os.path.join(outDir, '_pieces')): os.makedirs(os.path.join(outDir, '_pieces')) if not os.path.isdir(os.path.join(outDir, '_frames')): os.makedirs(os.path.join(outDir, '_frames')) anim_stats = [] maxFrameBounds = (10000, 10000, -10000, -10000) # get max bounds across all frames for idx, metaFrame in enumerate(effectData.frameData): for mt_idx, metaFramePiece in enumerate(metaFrame): # update bounds based on image fBounds = metaFramePiece.GetBounds() maxFrameBounds = exUtils.combineExtents(maxFrameBounds, fBounds) # update bounds based on offsets offset = effectData.offsetData[idx] maxFrameBounds = exUtils.combineExtents(maxFrameBounds, offset.GetBounds()) # round up to nearest x8 maxFrameBounds = exUtils.centerBounds(maxFrameBounds, (DRAW_CENTER_X, DRAW_CENTER_Y)) maxFrameBounds = exUtils.roundUpBox(maxFrameBounds) # create all frames, and visual representation of offsets tied to each frame frames = [] offsets = [] frames_bounds_tight = [] piece_imgs = {} for idx, metaFrame in enumerate(effectData.frameData): has_minus = False draw_queue = [] for mt_idx, metaFramePiece in enumerate(metaFrame): # create the piece parent_idx = MINUS_FRAME if metaFramePiece.imgIndex == MINUS_FRAME: has_minus = True prev_idx = mt_idx - 1 while metaFrame[prev_idx].imgIndex == MINUS_FRAME or metaFrame[ prev_idx].getTileNum() != metaFramePiece.getTileNum(): prev_idx = prev_idx - 1 parent_idx = metaFrame[prev_idx].imgIndex else: parent_idx = metaFramePiece.imgIndex if parent_idx in piece_imgs: img = piece_imgs[parent_idx] else: img = metaFramePiece.GeneratePiece(effectData.imgData, effectData.customPalette, parent_idx) piece_imgs[parent_idx] = img draw_queue.append((img, metaFramePiece)) # create an image to represent the full metaFrameGroup groupImg = Image.new('RGBA', (maxFrameBounds[2] - maxFrameBounds[0], maxFrameBounds[3] - maxFrameBounds[1]), (0, 0, 0, 0)) while len(draw_queue) > 0: img, metaFramePiece = draw_queue.pop() metaFramePiece.DrawOn(groupImg, img, (maxFrameBounds[0], maxFrameBounds[1])) if DEBUG_PRINT: groupImg.save( os.path.join(outDir, '_frames', 'F-' + format(idx, '02d') + '.png')) frames.append(groupImg) # create an image for particle offsets particleImg = Image.new('RGBA', (maxFrameBounds[2] - maxFrameBounds[0], maxFrameBounds[3] - maxFrameBounds[1]), (0, 0, 0, 0)) offset = effectData.offsetData[idx] offset.DrawOn(particleImg, (maxFrameBounds[0], maxFrameBounds[1])) if DEBUG_PRINT: particleImg.save( os.path.join(outDir, '_frames', 'F-' + format(idx, '02d') + '-Offsets.png')) offsets.append(particleImg) # create a tighter bounds representation of the frame, down to the pixel frame_rect = exUtils.getCoveredBounds(groupImg) frame_rect = exUtils.addToBounds(frame_rect, maxFrameBounds) frame_rect = exUtils.combineExtents(frame_rect, offset.GetBounds()) frames_bounds_tight.append(frame_rect) if DEBUG_PRINT: for piece_idx in piece_imgs: img = piece_imgs[piece_idx] img.save( os.path.join(outDir, '_pieces', 'P-' + format(piece_idx, '03d') + '.png')) # get max bounds for all animations groupBounds = [] shadow_rect = (sdwImg.size[0] // -2, sdwImg.size[1] // -2, sdwImg.size[0] // 2, sdwImg.size[1] // 2) shadow_rect_tight = exUtils.getCoveredBounds(sdwImg) shadow_rect_tight = exUtils.addToBounds(shadow_rect_tight, shadow_rect) for idx, animGroup in enumerate(effectData.animGroupData): maxBounds = (10000, 10000, -10000, -10000) for g_idx, singleAnim in enumerate(animGroup): for a_idx, animFrame in enumerate(singleAnim): frame_rect = frames_bounds_tight[animFrame.frameIndex] frameBounds = exUtils.addToBounds(frame_rect, animFrame.offset) maxBounds = exUtils.combineExtents(maxBounds, frameBounds) shadowBounds = exUtils.addToBounds(shadow_rect_tight, animFrame.shadow) maxBounds = exUtils.combineExtents(maxBounds, shadowBounds) # round up to nearest x8 maxBounds = exUtils.centerBounds(maxBounds, (DRAW_CENTER_X, DRAW_CENTER_Y)) maxBounds = exUtils.roundUpBox(maxBounds) groupBounds.append(maxBounds) # create animations for idx, animGroup in enumerate(effectData.animGroupData): animsPerGroup = len(animGroup) # some groups may be empty if animsPerGroup == 0: continue if anim_name_map[idx][0] == '': raise ValueError("Animation #{0} needs a name!".format(idx)) dupe_idx = -1 for cmp_idx in ANIM_ORDER: if cmp_idx == idx: break cmp_group = effectData.animGroupData[cmp_idx] if exWanUtils.animGroupsEqual(animGroup, cmp_group): dupe_idx = cmp_idx break if dupe_idx > -1: anim_stats.append( AnimStat(idx, anim_name_map[idx][0], None, anim_name_map[dupe_idx][0])) for extra_idx in range(1, len(anim_name_map[idx])): anim_stats.append( AnimStat(-1, anim_name_map[idx][extra_idx], None, anim_name_map[dupe_idx][0])) continue maxBounds = groupBounds[idx] maxSize = (maxBounds[2] - maxBounds[0], maxBounds[3] - maxBounds[1]) new_stat = AnimStat(idx, anim_name_map[idx][0], maxSize, None) framesPerAnim = 0 for g_idx, singleAnim in enumerate(animGroup): framesPerAnim = max(framesPerAnim, len(singleAnim)) animImg = Image.new( 'RGBA', (maxSize[0] * framesPerAnim, maxSize[1] * animsPerGroup), (0, 0, 0, 0)) particleImg = Image.new( 'RGBA', (maxSize[0] * framesPerAnim, maxSize[1] * animsPerGroup), (0, 0, 0, 0)) shadowImg = Image.new( 'RGBA', (maxSize[0] * framesPerAnim, maxSize[1] * animsPerGroup), (0, 0, 0, 0)) for g_idx, singleAnim in enumerate(animGroup): for a_idx, animFrame in enumerate(singleAnim): if a_idx >= len(new_stat.durations): new_stat.durations.append(animFrame.duration) if animFrame.IsRushPoint(): new_stat.rushFrame = a_idx if animFrame.IsHitPoint(): new_stat.hitFrame = a_idx if animFrame.IsReturnPoint(): new_stat.returnFrame = a_idx frameImg = frames[animFrame.frameIndex] tilePos = (a_idx * maxSize[0], g_idx * maxSize[1]) pastePos = (tilePos[0] - maxBounds[0] + maxFrameBounds[0] + animFrame.offset[0], tilePos[1] - maxBounds[1] + maxFrameBounds[1] + animFrame.offset[1]) animImg.paste(frameImg, pastePos, frameImg) offsetImg = offsets[animFrame.frameIndex] particleImg.paste(offsetImg, pastePos, offsetImg) shadowBounds = exUtils.addToBounds(shadow_rect, animFrame.shadow) shadowPastePos = (tilePos[0] - maxBounds[0] + shadowBounds[0], tilePos[1] - maxBounds[1] + shadowBounds[1]) shadowImg.paste(sdwImg, shadowPastePos, sdwImg) animImg.save(os.path.join(outDir, new_stat.name + '-Anim.png')) particleImg.save(os.path.join(outDir, new_stat.name + '-Offsets.png')) shadowImg.save(os.path.join(outDir, new_stat.name + '-Shadow.png')) for extra_idx in range(1, len(anim_name_map[idx])): anim_stats.append( AnimStat(-1, anim_name_map[idx][extra_idx], None, anim_name_map[idx][0])) anim_stats.append(new_stat) # export the xml root = ET.Element("AnimData") shadow_node = ET.SubElement(root, "ShadowSize") shadow_node.text = str(effectData.sdwSize) anims_node = ET.SubElement(root, "Anims") for stat in anim_stats: anim_node = ET.SubElement(anims_node, "Anim") name_node = ET.SubElement(anim_node, "Name") name_node.text = stat.name if stat.index > -1: index_node = ET.SubElement(anim_node, "Index") index_node.text = str(stat.index) if stat.backref is not None: backref_node = ET.SubElement(anim_node, "CopyOf") backref_node.text = stat.backref else: frame_width = ET.SubElement(anim_node, "FrameWidth") frame_width.text = str(stat.size[0]) frame_height = ET.SubElement(anim_node, "FrameHeight") frame_height.text = str(stat.size[1]) if stat.rushFrame > -1: rush_frame = ET.SubElement(anim_node, "RushFrame") rush_frame.text = str(stat.rushFrame) if stat.hitFrame > -1: hit_frame = ET.SubElement(anim_node, "HitFrame") hit_frame.text = str(stat.hitFrame) if stat.returnFrame > -1: return_frame = ET.SubElement(anim_node, "ReturnFrame") return_frame.text = str(stat.returnFrame) durations_node = ET.SubElement(anim_node, "Durations") for duration in stat.durations: dur_node = ET.SubElement(durations_node, "Duration") dur_node.text = str(duration) with open(os.path.join(outDir, 'AnimData.xml'), 'w') as f: f.write(prettify(root))