예제 #1
0
    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'))
예제 #2
0
    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.")
                )
예제 #3
0
#  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))
예제 #4
0
    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)
예제 #5
0
def write_xml(xml, fn):
    with open(os.path.join(output_dir, fn), 'w') as f:
        f.write(prettify(xml))
예제 #6
0
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))