Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added multiple file export at once #34

Merged
merged 6 commits into from
Aug 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading