Skip to content

Commit 5a619ed

Browse files
committed
Fix non-ansi character return values.
1 parent 10ccabc commit 5a619ed

1 file changed

Lines changed: 74 additions & 28 deletions

File tree

python/Scripts/generateshader.py

Lines changed: 74 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
'''
66

77
import sys, os, argparse, subprocess
8+
import re, locale
89

910
import MaterialX as mx
1011
import MaterialX.PyMaterialXGenGlsl as mx_gen_glsl
@@ -13,34 +14,60 @@
1314
import MaterialX.PyMaterialXGenOsl as mx_gen_osl
1415
import MaterialX.PyMaterialXGenShader as mx_gen_shader
1516

17+
# Ensure printing never raises UnicodeEncodeError in CI/PowerShell:
18+
try:
19+
# Change stdout/stderr behavior to replace unencodable chars
20+
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
21+
sys.stderr.reconfigure(encoding='utf-8', errors='replace')
22+
except Exception:
23+
# ignore on platforms without reconfigure
24+
pass
25+
26+
def safe_write(s):
27+
"""Write string to stdout using the terminal encoding, replacing unencodable chars."""
28+
try:
29+
enc = sys.stdout.encoding or locale.getpreferredencoding(False) or 'utf-8'
30+
b = s.encode(enc, errors='replace')
31+
# Use buffer to avoid Python trying to re-encode when printing
32+
sys.stdout.buffer.write(b)
33+
sys.stdout.buffer.write(b'\n')
34+
except Exception:
35+
# Fallback to a safe printable form
36+
print(s.encode('utf-8', errors='replace').decode('utf-8', errors='replace'))
37+
1638
def validateCode(sourceCodeFile, codevalidator, codevalidatorArgs):
17-
if codevalidator:
18-
cmd = codevalidator.split()
19-
cmd.append(sourceCodeFile)
20-
if codevalidatorArgs:
21-
cmd.append(codevalidatorArgs)
22-
cmd_flatten ='----- Run Validator: '
23-
for c in cmd:
24-
cmd_flatten += c + ' '
25-
print(cmd_flatten)
26-
try:
27-
output = subprocess.check_output(cmd, stderr=subprocess.STDOUT, timeout=30)
28-
result = output.decode(encoding='utf-8')
29-
# Return empty string on success, even if there's output
30-
if "Validation successful" in result:
31-
return ""
32-
return result
33-
except subprocess.CalledProcessError as out:
34-
print(f"--- Validator returned error code: {out.returncode}")
35-
# Encode the output to avoid UnicodeEncodeError
36-
print("--- Error log: ", out.output.decode('utf-8', errors='replace'))
37-
return (out.output.decode(encoding='utf-8', errors='replace'))
38-
except subprocess.TimeoutExpired:
39-
print(f"--- Validator timeout for: {sourceFile}")
40-
return "ERROR: Validator timeout"
41-
except Exception as e:
42-
print(f"--- Validator exception: {e}")
43-
return f"ERROR: {str(e)}"
39+
if not codevalidator:
40+
return ""
41+
42+
cmd = codevalidator.split()
43+
cmd.append(sourceCodeFile)
44+
if codevalidatorArgs:
45+
cmd.append(codevalidatorArgs)
46+
print('----- Run Validator: ' + ' '.join(cmd))
47+
48+
# Use environment vars to request no color/graphics from the validator
49+
env = os.environ.copy()
50+
env.update({'NO_COLOR': '1', 'TERM': 'dumb'})
51+
52+
try:
53+
output = subprocess.check_output(cmd, stderr=subprocess.STDOUT, timeout=30, env=env)
54+
text = keep_ansi_and_ascii(output)
55+
if "Validation successful" in text or text == "":
56+
return ""
57+
return text
58+
except subprocess.CalledProcessError as out:
59+
print(f"--- Validator returned error code: {out.returncode}")
60+
raw = out.output if hasattr(out, 'output') and out.output is not None else b''
61+
text = keep_ansi_and_ascii(raw)
62+
print('--- Error log:')
63+
print(text)
64+
return text
65+
except subprocess.TimeoutExpired:
66+
print(f"--- Validator timeout for: {sourceFile}")
67+
return "ERROR: Validator timeout"
68+
except Exception as e:
69+
print(f"--- Validator exception: {e}")
70+
return f"ERROR: {str(e)}"
4471
return ""
4572

4673
def getMaterialXFiles(rootPath):
@@ -55,6 +82,24 @@ def getMaterialXFiles(rootPath):
5582

5683
return filelist
5784

85+
def keep_ansi_and_ascii(b):
86+
"""Decode bytes, preserve ANSI escape sequences, and drop other non-ASCII glyphs."""
87+
if not b:
88+
return ""
89+
text = b.decode('utf-8', errors='replace')
90+
ansi_regex = re.compile(r'\x1B\[[0-?]*[ -/]*[@-~]')
91+
seqs = []
92+
def _repl(m):
93+
seqs.append(m.group(0))
94+
return f'__ANSI_SEQ_{len(seqs)-1}__'
95+
temp = ansi_regex.sub(_repl, text)
96+
# keep printable ASCII plus newline/tab/carriage return, drop other chars
97+
temp = ''.join(ch for ch in temp if ch in '\r\n\t' or (32 <= ord(ch) <= 126))
98+
# restore ANSI sequences
99+
for i, seq in enumerate(seqs):
100+
temp = temp.replace(f'__ANSI_SEQ_{i}__', seq)
101+
return '\n'.join(line.rstrip() for line in temp.splitlines())
102+
58103
def main():
59104
parser = argparse.ArgumentParser(description='Generate shader code for each renderable element in a MaterialX document or folder.')
60105
parser.add_argument('--path', dest='paths', action='append', nargs='+', help='An additional absolute search path location (e.g. "/projects/MaterialX")')
@@ -202,7 +247,8 @@ def main():
202247
if errors != "":
203248
print("--- Validation failed for element: ", elemName)
204249
print("----------------------------")
205-
print('--- Error log: ', errors)
250+
safe_write('--- Error log:')
251+
safe_write(errors)
206252
print("----------------------------")
207253
failedShaders += (elemName + ' ')
208254
else:

0 commit comments

Comments
 (0)