55'''
66
77import sys , os , argparse , subprocess
8+ import re , locale
89
910import MaterialX as mx
1011import MaterialX .PyMaterialXGenGlsl as mx_gen_glsl
1314import MaterialX .PyMaterialXGenOsl as mx_gen_osl
1415import 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+
1638def 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
4673def 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+
58103def 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