| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155 |
- #!/usr/bin/env python3
- """Validate a VisiCut settings directory against VisiCut's own conventions.
- Mirrors de.thomas_oster.visicut.misc.Helper.toPathName and
- LaserPropertyManager.getLaserPropertiesFile.
- """
- import os, sys, re
- import xml.etree.ElementTree as ET
- ALLOWED = set("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-")
- def to_path_name(name):
- return "".join(c if c in ALLOWED else "_%d_" % ord(c) for c in name)
- def jfloat(x):
- """Java Float.toString for the values we care about."""
- f = float(x)
- s = repr(f)
- return s
- ROOT = sys.argv[1] if len(sys.argv) > 1 else "."
- problems = []
- def P(cat, msg):
- problems.append((cat, msg))
- # ---- profiles -------------------------------------------------------------
- profiles = {}
- for fn in sorted(os.listdir(os.path.join(ROOT, "profiles"))):
- p = os.path.join(ROOT, "profiles", fn)
- try:
- root = ET.parse(p).getroot()
- except ET.ParseError as e:
- P("xml", "profiles/%s: malformed XML: %s" % (fn, e)); continue
- name = root.findtext("name")
- profiles[name] = root.tag
- if to_path_name(name) + ".xml" != fn:
- P("name", "profiles/%s should be named %s.xml (name=%r)" % (fn, to_path_name(name), name))
- # Expected XML element per profile kind. LaserPropertyManager only registers XStream
- # aliases for PowerSpeedFocus[Frequency]Property, FloatPowerSpeedFocusFrequencyProperty and
- # LaosCutterProperty -- any other short element name fails to deserialize. The Epilog driver
- # wants an EpilogEngraveProperty for raster parts; VisiCut converts the PowerSpeedFocusProperty
- # it reads here into one, copying power/speed/focus.
- EXPECT = {
- "vectorProfile": "PowerSpeedFocusFrequencyProperty",
- "rasterProfile": "PowerSpeedFocusProperty",
- "raster3dProfile":"PowerSpeedFocusProperty",
- }
- # ---- materials ------------------------------------------------------------
- materials = {} # name -> dict
- for fn in sorted(os.listdir(os.path.join(ROOT, "materials"))):
- if not fn.endswith(".xml"): continue
- p = os.path.join(ROOT, "materials", fn)
- try:
- root = ET.parse(p).getroot()
- except ET.ParseError as e:
- P("xml", "materials/%s: MALFORMED XML - VisiCut cannot load this file: %s" % (fn, e)); continue
- name = root.findtext("name")
- th = [float(x.text) for x in root.findall("materialThicknesses/float")]
- expect_fn = to_path_name(name) + ".xml"
- if expect_fn != fn:
- P("name", "materials/%s: VisiCut expects %s (name=%r)" % (fn, expect_fn, name))
- if name in materials:
- P("dup", "duplicate material name %r (%s and %s)" % (name, materials[name]["file"], fn))
- materials[name] = {"file": fn, "th": th, "dir": to_path_name(name),
- "thumb": root.findtext("thumbnailPath")}
- if th != sorted(th):
- P("order", "materials/%s: thicknesses not sorted: %s" % (fn, th))
- if len(th) != len(set(th)):
- P("dup", "materials/%s: duplicate thicknesses %s" % (fn, th))
- bydir = {}
- for n, m in materials.items():
- bydir.setdefault(m["dir"], []).append(n)
- # ---- laserprofiles --------------------------------------------------------
- lp_root = os.path.join(ROOT, "laserprofiles")
- covered = set()
- for dev in sorted(os.listdir(lp_root)):
- devdir = os.path.join(lp_root, dev)
- if not os.path.isdir(devdir): continue
- for mdir in sorted(os.listdir(devdir)):
- mpath = os.path.join(devdir, mdir)
- if not os.path.isdir(mpath): continue
- mnames = bydir.get(mdir)
- if not mnames:
- P("orphan", "laserprofiles/%s/%s/: no material has this encoded name "
- "-> invisible in VisiCut (would decode to %r)" % (dev, mdir, mdir))
- continue
- m = materials[mnames[0]]
- for tdir in sorted(os.listdir(mpath)):
- tpath = os.path.join(mpath, tdir)
- if not os.path.isdir(tpath): continue
- if not tdir.endswith("mm"):
- P("thick", "laserprofiles/%s/%s/%s: not a <thickness>mm folder" % (dev, mdir, tdir)); continue
- tval = tdir[:-2]
- if not any(jfloat(t) == tval for t in m["th"]):
- P("thick", "laserprofiles/%s/%s/%s: thickness not listed in materials/%s (%s)"
- % (dev, mdir, tdir, m["file"], ", ".join(jfloat(t) for t in m["th"])))
- else:
- covered.add((dev, mnames[0], tval))
- for pf in sorted(os.listdir(tpath)):
- if not pf.endswith(".xml"): continue
- pname = None
- for n in profiles:
- if to_path_name(n) + ".xml" == pf: pname = n
- if pname is None:
- P("orphan", "laserprofiles/%s/%s/%s/%s: no matching profiles/*.xml entry" % (dev, mdir, tdir, pf))
- continue
- f = os.path.join(tpath, pf)
- try:
- r = ET.parse(f).getroot()
- except ET.ParseError as e:
- P("xml", "%s: malformed XML: %s" % (f, e)); continue
- want = EXPECT[profiles[pname]]
- for child in r:
- if child.tag != want:
- P("class", "laserprofiles/%s/%s/%s/%s: <%s> but profile %r is a %s -> needs <%s>"
- % (dev, mdir, tdir, pf, child.tag, pname, profiles[pname], want))
- for k, lo, hi in (("power",0,100), ("speed",1,100), ("frequency",10,5000)):
- v = child.findtext(k)
- if v is None: continue
- if not (lo <= float(v) <= hi):
- P("range", "laserprofiles/%s/%s/%s/%s: %s=%s out of range %d..%d"
- % (dev, mdir, tdir, pf, k, v, lo, hi))
- # ---- coverage -------------------------------------------------------------
- for dev in sorted(os.listdir(lp_root)):
- if not os.path.isdir(os.path.join(lp_root, dev)): continue
- for n, m in sorted(materials.items()):
- for t in m["th"]:
- if (dev, n, jfloat(t)) not in covered:
- P("missing", "%s / %r / %smm: material offers this thickness but there are no laser settings"
- % (dev, n, jfloat(t)))
- # ---- thumbnails -----------------------------------------------------------
- for n, m in sorted(materials.items()):
- auto = os.path.join(ROOT, "materials", m["dir"] + ".png")
- if m["thumb"]:
- ref = os.path.join(ROOT, "materials", m["thumb"])
- if not os.path.exists(ref):
- P("thumb", "materials/%s: thumbnailPath %r does not exist" % (m["file"], m["thumb"]))
- elif not os.path.exists(auto):
- P("thumb", "materials/%s: no thumbnail (expected materials/%s.png)" % (m["file"], m["dir"]))
- order = ["xml","name","dup","class","range","orphan","thick","missing","order","thumb"]
- for cat in order:
- rows = [m for c, m in problems if c == cat]
- if rows:
- print("\n## %s (%d)" % (cat.upper(), len(rows)))
- for r in rows: print(" - " + r)
- print("\nTOTAL: %d problems, %d materials, %d profiles" % (len(problems), len(materials), len(profiles)))
- sys.exit(1 if problems else 0)
|