Skip to content

Commit

Permalink
Merge pull request #34 from CadQuery/multi-file
Browse files Browse the repository at this point in the history
Added multiple file export at once
  • Loading branch information
jmwright committed Aug 7, 2024
2 parents c7a5f5c + bc5d26a commit b009833
Show file tree
Hide file tree
Showing 3 changed files with 163 additions and 22 deletions.
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ dependencies = [
"cadquery-ocp>=7.7.0a0,<7.8",
"ezdxf",
"multimethod>=1.7,<2.0",
"numpy<2.0.0",
"nlopt",
"nptyping==2.0.1",
"typish",
"casadi",
"path",
Expand Down
95 changes: 74 additions & 21 deletions src/cq_cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,8 +141,11 @@ def get_params_from_file(param_json_path, errfile):

def main():
outfile = None
outfiles = None
errfile = None
codec_module = None
codecs = None
active_codecs = None
params = {}
output_opts = {}

Expand All @@ -155,7 +158,7 @@ def main():
)
parser.add_argument(
"--codec",
help="The codec to use when converting the CadQuery output. Must match the name of a codec file in the cqcodecs directory.",
help="(REQUIRED) The codec to use when converting the CadQuery output. Must match the name of a codec file in the cqcodecs directory. Multiple codecs can be specified, separated by the colon (;) character. The number of codecs must match the number of output files (outfile parameter).",
)
parser.add_argument(
"--getparams",
Expand All @@ -164,7 +167,7 @@ def main():
parser.add_argument("--infile", help="The input CadQuery script to convert.")
parser.add_argument(
"--outfile",
help="File to write the converted CadQuery output to. Prints to stdout if not specified.",
help="File to write the converted CadQuery output to. Prints to stdout if not specified. Multiple output files can be specified, separated by the colon (;) character. The number of codecs (codec parameter) must match the number of output files.",
)
parser.add_argument(
"--errfile",
Expand Down Expand Up @@ -210,6 +213,11 @@ def main():
if args.outfile != None:
outfile = args.outfile

# Handle the case of multiple outfiles
if ";" in outfile:
outfiles = outfile.split(";")
outfile = outfiles[0]

#
# Errfile handling
#
Expand Down Expand Up @@ -321,6 +329,11 @@ def main():
# Save the requested codec for later
codec = args.codec

# Handle multiple output files
if codec != None and ";" in codec:
codecs = codec.split(";")
codec = codecs[0]

# Attempt to auto-detect the codec if the user has not set the option
if args.outfile != None and args.codec == None:
# Determine the codec from the file extension
Expand All @@ -330,6 +343,24 @@ def main():
if codec_temp in loaded_codecs:
codec = codec_temp

# If there are multiple output files, make sure to set the codecs for all of them
if outfiles != None and codecs == None:
codecs = []
for i in range(len(outfiles)):
codec_temp = outfiles[i].split(".")[-1]
if codec_temp != None:
# Construct the codec module name
codec_temp = "cq_codec_" + codec_temp

if codec_temp in loaded_codecs:
# The codecs array needs just the short name, not the full module name
codecs.append(codec_temp.replace("cq_codec_", ""))

# Keep track of the codes that are being actively used
if active_codecs == None:
active_codecs = []
active_codecs.append(loaded_codecs[codec_temp])

# If the user has not supplied a codec, they need to be validating the script
if (codec == None and args.outfile == None) and (
args.validate == None or args.validate == "false"
Expand All @@ -351,6 +382,16 @@ def main():
if codec in key:
codec_module = loaded_codecs[key]

# Handle there being multiple codecs
if codecs != None:
for cur_codec in codecs:
for key in loaded_codecs:
# Check to make sure that the requested codec exists
if cur_codec in key:
if active_codecs == None:
active_codecs = []
active_codecs.append(loaded_codecs["cq_codec_" + cur_codec])

#
# Infile handling
#
Expand Down Expand Up @@ -451,26 +492,38 @@ def main():
#
# Build, parse and let the selected codec convert the CQ output
try:
# Use the codec plugin to do the conversion
converted = codec_module.convert(build_result, outfile, errfile, output_opts)

# If converted is None, assume that the output was written to file directly by the codec
if converted != None:
# Write the converted output to the appropriate place based on the command line arguments
if outfile == None:
print(converted)
else:
if isinstance(converted, str):
with open(outfile, "w") as file:
file.write(converted)
elif isinstance(converted, (bytes, bytearray)):
with open(outfile, "wb") as file:
file.write(converted)
# Handle the case of multiple output files
if outfiles == None:
outfiles = [outfile]

# Step through all of the potential output files
for i in range(len(outfiles)):
outfile = outfiles[i]
if len(outfiles) > 1:
codec_module = active_codecs[i]

# Use the codec plugin to do the conversion
converted = codec_module.convert(
build_result, outfile, errfile, output_opts
)

# If converted is None, assume that the output was written to file directly by the codec
if converted != None:
# Write the converted output to the appropriate place based on the command line arguments
if outfile == None:
print(converted)
else:
raise TypeError(
"Expected converted output to be str, bytes, or bytearray. Got '%s'"
% type(converted).__name__
)
if isinstance(converted, str):
with open(outfile, "w") as file:
file.write(converted)
elif isinstance(converted, (bytes, bytearray)):
with open(outfile, "wb") as file:
file.write(converted)
else:
raise TypeError(
"Expected converted output to be str, bytes, or bytearray. Got '%s'"
% type(converted).__name__
)

except Exception:
out_tb = traceback.format_exc()
Expand Down
88 changes: 88 additions & 0 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,68 @@ def test_codec_infile_outfile_errfile_arguments():
assert err_str == "Argument error: infile does not exist."


def test_no_codec_parameter():
"""
Tests the CLI's ability to infer the codec from the outfile extension.
"""
test_file = helpers.get_test_file_location("cube.py")

# Get a temporary output file location
temp_dir = tempfile.gettempdir()
temp_file = os.path.join(temp_dir, "temp_test_12.step")

command = [
"python",
"src/cq_cli/main.py",
"--infile",
test_file,
"--outfile",
temp_file,
]
out, err, exitcode = helpers.cli_call(command)

# Read the STEP output back from the outfile
with open(temp_file, "r") as file:
step_str = file.read()

assert step_str.startswith("ISO-10303-21;")


def test_no_codec_parameter_multiple_infiles():
"""
Tests the CLI's ability to infer the codecs from multiple infile extensions.
"""
test_file = helpers.get_test_file_location("cube.py")

# Get a temporary output file location
temp_dir = tempfile.gettempdir()
temp_file_step = os.path.join(temp_dir, "temp_test_13.step")
temp_file_stl = os.path.join(temp_dir, "temp_test_13.stl")
temp_paths = temp_file_step + ";" + temp_file_stl

command = [
"python",
"src/cq_cli/main.py",
"--infile",
test_file,
"--outfile",
temp_paths,
]
out, err, exitcode = helpers.cli_call(command)

# Read the STEP output back from the outfile to make sure it has the correct content
with open(temp_file_step, "r") as file:
step_str = file.read()
assert step_str.startswith("ISO-10303-21;")

# Read the STL output back from the outfile to make sure it has the correct content
with open(temp_file_stl, "r") as file:
stl_str = file.read()
assert stl_str.startswith("solid")

assert exitcode == 0


def test_parameter_file():
"""
Tests the CLI's ability to load JSON parameters from a file.
Expand Down Expand Up @@ -456,3 +518,29 @@ def test_expression_argument():

# cq-cli invocation should fail
assert exitcode == 200


def test_multiple_outfiles():
"""
Tests the CLI with multiple output files specified.
"""
test_file = helpers.get_test_file_location("cube.py")

# Get a temporary output file location
temp_dir = tempfile.gettempdir()
temp_file_step = os.path.join(temp_dir, "temp_test_11.step")
temp_file_stl = os.path.join(temp_dir, "temp_test_11.stl")
temp_paths = temp_file_step + ";" + temp_file_stl

command = [
"python",
"src/cq_cli/main.py",
"--codec",
"step;stl",
"--infile",
test_file,
"--outfile",
temp_paths,
]
out, err, exitcode = helpers.cli_call(command)
assert exitcode == 0

0 comments on commit b009833

Please sign in to comment.