Adam Goldsmith
a1859f4eff
it is always going to be the same, so there's no point in passing it around
217 lines
7.5 KiB
Python
Executable File
217 lines
7.5 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
# Note: SVG templates are heavily based on these:
|
|
# https://boardgamegeek.com/thread/813176/card-templates/page/1
|
|
|
|
import json
|
|
import math
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
|
|
from lxml import etree
|
|
|
|
if getattr(sys, 'frozen', False): # we are running in a bundle
|
|
bundle_dir = sys._MEIPASS
|
|
else: # we are running in a normal Python environment
|
|
bundle_dir = os.path.dirname(os.path.abspath(__file__))
|
|
|
|
CARD_WIDTH = 181
|
|
CARD_HEIGHT = 253
|
|
|
|
with open(bundle_dir + "/templates/card.json") as f:
|
|
CARD_JSON_TEMPLATE = json.load(f)
|
|
|
|
def setText(tree, id, text):
|
|
element = tree.find('.//*[@id="' + id + '"]')
|
|
if element is None:
|
|
print("id", id, "not found")
|
|
return
|
|
elif element.tag == "{http://www.w3.org/2000/svg}flowRoot":
|
|
for e in element.findall("{http://www.w3.org/2000/svg}flowPara"):
|
|
element.remove(e) # clear child paragraphs
|
|
lines = str(text).splitlines()
|
|
for line in lines:
|
|
etree.SubElement(element,
|
|
"{http://www.w3.org/2000/svg}flowPara").text=line
|
|
else:
|
|
element.text = str(text)
|
|
|
|
def removeElement(tree, id):
|
|
mark = tree.find('.//*[@id="' + id + '"]')
|
|
if mark is not None:
|
|
mark.getparent().remove(mark)
|
|
|
|
def makeSVG(base, properties):
|
|
tree = etree.parse(base)
|
|
for id, text in properties.items():
|
|
if id not in ["count", "art"]:
|
|
setText(tree, id, text)
|
|
|
|
# remove HP mark if there is no hp
|
|
if "hp" not in properties:
|
|
removeElement(tree, "hpMark")
|
|
|
|
# remove setup box if there is no setup
|
|
if "setup" not in properties:
|
|
removeElement(tree, "setupBox")
|
|
|
|
if "art" in properties:
|
|
art = tree.find('.//*[@id="art"]')
|
|
if art is not None:
|
|
art.set("{http://www.w3.org/1999/xlink}href",
|
|
"file://" + bundle_dir + "/" + properties["art"])
|
|
return tree
|
|
|
|
def addCardToBase(svg, baseImg, baseX, cardNum):
|
|
for e in svg.findall('{http://www.w3.org/2000/svg}g'):
|
|
e.set("transform",
|
|
"{} translate({} {})".format(
|
|
e.get("transform", ""),
|
|
str((cardNum % baseX) * CARD_WIDTH),
|
|
str(int(cardNum / baseX) * CARD_HEIGHT)))
|
|
baseImg.getroot().append(svg.getroot())
|
|
|
|
def makeFace(baseImage, baseX, cardNum, base, card):
|
|
fig = makeSVG(base, card)
|
|
addCardToBase(fig, baseImage, baseX, cardNum)
|
|
|
|
def makeFaces(deckJson, outfile):
|
|
baseX = math.ceil(math.sqrt(len(deckJson['deck']) + len(deckJson['character']) * 2))
|
|
baseImage = etree.ElementTree(
|
|
etree.Element('svg', attrib={'width': str(baseX * CARD_WIDTH) + "pt",
|
|
'height': str(baseX * CARD_HEIGHT) + "pt",
|
|
'version': "1.2",
|
|
'xmlns': "http://www.w3.org/2000/svg"}))
|
|
|
|
cardType = deckJson["type"]
|
|
|
|
cardNum = 0
|
|
# Make a card for each hero character card
|
|
if cardType == "hero":
|
|
for card in deckJson['character']:
|
|
makeFace(baseImage, baseX, cardNum,
|
|
os.path.join("images", cardType, "charFront.svg"), card)
|
|
cardNum += 1
|
|
makeFace(baseImage, baseX, cardNum,
|
|
os.path.join("images", cardType, "charBack.svg"), card)
|
|
cardNum += 1
|
|
|
|
# Make a character and instructions card for each villain card
|
|
elif cardType == "villain":
|
|
for card in deckJson['character']:
|
|
front = card["front"]
|
|
front["name"] = card["name"]
|
|
back = card["back"]
|
|
back["name"] = card["name"]
|
|
makeFace(baseImage, baseX, cardNum,
|
|
os.path.join("images", cardType, "character.svg"),
|
|
front)
|
|
cardNum += 1
|
|
makeFace(baseImage, baseX, cardNum,
|
|
os.path.join("images", cardType, "character.svg"),
|
|
back)
|
|
cardNum += 1
|
|
|
|
makeFace(baseImage, baseX, cardNum,
|
|
os.path.join("images", cardType, "instructions.svg"),
|
|
front)
|
|
cardNum += 1
|
|
makeFace(baseImage, baseX, cardNum,
|
|
os.path.join("images", cardType, "instructions.svg"),
|
|
back)
|
|
cardNum += 1
|
|
|
|
|
|
# Make a card for each card
|
|
for card in deckJson['deck']:
|
|
makeFace(baseImage, baseX, cardNum,
|
|
os.path.join("images", cardType, "card.svg"), card)
|
|
cardNum += 1
|
|
|
|
baseImage.write(outfile + ".svg")
|
|
command = ["inkscape", "-z",
|
|
"-f", outfile + ".svg",
|
|
"-w", str(baseX * CARD_WIDTH * 5),
|
|
"-e", outfile + ".png"]
|
|
print("To regenerate PNG after editing SVG, run:\n " + " ".join(command))
|
|
subprocess.run(command)
|
|
return baseX
|
|
|
|
def makeCardJson(nickname, description, cardID, outJson=None):
|
|
card = CARD_JSON_TEMPLATE.copy()
|
|
card.update({"Nickname": nickname,
|
|
"Description": description,
|
|
"CardID": cardID})
|
|
if outJson is not None:
|
|
outJson['ObjectStates'][0]['DeckIDs'].append(cardID)
|
|
outJson['ObjectStates'][0]['ContainedObjects'].append(card)
|
|
return card
|
|
|
|
def makeDoubleSidedCardJson(nickname, descriptionFront,
|
|
descriptionBack, cardID, outJson):
|
|
cardBack = makeCardJson(nickname, descriptionBack, cardID + 1)
|
|
|
|
card = makeCardJson(nickname, descriptionFront, cardID, outJson)
|
|
card.update({ "States": {"2": cardBack}})
|
|
return card
|
|
|
|
def makeJson(deckJson, imgWidth, outfile):
|
|
with open(bundle_dir + "/templates/deck.json") as f:
|
|
outJson = json.load(f)
|
|
|
|
# number of cards in x and y direction
|
|
outJson['ObjectStates'][0]['CustomDeck']['1'].update(
|
|
{"NumWidth": imgWidth,
|
|
"NumHeight": imgWidth,
|
|
#"FaceURL": "http://adam-desktop.dyn.wpi.edu:8000/" + outfile + ".png",
|
|
"FaceURL": "file://" + os.getcwd() + "/" + outfile + ".png",
|
|
"BackURL": "http://cloud-3.steamusercontent.com/ugc/156906385556221451/CE2C3AFE1759790CB0B532FFD636D05A99EC91F4/"})
|
|
|
|
# decks start at (10 * deck id)
|
|
cardNum = 100
|
|
|
|
if deckJson["type"] == "hero":
|
|
for card in deckJson['character']:
|
|
# character card
|
|
makeDoubleSidedCardJson(card['name'], "Active",
|
|
"Incapacitated", cardNum, outJson)
|
|
cardNum += 2
|
|
|
|
elif deckJson["type"] == "villain":
|
|
for card in deckJson['character']:
|
|
# character card
|
|
makeDoubleSidedCardJson(card['name'], "Front", "Back",
|
|
cardNum, outJson)
|
|
cardNum += 2
|
|
|
|
# instructions card
|
|
makeDoubleSidedCardJson(card['name'] + " instructions",
|
|
"Front", "Back", cardNum, outJson)
|
|
cardNum += 2
|
|
|
|
for card in deckJson['deck']:
|
|
for i in range(0, card.get('count', 1)):
|
|
# normal cards
|
|
makeCardJson(card['name'], card.get('keywords', ""),
|
|
cardNum, outJson)
|
|
cardNum += 1
|
|
|
|
with open(outfile + ".json", "w") as f:
|
|
json.dump(outJson, f)
|
|
|
|
if __name__ == '__main__':
|
|
if len(sys.argv) < 3:
|
|
print("not enough arguments!")
|
|
inputJson = input("Input file: ")
|
|
outfile = input("Output file (no suffix): ")
|
|
else:
|
|
inputJson = sys.argv[1]
|
|
outfile = sys.argv[2]
|
|
|
|
with open(inputJson) as f:
|
|
deckJson = json.load(f)
|
|
|
|
imgWidth = makeFaces(deckJson, outfile)
|
|
makeJson(deckJson, imgWidth, outfile)
|