Compare commits
5 Commits
dd2ffebca2
...
752cf011c3
Author | SHA1 | Date | |
---|---|---|---|
752cf011c3 | |||
7647f870a6 | |||
75d145096d | |||
d61a707591 | |||
5a4d30e162 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,2 +1,3 @@
|
|||||||
__pycache__
|
__pycache__
|
||||||
*.jpg
|
*.jpg
|
||||||
|
*.png
|
@ -17,5 +17,8 @@
|
|||||||
"margins": [20, 20, 20, 20],
|
"margins": [20, 20, 20, 20],
|
||||||
"arrowMargin": 4,
|
"arrowMargin": 4,
|
||||||
"valuesGap": 5,
|
"valuesGap": 5,
|
||||||
"arrowLabelDistance": 5
|
"arrowLabelDistance": 5,
|
||||||
|
"forceDescsOnSide": false,
|
||||||
|
"width": 1200,
|
||||||
|
"height": 800
|
||||||
}
|
}
|
@ -1,6 +1,7 @@
|
|||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
|
||||||
class Config:
|
class Config:
|
||||||
DEFAULT_FONT_FAMILY = "Ubuntu Mono"
|
DEFAULT_FONT_FAMILY = "Ubuntu Mono"
|
||||||
DEFAULT_FONT_SIZE = 16
|
DEFAULT_FONT_SIZE = 16
|
||||||
@ -21,6 +22,10 @@ class Config:
|
|||||||
ARROW_MARGIN = 4
|
ARROW_MARGIN = 4
|
||||||
VALUES_GAP = 5
|
VALUES_GAP = 5
|
||||||
ARROW_LABEL_DISTANCE = 5
|
ARROW_LABEL_DISTANCE = 5
|
||||||
|
FORCE_DESCS_ON_SIDE = False
|
||||||
|
LEFT_LABELS = False
|
||||||
|
WIDTH = 1200
|
||||||
|
HEIGHT = 800
|
||||||
|
|
||||||
def __init__(self, path: str = "config.json") -> None:
|
def __init__(self, path: str = "config.json") -> None:
|
||||||
self.load(path)
|
self.load(path)
|
||||||
|
36
main.py
36
main.py
@ -3,6 +3,7 @@ import os
|
|||||||
|
|
||||||
from schema import InstructionSetSchema
|
from schema import InstructionSetSchema
|
||||||
|
|
||||||
|
|
||||||
description = """Examples:
|
description = """Examples:
|
||||||
- Default theme (black on white):
|
- Default theme (black on white):
|
||||||
python main.py schema.xml -o out.jpg
|
python main.py schema.xml -o out.jpg
|
||||||
@ -15,20 +16,51 @@ description = """Examples:
|
|||||||
|
|
||||||
- Transparent background:
|
- Transparent background:
|
||||||
python main.py schema.xml -o out.png -c transparent.json
|
python main.py schema.xml -o out.png -c transparent.json
|
||||||
|
|
||||||
|
- Directory mode:
|
||||||
|
python main.py -d -o images/ schemas/
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def processFile(inPath, outPath, confPath, display):
|
||||||
|
schema = InstructionSetSchema(inPath, confPath, display)
|
||||||
|
schema.save(outPath)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
parser = argparse.ArgumentParser(description=description, formatter_class=argparse.RawTextHelpFormatter)
|
parser = argparse.ArgumentParser(description=description, formatter_class=argparse.RawTextHelpFormatter)
|
||||||
parser.add_argument("schema", help="Path to the schema description. Accepted formats are: YAML, JSON and XML")
|
parser.add_argument("schema", help="Path to the schema description. Accepted formats are: YAML, JSON and XML")
|
||||||
parser.add_argument("-o", "--output", help="Output path. By default, the output file will have the same name as the schema description with the extension .png")
|
parser.add_argument("-o", "--output", help="Output path. By default, the output file will have the same name as the schema description with the extension .png")
|
||||||
parser.add_argument("-c", "--config", help="Path to the config file", default="config.json")
|
parser.add_argument("-c", "--config", help="Path to the config file", default="config.json")
|
||||||
parser.add_argument("-D", "--display", help="Enable pygame display of the result", action="store_true")
|
parser.add_argument("-D", "--display", help="Enable pygame display of the result", action="store_true")
|
||||||
|
parser.add_argument("-d", "--directory", help="Enable directory mode. If set, the input and output paths are directories and all files inside the input directory are processed", action="store_true")
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
if args.directory:
|
||||||
|
if not os.path.isdir(args.schema):
|
||||||
|
print(f"{args.schema} is not a directory")
|
||||||
|
exit(-1)
|
||||||
|
|
||||||
|
output = args.output
|
||||||
|
if output is None:
|
||||||
|
output = args.schema
|
||||||
|
|
||||||
|
if not os.path.isdir(output):
|
||||||
|
print(f"{output} is not a directory")
|
||||||
|
exit(-1)
|
||||||
|
|
||||||
|
paths = os.listdir(args.schema)
|
||||||
|
for path in paths:
|
||||||
|
inPath = os.path.join(args.schema, path)
|
||||||
|
outPath = os.path.join(output, path)
|
||||||
|
outPath = os.path.splitext(outPath)[0] + ".png"
|
||||||
|
|
||||||
|
processFile(inPath, outPath, args.config, args.display)
|
||||||
|
|
||||||
|
else:
|
||||||
output = args.output
|
output = args.output
|
||||||
if output is None:
|
if output is None:
|
||||||
output = os.path.splitext(args.schema)[0] + ".png"
|
output = os.path.splitext(args.schema)[0] + ".png"
|
||||||
|
|
||||||
schema = InstructionSetSchema(args.schema, args.config, args.display)
|
processFile(args.schema, output, args.config, args.display)
|
||||||
schema.save(output)
|
|
3
range.py
3
range.py
@ -1,6 +1,8 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Union
|
from typing import Union
|
||||||
|
|
||||||
|
|
||||||
class Range:
|
class Range:
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
start: int,
|
start: int,
|
||||||
@ -16,6 +18,7 @@ class Range:
|
|||||||
self.description = description
|
self.description = description
|
||||||
self.values = values
|
self.values = values
|
||||||
self.dependsOn = dependsOn
|
self.dependsOn = dependsOn
|
||||||
|
self.lastValueY = -1
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def bits(self) -> int:
|
def bits(self) -> int:
|
||||||
|
79
renderer.py
79
renderer.py
@ -1,28 +1,28 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import os
|
import os
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
if TYPE_CHECKING:
|
|
||||||
from config import Config
|
|
||||||
from range import Range
|
|
||||||
from schema import InstructionSetSchema
|
|
||||||
|
|
||||||
import pygame
|
import pygame
|
||||||
|
|
||||||
from structure import Structure
|
from structure import Structure
|
||||||
from vec import Vec
|
from vec import Vec
|
||||||
|
|
||||||
class Renderer:
|
if TYPE_CHECKING:
|
||||||
WIDTH = 1200
|
from config import Config
|
||||||
HEIGHT = 800
|
from range import Range
|
||||||
|
from schema import InstructionSetSchema
|
||||||
|
|
||||||
|
|
||||||
|
class Renderer:
|
||||||
def __init__(self, config: Config, display: bool = False) -> None:
|
def __init__(self, config: Config, display: bool = False) -> None:
|
||||||
self.config = config
|
self.config = config
|
||||||
self.display = display
|
self.display = display
|
||||||
pygame.init()
|
pygame.init()
|
||||||
if self.display:
|
if self.display:
|
||||||
self.win = pygame.display.set_mode([Renderer.WIDTH, Renderer.HEIGHT])
|
self.win = pygame.display.set_mode([self.config.WIDTH, self.config.HEIGHT])
|
||||||
|
|
||||||
self.surf = pygame.Surface([Renderer.WIDTH, Renderer.HEIGHT], pygame.SRCALPHA)
|
self.surf = pygame.Surface([self.config.WIDTH, self.config.HEIGHT], pygame.SRCALPHA)
|
||||||
self.font = pygame.font.SysFont(self.config.DEFAULT_FONT_FAMILY, self.config.DEFAULT_FONT_SIZE)
|
self.font = pygame.font.SysFont(self.config.DEFAULT_FONT_FAMILY, self.config.DEFAULT_FONT_SIZE)
|
||||||
self.italicFont = pygame.font.SysFont(self.config.ITALIC_FONT_FAMILY, self.config.ITALIC_FONT_SIZE, italic=True)
|
self.italicFont = pygame.font.SysFont(self.config.ITALIC_FONT_FAMILY, self.config.ITALIC_FONT_SIZE, italic=True)
|
||||||
|
|
||||||
@ -31,7 +31,12 @@ class Renderer:
|
|||||||
def render(self, schema: InstructionSetSchema) -> None:
|
def render(self, schema: InstructionSetSchema) -> None:
|
||||||
self.surf.fill(self.config.BACKGROUND_COLOR)
|
self.surf.fill(self.config.BACKGROUND_COLOR)
|
||||||
|
|
||||||
self.drawStructure(schema.structures["main"], schema.structures, self.margins[3], self.margins[0])
|
mainStruct: Structure = schema.structures["main"]
|
||||||
|
ox = self.margins[3]
|
||||||
|
if self.config.LEFT_LABELS:
|
||||||
|
ox = self.config.WIDTH - self.margins[3] - mainStruct.bits * self.config.BIT_WIDTH
|
||||||
|
|
||||||
|
self.drawStructure(schema.structures["main"], schema.structures, ox, self.margins[0])
|
||||||
|
|
||||||
if self.display:
|
if self.display:
|
||||||
name = os.path.basename(schema.path)
|
name = os.path.basename(schema.path)
|
||||||
@ -45,8 +50,7 @@ class Renderer:
|
|||||||
|
|
||||||
def drawStructure(self,
|
def drawStructure(self,
|
||||||
struct: Structure,
|
struct: Structure,
|
||||||
structures: dict[str,
|
structures: dict[str, Structure],
|
||||||
Structure],
|
|
||||||
ox: float = 0,
|
ox: float = 0,
|
||||||
oy: float = 0) -> float:
|
oy: float = 0) -> float:
|
||||||
|
|
||||||
@ -77,7 +81,21 @@ class Renderer:
|
|||||||
pygame.draw.line(self.surf, borderCol, [bitX, bitsY], [bitX, bitsY + bitH])
|
pygame.draw.line(self.surf, borderCol, [bitX, bitsY], [bitX, bitsY + bitH])
|
||||||
|
|
||||||
ranges = struct.getSortedRanges()
|
ranges = struct.getSortedRanges()
|
||||||
descX = ox + max(0, (struct.bits-12) * bitW)
|
if self.config.LEFT_LABELS:
|
||||||
|
ranges.reverse()
|
||||||
|
|
||||||
|
if self.config.FORCE_DESCS_ON_SIDE:
|
||||||
|
if self.config.LEFT_LABELS:
|
||||||
|
descX = self.config.WIDTH - self.margins[3] - structures["main"].bits * bitW
|
||||||
|
else:
|
||||||
|
descX = self.margins[3] + structures["main"].bits * bitW
|
||||||
|
|
||||||
|
else:
|
||||||
|
if self.config.LEFT_LABELS:
|
||||||
|
descX = ox + struct.bits * bitW
|
||||||
|
else:
|
||||||
|
descX = ox
|
||||||
|
|
||||||
descY = bitsY + bitH * 2
|
descY = bitsY + bitH * 2
|
||||||
|
|
||||||
# Names + simple descriptions
|
# Names + simple descriptions
|
||||||
@ -123,10 +141,16 @@ class Renderer:
|
|||||||
bitH = self.config.BIT_HEIGHT
|
bitH = self.config.BIT_HEIGHT
|
||||||
arrowMargin = self.config.ARROW_MARGIN
|
arrowMargin = self.config.ARROW_MARGIN
|
||||||
|
|
||||||
|
if endX > startX:
|
||||||
|
endX -= arrowMargin
|
||||||
|
|
||||||
|
else:
|
||||||
|
endX += arrowMargin
|
||||||
|
|
||||||
pygame.draw.lines(self.surf, self.config.LINK_COLOR, False, [
|
pygame.draw.lines(self.surf, self.config.LINK_COLOR, False, [
|
||||||
[startX, startY + bitH*1.5],
|
[startX, startY + bitH*1.5],
|
||||||
[startX, endY + bitH/2],
|
[startX, endY + bitH/2],
|
||||||
[endX - arrowMargin, endY + bitH/2]
|
[endX, endY + bitH/2]
|
||||||
])
|
])
|
||||||
|
|
||||||
def drawDescription(self,
|
def drawDescription(self,
|
||||||
@ -140,6 +164,9 @@ class Renderer:
|
|||||||
bitW = self.config.BIT_WIDTH
|
bitW = self.config.BIT_WIDTH
|
||||||
bitH = self.config.BIT_HEIGHT
|
bitH = self.config.BIT_HEIGHT
|
||||||
|
|
||||||
|
if self.config.LEFT_LABELS:
|
||||||
|
descX = min(descX, rStartX + rWidth/2 - bitW)
|
||||||
|
else:
|
||||||
descX = max(descX, rStartX + rWidth/2 + bitW)
|
descX = max(descX, rStartX + rWidth/2 + bitW)
|
||||||
|
|
||||||
self.drawUnderbracket(rStartX, rStartX + rWidth, rStartY)
|
self.drawUnderbracket(rStartX, rStartX + rWidth, rStartY)
|
||||||
@ -148,12 +175,17 @@ class Renderer:
|
|||||||
self.drawLink(midX, rStartY, descX, descY)
|
self.drawLink(midX, rStartY, descX, descY)
|
||||||
|
|
||||||
descTxt = self.font.render(range_.description, True, self.config.TEXT_COLOR)
|
descTxt = self.font.render(range_.description, True, self.config.TEXT_COLOR)
|
||||||
self.surf.blit(descTxt, [descX, descY + (bitH - descTxt.get_height())/2])
|
txtX = descX
|
||||||
|
|
||||||
|
if self.config.LEFT_LABELS:
|
||||||
|
txtX -= descTxt.get_width()
|
||||||
|
|
||||||
|
self.surf.blit(descTxt, [txtX, descY + (bitH - descTxt.get_height())/2])
|
||||||
|
|
||||||
descY += descTxt.get_height()
|
descY += descTxt.get_height()
|
||||||
|
|
||||||
if range_.values is not None and range_.dependsOn is None:
|
if range_.values is not None and range_.dependsOn is None:
|
||||||
descX, descY = self.drawValues(range_.values, descX, descY)
|
txtX, descY = self.drawValues(range_.values, txtX, descY)
|
||||||
|
|
||||||
descY += self.config.DESCRIPTION_MARGIN
|
descY += self.config.DESCRIPTION_MARGIN
|
||||||
|
|
||||||
@ -191,8 +223,13 @@ class Renderer:
|
|||||||
rWidth = range_.bits * bitW
|
rWidth = range_.bits * bitW
|
||||||
|
|
||||||
self.drawUnderbracket(rStartX, rStartX + rWidth, bitsY)
|
self.drawUnderbracket(rStartX, rStartX + rWidth, bitsY)
|
||||||
prevY = bitsY + bitH * 1.5
|
|
||||||
dependRange = struct.ranges[range_.dependsOn]
|
dependRange = struct.ranges[range_.dependsOn]
|
||||||
|
prevRangeY = bitsY + bitH * 1.5
|
||||||
|
if dependRange.lastValueY == -1:
|
||||||
|
prevDependY = bitsY + bitH * 1.5
|
||||||
|
else:
|
||||||
|
prevDependY = dependRange.lastValueY
|
||||||
|
|
||||||
dependStartI = struct.bits - dependRange.end - 1
|
dependStartI = struct.bits - dependRange.end - 1
|
||||||
dependStartX = bitsX + dependStartI * bitW
|
dependStartX = bitsX + dependStartI * bitW
|
||||||
dependWidth = dependRange.bits * bitW
|
dependWidth = dependRange.bits * bitW
|
||||||
@ -200,7 +237,7 @@ class Renderer:
|
|||||||
self.drawUnderbracket(dependStartX, dependStartX + dependWidth, bitsY)
|
self.drawUnderbracket(dependStartX, dependStartX + dependWidth, bitsY)
|
||||||
|
|
||||||
for val, data in sorted(range_.values.items(), key=lambda vd: vd[0]):
|
for val, data in sorted(range_.values.items(), key=lambda vd: vd[0]):
|
||||||
self.drawArrow(dependMid, prevY, dependMid, descY - arrowMargin)
|
self.drawArrow(dependMid, prevDependY, dependMid, descY - arrowMargin)
|
||||||
|
|
||||||
valRanges = {}
|
valRanges = {}
|
||||||
for i in range(dependRange.bits):
|
for i in range(dependRange.bits):
|
||||||
@ -232,11 +269,13 @@ class Renderer:
|
|||||||
self.drawArrow(x1, y, x2, y, data["description"])
|
self.drawArrow(x1, y, x2, y, data["description"])
|
||||||
|
|
||||||
self.drawArrow(rStartX + rWidth - bitW,
|
self.drawArrow(rStartX + rWidth - bitW,
|
||||||
prevY,
|
prevRangeY,
|
||||||
rStartX + rWidth - bitW,
|
rStartX + rWidth - bitW,
|
||||||
descY + bitH - arrowMargin)
|
descY + bitH - arrowMargin)
|
||||||
|
|
||||||
prevY = descY + bitH*2 + arrowMargin
|
prevDependY = descY + bitH*2 + arrowMargin
|
||||||
|
prevRangeY =prevDependY
|
||||||
|
dependRange.lastValueY = prevDependY
|
||||||
descY = self.drawStructure(structures[data["structure"]], structures, rStartX, descY)
|
descY = self.drawStructure(structures[data["structure"]], structures, rStartX, descY)
|
||||||
|
|
||||||
return (descX, descY)
|
return (descX, descY)
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
from config import Config
|
from config import Config
|
||||||
@ -7,9 +8,11 @@ from renderer import Renderer
|
|||||||
from structure import Structure
|
from structure import Structure
|
||||||
from xml_loader import XMLLoader
|
from xml_loader import XMLLoader
|
||||||
|
|
||||||
|
|
||||||
class UnsupportedFormatException(Exception):
|
class UnsupportedFormatException(Exception):
|
||||||
...
|
...
|
||||||
|
|
||||||
|
|
||||||
class InstructionSetSchema:
|
class InstructionSetSchema:
|
||||||
VALID_EXTENSIONS = ("yaml", "json", "xml")
|
VALID_EXTENSIONS = ("yaml", "json", "xml")
|
||||||
|
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from range import Range
|
from range import Range
|
||||||
|
|
||||||
|
|
||||||
class Structure:
|
class Structure:
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
name: str,
|
name: str,
|
||||||
|
2
vec.py
2
vec.py
@ -1,6 +1,8 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from math import sqrt
|
from math import sqrt
|
||||||
|
|
||||||
|
|
||||||
class Vec:
|
class Vec:
|
||||||
def __init__(self, x: float = 0, y: float = 0) -> None:
|
def __init__(self, x: float = 0, y: float = 0) -> None:
|
||||||
self.x = x
|
self.x = x
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
from bs4 import BeautifulSoup
|
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
from bs4 import BeautifulSoup
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from io import TextIOWrapper
|
from io import TextIOWrapper
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user