Skip to content

Commit

Permalink
support config.yaml and multiple file generating
Browse files Browse the repository at this point in the history
  • Loading branch information
hitsmaxft committed Jan 17, 2025
1 parent 79d8153 commit 69af128
Show file tree
Hide file tree
Showing 4 changed files with 156 additions and 67 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.direnv
config.yaml
output/
*.xml
51 changes: 41 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
"configs": [
{
"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.
13 changes: 13 additions & 0 deletions config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"configs": [
{
"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]
}
]
}
155 changes: 98 additions & 57 deletions main.py
Original file line number Diff line number Diff line change
@@ -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:
configs = json.load(file)['configs']


def designator(val: float):
Expand All @@ -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):
Expand All @@ -41,30 +61,28 @@ def threads(self, designation):
pass



class Metric3Dprinted(ThreadProfile):
class Designation:
def __init__(self, diameter, pitch):
self.nominalDiameter = diameter
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
Expand All @@ -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"
Expand All @@ -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(configs):
for config in configs:
name = config['name']
custom_name = config.get('customName', name)
unit = config['unit']
sizes = parse_sizes(config['sizes'])
angle = config['angle']
pitches = config['pitches']
offsets = config['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(configs)

0 comments on commit 69af128

Please sign in to comment.