From 0919ac5224c528db2d5ada14bc7b5994f3448dbb Mon Sep 17 00:00:00 2001 From: hitsmaxft Date: Fri, 17 Jan 2025 06:43:19 +0000 Subject: [PATCH] support config.yaml and multiple file generating --- .gitignore | 4 ++ README.md | 51 +++++++++++++---- config.json | 13 +++++ main.py | 155 +++++++++++++++++++++++++++++++++------------------- 4 files changed, 156 insertions(+), 67 deletions(-) create mode 100644 .gitignore create mode 100644 config.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..611b04e --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.direnv +config.yaml +output/ +*.xml diff --git a/README.md b/README.md index 57e4380..1f1ae84 100644 --- a/README.md +++ b/README.md @@ -45,21 +45,52 @@ When you now create or edit a *Thread* feature, you should be able to select the You can generate your own thread profile file using the `main.py` script. To execute the script, **Python 3.9** or newer is required. -The script has no parameters and can be executed like so: +The script can be executed like so: ```bash python main.py ``` -This will create a file named `output.xml` in the working directory which you can then rename and install in Fusion as described above. +This will create XML files for each configuration defined in the `config.json` file in the working directory, which you can then rename and install in Fusion as described above. + +To customize the generated profiles, simply edit the values defined in the `config.json` file. + +```json +{ + "profiles": [ + { + "name": "3DPrintedMetricV3", + "customName": "3D-printed Metric Threads V3", + "unit": "mm", + "angle": 60.0, + "sizes": "8:50", + "pitches": [3.5, 5.0], + "offsets": [0.0, 0.1, 0.2, 0.4, 0.8] + } + ] +} +``` + +To use a custom JSON file for the configurations, specify the path to the custom JSON file when executing the script: + +```bash +python main.py path/to/custom/config.json +``` + +For example, if your custom configuration file is located at `configs/new_config.json`, you can run the script like this: + +```bash +python main.py configs/new_config.json +``` + +This will load the configurations from the specified JSON file and generate the corresponding XML files. + -To customize the generated profiles, simply edit the values defined at the top of `main.py`. -```python -NAME = "3D-printed Metric Threads V3" -UNIT = "mm" -ANGLE = 60.0 -SIZES = list(range(8, 51)) -PITCHES = [3.5, 5.0] -OFFSETS = [.0, .1, .2, .4, .8] +To see all available options and arguments for the script, you can use the `-h` or `--help` flag: + +```bash +python main.py -h ``` + +This will display a help message with descriptions of all the available command-line arguments. \ No newline at end of file diff --git a/config.json b/config.json new file mode 100644 index 0000000..91ec8a8 --- /dev/null +++ b/config.json @@ -0,0 +1,13 @@ +{ + "profiles": [ + { + "name": "3DPrintedMetricV3", + "customName": "3D-printed Metric Threads V3", + "unit": "mm", + "angle": 60.0, + "sizes": "8:50", + "pitches": [3.5, 5.0], + "offsets": [0.0, 0.1, 0.2, 0.4, 0.8] + } + ] +} \ No newline at end of file diff --git a/main.py b/main.py index f85d698..8db70f7 100644 --- a/main.py +++ b/main.py @@ -1,13 +1,27 @@ import math import xml.etree.ElementTree as ET from abc import ABC, abstractmethod +import json +import logging +import sys +import argparse -NAME = "3D-printed Metric Threads V3" -UNIT = "mm" -ANGLE = 60.0 -SIZES = list(range(8, 51)) -PITCHES = [3.5, 5.0] -OFFSETS = [.0, .1, .2, .4, .8] +# Configure logging +logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') + +# Parse command-line arguments +parser = argparse.ArgumentParser(description='Generate XML files for custom threads.') +parser.add_argument('config_file', nargs='?', default='./config.json', help='Path to the configuration JSON file, default value is ./config.json') +parser.add_argument('-v', '--verbose', action='store_true', help='Enable verbose logging') +args = parser.parse_args() + +# Set logging level based on verbose flag +if args.verbose: + logging.getLogger().setLevel(logging.DEBUG) + +# Load configurations from JSON file +with open(args.config_file, 'r') as file: + profiles = json.load(file)['profiles'] def designator(val: float): @@ -28,9 +42,15 @@ def __init__(self): class ThreadProfile(ABC): - @abstractmethod + + def __init__(self, sizes, pitches, offsets, angle): + self._sizes = sizes + self.pitches = pitches + self.offsets = offsets + self.angle = angle + def sizes(self): - pass + return self._sizes @abstractmethod def designations(self, size): @@ -41,6 +61,7 @@ def threads(self, designation): pass + class Metric3Dprinted(ThreadProfile): class Designation: def __init__(self, diameter, pitch): @@ -48,23 +69,20 @@ def __init__(self, diameter, pitch): self.pitch = pitch self.name = "M{}x{}".format(designator(self.nominalDiameter), designator(self.pitch)) - def __init__(self): - self.offsets = OFFSETS - - def sizes(self): - return SIZES + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) def designations(self, size): - return [Metric3Dprinted.Designation(size, pitch) for pitch in PITCHES] + return [self.Designation(size, pitch) for pitch in self.pitches] + def threads(self, designation): - ts = [] + threads = [] for offset in self.offsets: offset_decimals = str(offset)[2:] # skips the '0.' at the start - # see https://en.wikipedia.org/wiki/ISO_metric_screw_thread P = designation.pitch - H = 1/math.tan(math.radians(ANGLE/2)) * (P/2) + H = 1/math.tan(math.radians(self.angle/2)) * (P/2) D = designation.nominalDiameter Dp = D - 2 * 3*H/8 Dmin = D - 2 * 5*H/8 @@ -75,7 +93,7 @@ def threads(self, designation): t.majorDia = D - offset t.pitchDia = Dp - offset t.minorDia = Dmin - offset - ts.append(t) + threads.append(t) t = Thread() t.gender = "internal" @@ -84,42 +102,65 @@ def threads(self, designation): t.pitchDia = Dp + offset t.minorDia = Dmin + offset t.tapDrill = D - P - ts.append(t) - return ts - - -def generate(): - profile = Metric3Dprinted() - - root = ET.Element('ThreadType') - tree = ET.ElementTree(root) - - ET.SubElement(root, "Name").text = NAME - ET.SubElement(root, "CustomName").text = NAME - ET.SubElement(root, "Unit").text = UNIT - ET.SubElement(root, "Angle").text = str(ANGLE) - ET.SubElement(root, "SortOrder").text = "3" - - for size in profile.sizes(): - thread_size_element = ET.SubElement(root, "ThreadSize") - ET.SubElement(thread_size_element, "Size").text = str(size) - for designation in profile.designations(size): - designation_element = ET.SubElement(thread_size_element, "Designation") - ET.SubElement(designation_element, "ThreadDesignation").text = designation.name - ET.SubElement(designation_element, "CTD").text = designation.name - ET.SubElement(designation_element, "Pitch").text = str(designation.pitch) - for thread in profile.threads(designation): - thread_element = ET.SubElement(designation_element, "Thread") - ET.SubElement(thread_element, "Gender").text = thread.gender - ET.SubElement(thread_element, "Class").text = thread.clazz - ET.SubElement(thread_element, "MajorDia").text = "{:.4g}".format(thread.majorDia) - ET.SubElement(thread_element, "PitchDia").text = "{:.4g}".format(thread.pitchDia) - ET.SubElement(thread_element, "MinorDia").text = "{:.4g}".format(thread.minorDia) - if thread.tapDrill: - ET.SubElement(thread_element, "TapDrill").text = "{:.4g}".format(thread.tapDrill) - - ET.indent(tree) - tree.write('3DPrintedMetricV3.xml', encoding='UTF-8', xml_declaration=True) - - -generate() + threads.append(t) + return threads + +def parse_sizes(sizes): + if isinstance(sizes, str): + if ':' in sizes: + parts = sizes.split(',') + start, end = map(int, parts[0].split(':')) + step = int(parts[1]) if len(parts) > 1 else 1 + return list(range(start, end + 1, step)) + elif isinstance(sizes, list): + return sizes + else: + raise ValueError("Invalid sizes format, should be a list or a range separated by ':'") + +def generate_xml_files(profiles): + for profile in profiles: + name = profile['name'] + custom_name = profile.get('customName', name) + unit = profile['unit'] + sizes = parse_sizes(profile['sizes']) + angle = profile['angle'] + pitches = profile['pitches'] + offsets = profile['offsets'] + logging.info(f"Generating XML for {custom_name}") + profile = Metric3Dprinted(sizes, pitches, offsets, angle) + + root = ET.Element('ThreadType') + + tree = ET.ElementTree(root) + ET.SubElement(root, "Name").text = custom_name + ET.SubElement(root, "CustomName").text = custom_name + ET.SubElement(root, "Unit").text = unit + + ET.SubElement(root, "Angle").text = str(angle) + ET.SubElement(root, "SortOrder").text = "3" + for size in profile.sizes(): + logging.info(f"Processing size: {size}") + thread_size_element = ET.SubElement(root, "ThreadSize") + ET.SubElement(thread_size_element, "Size").text = str(size) + for designation in profile.designations(size): + designation_element = ET.SubElement(thread_size_element, "Designation") + ET.SubElement(designation_element, "ThreadDesignation").text = designation.name + ET.SubElement(designation_element, "CTD").text = designation.name + ET.SubElement(designation_element, "Pitch").text = str(designation.pitch) + for thread in profile.threads(designation): + logging.debug(f"Processing thread: Gender={thread.gender}, Class={thread.clazz}, MajorDia={thread.majorDia}, PitchDia={thread.pitchDia}, MinorDia={thread.minorDia}, TapDrill={thread.tapDrill}") + thread_element = ET.SubElement(designation_element, "Thread") + ET.SubElement(thread_element, "Gender").text = thread.gender + ET.SubElement(thread_element, "Class").text = thread.clazz + ET.SubElement(thread_element, "MajorDia").text = "{:.4g}".format(thread.majorDia) + ET.SubElement(thread_element, "PitchDia").text = "{:.4g}".format(thread.pitchDia) + ET.SubElement(thread_element, "MinorDia").text = "{:.4g}".format(thread.minorDia) + if thread.tapDrill: + ET.SubElement(thread_element, "TapDrill").text = "{:.4g}".format(thread.tapDrill) + + ET.indent(tree) + tree.write(f"{name}.xml", encoding='UTF-8', xml_declaration=True) + logging.info(f"XML file {name}.xml generated successfully") + +# Example usage +generate_xml_files(profiles)