From 677175183d03bd352d54a02ac7b93359846e997c Mon Sep 17 00:00:00 2001 From: cuban2 <147667036+cuban2@users.noreply.github.com> Date: Mon, 8 Sep 2025 11:36:35 -0700 Subject: [PATCH 01/31] Expanded_main.py Change Log: Metric Thread Generator Script Moved all user inputs to the top of the script for easy access. Added clear START and END markers around the user input section. Replaced static pitch list with dynamic pitch range using pitch_start, pitch_end, and pitch_step. Created a function called generate_pitch_list() to calculate pitch values from the input range. Renamed designator() function to format_number() for clarity. Introduced a Thread class to store thread properties with named attributes. Modularized the code using named functions: generate_pitch_list() and generate_xml(). Grouped thread generation logic into a MetricThreadGenerator class. Made output filename customizable using output_filename variable. Added metadata fields to the XML output: Name, CustomName, Unit, Angle, SortOrder. Used ET.indent() to format the XML output (requires Python 3.9 or later). Added detailed comments throughout the script to explain each section. Included instructional examples in the input section to guide new users. Added a confirmation message at the end of the script to notify successful XML creation. Preserved original ISO metric thread geometry calculations (H, pitch diameter, minor diameter). Maintained original offset logic for internal and external threads. Preserved tap drill calculation for internal threads (D - P). Ensured compatibility with Python 3.9 and avoided advanced syntax to keep the script beginner-friendly. --- Expanded_main.py | 150 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100644 Expanded_main.py diff --git a/Expanded_main.py b/Expanded_main.py new file mode 100644 index 0000000..6785760 --- /dev/null +++ b/Expanded_main.py @@ -0,0 +1,150 @@ +# ๐Ÿš€ USER INPUT SECTION โ€” CHANGE THESE VALUES TO CUSTOMIZE YOUR THREADS + +# ๐Ÿ”ง Set the pitch range you want to generate: +pitch_start = .25 # Starting pitch value in mm (e.g., 1.0) +pitch_end = 3.5 # Ending pitch value in mm (e.g., 6.0) +pitch_step = 0.01 # Interval between pitches in mm (e.g., 0.5) + +# ๐Ÿ“ Set the thread diameters you want to include: +thread_sizes = list(range(8, 51)) # Diameters from 8mm to 50mm + +# โš™๏ธ Set tolerance offsets to simulate different thread classes: +tolerance_offsets = [0.0, 0.1, 0.2, 0.4, 0.8] + +# ๐Ÿงพ Set the name of the output XML file: +output_filename = "3DPrintedMetricV3.xml" + +# ๐Ÿงพ Metadata for the thread type +thread_name = "3D-Printed Metric Threads V3" +unit = "mm" +thread_angle = 60.0 # Standard ISO metric thread angle + +# ๐Ÿ“ฆ Import required modules +import math +import xml.etree.ElementTree as ET +from abc import ABC, abstractmethod + +# ๐Ÿ”ข Generate pitch list from user input +def generate_pitch_list(start, end, step): + pitches = [] + current = start + while current <= end + 1e-9: + pitches.append(round(current, 4)) + current += step + return pitches + +# ๐Ÿงผ Format numbers for display +def format_number(val): + return str(int(val)) if val == int(val) else str(val) + +# ๐Ÿงต Thread object to store dimensions +class Thread: + def __init__(self): + self.gender = None + self.thread_class = None + self.major_dia = 0.0 + self.pitch_dia = 0.0 + self.minor_dia = 0.0 + self.tap_drill = None + +# ๐Ÿงฉ Abstract base class +class ThreadProfile(ABC): + @abstractmethod + def sizes(self): + pass + + @abstractmethod + def designations(self, size): + pass + + @abstractmethod + def threads(self, designation): + pass + +# ๐Ÿงต Metric thread generator class +class MetricThreadGenerator(ThreadProfile): + class Designation: + def __init__(self, diameter, pitch): + self.nominal_diameter = diameter + self.pitch = pitch + self.name = f"M{format_number(diameter)}x{format_number(pitch)}" + + def __init__(self, pitch_list): + self.pitches = pitch_list + + def sizes(self): + return thread_sizes + + def designations(self, size): + return [self.Designation(size, pitch) for pitch in self.pitches] + + def threads(self, designation): + threads = [] + P = designation.pitch + D = designation.nominal_diameter + H = (P / 2) / math.tan(math.radians(thread_angle / 2)) + pitch_dia = D - 2 * (3 * H / 8) + minor_dia = D - 2 * (5 * H / 8) + + for offset in tolerance_offsets: + class_label = f"O.{str(offset)[2:]}" # e.g., 0.1 โ†’ "O.1" + + ext = Thread() + ext.gender = "external" + ext.thread_class = class_label + ext.major_dia = D - offset + ext.pitch_dia = pitch_dia - offset + ext.minor_dia = minor_dia - offset + threads.append(ext) + + int_thread = Thread() + int_thread.gender = "internal" + int_thread.thread_class = class_label + int_thread.major_dia = D + offset + int_thread.pitch_dia = pitch_dia + offset + int_thread.minor_dia = minor_dia + offset + int_thread.tap_drill = D - P + threads.append(int_thread) + + return threads + +# ๐Ÿงพ XML generator +def generate_xml(): + pitch_list = generate_pitch_list(pitch_start, pitch_end, pitch_step) + profile = MetricThreadGenerator(pitch_list) + + root = ET.Element("ThreadType") + tree = ET.ElementTree(root) + + ET.SubElement(root, "Name").text = thread_name + ET.SubElement(root, "CustomName").text = thread_name + ET.SubElement(root, "Unit").text = unit + ET.SubElement(root, "Angle").text = str(thread_angle) + ET.SubElement(root, "SortOrder").text = "3" + + for size in profile.sizes(): + size_elem = ET.SubElement(root, "ThreadSize") + ET.SubElement(size_elem, "Size").text = str(size) + for designation in profile.designations(size): + des_elem = ET.SubElement(size_elem, "Designation") + ET.SubElement(des_elem, "ThreadDesignation").text = designation.name + ET.SubElement(des_elem, "CTD").text = designation.name + ET.SubElement(des_elem, "Pitch").text = str(designation.pitch) + for thread in profile.threads(designation): + thread_elem = ET.SubElement(des_elem, "Thread") + ET.SubElement(thread_elem, "Gender").text = thread.gender + ET.SubElement(thread_elem, "Class").text = thread.thread_class + ET.SubElement(thread_elem, "MajorDia").text = f"{thread.major_dia:.4g}" + ET.SubElement(thread_elem, "PitchDia").text = f"{thread.pitch_dia:.4g}" + ET.SubElement(thread_elem, "MinorDia").text = f"{thread.minor_dia:.4g}" + if thread.tap_drill is not None: + ET.SubElement(thread_elem, "TapDrill").text = f"{thread.tap_drill:.4g}" + + ET.indent(tree) + tree.write(output_filename, encoding="UTF-8", xml_declaration=True) + +# ๐Ÿš€ Run the generator +if __name__ == "__main__": + generate_xml() + print(f"โœ… XML file '{output_filename}' created for pitches from {pitch_start} mm to {pitch_end} mm in {pitch_step} mm steps.") + From b6b4407a0fad181d96338aa849c2ce758851e602 Mon Sep 17 00:00:00 2001 From: cuban2 <147667036+cuban2@users.noreply.github.com> Date: Mon, 8 Sep 2025 12:49:56 -0700 Subject: [PATCH 02/31] Add files via upload --- expand_threads.py | 257 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 257 insertions(+) create mode 100644 expand_threads.py diff --git a/expand_threads.py b/expand_threads.py new file mode 100644 index 0000000..85ec1ec --- /dev/null +++ b/expand_threads.py @@ -0,0 +1,257 @@ +#!/usr/bin/env python3 +import xml.etree.ElementTree as ET +from xml.sax.saxutils import escape +from decimal import Decimal, getcontext +import os +import math + +# === CONFIGURATION === + +INPUT_FILE = "ISOMetricprofile.txt" # Base Fusion XML to clone/augment +OUTPUT_FILE = "3D_Printable_ThreadLibrary_MultiFit.txt" + +THREAD_ANGLE = Decimal("90.0") # 45ยฐ printable profile + +# Size/pitch ranges +SIZE_START = Decimal("5.0") +SIZE_END = Decimal("30.0") +SIZE_STEP = Decimal("1.0") + +PITCH_START = Decimal("0.5") +PITCH_END = Decimal("3.5") +PITCH_STEP = Decimal("0.1") + +# Fit types (tune for printer/material) +# min_clearance is radial (difference in diameters) +FIT_TYPES = { + "Functional": { + "min_clearance": Decimal("0.20"), + "es": Decimal("0.15"), # external upper deviation (shifts external down) + "Td": Decimal("0.20") # tolerance width proxy + }, + "Loose": { + "min_clearance": Decimal("0.30"), + "es": Decimal("0.10"), + "Td": Decimal("0.25") + } +} + +# Heuristic build limit to avoid ultra-tight helices that tend to fail in Fusion +MIN_PITCH_TO_DIAM_RATIO = Decimal("0.01") # allow smaller pitches than before + +# Numeric stability for Decimal ops +getcontext().prec = 12 + +# === GEOMETRY HELPERS === + +def thread_height(pitch: Decimal, angle_deg: Decimal) -> Decimal: + # H = 0.5 * P * cot(theta/2) + return Decimal("0.5") * pitch * Decimal(str(1 / math.tan(math.radians(float(angle_deg) / 2.0)))) + +def h3(pitch: Decimal, angle_deg: Decimal) -> Decimal: + # h3 = 5/8 * H + return Decimal("0.625") * thread_height(pitch, angle_deg) + +def crest_flat(pitch: Decimal, angle_deg: Decimal) -> Decimal: + # Clamp crest flat so it doesn't exceed half the thread height + cf = pitch / Decimal("8") + H = thread_height(pitch, angle_deg) + return cf if cf <= H / Decimal("2") else H / Decimal("2") + +def root_flat(pitch: Decimal) -> Decimal: + return pitch / Decimal("4") + +def frange(start: Decimal, stop: Decimal, step: Decimal): + cur = start + while cur <= stop + Decimal("0.0001"): + yield cur.quantize(Decimal("0.001")) + cur += step + +# === TOLERANCE CALCULATOR (scaled EI to help small pitches) === +# External: +# major_max = size - es +# pitch_max = size - es (proxy for tolerance band top) +# Internal: +# Choose EI so that internal_minor >= external_major + clearance +# EI_required = es + clearance - 2*h3 +# Also scale EI with pitch to keep small threads printable: +# EI_scaled = 0.05 + 0.5 * pitch (tunable) +# EI = max(EI_required, EI_scaled) +# We carry TD/Td as metadata; geometry uses top values to keep XML simple. + +def calculate_tolerances(size: Decimal, pitch: Decimal, fit: dict) -> dict: + h3_val = h3(pitch, THREAD_ANGLE) + es = fit["es"] + Td = fit["Td"] + clearance = fit["min_clearance"] + + EI_required = es + clearance - Decimal("2") * h3_val + EI_scaled = Decimal("0.05") + (Decimal("0.5") * pitch) + EI = EI_required if EI_required >= EI_scaled else EI_scaled + + return { + "external": {"es": es, "Td": Td}, + "internal": {"EI": EI, "TD": Td} + } + +# === BUILDABILITY CHECKS FOR EXTERNAL === + +def external_buildable(size: Decimal, pitch: Decimal, tol: dict, fit: dict) -> bool: + # Helix tightness check + if size > 0 and pitch < size * MIN_PITCH_TO_DIAM_RATIO: + return False + + # Clearance check: internal minor >= external major + min_clearance + h3_val = h3(pitch, THREAD_ANGLE) + minor_dia_nom = size - Decimal("2") * h3_val + external_major_max = size - tol["external"]["es"] + internal_minor_min = minor_dia_nom + tol["internal"]["EI"] + if internal_minor_min < external_major_max + fit["min_clearance"]: + return False + + # Crest flat sanity check + H = thread_height(pitch, THREAD_ANGLE) + if crest_flat(pitch, THREAD_ANGLE) > H / Decimal("2"): + return False + + return True + +# === THREAD XML BUILDERS === + +def make_thread(gender: str, size: Decimal, pitch: Decimal, tol: dict, fit_name: str) -> ET.Element: + H = thread_height(pitch, THREAD_ANGLE) + h3_val = h3(pitch, THREAD_ANGLE) + + pitch_dia_nom = (size - Decimal("0.75") * H).quantize(Decimal("0.000")) + minor_dia_nom = (size - Decimal("2") * h3_val).quantize(Decimal("0.000")) + + if gender == "external": + es = tol["external"]["es"] + # We publish the top-of-zone (max) values in the XML fields + major_max = (size - es).quantize(Decimal("0.000")) + pitch_max = (size - es).quantize(Decimal("0.000")) + minor = minor_dia_nom # geometry-based for modeled shape + flat_val = crest_flat(pitch, THREAD_ANGLE).quantize(Decimal("0.000")) + flat_tag = "CrestFlat" + maj_out = major_max + p_out = pitch_max + else: + EI = tol["internal"]["EI"] + TD = tol["internal"]["TD"] + major_min = (size + EI).quantize(Decimal("0.000")) + major_max = (major_min + TD).quantize(Decimal("0.000")) + pitch_max = (major_min + TD).quantize(Decimal("0.000")) + minor = (minor_dia_nom + EI).quantize(Decimal("0.000")) + flat_val = root_flat(pitch).quantize(Decimal("0.000")) + flat_tag = "RootFlat" + maj_out = major_max + p_out = pitch_max + + t = ET.Element("Thread") + ET.SubElement(t, "Gender").text = gender + ET.SubElement(t, "Class").text = fit_name + ET.SubElement(t, "MajorDia").text = f"{maj_out:.3f}" + ET.SubElement(t, "PitchDia").text = f"{p_out:.3f}" + ET.SubElement(t, "MinorDia").text = f"{minor:.3f}" + ET.SubElement(t, flat_tag).text = f"{flat_val:.3f}" + return t + +def make_designation(size: Decimal, pitch: Decimal) -> ET.Element: + size_str = f"{size:.3f}".rstrip("0").rstrip(".") + pitch_str = f"{pitch:.3f}".rstrip("0").rstrip(".") + label = f"M{size_str}x{pitch_str}" + + des = ET.Element("Designation") + ET.SubElement(des, "ThreadDesignation").text = label + ET.SubElement(des, "CTD").text = label + ET.SubElement(des, "Pitch").text = pitch_str + + for fit_name, fit in FIT_TYPES.items(): + tol = calculate_tolerances(size, pitch, fit) + + # External-first policy: if external fails, skip the pair (as requested) + if not external_buildable(size, pitch, tol, fit): + # Optional: uncomment for debug logging + # print(f"Skip M{size}x{pitch} [{fit_name}] - external failed checks") + continue + + ext_thread = make_thread("external", size, pitch, tol, fit_name) + int_thread = make_thread("internal", size, pitch, tol, fit_name) + + des.append(ext_thread) + des.append(int_thread) + + return des + +def make_thread_size(size: Decimal) -> ET.Element: + ts = ET.Element("ThreadSize") + ET.SubElement(ts, "Size").text = f"{size:.3f}".rstrip("0").rstrip(".") + for pitch in frange(PITCH_START, PITCH_END, PITCH_STEP): + des = make_designation(size, pitch) + # Only append designation if it contains at least one valid thread pair + if list(des.findall("Thread")): + ts.append(des) + return ts + +# === XML WRITER (tabs + newlines) === + +def serialize_elem(elem: ET.Element, level: int = 0) -> str: + indent = "\t" * level + tag = elem.tag + text = (elem.text or "").strip() + children = list(elem) + + if not children: + return f"{indent}<{tag}>{escape(text)}" if text else f"{indent}<{tag}>" + + lines = [f"{indent}<{tag}>"] + if text: + lines.append(f"{indent}\t{escape(text)}") + for child in children: + lines.append(serialize_elem(child, level + 1)) + lines.append(f"{indent}") + return "\n".join(lines) + +def write_xml_with_tabs(root: ET.Element, output_file: str): + xml_header = "\n" + body = serialize_elem(root, 0) + with open(output_file, "w", encoding="utf-8") as f: + f.write(xml_header + body) + +# === MAIN === + +def main(): + tree = ET.parse(INPUT_FILE) + root = tree.getroot() + + # Reset key metadata + for tag in ("Name", "CustomName", "Angle", "Unit", "SortOrder"): + node = root.find(tag) + if node is not None: + root.remove(node) + + ET.SubElement(root, "Name").text = "3D Printable Metric Profile (Multi-Fit, Safe External)" + ET.SubElement(root, "CustomName").text = "Threads โ€” Functional & Loose (skip unbuildable pairs)" + ET.SubElement(root, "Unit").text = "mm" + ET.SubElement(root, "Angle").text = f"{THREAD_ANGLE:.1f}" + ET.SubElement(root, "SortOrder").text = "3" + + # Remove existing sizes + for ts in list(root.findall("ThreadSize")): + root.remove(ts) + + # Build sizes + any_sizes = False + for size in frange(SIZE_START, SIZE_END, SIZE_STEP): + ts_elem = make_thread_size(size) + if list(ts_elem.findall("Designation")): + root.append(ts_elem) + any_sizes = True + + write_xml_with_tabs(root, OUTPUT_FILE) + print(f"โœ… Wrote: {os.path.abspath(OUTPUT_FILE)}") + if not any_sizes: + print("โš ๏ธ No valid thread pairs generated. Consider loosening fit settings or angle/pitch limits.") + +if __name__ == "__main__": + main() From 084d2eddade6906306d08341c062187a7dd5b624 Mon Sep 17 00:00:00 2001 From: cuban2 <147667036+cuban2@users.noreply.github.com> Date: Mon, 8 Sep 2025 12:55:33 -0700 Subject: [PATCH 03/31] Delete Expanded_main.py --- Expanded_main.py | 150 ----------------------------------------------- 1 file changed, 150 deletions(-) delete mode 100644 Expanded_main.py diff --git a/Expanded_main.py b/Expanded_main.py deleted file mode 100644 index 6785760..0000000 --- a/Expanded_main.py +++ /dev/null @@ -1,150 +0,0 @@ -# ๐Ÿš€ USER INPUT SECTION โ€” CHANGE THESE VALUES TO CUSTOMIZE YOUR THREADS - -# ๐Ÿ”ง Set the pitch range you want to generate: -pitch_start = .25 # Starting pitch value in mm (e.g., 1.0) -pitch_end = 3.5 # Ending pitch value in mm (e.g., 6.0) -pitch_step = 0.01 # Interval between pitches in mm (e.g., 0.5) - -# ๐Ÿ“ Set the thread diameters you want to include: -thread_sizes = list(range(8, 51)) # Diameters from 8mm to 50mm - -# โš™๏ธ Set tolerance offsets to simulate different thread classes: -tolerance_offsets = [0.0, 0.1, 0.2, 0.4, 0.8] - -# ๐Ÿงพ Set the name of the output XML file: -output_filename = "3DPrintedMetricV3.xml" - -# ๐Ÿงพ Metadata for the thread type -thread_name = "3D-Printed Metric Threads V3" -unit = "mm" -thread_angle = 60.0 # Standard ISO metric thread angle - -# ๐Ÿ“ฆ Import required modules -import math -import xml.etree.ElementTree as ET -from abc import ABC, abstractmethod - -# ๐Ÿ”ข Generate pitch list from user input -def generate_pitch_list(start, end, step): - pitches = [] - current = start - while current <= end + 1e-9: - pitches.append(round(current, 4)) - current += step - return pitches - -# ๐Ÿงผ Format numbers for display -def format_number(val): - return str(int(val)) if val == int(val) else str(val) - -# ๐Ÿงต Thread object to store dimensions -class Thread: - def __init__(self): - self.gender = None - self.thread_class = None - self.major_dia = 0.0 - self.pitch_dia = 0.0 - self.minor_dia = 0.0 - self.tap_drill = None - -# ๐Ÿงฉ Abstract base class -class ThreadProfile(ABC): - @abstractmethod - def sizes(self): - pass - - @abstractmethod - def designations(self, size): - pass - - @abstractmethod - def threads(self, designation): - pass - -# ๐Ÿงต Metric thread generator class -class MetricThreadGenerator(ThreadProfile): - class Designation: - def __init__(self, diameter, pitch): - self.nominal_diameter = diameter - self.pitch = pitch - self.name = f"M{format_number(diameter)}x{format_number(pitch)}" - - def __init__(self, pitch_list): - self.pitches = pitch_list - - def sizes(self): - return thread_sizes - - def designations(self, size): - return [self.Designation(size, pitch) for pitch in self.pitches] - - def threads(self, designation): - threads = [] - P = designation.pitch - D = designation.nominal_diameter - H = (P / 2) / math.tan(math.radians(thread_angle / 2)) - pitch_dia = D - 2 * (3 * H / 8) - minor_dia = D - 2 * (5 * H / 8) - - for offset in tolerance_offsets: - class_label = f"O.{str(offset)[2:]}" # e.g., 0.1 โ†’ "O.1" - - ext = Thread() - ext.gender = "external" - ext.thread_class = class_label - ext.major_dia = D - offset - ext.pitch_dia = pitch_dia - offset - ext.minor_dia = minor_dia - offset - threads.append(ext) - - int_thread = Thread() - int_thread.gender = "internal" - int_thread.thread_class = class_label - int_thread.major_dia = D + offset - int_thread.pitch_dia = pitch_dia + offset - int_thread.minor_dia = minor_dia + offset - int_thread.tap_drill = D - P - threads.append(int_thread) - - return threads - -# ๐Ÿงพ XML generator -def generate_xml(): - pitch_list = generate_pitch_list(pitch_start, pitch_end, pitch_step) - profile = MetricThreadGenerator(pitch_list) - - root = ET.Element("ThreadType") - tree = ET.ElementTree(root) - - ET.SubElement(root, "Name").text = thread_name - ET.SubElement(root, "CustomName").text = thread_name - ET.SubElement(root, "Unit").text = unit - ET.SubElement(root, "Angle").text = str(thread_angle) - ET.SubElement(root, "SortOrder").text = "3" - - for size in profile.sizes(): - size_elem = ET.SubElement(root, "ThreadSize") - ET.SubElement(size_elem, "Size").text = str(size) - for designation in profile.designations(size): - des_elem = ET.SubElement(size_elem, "Designation") - ET.SubElement(des_elem, "ThreadDesignation").text = designation.name - ET.SubElement(des_elem, "CTD").text = designation.name - ET.SubElement(des_elem, "Pitch").text = str(designation.pitch) - for thread in profile.threads(designation): - thread_elem = ET.SubElement(des_elem, "Thread") - ET.SubElement(thread_elem, "Gender").text = thread.gender - ET.SubElement(thread_elem, "Class").text = thread.thread_class - ET.SubElement(thread_elem, "MajorDia").text = f"{thread.major_dia:.4g}" - ET.SubElement(thread_elem, "PitchDia").text = f"{thread.pitch_dia:.4g}" - ET.SubElement(thread_elem, "MinorDia").text = f"{thread.minor_dia:.4g}" - if thread.tap_drill is not None: - ET.SubElement(thread_elem, "TapDrill").text = f"{thread.tap_drill:.4g}" - - ET.indent(tree) - tree.write(output_filename, encoding="UTF-8", xml_declaration=True) - -# ๐Ÿš€ Run the generator -if __name__ == "__main__": - generate_xml() - print(f"โœ… XML file '{output_filename}' created for pitches from {pitch_start} mm to {pitch_end} mm in {pitch_step} mm steps.") - From 2b28b2c85e5370082db261775f13c5477691d818 Mon Sep 17 00:00:00 2001 From: cuban2 <147667036+cuban2@users.noreply.github.com> Date: Mon, 8 Sep 2025 12:57:56 -0700 Subject: [PATCH 04/31] Delete expand_threads.py --- expand_threads.py | 257 ---------------------------------------------- 1 file changed, 257 deletions(-) delete mode 100644 expand_threads.py diff --git a/expand_threads.py b/expand_threads.py deleted file mode 100644 index 85ec1ec..0000000 --- a/expand_threads.py +++ /dev/null @@ -1,257 +0,0 @@ -#!/usr/bin/env python3 -import xml.etree.ElementTree as ET -from xml.sax.saxutils import escape -from decimal import Decimal, getcontext -import os -import math - -# === CONFIGURATION === - -INPUT_FILE = "ISOMetricprofile.txt" # Base Fusion XML to clone/augment -OUTPUT_FILE = "3D_Printable_ThreadLibrary_MultiFit.txt" - -THREAD_ANGLE = Decimal("90.0") # 45ยฐ printable profile - -# Size/pitch ranges -SIZE_START = Decimal("5.0") -SIZE_END = Decimal("30.0") -SIZE_STEP = Decimal("1.0") - -PITCH_START = Decimal("0.5") -PITCH_END = Decimal("3.5") -PITCH_STEP = Decimal("0.1") - -# Fit types (tune for printer/material) -# min_clearance is radial (difference in diameters) -FIT_TYPES = { - "Functional": { - "min_clearance": Decimal("0.20"), - "es": Decimal("0.15"), # external upper deviation (shifts external down) - "Td": Decimal("0.20") # tolerance width proxy - }, - "Loose": { - "min_clearance": Decimal("0.30"), - "es": Decimal("0.10"), - "Td": Decimal("0.25") - } -} - -# Heuristic build limit to avoid ultra-tight helices that tend to fail in Fusion -MIN_PITCH_TO_DIAM_RATIO = Decimal("0.01") # allow smaller pitches than before - -# Numeric stability for Decimal ops -getcontext().prec = 12 - -# === GEOMETRY HELPERS === - -def thread_height(pitch: Decimal, angle_deg: Decimal) -> Decimal: - # H = 0.5 * P * cot(theta/2) - return Decimal("0.5") * pitch * Decimal(str(1 / math.tan(math.radians(float(angle_deg) / 2.0)))) - -def h3(pitch: Decimal, angle_deg: Decimal) -> Decimal: - # h3 = 5/8 * H - return Decimal("0.625") * thread_height(pitch, angle_deg) - -def crest_flat(pitch: Decimal, angle_deg: Decimal) -> Decimal: - # Clamp crest flat so it doesn't exceed half the thread height - cf = pitch / Decimal("8") - H = thread_height(pitch, angle_deg) - return cf if cf <= H / Decimal("2") else H / Decimal("2") - -def root_flat(pitch: Decimal) -> Decimal: - return pitch / Decimal("4") - -def frange(start: Decimal, stop: Decimal, step: Decimal): - cur = start - while cur <= stop + Decimal("0.0001"): - yield cur.quantize(Decimal("0.001")) - cur += step - -# === TOLERANCE CALCULATOR (scaled EI to help small pitches) === -# External: -# major_max = size - es -# pitch_max = size - es (proxy for tolerance band top) -# Internal: -# Choose EI so that internal_minor >= external_major + clearance -# EI_required = es + clearance - 2*h3 -# Also scale EI with pitch to keep small threads printable: -# EI_scaled = 0.05 + 0.5 * pitch (tunable) -# EI = max(EI_required, EI_scaled) -# We carry TD/Td as metadata; geometry uses top values to keep XML simple. - -def calculate_tolerances(size: Decimal, pitch: Decimal, fit: dict) -> dict: - h3_val = h3(pitch, THREAD_ANGLE) - es = fit["es"] - Td = fit["Td"] - clearance = fit["min_clearance"] - - EI_required = es + clearance - Decimal("2") * h3_val - EI_scaled = Decimal("0.05") + (Decimal("0.5") * pitch) - EI = EI_required if EI_required >= EI_scaled else EI_scaled - - return { - "external": {"es": es, "Td": Td}, - "internal": {"EI": EI, "TD": Td} - } - -# === BUILDABILITY CHECKS FOR EXTERNAL === - -def external_buildable(size: Decimal, pitch: Decimal, tol: dict, fit: dict) -> bool: - # Helix tightness check - if size > 0 and pitch < size * MIN_PITCH_TO_DIAM_RATIO: - return False - - # Clearance check: internal minor >= external major + min_clearance - h3_val = h3(pitch, THREAD_ANGLE) - minor_dia_nom = size - Decimal("2") * h3_val - external_major_max = size - tol["external"]["es"] - internal_minor_min = minor_dia_nom + tol["internal"]["EI"] - if internal_minor_min < external_major_max + fit["min_clearance"]: - return False - - # Crest flat sanity check - H = thread_height(pitch, THREAD_ANGLE) - if crest_flat(pitch, THREAD_ANGLE) > H / Decimal("2"): - return False - - return True - -# === THREAD XML BUILDERS === - -def make_thread(gender: str, size: Decimal, pitch: Decimal, tol: dict, fit_name: str) -> ET.Element: - H = thread_height(pitch, THREAD_ANGLE) - h3_val = h3(pitch, THREAD_ANGLE) - - pitch_dia_nom = (size - Decimal("0.75") * H).quantize(Decimal("0.000")) - minor_dia_nom = (size - Decimal("2") * h3_val).quantize(Decimal("0.000")) - - if gender == "external": - es = tol["external"]["es"] - # We publish the top-of-zone (max) values in the XML fields - major_max = (size - es).quantize(Decimal("0.000")) - pitch_max = (size - es).quantize(Decimal("0.000")) - minor = minor_dia_nom # geometry-based for modeled shape - flat_val = crest_flat(pitch, THREAD_ANGLE).quantize(Decimal("0.000")) - flat_tag = "CrestFlat" - maj_out = major_max - p_out = pitch_max - else: - EI = tol["internal"]["EI"] - TD = tol["internal"]["TD"] - major_min = (size + EI).quantize(Decimal("0.000")) - major_max = (major_min + TD).quantize(Decimal("0.000")) - pitch_max = (major_min + TD).quantize(Decimal("0.000")) - minor = (minor_dia_nom + EI).quantize(Decimal("0.000")) - flat_val = root_flat(pitch).quantize(Decimal("0.000")) - flat_tag = "RootFlat" - maj_out = major_max - p_out = pitch_max - - t = ET.Element("Thread") - ET.SubElement(t, "Gender").text = gender - ET.SubElement(t, "Class").text = fit_name - ET.SubElement(t, "MajorDia").text = f"{maj_out:.3f}" - ET.SubElement(t, "PitchDia").text = f"{p_out:.3f}" - ET.SubElement(t, "MinorDia").text = f"{minor:.3f}" - ET.SubElement(t, flat_tag).text = f"{flat_val:.3f}" - return t - -def make_designation(size: Decimal, pitch: Decimal) -> ET.Element: - size_str = f"{size:.3f}".rstrip("0").rstrip(".") - pitch_str = f"{pitch:.3f}".rstrip("0").rstrip(".") - label = f"M{size_str}x{pitch_str}" - - des = ET.Element("Designation") - ET.SubElement(des, "ThreadDesignation").text = label - ET.SubElement(des, "CTD").text = label - ET.SubElement(des, "Pitch").text = pitch_str - - for fit_name, fit in FIT_TYPES.items(): - tol = calculate_tolerances(size, pitch, fit) - - # External-first policy: if external fails, skip the pair (as requested) - if not external_buildable(size, pitch, tol, fit): - # Optional: uncomment for debug logging - # print(f"Skip M{size}x{pitch} [{fit_name}] - external failed checks") - continue - - ext_thread = make_thread("external", size, pitch, tol, fit_name) - int_thread = make_thread("internal", size, pitch, tol, fit_name) - - des.append(ext_thread) - des.append(int_thread) - - return des - -def make_thread_size(size: Decimal) -> ET.Element: - ts = ET.Element("ThreadSize") - ET.SubElement(ts, "Size").text = f"{size:.3f}".rstrip("0").rstrip(".") - for pitch in frange(PITCH_START, PITCH_END, PITCH_STEP): - des = make_designation(size, pitch) - # Only append designation if it contains at least one valid thread pair - if list(des.findall("Thread")): - ts.append(des) - return ts - -# === XML WRITER (tabs + newlines) === - -def serialize_elem(elem: ET.Element, level: int = 0) -> str: - indent = "\t" * level - tag = elem.tag - text = (elem.text or "").strip() - children = list(elem) - - if not children: - return f"{indent}<{tag}>{escape(text)}" if text else f"{indent}<{tag}>" - - lines = [f"{indent}<{tag}>"] - if text: - lines.append(f"{indent}\t{escape(text)}") - for child in children: - lines.append(serialize_elem(child, level + 1)) - lines.append(f"{indent}") - return "\n".join(lines) - -def write_xml_with_tabs(root: ET.Element, output_file: str): - xml_header = "\n" - body = serialize_elem(root, 0) - with open(output_file, "w", encoding="utf-8") as f: - f.write(xml_header + body) - -# === MAIN === - -def main(): - tree = ET.parse(INPUT_FILE) - root = tree.getroot() - - # Reset key metadata - for tag in ("Name", "CustomName", "Angle", "Unit", "SortOrder"): - node = root.find(tag) - if node is not None: - root.remove(node) - - ET.SubElement(root, "Name").text = "3D Printable Metric Profile (Multi-Fit, Safe External)" - ET.SubElement(root, "CustomName").text = "Threads โ€” Functional & Loose (skip unbuildable pairs)" - ET.SubElement(root, "Unit").text = "mm" - ET.SubElement(root, "Angle").text = f"{THREAD_ANGLE:.1f}" - ET.SubElement(root, "SortOrder").text = "3" - - # Remove existing sizes - for ts in list(root.findall("ThreadSize")): - root.remove(ts) - - # Build sizes - any_sizes = False - for size in frange(SIZE_START, SIZE_END, SIZE_STEP): - ts_elem = make_thread_size(size) - if list(ts_elem.findall("Designation")): - root.append(ts_elem) - any_sizes = True - - write_xml_with_tabs(root, OUTPUT_FILE) - print(f"โœ… Wrote: {os.path.abspath(OUTPUT_FILE)}") - if not any_sizes: - print("โš ๏ธ No valid thread pairs generated. Consider loosening fit settings or angle/pitch limits.") - -if __name__ == "__main__": - main() From 4bf1f9cdf3c8b216e7b9559f13aa74a41607b59b Mon Sep 17 00:00:00 2001 From: cuban2 <147667036+cuban2@users.noreply.github.com> Date: Mon, 8 Sep 2025 12:58:36 -0700 Subject: [PATCH 05/31] Add files via upload --- Expanded_3DPrintable_CutomeThreads.py | 161 ++++++++++++++++++++++++++ 1 file changed, 161 insertions(+) create mode 100644 Expanded_3DPrintable_CutomeThreads.py diff --git a/Expanded_3DPrintable_CutomeThreads.py b/Expanded_3DPrintable_CutomeThreads.py new file mode 100644 index 0000000..5853870 --- /dev/null +++ b/Expanded_3DPrintable_CutomeThreads.py @@ -0,0 +1,161 @@ +# ============================================================ +# ๐Ÿš€ USER INPUT SECTION โ€” START HERE +# Change the values below to customize your thread generation +# ============================================================ + +# ๐Ÿ”ง Pitch range settings (in millimeters) +pitch_start = 1.0 # Starting pitch value (e.g., 1.0 mm) +pitch_end = 6.0 # Ending pitch value (e.g., 6.0 mm) +pitch_step = 0.5 # Interval between pitches (e.g., 0.5 mm) + +# ๐Ÿ“ Thread diameters to include (in millimeters) +thread_sizes = list(range(8, 51)) # Diameters from 8 mm to 50 mm + +# โš™๏ธ Tolerance offsets to simulate different thread classes +tolerance_offsets = [0.0, 0.1, 0.2, 0.4, 0.8] + +# ๐Ÿงพ Output file name +output_filename = "3DPrintedMetricThreads.xml" + +# ๐Ÿ“‹ Metadata for the thread type +thread_name = "3D-Printed Metric Threads V3" +unit = "mm" +thread_angle = 60.0 # Standard ISO metric thread angle + +# ============================================================ +# ๐Ÿšซ USER INPUT SECTION โ€” END HERE +# You don't need to change anything below this line +# ============================================================ + +import math +import xml.etree.ElementTree as ET +from abc import ABC, abstractmethod + +# ๐Ÿ”ข Generate pitch values from user-defined range +def generate_pitch_list(start, end, step): + pitches = [] + current = start + while current <= end + 1e-9: + pitches.append(round(current, 4)) + current += step + return pitches + +# ๐Ÿงผ Format numbers for display (e.g., 5.0 โ†’ "5") +def format_number(val): + return str(int(val)) if val == int(val) else str(val) + +# ๐Ÿงต Thread object to store calculated dimensions +class Thread: + def __init__(self): + self.gender = None + self.thread_class = None + self.major_dia = 0.0 + self.pitch_dia = 0.0 + self.minor_dia = 0.0 + self.tap_drill = None + +# ๐Ÿงฉ Abstract base class for thread profiles +class ThreadProfile(ABC): + @abstractmethod + def sizes(self): + pass + + @abstractmethod + def designations(self, size): + pass + + @abstractmethod + def threads(self, designation): + pass + +# ๐Ÿงต Metric thread generator class +class MetricThreadGenerator(ThreadProfile): + class Designation: + def __init__(self, diameter, pitch): + self.nominal_diameter = diameter + self.pitch = pitch + self.name = f"M{format_number(diameter)}x{format_number(pitch)}" + + def __init__(self, pitch_list): + self.pitches = pitch_list + + def sizes(self): + return thread_sizes + + def designations(self, size): + return [self.Designation(size, pitch) for pitch in self.pitches] + + def threads(self, designation): + threads = [] + P = designation.pitch + D = designation.nominal_diameter + H = (P / 2) / math.tan(math.radians(thread_angle / 2)) + pitch_dia = D - 2 * (3 * H / 8) + minor_dia = D - 2 * (5 * H / 8) + + for offset in tolerance_offsets: + class_label = f"O.{str(offset)[2:]}" # e.g., 0.1 โ†’ "O.1" + + # External thread + ext = Thread() + ext.gender = "external" + ext.thread_class = class_label + ext.major_dia = D - offset + ext.pitch_dia = pitch_dia - offset + ext.minor_dia = minor_dia - offset + threads.append(ext) + + # Internal thread + int_thread = Thread() + int_thread.gender = "internal" + int_thread.thread_class = class_label + int_thread.major_dia = D + offset + int_thread.pitch_dia = pitch_dia + offset + int_thread.minor_dia = minor_dia + offset + int_thread.tap_drill = D - P + threads.append(int_thread) + + return threads + +# ๐Ÿงพ XML generator function +def generate_xml(): + pitch_list = generate_pitch_list(pitch_start, pitch_end, pitch_step) + profile = MetricThreadGenerator(pitch_list) + + root = ET.Element("ThreadType") + tree = ET.ElementTree(root) + + # Add metadata + ET.SubElement(root, "Name").text = thread_name + ET.SubElement(root, "CustomName").text = thread_name + ET.SubElement(root, "Unit").text = unit + ET.SubElement(root, "Angle").text = str(thread_angle) + ET.SubElement(root, "SortOrder").text = "3" + + # Add thread sizes and designations + for size in profile.sizes(): + size_elem = ET.SubElement(root, "ThreadSize") + ET.SubElement(size_elem, "Size").text = str(size) + for designation in profile.designations(size): + des_elem = ET.SubElement(size_elem, "Designation") + ET.SubElement(des_elem, "ThreadDesignation").text = designation.name + ET.SubElement(des_elem, "CTD").text = designation.name + ET.SubElement(des_elem, "Pitch").text = str(designation.pitch) + for thread in profile.threads(designation): + thread_elem = ET.SubElement(des_elem, "Thread") + ET.SubElement(thread_elem, "Gender").text = thread.gender + ET.SubElement(thread_elem, "Class").text = thread.thread_class + ET.SubElement(thread_elem, "MajorDia").text = f"{thread.major_dia:.4g}" + ET.SubElement(thread_elem, "PitchDia").text = f"{thread.pitch_dia:.4g}" + ET.SubElement(thread_elem, "MinorDia").text = f"{thread.minor_dia:.4g}" + if thread.tap_drill is not None: + ET.SubElement(thread_elem, "TapDrill").text = f"{thread.tap_drill:.4g}" + + ET.indent(tree) + tree.write(output_filename, encoding="UTF-8", xml_declaration=True) + +# ๐Ÿš€ Run the generator +if __name__ == "__main__": + generate_xml() + print(f"โœ… XML file '{output_filename}' created for pitches from {pitch_start} mm to {pitch_end} mm in {pitch_step} mm steps.") + From b0953da6d19b04fc4f4951ce9bedfa2450bb5a26 Mon Sep 17 00:00:00 2001 From: cuban2 <147667036+cuban2@users.noreply.github.com> Date: Mon, 8 Sep 2025 13:01:11 -0700 Subject: [PATCH 06/31] Delete Expanded_3DPrintable_CutomeThreads.py --- Expanded_3DPrintable_CutomeThreads.py | 161 -------------------------- 1 file changed, 161 deletions(-) delete mode 100644 Expanded_3DPrintable_CutomeThreads.py diff --git a/Expanded_3DPrintable_CutomeThreads.py b/Expanded_3DPrintable_CutomeThreads.py deleted file mode 100644 index 5853870..0000000 --- a/Expanded_3DPrintable_CutomeThreads.py +++ /dev/null @@ -1,161 +0,0 @@ -# ============================================================ -# ๐Ÿš€ USER INPUT SECTION โ€” START HERE -# Change the values below to customize your thread generation -# ============================================================ - -# ๐Ÿ”ง Pitch range settings (in millimeters) -pitch_start = 1.0 # Starting pitch value (e.g., 1.0 mm) -pitch_end = 6.0 # Ending pitch value (e.g., 6.0 mm) -pitch_step = 0.5 # Interval between pitches (e.g., 0.5 mm) - -# ๐Ÿ“ Thread diameters to include (in millimeters) -thread_sizes = list(range(8, 51)) # Diameters from 8 mm to 50 mm - -# โš™๏ธ Tolerance offsets to simulate different thread classes -tolerance_offsets = [0.0, 0.1, 0.2, 0.4, 0.8] - -# ๐Ÿงพ Output file name -output_filename = "3DPrintedMetricThreads.xml" - -# ๐Ÿ“‹ Metadata for the thread type -thread_name = "3D-Printed Metric Threads V3" -unit = "mm" -thread_angle = 60.0 # Standard ISO metric thread angle - -# ============================================================ -# ๐Ÿšซ USER INPUT SECTION โ€” END HERE -# You don't need to change anything below this line -# ============================================================ - -import math -import xml.etree.ElementTree as ET -from abc import ABC, abstractmethod - -# ๐Ÿ”ข Generate pitch values from user-defined range -def generate_pitch_list(start, end, step): - pitches = [] - current = start - while current <= end + 1e-9: - pitches.append(round(current, 4)) - current += step - return pitches - -# ๐Ÿงผ Format numbers for display (e.g., 5.0 โ†’ "5") -def format_number(val): - return str(int(val)) if val == int(val) else str(val) - -# ๐Ÿงต Thread object to store calculated dimensions -class Thread: - def __init__(self): - self.gender = None - self.thread_class = None - self.major_dia = 0.0 - self.pitch_dia = 0.0 - self.minor_dia = 0.0 - self.tap_drill = None - -# ๐Ÿงฉ Abstract base class for thread profiles -class ThreadProfile(ABC): - @abstractmethod - def sizes(self): - pass - - @abstractmethod - def designations(self, size): - pass - - @abstractmethod - def threads(self, designation): - pass - -# ๐Ÿงต Metric thread generator class -class MetricThreadGenerator(ThreadProfile): - class Designation: - def __init__(self, diameter, pitch): - self.nominal_diameter = diameter - self.pitch = pitch - self.name = f"M{format_number(diameter)}x{format_number(pitch)}" - - def __init__(self, pitch_list): - self.pitches = pitch_list - - def sizes(self): - return thread_sizes - - def designations(self, size): - return [self.Designation(size, pitch) for pitch in self.pitches] - - def threads(self, designation): - threads = [] - P = designation.pitch - D = designation.nominal_diameter - H = (P / 2) / math.tan(math.radians(thread_angle / 2)) - pitch_dia = D - 2 * (3 * H / 8) - minor_dia = D - 2 * (5 * H / 8) - - for offset in tolerance_offsets: - class_label = f"O.{str(offset)[2:]}" # e.g., 0.1 โ†’ "O.1" - - # External thread - ext = Thread() - ext.gender = "external" - ext.thread_class = class_label - ext.major_dia = D - offset - ext.pitch_dia = pitch_dia - offset - ext.minor_dia = minor_dia - offset - threads.append(ext) - - # Internal thread - int_thread = Thread() - int_thread.gender = "internal" - int_thread.thread_class = class_label - int_thread.major_dia = D + offset - int_thread.pitch_dia = pitch_dia + offset - int_thread.minor_dia = minor_dia + offset - int_thread.tap_drill = D - P - threads.append(int_thread) - - return threads - -# ๐Ÿงพ XML generator function -def generate_xml(): - pitch_list = generate_pitch_list(pitch_start, pitch_end, pitch_step) - profile = MetricThreadGenerator(pitch_list) - - root = ET.Element("ThreadType") - tree = ET.ElementTree(root) - - # Add metadata - ET.SubElement(root, "Name").text = thread_name - ET.SubElement(root, "CustomName").text = thread_name - ET.SubElement(root, "Unit").text = unit - ET.SubElement(root, "Angle").text = str(thread_angle) - ET.SubElement(root, "SortOrder").text = "3" - - # Add thread sizes and designations - for size in profile.sizes(): - size_elem = ET.SubElement(root, "ThreadSize") - ET.SubElement(size_elem, "Size").text = str(size) - for designation in profile.designations(size): - des_elem = ET.SubElement(size_elem, "Designation") - ET.SubElement(des_elem, "ThreadDesignation").text = designation.name - ET.SubElement(des_elem, "CTD").text = designation.name - ET.SubElement(des_elem, "Pitch").text = str(designation.pitch) - for thread in profile.threads(designation): - thread_elem = ET.SubElement(des_elem, "Thread") - ET.SubElement(thread_elem, "Gender").text = thread.gender - ET.SubElement(thread_elem, "Class").text = thread.thread_class - ET.SubElement(thread_elem, "MajorDia").text = f"{thread.major_dia:.4g}" - ET.SubElement(thread_elem, "PitchDia").text = f"{thread.pitch_dia:.4g}" - ET.SubElement(thread_elem, "MinorDia").text = f"{thread.minor_dia:.4g}" - if thread.tap_drill is not None: - ET.SubElement(thread_elem, "TapDrill").text = f"{thread.tap_drill:.4g}" - - ET.indent(tree) - tree.write(output_filename, encoding="UTF-8", xml_declaration=True) - -# ๐Ÿš€ Run the generator -if __name__ == "__main__": - generate_xml() - print(f"โœ… XML file '{output_filename}' created for pitches from {pitch_start} mm to {pitch_end} mm in {pitch_step} mm steps.") - From 0da427303ee66f2ab9868e9e8a0d758808ba42ee Mon Sep 17 00:00:00 2001 From: cuban2 <147667036+cuban2@users.noreply.github.com> Date: Mon, 8 Sep 2025 13:01:28 -0700 Subject: [PATCH 07/31] Add files via upload --- Expanded_3DPrintable_CustomThreads.py | 161 ++++++++++++++++++++++++++ 1 file changed, 161 insertions(+) create mode 100644 Expanded_3DPrintable_CustomThreads.py diff --git a/Expanded_3DPrintable_CustomThreads.py b/Expanded_3DPrintable_CustomThreads.py new file mode 100644 index 0000000..5853870 --- /dev/null +++ b/Expanded_3DPrintable_CustomThreads.py @@ -0,0 +1,161 @@ +# ============================================================ +# ๐Ÿš€ USER INPUT SECTION โ€” START HERE +# Change the values below to customize your thread generation +# ============================================================ + +# ๐Ÿ”ง Pitch range settings (in millimeters) +pitch_start = 1.0 # Starting pitch value (e.g., 1.0 mm) +pitch_end = 6.0 # Ending pitch value (e.g., 6.0 mm) +pitch_step = 0.5 # Interval between pitches (e.g., 0.5 mm) + +# ๐Ÿ“ Thread diameters to include (in millimeters) +thread_sizes = list(range(8, 51)) # Diameters from 8 mm to 50 mm + +# โš™๏ธ Tolerance offsets to simulate different thread classes +tolerance_offsets = [0.0, 0.1, 0.2, 0.4, 0.8] + +# ๐Ÿงพ Output file name +output_filename = "3DPrintedMetricThreads.xml" + +# ๐Ÿ“‹ Metadata for the thread type +thread_name = "3D-Printed Metric Threads V3" +unit = "mm" +thread_angle = 60.0 # Standard ISO metric thread angle + +# ============================================================ +# ๐Ÿšซ USER INPUT SECTION โ€” END HERE +# You don't need to change anything below this line +# ============================================================ + +import math +import xml.etree.ElementTree as ET +from abc import ABC, abstractmethod + +# ๐Ÿ”ข Generate pitch values from user-defined range +def generate_pitch_list(start, end, step): + pitches = [] + current = start + while current <= end + 1e-9: + pitches.append(round(current, 4)) + current += step + return pitches + +# ๐Ÿงผ Format numbers for display (e.g., 5.0 โ†’ "5") +def format_number(val): + return str(int(val)) if val == int(val) else str(val) + +# ๐Ÿงต Thread object to store calculated dimensions +class Thread: + def __init__(self): + self.gender = None + self.thread_class = None + self.major_dia = 0.0 + self.pitch_dia = 0.0 + self.minor_dia = 0.0 + self.tap_drill = None + +# ๐Ÿงฉ Abstract base class for thread profiles +class ThreadProfile(ABC): + @abstractmethod + def sizes(self): + pass + + @abstractmethod + def designations(self, size): + pass + + @abstractmethod + def threads(self, designation): + pass + +# ๐Ÿงต Metric thread generator class +class MetricThreadGenerator(ThreadProfile): + class Designation: + def __init__(self, diameter, pitch): + self.nominal_diameter = diameter + self.pitch = pitch + self.name = f"M{format_number(diameter)}x{format_number(pitch)}" + + def __init__(self, pitch_list): + self.pitches = pitch_list + + def sizes(self): + return thread_sizes + + def designations(self, size): + return [self.Designation(size, pitch) for pitch in self.pitches] + + def threads(self, designation): + threads = [] + P = designation.pitch + D = designation.nominal_diameter + H = (P / 2) / math.tan(math.radians(thread_angle / 2)) + pitch_dia = D - 2 * (3 * H / 8) + minor_dia = D - 2 * (5 * H / 8) + + for offset in tolerance_offsets: + class_label = f"O.{str(offset)[2:]}" # e.g., 0.1 โ†’ "O.1" + + # External thread + ext = Thread() + ext.gender = "external" + ext.thread_class = class_label + ext.major_dia = D - offset + ext.pitch_dia = pitch_dia - offset + ext.minor_dia = minor_dia - offset + threads.append(ext) + + # Internal thread + int_thread = Thread() + int_thread.gender = "internal" + int_thread.thread_class = class_label + int_thread.major_dia = D + offset + int_thread.pitch_dia = pitch_dia + offset + int_thread.minor_dia = minor_dia + offset + int_thread.tap_drill = D - P + threads.append(int_thread) + + return threads + +# ๐Ÿงพ XML generator function +def generate_xml(): + pitch_list = generate_pitch_list(pitch_start, pitch_end, pitch_step) + profile = MetricThreadGenerator(pitch_list) + + root = ET.Element("ThreadType") + tree = ET.ElementTree(root) + + # Add metadata + ET.SubElement(root, "Name").text = thread_name + ET.SubElement(root, "CustomName").text = thread_name + ET.SubElement(root, "Unit").text = unit + ET.SubElement(root, "Angle").text = str(thread_angle) + ET.SubElement(root, "SortOrder").text = "3" + + # Add thread sizes and designations + for size in profile.sizes(): + size_elem = ET.SubElement(root, "ThreadSize") + ET.SubElement(size_elem, "Size").text = str(size) + for designation in profile.designations(size): + des_elem = ET.SubElement(size_elem, "Designation") + ET.SubElement(des_elem, "ThreadDesignation").text = designation.name + ET.SubElement(des_elem, "CTD").text = designation.name + ET.SubElement(des_elem, "Pitch").text = str(designation.pitch) + for thread in profile.threads(designation): + thread_elem = ET.SubElement(des_elem, "Thread") + ET.SubElement(thread_elem, "Gender").text = thread.gender + ET.SubElement(thread_elem, "Class").text = thread.thread_class + ET.SubElement(thread_elem, "MajorDia").text = f"{thread.major_dia:.4g}" + ET.SubElement(thread_elem, "PitchDia").text = f"{thread.pitch_dia:.4g}" + ET.SubElement(thread_elem, "MinorDia").text = f"{thread.minor_dia:.4g}" + if thread.tap_drill is not None: + ET.SubElement(thread_elem, "TapDrill").text = f"{thread.tap_drill:.4g}" + + ET.indent(tree) + tree.write(output_filename, encoding="UTF-8", xml_declaration=True) + +# ๐Ÿš€ Run the generator +if __name__ == "__main__": + generate_xml() + print(f"โœ… XML file '{output_filename}' created for pitches from {pitch_start} mm to {pitch_end} mm in {pitch_step} mm steps.") + From 170f682d90fe4ea6904445b75361a329d7937eba Mon Sep 17 00:00:00 2001 From: Autistaman <90534842+Autistaman@users.noreply.github.com> Date: Thu, 25 Dec 2025 19:19:08 +0000 Subject: [PATCH 08/31] Adding threadform parameter --- 3DPrintedMetricV3.xml | 1 + main.py | 2 ++ 2 files changed, 3 insertions(+) diff --git a/3DPrintedMetricV3.xml b/3DPrintedMetricV3.xml index c395df0..a3524a9 100644 --- a/3DPrintedMetricV3.xml +++ b/3DPrintedMetricV3.xml @@ -4,6 +4,7 @@ 3D-printed Metric Threads V3 mm 60.0 + 8 3 8 diff --git a/main.py b/main.py index f85d698..e4e1333 100644 --- a/main.py +++ b/main.py @@ -8,6 +8,7 @@ SIZES = list(range(8, 51)) PITCHES = [3.5, 5.0] OFFSETS = [.0, .1, .2, .4, .8] +THREAD_FORM = 8 def designator(val: float): @@ -98,6 +99,7 @@ def generate(): ET.SubElement(root, "CustomName").text = NAME ET.SubElement(root, "Unit").text = UNIT ET.SubElement(root, "Angle").text = str(ANGLE) + ET.SubElement(root, "ThreadForm").text = str(THREAD_FORM) ET.SubElement(root, "SortOrder").text = "3" for size in profile.sizes(): From cf67676be0ce8207d4453ab697e200f9eee115d0 Mon Sep 17 00:00:00 2001 From: Autistaman <90534842+Autistaman@users.noreply.github.com> Date: Thu, 25 Dec 2025 19:27:52 +0000 Subject: [PATCH 09/31] Updated readme to fix an error and add threadform --- README.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 57e4380..24ad29f 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ This project defines thread profiles that are aimed at 3D printing so that they The provided `3DPrintedMetricV3.xml` file contains the thread profiles listed below. Additional profiles can be easily generated from the included Python script. -**Shape:** 60ยฐ trapezoidal +**Shape:** 60ยฐ whitworth **Size (OD):** 8-50mm **Pitch:** 3.5mm, 5mm **Classes:** O.0, O.1, O.2, O.4, O.8 @@ -51,10 +51,17 @@ The script has no parameters and can be executed like so: 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 a file named `3DPrintedMetricV3.xml` 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 at the top of `main.py`. +To change the profile of the thread change the "THREAD_FORM" variable and reference the below chart. + +0 = Trapezoidal +1 = sharp V shape +6 = Square +8 = whitworth + ```python NAME = "3D-printed Metric Threads V3" UNIT = "mm" @@ -62,4 +69,5 @@ ANGLE = 60.0 SIZES = list(range(8, 51)) PITCHES = [3.5, 5.0] OFFSETS = [.0, .1, .2, .4, .8] +THREAD_FORM = 8 ``` From f968616faf3555f900fbb9d31bc4b3faf6d82403 Mon Sep 17 00:00:00 2001 From: Philip <44484441+EckPhi@users.noreply.github.com> Date: Sun, 29 Mar 2026 12:47:33 +0200 Subject: [PATCH 10/31] Sets up project with Poetry and initial .gitignore --- .gitignore | 74 ++++++++++++++++++++++++++++++++++++++++++++++++++ pyproject.toml | 24 ++++++++++++++++ 2 files changed, 98 insertions(+) create mode 100644 .gitignore create mode 100644 pyproject.toml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1201370 --- /dev/null +++ b/.gitignore @@ -0,0 +1,74 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +*.manifest +*.spec + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ + +# Poetry +poetry.lock + +# Virtual environments +venv/ +ENV/ +env/ +.venv + +# IDEs +.vscode/ +.idea/ +*.swp +*.swo +*~ +.DS_Store + +# Generated XML files - these are created by CI pipeline +*.xml + +# Build artifacts +/build/ +/dist/ +/output/ + +# GitHub Actions +.github/workflows/ diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..e65f0bb --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,24 @@ +[tool.poetry] +name = "customthreads" +version = "1.0.0" +description = "3D-Printed Metric Threads Generator - Generate standardized ISO metric thread profiles for 3D printing" +authors = ["Philip Ecker "] +readme = "README.md" +packages = [{include = "customthreads", from = "src"}] + +[tool.poetry.dependencies] +python = "^3.11" + +[tool.poetry.group.dev.dependencies] +pytest = "^7.4.3" + +[tool.pytest.ini_options] +testpaths = ["tests"] +python_files = ["test_*.py"] +python_classes = ["Test*"] +python_functions = ["test_*"] +addopts = "-v --tb=short" + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" From 84597fb2e31b2f52cc31d1d2d6f36051d2e11da1 Mon Sep 17 00:00:00 2001 From: Philip <44484441+EckPhi@users.noreply.github.com> Date: Sun, 29 Mar 2026 12:47:33 +0200 Subject: [PATCH 11/31] Refactors thread generation into a Python package --- Expanded_3DPrintable_CustomThreads.py | 161 -------------------------- main.py | 127 -------------------- src/customthreads/__init__.py | 14 +++ src/customthreads/cli.py | 57 +++++++++ src/customthreads/generator.py | 98 ++++++++++++++++ src/customthreads/models.py | 101 ++++++++++++++++ 6 files changed, 270 insertions(+), 288 deletions(-) delete mode 100644 Expanded_3DPrintable_CustomThreads.py delete mode 100644 main.py create mode 100644 src/customthreads/__init__.py create mode 100644 src/customthreads/cli.py create mode 100644 src/customthreads/generator.py create mode 100644 src/customthreads/models.py diff --git a/Expanded_3DPrintable_CustomThreads.py b/Expanded_3DPrintable_CustomThreads.py deleted file mode 100644 index 5853870..0000000 --- a/Expanded_3DPrintable_CustomThreads.py +++ /dev/null @@ -1,161 +0,0 @@ -# ============================================================ -# ๐Ÿš€ USER INPUT SECTION โ€” START HERE -# Change the values below to customize your thread generation -# ============================================================ - -# ๐Ÿ”ง Pitch range settings (in millimeters) -pitch_start = 1.0 # Starting pitch value (e.g., 1.0 mm) -pitch_end = 6.0 # Ending pitch value (e.g., 6.0 mm) -pitch_step = 0.5 # Interval between pitches (e.g., 0.5 mm) - -# ๐Ÿ“ Thread diameters to include (in millimeters) -thread_sizes = list(range(8, 51)) # Diameters from 8 mm to 50 mm - -# โš™๏ธ Tolerance offsets to simulate different thread classes -tolerance_offsets = [0.0, 0.1, 0.2, 0.4, 0.8] - -# ๐Ÿงพ Output file name -output_filename = "3DPrintedMetricThreads.xml" - -# ๐Ÿ“‹ Metadata for the thread type -thread_name = "3D-Printed Metric Threads V3" -unit = "mm" -thread_angle = 60.0 # Standard ISO metric thread angle - -# ============================================================ -# ๐Ÿšซ USER INPUT SECTION โ€” END HERE -# You don't need to change anything below this line -# ============================================================ - -import math -import xml.etree.ElementTree as ET -from abc import ABC, abstractmethod - -# ๐Ÿ”ข Generate pitch values from user-defined range -def generate_pitch_list(start, end, step): - pitches = [] - current = start - while current <= end + 1e-9: - pitches.append(round(current, 4)) - current += step - return pitches - -# ๐Ÿงผ Format numbers for display (e.g., 5.0 โ†’ "5") -def format_number(val): - return str(int(val)) if val == int(val) else str(val) - -# ๐Ÿงต Thread object to store calculated dimensions -class Thread: - def __init__(self): - self.gender = None - self.thread_class = None - self.major_dia = 0.0 - self.pitch_dia = 0.0 - self.minor_dia = 0.0 - self.tap_drill = None - -# ๐Ÿงฉ Abstract base class for thread profiles -class ThreadProfile(ABC): - @abstractmethod - def sizes(self): - pass - - @abstractmethod - def designations(self, size): - pass - - @abstractmethod - def threads(self, designation): - pass - -# ๐Ÿงต Metric thread generator class -class MetricThreadGenerator(ThreadProfile): - class Designation: - def __init__(self, diameter, pitch): - self.nominal_diameter = diameter - self.pitch = pitch - self.name = f"M{format_number(diameter)}x{format_number(pitch)}" - - def __init__(self, pitch_list): - self.pitches = pitch_list - - def sizes(self): - return thread_sizes - - def designations(self, size): - return [self.Designation(size, pitch) for pitch in self.pitches] - - def threads(self, designation): - threads = [] - P = designation.pitch - D = designation.nominal_diameter - H = (P / 2) / math.tan(math.radians(thread_angle / 2)) - pitch_dia = D - 2 * (3 * H / 8) - minor_dia = D - 2 * (5 * H / 8) - - for offset in tolerance_offsets: - class_label = f"O.{str(offset)[2:]}" # e.g., 0.1 โ†’ "O.1" - - # External thread - ext = Thread() - ext.gender = "external" - ext.thread_class = class_label - ext.major_dia = D - offset - ext.pitch_dia = pitch_dia - offset - ext.minor_dia = minor_dia - offset - threads.append(ext) - - # Internal thread - int_thread = Thread() - int_thread.gender = "internal" - int_thread.thread_class = class_label - int_thread.major_dia = D + offset - int_thread.pitch_dia = pitch_dia + offset - int_thread.minor_dia = minor_dia + offset - int_thread.tap_drill = D - P - threads.append(int_thread) - - return threads - -# ๐Ÿงพ XML generator function -def generate_xml(): - pitch_list = generate_pitch_list(pitch_start, pitch_end, pitch_step) - profile = MetricThreadGenerator(pitch_list) - - root = ET.Element("ThreadType") - tree = ET.ElementTree(root) - - # Add metadata - ET.SubElement(root, "Name").text = thread_name - ET.SubElement(root, "CustomName").text = thread_name - ET.SubElement(root, "Unit").text = unit - ET.SubElement(root, "Angle").text = str(thread_angle) - ET.SubElement(root, "SortOrder").text = "3" - - # Add thread sizes and designations - for size in profile.sizes(): - size_elem = ET.SubElement(root, "ThreadSize") - ET.SubElement(size_elem, "Size").text = str(size) - for designation in profile.designations(size): - des_elem = ET.SubElement(size_elem, "Designation") - ET.SubElement(des_elem, "ThreadDesignation").text = designation.name - ET.SubElement(des_elem, "CTD").text = designation.name - ET.SubElement(des_elem, "Pitch").text = str(designation.pitch) - for thread in profile.threads(designation): - thread_elem = ET.SubElement(des_elem, "Thread") - ET.SubElement(thread_elem, "Gender").text = thread.gender - ET.SubElement(thread_elem, "Class").text = thread.thread_class - ET.SubElement(thread_elem, "MajorDia").text = f"{thread.major_dia:.4g}" - ET.SubElement(thread_elem, "PitchDia").text = f"{thread.pitch_dia:.4g}" - ET.SubElement(thread_elem, "MinorDia").text = f"{thread.minor_dia:.4g}" - if thread.tap_drill is not None: - ET.SubElement(thread_elem, "TapDrill").text = f"{thread.tap_drill:.4g}" - - ET.indent(tree) - tree.write(output_filename, encoding="UTF-8", xml_declaration=True) - -# ๐Ÿš€ Run the generator -if __name__ == "__main__": - generate_xml() - print(f"โœ… XML file '{output_filename}' created for pitches from {pitch_start} mm to {pitch_end} mm in {pitch_step} mm steps.") - diff --git a/main.py b/main.py deleted file mode 100644 index e4e1333..0000000 --- a/main.py +++ /dev/null @@ -1,127 +0,0 @@ -import math -import xml.etree.ElementTree as ET -from abc import ABC, abstractmethod - -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] -THREAD_FORM = 8 - - -def designator(val: float): - if int(val) == val: - return str(int(val)) - else: - return str(val) - - -class Thread: - def __init__(self): - self.gender = None - self.clazz = None - self.majorDia = 0 - self.pitchDia = 0 - self.minorDia = 0 - self.tapDrill = None - - -class ThreadProfile(ABC): - @abstractmethod - def sizes(self): - pass - - @abstractmethod - def designations(self, size): - pass - - @abstractmethod - 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 designations(self, size): - return [Metric3Dprinted.Designation(size, pitch) for pitch in PITCHES] - - def threads(self, designation): - ts = [] - 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) - D = designation.nominalDiameter - Dp = D - 2 * 3*H/8 - Dmin = D - 2 * 5*H/8 - - t = Thread() - t.gender = "external" - t.clazz = "O.{}".format(offset_decimals) - t.majorDia = D - offset - t.pitchDia = Dp - offset - t.minorDia = Dmin - offset - ts.append(t) - - t = Thread() - t.gender = "internal" - t.clazz = "O.{}".format(offset_decimals) - t.majorDia = D + offset - 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, "ThreadForm").text = str(THREAD_FORM) - 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() diff --git a/src/customthreads/__init__.py b/src/customthreads/__init__.py new file mode 100644 index 0000000..01b9246 --- /dev/null +++ b/src/customthreads/__init__.py @@ -0,0 +1,14 @@ +"""3D-Printed Metric Threads Generator - Core package.""" + +from .models import Thread, ThreadProfile, MetricThreadGenerator +from .generator import generate_pitch_list, format_number, generate_xml + +__version__ = "1.0.0" +__all__ = [ + "Thread", + "ThreadProfile", + "MetricThreadGenerator", + "generate_pitch_list", + "format_number", + "generate_xml", +] diff --git a/src/customthreads/cli.py b/src/customthreads/cli.py new file mode 100644 index 0000000..52c0a88 --- /dev/null +++ b/src/customthreads/cli.py @@ -0,0 +1,57 @@ +"""Command-line interface for thread generation.""" + +from .generator import generate_xml + + +def main(): + """Main entry point for CLI thread generation.""" + # ============================================================ + # ๐Ÿš€ USER INPUT SECTION โ€” START HERE + # Change the values below to customize your thread generation + # ============================================================ + + # ๐Ÿ”ง Pitch range settings (in millimeters) + pitch_start = 1.0 # Starting pitch value (e.g., 1.0 mm) + pitch_end = 6.0 # Ending pitch value (e.g., 6.0 mm) + pitch_step = 0.5 # Interval between pitches (e.g., 0.5 mm) + + # ๐Ÿ“ Thread diameters to include (in millimeters) + thread_sizes = list(range(8, 51)) # Diameters from 8 mm to 50 mm + + # โš™๏ธ Tolerance offsets to simulate different thread classes + tolerance_offsets = [0.0, 0.1, 0.2, 0.4, 0.8] + + # ๐Ÿงพ Output file name + output_filename = "3DPrintedMetricThreads.xml" + + # ๐Ÿ“‹ Metadata for the thread type + thread_name = "3D-Printed Metric Threads V3" + unit = "mm" + thread_angle = 60.0 # Standard ISO metric thread angle + + # ๐Ÿ”ง Thread form code (for XML) + thread_form = 8 + + # ============================================================ + # ๐Ÿšซ USER INPUT SECTION โ€” END HERE + # You don't need to change anything below this line + # ============================================================ + + generate_xml( + output_filename=output_filename, + thread_name=thread_name, + unit=unit, + thread_angle=thread_angle, + thread_form=thread_form, + pitch_start=pitch_start, + pitch_end=pitch_end, + pitch_step=pitch_step, + thread_sizes=thread_sizes, + tolerance_offsets=tolerance_offsets, + ) + + print(f"โœ… XML file '{output_filename}' created for pitches from {pitch_start} mm to {pitch_end} mm in {pitch_step} mm steps.") + + +if __name__ == "__main__": + main() diff --git a/src/customthreads/generator.py b/src/customthreads/generator.py new file mode 100644 index 0000000..f5dda84 --- /dev/null +++ b/src/customthreads/generator.py @@ -0,0 +1,98 @@ +"""XML generation and utility functions for thread generators.""" + +import xml.etree.ElementTree as ET +from .models import MetricThreadGenerator + + +def generate_pitch_list(start, end, step): + """Generate pitch values from user-defined range. + + Args: + start: Starting pitch value in mm + end: Ending pitch value in mm + step: Interval between pitches in mm + + Returns: + List of pitch values (rounded to 4 decimals) + """ + pitches = [] + current = start + while current <= end + 1e-9: + pitches.append(round(current, 4)) + current += step + return pitches + + +def format_number(val): + """Format numbers for display (e.g., 5.0 โ†’ "5"). + + Args: + val: Numeric value to format + + Returns: + String representation without unnecessary decimals + """ + return str(int(val)) if val == int(val) else str(val) + + +def generate_xml( + output_filename, + thread_name, + unit, + thread_angle, + thread_form, + pitch_start, + pitch_end, + pitch_step, + thread_sizes, + tolerance_offsets, +): + """Generate XML file with thread specifications. + + Args: + output_filename: Name of output XML file + thread_name: Display name of thread type + unit: Unit of measurement (usually "mm") + thread_angle: Thread angle in degrees (usually 60.0) + thread_form: Thread form code (usually 8) + pitch_start: Starting pitch value + pitch_end: Ending pitch value + pitch_step: Pitch step interval + thread_sizes: List of thread diameters to generate + tolerance_offsets: List of tolerance offsets + """ + pitch_list = generate_pitch_list(pitch_start, pitch_end, pitch_step) + profile = MetricThreadGenerator( + pitch_list, thread_sizes, tolerance_offsets, thread_angle + ) + + root = ET.Element("ThreadType") + tree = ET.ElementTree(root) + + ET.SubElement(root, "Name").text = thread_name + ET.SubElement(root, "CustomName").text = thread_name + ET.SubElement(root, "Unit").text = unit + ET.SubElement(root, "Angle").text = str(thread_angle) + ET.SubElement(root, "ThreadForm").text = str(thread_form) + 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 = f"{thread.majorDia:.4g}" + ET.SubElement(thread_element, "PitchDia").text = f"{thread.pitchDia:.4g}" + ET.SubElement(thread_element, "MinorDia").text = f"{thread.minorDia:.4g}" + if thread.tapDrill: + ET.SubElement(thread_element, "TapDrill").text = f"{thread.tapDrill:.4g}" + + ET.indent(tree) + tree.write(output_filename, encoding="UTF-8", xml_declaration=True) diff --git a/src/customthreads/models.py b/src/customthreads/models.py new file mode 100644 index 0000000..27067a8 --- /dev/null +++ b/src/customthreads/models.py @@ -0,0 +1,101 @@ +"""Thread profile and generator models.""" + +import math +from abc import ABC, abstractmethod + + +class Thread: + """Represents a single thread with calculated dimensions.""" + + def __init__(self): + self.gender = None + self.clazz = None + self.majorDia = 0 + self.pitchDia = 0 + self.minorDia = 0 + self.tapDrill = None + + +class ThreadProfile(ABC): + """Abstract base class for thread profiles.""" + + @abstractmethod + def sizes(self): + """Return list of available thread sizes.""" + pass + + @abstractmethod + def designations(self, size): + """Return designations for a given size.""" + pass + + @abstractmethod + def threads(self, designation): + """Return threads for a given designation.""" + pass + + +class MetricThreadGenerator(ThreadProfile): + """Generator for metric 3D-printed threads.""" + + class Designation: + """Represents a thread designation (e.g., M8x1).""" + + def __init__(self, diameter, pitch): + self.nominalDiameter = diameter + self.pitch = pitch + from .generator import format_number + self.name = f"M{format_number(diameter)}x{format_number(pitch)}" + + def __init__(self, pitch_list, thread_sizes, tolerance_offsets, thread_angle): + """Initialize generator with configuration. + + Args: + pitch_list: List of pitch values to generate + thread_sizes: List of thread diameters to generate + tolerance_offsets: List of tolerance offsets for thread classes + thread_angle: Thread angle in degrees (typically 60.0) + """ + self.pitches = pitch_list + self.thread_sizes = thread_sizes + self.tolerance_offsets = tolerance_offsets + self.thread_angle = thread_angle + + def sizes(self): + """Return available thread sizes.""" + return self.thread_sizes + + def designations(self, size): + """Return designations for a given size.""" + return [self.Designation(size, pitch) for pitch in self.pitches] + + def threads(self, designation): + """Generate threads for a designation with all tolerance classes.""" + ts = [] + for offset in self.tolerance_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(self.thread_angle / 2)) * (P / 2) + D = designation.nominalDiameter + Dp = D - 2 * 3 * H / 8 + Dmin = D - 2 * 5 * H / 8 + + t = Thread() + t.gender = "external" + t.clazz = f"O.{offset_decimals}" + t.majorDia = D - offset + t.pitchDia = Dp - offset + t.minorDia = Dmin - offset + ts.append(t) + + t = Thread() + t.gender = "internal" + t.clazz = f"O.{offset_decimals}" + t.majorDia = D + offset + t.pitchDia = Dp + offset + t.minorDia = Dmin + offset + t.tapDrill = D - P + ts.append(t) + return ts From 7af498b5bd7e48a968de819b9becf7eb9890ad6a Mon Sep 17 00:00:00 2001 From: Philip <44484441+EckPhi@users.noreply.github.com> Date: Sun, 29 Mar 2026 12:47:33 +0200 Subject: [PATCH 12/31] Adds automated test suite for thread generation --- tests/__init__.py | 1 + tests/test_main.py | 206 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 207 insertions(+) create mode 100644 tests/__init__.py create mode 100644 tests/test_main.py diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..66173ae --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1 @@ +# Test package diff --git a/tests/test_main.py b/tests/test_main.py new file mode 100644 index 0000000..7ef9e30 --- /dev/null +++ b/tests/test_main.py @@ -0,0 +1,206 @@ +import pytest +import os +import xml.etree.ElementTree as ET +from pathlib import Path +from customthreads import ( + MetricThreadGenerator, + generate_pitch_list, + format_number, +) +from customthreads.generator import generate_xml + + +class TestThreadGeneration: + """Integration tests for thread XML generation.""" + + @pytest.fixture(autouse=True) + def cleanup(self): + """Clean up generated XML files after each test.""" + yield + # Clean up test files + test_files = [ + "test_output_1.xml", + "test_output_2.xml", + "test_output_3.xml", + "test_output_4.xml", + ] + for f in test_files: + if os.path.exists(f): + os.remove(f) + + def test_pitch_list_generation(self): + """Test that pitch_list is generated correctly.""" + pitches = generate_pitch_list(1.0, 2.0, 0.5) + assert pitches == [1.0, 1.5, 2.0] + + def test_pitch_list_edge_case(self): + """Test pitch list generation with floating point edge cases.""" + pitches = generate_pitch_list(1.0, 3.5, 0.5) + assert len(pitches) == 6 + assert pitches[0] == 1.0 + assert pitches[-1] == 3.5 + + def test_format_number_integer(self): + """Test format_number removes .0 from integers.""" + assert format_number(5.0) == "5" + assert format_number(10.0) == "10" + + def test_format_number_decimal(self): + """Test format_number preserves decimals.""" + assert format_number(5.5) == "5.5" + assert format_number(2.75) == "2.75" + + def test_xml_generation_default_config(self): + """Test XML generation with default configuration.""" + # Generate XML with test config + generate_xml( + output_filename="test_output_1.xml", + thread_name="Test Threads", + unit="mm", + thread_angle=60.0, + thread_form=8, + pitch_start=1.0, + pitch_end=1.5, + pitch_step=0.5, + thread_sizes=list(range(8, 11)), + tolerance_offsets=[0.0, 0.1], + ) + + # Verify file exists + assert os.path.exists("test_output_1.xml") + + # Parse and verify structure + tree = ET.parse("test_output_1.xml") + root = tree.getroot() + + assert root.tag == "ThreadType" + assert root.find("Name").text == "Test Threads" + assert root.find("Unit").text == "mm" + assert root.find("Angle").text == "60.0" + assert root.find("ThreadForm").text == "8" + + def test_xml_thread_sizes(self): + """Test that XML contains expected thread sizes.""" + generate_xml( + output_filename="test_output_2.xml", + thread_name="Test Threads", + unit="mm", + thread_angle=60.0, + thread_form=8, + pitch_start=1.0, + pitch_end=1.0, + pitch_step=1.0, + thread_sizes=list(range(8, 12)), + tolerance_offsets=[0.0], + ) + + tree = ET.parse("test_output_2.xml") + root = tree.getroot() + + thread_sizes = root.findall("ThreadSize") + assert len(thread_sizes) == 4 + + # Verify size elements exist + for size_elem in thread_sizes: + size = size_elem.find("Size").text + assert size is not None + assert int(size) >= 8 + + def test_xml_thread_designations(self): + """Test that thread designations are correctly named.""" + generate_xml( + output_filename="test_output_3.xml", + thread_name="Test Threads", + unit="mm", + thread_angle=60.0, + thread_form=8, + pitch_start=1.0, + pitch_end=1.0, + pitch_step=1.0, + thread_sizes=list(range(8, 11)), + tolerance_offsets=[0.0], + ) + + tree = ET.parse("test_output_3.xml") + root = tree.getroot() + + # Find designations + designations = root.findall(".//Designation") + assert len(designations) > 0 + + # Check format of thread designation (should be like M8x1) + for des in designations[:3]: + des_text = des.find("ThreadDesignation").text + assert "M" in des_text + assert "x" in des_text + + def test_xml_contains_threads(self): + """Test that XML contains external and internal threads.""" + generate_xml( + output_filename="test_output_4.xml", + thread_name="Test Threads", + unit="mm", + thread_angle=60.0, + thread_form=8, + pitch_start=1.0, + pitch_end=1.0, + pitch_step=1.0, + thread_sizes=list(range(8, 11)), + tolerance_offsets=[0.0, 0.1], + ) + + tree = ET.parse("test_output_4.xml") + root = tree.getroot() + + threads = root.findall(".//Thread") + assert len(threads) > 0 + + # Check for both genders + genders = {thread.find("Gender").text for thread in threads[:4]} + assert "external" in genders or "internal" in genders + + +class TestThreadProfile: + """Tests for MetricThreadGenerator class.""" + + def test_generator_initialization(self): + """Test MetricThreadGenerator accepts configuration.""" + pitches = [1.0, 1.5, 2.0] + sizes = list(range(8, 51)) + offsets = [0.0, 0.1] + generator = MetricThreadGenerator(pitches, sizes, offsets, 60.0) + assert generator.pitches == pitches + + def test_generator_sizes(self): + """Test generator returns correct sizes.""" + sizes = list(range(8, 51)) + offsets = [0.0, 0.1] + generator = MetricThreadGenerator([1.0], sizes, offsets, 60.0) + gen_sizes = generator.sizes() + assert len(gen_sizes) == len(sizes) + assert gen_sizes[0] == 8 + + def test_generator_designations(self): + """Test generator creates designations for each size and pitch.""" + pitches = [1.0, 1.5] + sizes = list(range(8, 51)) + offsets = [0.0, 0.1] + generator = MetricThreadGenerator(pitches, sizes, offsets, 60.0) + + designations = generator.designations(10) + assert len(designations) == 2 + assert designations[0].name == "M10x1" + assert designations[1].name == "M10x1.5" + + def test_generator_threads(self): + """Test generator creates threads for each tolerance offset.""" + pitches = [1.0] + sizes = list(range(8, 51)) + offsets = [0.0, 0.1, 0.2, 0.4, 0.8] + generator = MetricThreadGenerator(pitches, sizes, offsets, 60.0) + + des = MetricThreadGenerator.Designation(10, 1.0) + threads = generator.threads(des) + + # Should have 2 threads per offset (external + internal) = 10 threads + assert len(threads) == len(offsets) * 2 From d8be7efecc079ccbbfce31b1d0280c937de6afb1 Mon Sep 17 00:00:00 2001 From: Philip <44484441+EckPhi@users.noreply.github.com> Date: Sun, 29 Mar 2026 12:47:33 +0200 Subject: [PATCH 13/31] Documents CI/CD pipeline for automated releases --- .github/CI_GUIDE.md | 206 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 206 insertions(+) create mode 100644 .github/CI_GUIDE.md diff --git a/.github/CI_GUIDE.md b/.github/CI_GUIDE.md new file mode 100644 index 0000000..2540010 --- /dev/null +++ b/.github/CI_GUIDE.md @@ -0,0 +1,206 @@ +# CI/CD Pipeline Documentation + +This document explains the GitHub Actions workflow for automatically generating and releasing XML thread profiles. + +## Overview + +The automated pipeline ensures that: +1. โœ… XML files are **never committed** to the repository +2. โœ… Every push to `main` triggers a new release +3. โœ… Tests run before each release to ensure quality +4. โœ… Releases are timestamped with UTC datetime +5. โœ… Users always have access to the latest profiles + +## Workflow Details + +**File:** `.github/workflows/generate-xml-release.yml` + +### Trigger Events + +The workflow runs on: + +```yaml +on: + push: + branches: + - main # Automatic on every push to main + workflow_dispatch: # Manual trigger via GitHub UI +``` + +### Pipeline Stages + +#### 1. Setup (automatic) +- Checkout repository +- Install Python 3.11 +- Install Poetry +- Cache dependencies + +#### 2. Generate (automatic) +- Run all tests to verify code quality +- Generate XML files using `poetry run python -m customthreads.cli` +- Place artifacts in `build/xml/` directory + +#### 3. Release (automatic) +- Create Git tag with datetime format: `v{YYYY}.{MM}.{DD}-{HHMM}` +- Create GitHub Release with metadata +- Upload XML file as downloadable artifact + +#### 4. Verify (automatic) +- Validate generated XML structure +- Ensure all tests passed + +### Versioning Scheme + +Release versions use **calendar versioning with minute precision**: + +``` +v2026.03.29-1245 + โ””โ”€ 2026: Year + โ””โ”€ 03: Month (March) + โ””โ”€ 29: Day + โ””โ”€ 1245: Time in UTC (12:45 UTC) +``` + +**Advantages:** +- Easy to see when profiles were generated +- No manual version management needed +- Chronological ordering in release history +- Preserves full timestamp accuracy + +**Examples:** +- `v2026.03.29-0000` = March 29, 2026 at midnight UTC +- `v2026.03.29-1200` = March 29, 2026 at noon UTC +- `v2026.12.31-2359` = December 31, 2026 at 23:59 UTC + +## Using Releases + +### Download Options + +1. **GitHub UI (Recommended)** + - Visit Repository โ†’ Releases page + - Click on latest release + - Download `3DPrintedMetricThreads.xml` + +2. **Command Line** + ```bash + # Get latest release URL + curl -s https://api.github.com/repos/USERNAME/REPO/releases/latest | \ + grep "browser_download_url" | \ + grep ".xml" | \ + cut -d '"' -f 4 | \ + xargs wget -O + ``` + +3. **Direct URL (latest)** + ``` + https://github.com/USERNAME/REPO/releases/latest/download/3DPrintedMetricThreads.xml + ``` + +### Installation + +After downloading, place the XML file in your Fusion 360 thread data directory (see README.md for paths). + +## Customizing the Workflow + +### Change Release Trigger + +To manually trigger releases instead of automatic: + +Edit `.github/workflows/generate-xml-release.yml`: +```yaml +on: + workflow_dispatch: # Manual trigger only (remove 'push' section) +``` + +Then manually trigger from GitHub UI: +1. Go to Actions โ†’ "Generate and Release XML Thread Profiles" +2. Click "Run workflow" +3. Select branch and click "Run workflow" + +### Change Release Schedule + +To generate profiles on a schedule (e.g., weekly), add: + +```yaml +on: + schedule: + - cron: '0 0 * * 0' # Every Sunday at midnight UTC + workflow_dispatch: # Plus manual trigger +``` + +### Customize Generated Profiles + +Edit `src/customthreads/cli.py` configuration section: + +```python +pitch_start = 1.0 # Starting pitch in mm +pitch_end = 6.0 # Ending pitch in mm +pitch_step = 0.5 # Pitch interval in mm +thread_sizes = list(range(8, 51)) # Diameters 8-50mm +tolerance_offsets = [0.0, 0.1, 0.2, 0.4, 0.8] # Tolerance classes +``` + +Then commit and push to trigger a new release with your configuration. + +### Modify Release Notes + +Edit the `body` section of `create-release` step: + +```yaml +- name: Create Release + with: + body: | + Your custom release notes here... + - Item 1 + - Item 2 +``` + +## Troubleshooting + +### Release Not Created + +1. Check "Actions" tab for workflow errors +2. Verify branch is `main` (not a feature branch) +3. Ensure `pyproject.toml` is present +4. Check Poetry dependencies are correct + +### XML File Invalid + +1. Run tests locally: `poetry run pytest -v` +2. Generate locally: `poetry run python -m customthreads.cli` +3. Validate XML: `python3 -c "import xml.etree.ElementTree as ET; ET.parse('file.xml')"` + +### Workflow Not Triggering + +1. Verify `.github/workflows/generate-xml-release.yml` is in `main` branch +2. Check repository settings โ†’ Actions permissions are enabled +3. Ensure your GitHub token has write access to releases + +## Security Considerations + +- โœ… Uses only official actions (`actions/checkout`, `actions/setup-python`) +- โœ… Read-only access to repository except for release creation +- โœ… `secrets.GITHUB_TOKEN` automatically provided by GitHub +- โœ… No external secrets or credentials needed + +## Performance + +- **Runtime:** ~2-3 minutes per release +- **Test Duration:** ~3 seconds +- **XML Generation:** <1 second +- **Total Including Setup:** ~2 minutes + +## Future Enhancements + +Potential improvements: +- Generate multiple XML variants (different pitch ranges, sizes) +- Create changelog from commit messages +- Upload to GitHub Pages as well +- Multi-architecture testing (Windows, macOS, Linux) +- Code coverage reports + +## References + +- [GitHub Actions Documentation](https://docs.github.com/en/actions) +- [Workflow Syntax](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions) +- [Poetry CI/CD Integration](https://python-poetry.org/docs/ci-cd/) From f57910698cb83e0c9db670e60208a7a8fd192a41 Mon Sep 17 00:00:00 2001 From: Philip <44484441+EckPhi@users.noreply.github.com> Date: Sun, 29 Mar 2026 12:47:33 +0200 Subject: [PATCH 14/31] Removes outdated XML output files from repository --- 3DPrintedMetric.xml | 7017 ---------------------------------------- 3DPrintedMetricV2.xml | 7017 ---------------------------------------- 3DPrintedMetricV3.xml | 7018 ----------------------------------------- 3 files changed, 21052 deletions(-) delete mode 100644 3DPrintedMetric.xml delete mode 100644 3DPrintedMetricV2.xml delete mode 100644 3DPrintedMetricV3.xml diff --git a/3DPrintedMetric.xml b/3DPrintedMetric.xml deleted file mode 100644 index 061fc5e..0000000 --- a/3DPrintedMetric.xml +++ /dev/null @@ -1,7017 +0,0 @@ - - - 3D-printed Metric Threads - 3D-printed Metric Threads - mm - 60.0 - 3 - - 8 - - M8x3.5 - M8x3.5 - 3.5 - - external - O.0 - 7.75 - 5.55 - 3.7 - - - internal - O.0 - 8.4 - 5.85 - 4.5 - 4.5 - - - external - O.1 - 7.65 - 5.45 - 3.6 - - - internal - O.1 - 8.5 - 5.95 - 4.6 - 4.6 - - - external - O.2 - 7.55 - 5.35 - 3.5 - - - internal - O.2 - 8.6 - 6.05 - 4.7 - 4.7 - - - external - O.4 - 7.35 - 5.15 - 3.3 - - - internal - O.4 - 8.8 - 6.25 - 4.9 - 4.9 - - - external - O.8 - 6.95 - 4.75 - 2.9 - - - internal - O.8 - 9.2 - 6.65 - 5.3 - 5.3 - - - - M8x5 - M8x5 - 5.0 - - external - O.0 - 7.75 - 4.8 - 2.2 - - - internal - O.0 - 8.4 - 5.1 - 3 - 3 - - - external - O.1 - 7.65 - 4.7 - 2.1 - - - internal - O.1 - 8.5 - 5.2 - 3.1 - 3.1 - - - external - O.2 - 7.55 - 4.6 - 2 - - - internal - O.2 - 8.6 - 5.3 - 3.2 - 3.2 - - - external - O.4 - 7.35 - 4.4 - 1.8 - - - internal - O.4 - 8.8 - 5.5 - 3.4 - 3.4 - - - external - O.8 - 6.95 - 4 - 1.4 - - - internal - O.8 - 9.2 - 5.9 - 3.8 - 3.8 - - - - - 9 - - M9x3.5 - M9x3.5 - 3.5 - - external - O.0 - 8.75 - 6.55 - 4.7 - - - internal - O.0 - 9.4 - 6.85 - 5.5 - 5.5 - - - external - O.1 - 8.65 - 6.45 - 4.6 - - - internal - O.1 - 9.5 - 6.95 - 5.6 - 5.6 - - - external - O.2 - 8.55 - 6.35 - 4.5 - - - internal - O.2 - 9.6 - 7.05 - 5.7 - 5.7 - - - external - O.4 - 8.35 - 6.15 - 4.3 - - - internal - O.4 - 9.8 - 7.25 - 5.9 - 5.9 - - - external - O.8 - 7.95 - 5.75 - 3.9 - - - internal - O.8 - 10.2 - 7.65 - 6.3 - 6.3 - - - - M9x5 - M9x5 - 5.0 - - external - O.0 - 8.75 - 5.8 - 3.2 - - - internal - O.0 - 9.4 - 6.1 - 4 - 4 - - - external - O.1 - 8.65 - 5.7 - 3.1 - - - internal - O.1 - 9.5 - 6.2 - 4.1 - 4.1 - - - external - O.2 - 8.55 - 5.6 - 3 - - - internal - O.2 - 9.6 - 6.3 - 4.2 - 4.2 - - - external - O.4 - 8.35 - 5.4 - 2.8 - - - internal - O.4 - 9.8 - 6.5 - 4.4 - 4.4 - - - external - O.8 - 7.95 - 5 - 2.4 - - - internal - O.8 - 10.2 - 6.9 - 4.8 - 4.8 - - - - - 10 - - M10x3.5 - M10x3.5 - 3.5 - - external - O.0 - 9.75 - 7.55 - 5.7 - - - internal - O.0 - 10.4 - 7.85 - 6.5 - 6.5 - - - external - O.1 - 9.65 - 7.45 - 5.6 - - - internal - O.1 - 10.5 - 7.95 - 6.6 - 6.6 - - - external - O.2 - 9.55 - 7.35 - 5.5 - - - internal - O.2 - 10.6 - 8.05 - 6.7 - 6.7 - - - external - O.4 - 9.35 - 7.15 - 5.3 - - - internal - O.4 - 10.8 - 8.25 - 6.9 - 6.9 - - - external - O.8 - 8.95 - 6.75 - 4.9 - - - internal - O.8 - 11.2 - 8.65 - 7.3 - 7.3 - - - - M10x5 - M10x5 - 5.0 - - external - O.0 - 9.75 - 6.8 - 4.2 - - - internal - O.0 - 10.4 - 7.1 - 5 - 5 - - - external - O.1 - 9.65 - 6.7 - 4.1 - - - internal - O.1 - 10.5 - 7.2 - 5.1 - 5.1 - - - external - O.2 - 9.55 - 6.6 - 4 - - - internal - O.2 - 10.6 - 7.3 - 5.2 - 5.2 - - - external - O.4 - 9.35 - 6.4 - 3.8 - - - internal - O.4 - 10.8 - 7.5 - 5.4 - 5.4 - - - external - O.8 - 8.95 - 6 - 3.4 - - - internal - O.8 - 11.2 - 7.9 - 5.8 - 5.8 - - - - - 11 - - M11x3.5 - M11x3.5 - 3.5 - - external - O.0 - 10.75 - 8.55 - 6.7 - - - internal - O.0 - 11.4 - 8.85 - 7.5 - 7.5 - - - external - O.1 - 10.65 - 8.45 - 6.6 - - - internal - O.1 - 11.5 - 8.95 - 7.6 - 7.6 - - - external - O.2 - 10.55 - 8.35 - 6.5 - - - internal - O.2 - 11.6 - 9.05 - 7.7 - 7.7 - - - external - O.4 - 10.35 - 8.15 - 6.3 - - - internal - O.4 - 11.8 - 9.25 - 7.9 - 7.9 - - - external - O.8 - 9.95 - 7.75 - 5.9 - - - internal - O.8 - 12.2 - 9.65 - 8.3 - 8.3 - - - - M11x5 - M11x5 - 5.0 - - external - O.0 - 10.75 - 7.8 - 5.2 - - - internal - O.0 - 11.4 - 8.1 - 6 - 6 - - - external - O.1 - 10.65 - 7.7 - 5.1 - - - internal - O.1 - 11.5 - 8.2 - 6.1 - 6.1 - - - external - O.2 - 10.55 - 7.6 - 5 - - - internal - O.2 - 11.6 - 8.3 - 6.2 - 6.2 - - - external - O.4 - 10.35 - 7.4 - 4.8 - - - internal - O.4 - 11.8 - 8.5 - 6.4 - 6.4 - - - external - O.8 - 9.95 - 7 - 4.4 - - - internal - O.8 - 12.2 - 8.9 - 6.8 - 6.8 - - - - - 12 - - M12x3.5 - M12x3.5 - 3.5 - - external - O.0 - 11.75 - 9.55 - 7.7 - - - internal - O.0 - 12.4 - 9.85 - 8.5 - 8.5 - - - external - O.1 - 11.65 - 9.45 - 7.6 - - - internal - O.1 - 12.5 - 9.95 - 8.6 - 8.6 - - - external - O.2 - 11.55 - 9.35 - 7.5 - - - internal - O.2 - 12.6 - 10.05 - 8.7 - 8.7 - - - external - O.4 - 11.35 - 9.15 - 7.3 - - - internal - O.4 - 12.8 - 10.25 - 8.9 - 8.9 - - - external - O.8 - 10.95 - 8.75 - 6.9 - - - internal - O.8 - 13.2 - 10.65 - 9.3 - 9.3 - - - - M12x5 - M12x5 - 5.0 - - external - O.0 - 11.75 - 8.8 - 6.2 - - - internal - O.0 - 12.4 - 9.1 - 7 - 7 - - - external - O.1 - 11.65 - 8.7 - 6.1 - - - internal - O.1 - 12.5 - 9.2 - 7.1 - 7.1 - - - external - O.2 - 11.55 - 8.6 - 6 - - - internal - O.2 - 12.6 - 9.3 - 7.2 - 7.2 - - - external - O.4 - 11.35 - 8.4 - 5.8 - - - internal - O.4 - 12.8 - 9.5 - 7.4 - 7.4 - - - external - O.8 - 10.95 - 8 - 5.4 - - - internal - O.8 - 13.2 - 9.9 - 7.8 - 7.8 - - - - - 13 - - M13x3.5 - M13x3.5 - 3.5 - - external - O.0 - 12.75 - 10.55 - 8.7 - - - internal - O.0 - 13.4 - 10.85 - 9.5 - 9.5 - - - external - O.1 - 12.65 - 10.45 - 8.6 - - - internal - O.1 - 13.5 - 10.95 - 9.6 - 9.6 - - - external - O.2 - 12.55 - 10.35 - 8.5 - - - internal - O.2 - 13.6 - 11.05 - 9.7 - 9.7 - - - external - O.4 - 12.35 - 10.15 - 8.3 - - - internal - O.4 - 13.8 - 11.25 - 9.9 - 9.9 - - - external - O.8 - 11.95 - 9.75 - 7.9 - - - internal - O.8 - 14.2 - 11.65 - 10.3 - 10.3 - - - - M13x5 - M13x5 - 5.0 - - external - O.0 - 12.75 - 9.8 - 7.2 - - - internal - O.0 - 13.4 - 10.1 - 8 - 8 - - - external - O.1 - 12.65 - 9.7 - 7.1 - - - internal - O.1 - 13.5 - 10.2 - 8.1 - 8.1 - - - external - O.2 - 12.55 - 9.6 - 7 - - - internal - O.2 - 13.6 - 10.3 - 8.2 - 8.2 - - - external - O.4 - 12.35 - 9.4 - 6.8 - - - internal - O.4 - 13.8 - 10.5 - 8.4 - 8.4 - - - external - O.8 - 11.95 - 9 - 6.4 - - - internal - O.8 - 14.2 - 10.9 - 8.8 - 8.8 - - - - - 14 - - M14x3.5 - M14x3.5 - 3.5 - - external - O.0 - 13.75 - 11.55 - 9.7 - - - internal - O.0 - 14.4 - 11.85 - 10.5 - 10.5 - - - external - O.1 - 13.65 - 11.45 - 9.6 - - - internal - O.1 - 14.5 - 11.95 - 10.6 - 10.6 - - - external - O.2 - 13.55 - 11.35 - 9.5 - - - internal - O.2 - 14.6 - 12.05 - 10.7 - 10.7 - - - external - O.4 - 13.35 - 11.15 - 9.3 - - - internal - O.4 - 14.8 - 12.25 - 10.9 - 10.9 - - - external - O.8 - 12.95 - 10.75 - 8.9 - - - internal - O.8 - 15.2 - 12.65 - 11.3 - 11.3 - - - - M14x5 - M14x5 - 5.0 - - external - O.0 - 13.75 - 10.8 - 8.2 - - - internal - O.0 - 14.4 - 11.1 - 9 - 9 - - - external - O.1 - 13.65 - 10.7 - 8.1 - - - internal - O.1 - 14.5 - 11.2 - 9.1 - 9.1 - - - external - O.2 - 13.55 - 10.6 - 8 - - - internal - O.2 - 14.6 - 11.3 - 9.2 - 9.2 - - - external - O.4 - 13.35 - 10.4 - 7.8 - - - internal - O.4 - 14.8 - 11.5 - 9.4 - 9.4 - - - external - O.8 - 12.95 - 10 - 7.4 - - - internal - O.8 - 15.2 - 11.9 - 9.8 - 9.8 - - - - - 15 - - M15x3.5 - M15x3.5 - 3.5 - - external - O.0 - 14.75 - 12.55 - 10.7 - - - internal - O.0 - 15.4 - 12.85 - 11.5 - 11.5 - - - external - O.1 - 14.65 - 12.45 - 10.6 - - - internal - O.1 - 15.5 - 12.95 - 11.6 - 11.6 - - - external - O.2 - 14.55 - 12.35 - 10.5 - - - internal - O.2 - 15.6 - 13.05 - 11.7 - 11.7 - - - external - O.4 - 14.35 - 12.15 - 10.3 - - - internal - O.4 - 15.8 - 13.25 - 11.9 - 11.9 - - - external - O.8 - 13.95 - 11.75 - 9.9 - - - internal - O.8 - 16.2 - 13.65 - 12.3 - 12.3 - - - - M15x5 - M15x5 - 5.0 - - external - O.0 - 14.75 - 11.8 - 9.2 - - - internal - O.0 - 15.4 - 12.1 - 10 - 10 - - - external - O.1 - 14.65 - 11.7 - 9.1 - - - internal - O.1 - 15.5 - 12.2 - 10.1 - 10.1 - - - external - O.2 - 14.55 - 11.6 - 9 - - - internal - O.2 - 15.6 - 12.3 - 10.2 - 10.2 - - - external - O.4 - 14.35 - 11.4 - 8.8 - - - internal - O.4 - 15.8 - 12.5 - 10.4 - 10.4 - - - external - O.8 - 13.95 - 11 - 8.4 - - - internal - O.8 - 16.2 - 12.9 - 10.8 - 10.8 - - - - - 16 - - M16x3.5 - M16x3.5 - 3.5 - - external - O.0 - 15.75 - 13.55 - 11.7 - - - internal - O.0 - 16.4 - 13.85 - 12.5 - 12.5 - - - external - O.1 - 15.65 - 13.45 - 11.6 - - - internal - O.1 - 16.5 - 13.95 - 12.6 - 12.6 - - - external - O.2 - 15.55 - 13.35 - 11.5 - - - internal - O.2 - 16.6 - 14.05 - 12.7 - 12.7 - - - external - O.4 - 15.35 - 13.15 - 11.3 - - - internal - O.4 - 16.8 - 14.25 - 12.9 - 12.9 - - - external - O.8 - 14.95 - 12.75 - 10.9 - - - internal - O.8 - 17.2 - 14.65 - 13.3 - 13.3 - - - - M16x5 - M16x5 - 5.0 - - external - O.0 - 15.75 - 12.8 - 10.2 - - - internal - O.0 - 16.4 - 13.1 - 11 - 11 - - - external - O.1 - 15.65 - 12.7 - 10.1 - - - internal - O.1 - 16.5 - 13.2 - 11.1 - 11.1 - - - external - O.2 - 15.55 - 12.6 - 10 - - - internal - O.2 - 16.6 - 13.3 - 11.2 - 11.2 - - - external - O.4 - 15.35 - 12.4 - 9.8 - - - internal - O.4 - 16.8 - 13.5 - 11.4 - 11.4 - - - external - O.8 - 14.95 - 12 - 9.4 - - - internal - O.8 - 17.2 - 13.9 - 11.8 - 11.8 - - - - - 17 - - M17x3.5 - M17x3.5 - 3.5 - - external - O.0 - 16.75 - 14.55 - 12.7 - - - internal - O.0 - 17.4 - 14.85 - 13.5 - 13.5 - - - external - O.1 - 16.65 - 14.45 - 12.6 - - - internal - O.1 - 17.5 - 14.95 - 13.6 - 13.6 - - - external - O.2 - 16.55 - 14.35 - 12.5 - - - internal - O.2 - 17.6 - 15.05 - 13.7 - 13.7 - - - external - O.4 - 16.35 - 14.15 - 12.3 - - - internal - O.4 - 17.8 - 15.25 - 13.9 - 13.9 - - - external - O.8 - 15.95 - 13.75 - 11.9 - - - internal - O.8 - 18.2 - 15.65 - 14.3 - 14.3 - - - - M17x5 - M17x5 - 5.0 - - external - O.0 - 16.75 - 13.8 - 11.2 - - - internal - O.0 - 17.4 - 14.1 - 12 - 12 - - - external - O.1 - 16.65 - 13.7 - 11.1 - - - internal - O.1 - 17.5 - 14.2 - 12.1 - 12.1 - - - external - O.2 - 16.55 - 13.6 - 11 - - - internal - O.2 - 17.6 - 14.3 - 12.2 - 12.2 - - - external - O.4 - 16.35 - 13.4 - 10.8 - - - internal - O.4 - 17.8 - 14.5 - 12.4 - 12.4 - - - external - O.8 - 15.95 - 13 - 10.4 - - - internal - O.8 - 18.2 - 14.9 - 12.8 - 12.8 - - - - - 18 - - M18x3.5 - M18x3.5 - 3.5 - - external - O.0 - 17.75 - 15.55 - 13.7 - - - internal - O.0 - 18.4 - 15.85 - 14.5 - 14.5 - - - external - O.1 - 17.65 - 15.45 - 13.6 - - - internal - O.1 - 18.5 - 15.95 - 14.6 - 14.6 - - - external - O.2 - 17.55 - 15.35 - 13.5 - - - internal - O.2 - 18.6 - 16.05 - 14.7 - 14.7 - - - external - O.4 - 17.35 - 15.15 - 13.3 - - - internal - O.4 - 18.8 - 16.25 - 14.9 - 14.9 - - - external - O.8 - 16.95 - 14.75 - 12.9 - - - internal - O.8 - 19.2 - 16.65 - 15.3 - 15.3 - - - - M18x5 - M18x5 - 5.0 - - external - O.0 - 17.75 - 14.8 - 12.2 - - - internal - O.0 - 18.4 - 15.1 - 13 - 13 - - - external - O.1 - 17.65 - 14.7 - 12.1 - - - internal - O.1 - 18.5 - 15.2 - 13.1 - 13.1 - - - external - O.2 - 17.55 - 14.6 - 12 - - - internal - O.2 - 18.6 - 15.3 - 13.2 - 13.2 - - - external - O.4 - 17.35 - 14.4 - 11.8 - - - internal - O.4 - 18.8 - 15.5 - 13.4 - 13.4 - - - external - O.8 - 16.95 - 14 - 11.4 - - - internal - O.8 - 19.2 - 15.9 - 13.8 - 13.8 - - - - - 19 - - M19x3.5 - M19x3.5 - 3.5 - - external - O.0 - 18.75 - 16.55 - 14.7 - - - internal - O.0 - 19.4 - 16.85 - 15.5 - 15.5 - - - external - O.1 - 18.65 - 16.45 - 14.6 - - - internal - O.1 - 19.5 - 16.95 - 15.6 - 15.6 - - - external - O.2 - 18.55 - 16.35 - 14.5 - - - internal - O.2 - 19.6 - 17.05 - 15.7 - 15.7 - - - external - O.4 - 18.35 - 16.15 - 14.3 - - - internal - O.4 - 19.8 - 17.25 - 15.9 - 15.9 - - - external - O.8 - 17.95 - 15.75 - 13.9 - - - internal - O.8 - 20.2 - 17.65 - 16.3 - 16.3 - - - - M19x5 - M19x5 - 5.0 - - external - O.0 - 18.75 - 15.8 - 13.2 - - - internal - O.0 - 19.4 - 16.1 - 14 - 14 - - - external - O.1 - 18.65 - 15.7 - 13.1 - - - internal - O.1 - 19.5 - 16.2 - 14.1 - 14.1 - - - external - O.2 - 18.55 - 15.6 - 13 - - - internal - O.2 - 19.6 - 16.3 - 14.2 - 14.2 - - - external - O.4 - 18.35 - 15.4 - 12.8 - - - internal - O.4 - 19.8 - 16.5 - 14.4 - 14.4 - - - external - O.8 - 17.95 - 15 - 12.4 - - - internal - O.8 - 20.2 - 16.9 - 14.8 - 14.8 - - - - - 20 - - M20x3.5 - M20x3.5 - 3.5 - - external - O.0 - 19.75 - 17.55 - 15.7 - - - internal - O.0 - 20.4 - 17.85 - 16.5 - 16.5 - - - external - O.1 - 19.65 - 17.45 - 15.6 - - - internal - O.1 - 20.5 - 17.95 - 16.6 - 16.6 - - - external - O.2 - 19.55 - 17.35 - 15.5 - - - internal - O.2 - 20.6 - 18.05 - 16.7 - 16.7 - - - external - O.4 - 19.35 - 17.15 - 15.3 - - - internal - O.4 - 20.8 - 18.25 - 16.9 - 16.9 - - - external - O.8 - 18.95 - 16.75 - 14.9 - - - internal - O.8 - 21.2 - 18.65 - 17.3 - 17.3 - - - - M20x5 - M20x5 - 5.0 - - external - O.0 - 19.75 - 16.8 - 14.2 - - - internal - O.0 - 20.4 - 17.1 - 15 - 15 - - - external - O.1 - 19.65 - 16.7 - 14.1 - - - internal - O.1 - 20.5 - 17.2 - 15.1 - 15.1 - - - external - O.2 - 19.55 - 16.6 - 14 - - - internal - O.2 - 20.6 - 17.3 - 15.2 - 15.2 - - - external - O.4 - 19.35 - 16.4 - 13.8 - - - internal - O.4 - 20.8 - 17.5 - 15.4 - 15.4 - - - external - O.8 - 18.95 - 16 - 13.4 - - - internal - O.8 - 21.2 - 17.9 - 15.8 - 15.8 - - - - - 21 - - M21x3.5 - M21x3.5 - 3.5 - - external - O.0 - 20.75 - 18.55 - 16.7 - - - internal - O.0 - 21.4 - 18.85 - 17.5 - 17.5 - - - external - O.1 - 20.65 - 18.45 - 16.6 - - - internal - O.1 - 21.5 - 18.95 - 17.6 - 17.6 - - - external - O.2 - 20.55 - 18.35 - 16.5 - - - internal - O.2 - 21.6 - 19.05 - 17.7 - 17.7 - - - external - O.4 - 20.35 - 18.15 - 16.3 - - - internal - O.4 - 21.8 - 19.25 - 17.9 - 17.9 - - - external - O.8 - 19.95 - 17.75 - 15.9 - - - internal - O.8 - 22.2 - 19.65 - 18.3 - 18.3 - - - - M21x5 - M21x5 - 5.0 - - external - O.0 - 20.75 - 17.8 - 15.2 - - - internal - O.0 - 21.4 - 18.1 - 16 - 16 - - - external - O.1 - 20.65 - 17.7 - 15.1 - - - internal - O.1 - 21.5 - 18.2 - 16.1 - 16.1 - - - external - O.2 - 20.55 - 17.6 - 15 - - - internal - O.2 - 21.6 - 18.3 - 16.2 - 16.2 - - - external - O.4 - 20.35 - 17.4 - 14.8 - - - internal - O.4 - 21.8 - 18.5 - 16.4 - 16.4 - - - external - O.8 - 19.95 - 17 - 14.4 - - - internal - O.8 - 22.2 - 18.9 - 16.8 - 16.8 - - - - - 22 - - M22x3.5 - M22x3.5 - 3.5 - - external - O.0 - 21.75 - 19.55 - 17.7 - - - internal - O.0 - 22.4 - 19.85 - 18.5 - 18.5 - - - external - O.1 - 21.65 - 19.45 - 17.6 - - - internal - O.1 - 22.5 - 19.95 - 18.6 - 18.6 - - - external - O.2 - 21.55 - 19.35 - 17.5 - - - internal - O.2 - 22.6 - 20.05 - 18.7 - 18.7 - - - external - O.4 - 21.35 - 19.15 - 17.3 - - - internal - O.4 - 22.8 - 20.25 - 18.9 - 18.9 - - - external - O.8 - 20.95 - 18.75 - 16.9 - - - internal - O.8 - 23.2 - 20.65 - 19.3 - 19.3 - - - - M22x5 - M22x5 - 5.0 - - external - O.0 - 21.75 - 18.8 - 16.2 - - - internal - O.0 - 22.4 - 19.1 - 17 - 17 - - - external - O.1 - 21.65 - 18.7 - 16.1 - - - internal - O.1 - 22.5 - 19.2 - 17.1 - 17.1 - - - external - O.2 - 21.55 - 18.6 - 16 - - - internal - O.2 - 22.6 - 19.3 - 17.2 - 17.2 - - - external - O.4 - 21.35 - 18.4 - 15.8 - - - internal - O.4 - 22.8 - 19.5 - 17.4 - 17.4 - - - external - O.8 - 20.95 - 18 - 15.4 - - - internal - O.8 - 23.2 - 19.9 - 17.8 - 17.8 - - - - - 23 - - M23x3.5 - M23x3.5 - 3.5 - - external - O.0 - 22.75 - 20.55 - 18.7 - - - internal - O.0 - 23.4 - 20.85 - 19.5 - 19.5 - - - external - O.1 - 22.65 - 20.45 - 18.6 - - - internal - O.1 - 23.5 - 20.95 - 19.6 - 19.6 - - - external - O.2 - 22.55 - 20.35 - 18.5 - - - internal - O.2 - 23.6 - 21.05 - 19.7 - 19.7 - - - external - O.4 - 22.35 - 20.15 - 18.3 - - - internal - O.4 - 23.8 - 21.25 - 19.9 - 19.9 - - - external - O.8 - 21.95 - 19.75 - 17.9 - - - internal - O.8 - 24.2 - 21.65 - 20.3 - 20.3 - - - - M23x5 - M23x5 - 5.0 - - external - O.0 - 22.75 - 19.8 - 17.2 - - - internal - O.0 - 23.4 - 20.1 - 18 - 18 - - - external - O.1 - 22.65 - 19.7 - 17.1 - - - internal - O.1 - 23.5 - 20.2 - 18.1 - 18.1 - - - external - O.2 - 22.55 - 19.6 - 17 - - - internal - O.2 - 23.6 - 20.3 - 18.2 - 18.2 - - - external - O.4 - 22.35 - 19.4 - 16.8 - - - internal - O.4 - 23.8 - 20.5 - 18.4 - 18.4 - - - external - O.8 - 21.95 - 19 - 16.4 - - - internal - O.8 - 24.2 - 20.9 - 18.8 - 18.8 - - - - - 24 - - M24x3.5 - M24x3.5 - 3.5 - - external - O.0 - 23.75 - 21.55 - 19.7 - - - internal - O.0 - 24.4 - 21.85 - 20.5 - 20.5 - - - external - O.1 - 23.65 - 21.45 - 19.6 - - - internal - O.1 - 24.5 - 21.95 - 20.6 - 20.6 - - - external - O.2 - 23.55 - 21.35 - 19.5 - - - internal - O.2 - 24.6 - 22.05 - 20.7 - 20.7 - - - external - O.4 - 23.35 - 21.15 - 19.3 - - - internal - O.4 - 24.8 - 22.25 - 20.9 - 20.9 - - - external - O.8 - 22.95 - 20.75 - 18.9 - - - internal - O.8 - 25.2 - 22.65 - 21.3 - 21.3 - - - - M24x5 - M24x5 - 5.0 - - external - O.0 - 23.75 - 20.8 - 18.2 - - - internal - O.0 - 24.4 - 21.1 - 19 - 19 - - - external - O.1 - 23.65 - 20.7 - 18.1 - - - internal - O.1 - 24.5 - 21.2 - 19.1 - 19.1 - - - external - O.2 - 23.55 - 20.6 - 18 - - - internal - O.2 - 24.6 - 21.3 - 19.2 - 19.2 - - - external - O.4 - 23.35 - 20.4 - 17.8 - - - internal - O.4 - 24.8 - 21.5 - 19.4 - 19.4 - - - external - O.8 - 22.95 - 20 - 17.4 - - - internal - O.8 - 25.2 - 21.9 - 19.8 - 19.8 - - - - - 25 - - M25x3.5 - M25x3.5 - 3.5 - - external - O.0 - 24.75 - 22.55 - 20.7 - - - internal - O.0 - 25.4 - 22.85 - 21.5 - 21.5 - - - external - O.1 - 24.65 - 22.45 - 20.6 - - - internal - O.1 - 25.5 - 22.95 - 21.6 - 21.6 - - - external - O.2 - 24.55 - 22.35 - 20.5 - - - internal - O.2 - 25.6 - 23.05 - 21.7 - 21.7 - - - external - O.4 - 24.35 - 22.15 - 20.3 - - - internal - O.4 - 25.8 - 23.25 - 21.9 - 21.9 - - - external - O.8 - 23.95 - 21.75 - 19.9 - - - internal - O.8 - 26.2 - 23.65 - 22.3 - 22.3 - - - - M25x5 - M25x5 - 5.0 - - external - O.0 - 24.75 - 21.8 - 19.2 - - - internal - O.0 - 25.4 - 22.1 - 20 - 20 - - - external - O.1 - 24.65 - 21.7 - 19.1 - - - internal - O.1 - 25.5 - 22.2 - 20.1 - 20.1 - - - external - O.2 - 24.55 - 21.6 - 19 - - - internal - O.2 - 25.6 - 22.3 - 20.2 - 20.2 - - - external - O.4 - 24.35 - 21.4 - 18.8 - - - internal - O.4 - 25.8 - 22.5 - 20.4 - 20.4 - - - external - O.8 - 23.95 - 21 - 18.4 - - - internal - O.8 - 26.2 - 22.9 - 20.8 - 20.8 - - - - - 26 - - M26x3.5 - M26x3.5 - 3.5 - - external - O.0 - 25.75 - 23.55 - 21.7 - - - internal - O.0 - 26.4 - 23.85 - 22.5 - 22.5 - - - external - O.1 - 25.65 - 23.45 - 21.6 - - - internal - O.1 - 26.5 - 23.95 - 22.6 - 22.6 - - - external - O.2 - 25.55 - 23.35 - 21.5 - - - internal - O.2 - 26.6 - 24.05 - 22.7 - 22.7 - - - external - O.4 - 25.35 - 23.15 - 21.3 - - - internal - O.4 - 26.8 - 24.25 - 22.9 - 22.9 - - - external - O.8 - 24.95 - 22.75 - 20.9 - - - internal - O.8 - 27.2 - 24.65 - 23.3 - 23.3 - - - - M26x5 - M26x5 - 5.0 - - external - O.0 - 25.75 - 22.8 - 20.2 - - - internal - O.0 - 26.4 - 23.1 - 21 - 21 - - - external - O.1 - 25.65 - 22.7 - 20.1 - - - internal - O.1 - 26.5 - 23.2 - 21.1 - 21.1 - - - external - O.2 - 25.55 - 22.6 - 20 - - - internal - O.2 - 26.6 - 23.3 - 21.2 - 21.2 - - - external - O.4 - 25.35 - 22.4 - 19.8 - - - internal - O.4 - 26.8 - 23.5 - 21.4 - 21.4 - - - external - O.8 - 24.95 - 22 - 19.4 - - - internal - O.8 - 27.2 - 23.9 - 21.8 - 21.8 - - - - - 27 - - M27x3.5 - M27x3.5 - 3.5 - - external - O.0 - 26.75 - 24.55 - 22.7 - - - internal - O.0 - 27.4 - 24.85 - 23.5 - 23.5 - - - external - O.1 - 26.65 - 24.45 - 22.6 - - - internal - O.1 - 27.5 - 24.95 - 23.6 - 23.6 - - - external - O.2 - 26.55 - 24.35 - 22.5 - - - internal - O.2 - 27.6 - 25.05 - 23.7 - 23.7 - - - external - O.4 - 26.35 - 24.15 - 22.3 - - - internal - O.4 - 27.8 - 25.25 - 23.9 - 23.9 - - - external - O.8 - 25.95 - 23.75 - 21.9 - - - internal - O.8 - 28.2 - 25.65 - 24.3 - 24.3 - - - - M27x5 - M27x5 - 5.0 - - external - O.0 - 26.75 - 23.8 - 21.2 - - - internal - O.0 - 27.4 - 24.1 - 22 - 22 - - - external - O.1 - 26.65 - 23.7 - 21.1 - - - internal - O.1 - 27.5 - 24.2 - 22.1 - 22.1 - - - external - O.2 - 26.55 - 23.6 - 21 - - - internal - O.2 - 27.6 - 24.3 - 22.2 - 22.2 - - - external - O.4 - 26.35 - 23.4 - 20.8 - - - internal - O.4 - 27.8 - 24.5 - 22.4 - 22.4 - - - external - O.8 - 25.95 - 23 - 20.4 - - - internal - O.8 - 28.2 - 24.9 - 22.8 - 22.8 - - - - - 28 - - M28x3.5 - M28x3.5 - 3.5 - - external - O.0 - 27.75 - 25.55 - 23.7 - - - internal - O.0 - 28.4 - 25.85 - 24.5 - 24.5 - - - external - O.1 - 27.65 - 25.45 - 23.6 - - - internal - O.1 - 28.5 - 25.95 - 24.6 - 24.6 - - - external - O.2 - 27.55 - 25.35 - 23.5 - - - internal - O.2 - 28.6 - 26.05 - 24.7 - 24.7 - - - external - O.4 - 27.35 - 25.15 - 23.3 - - - internal - O.4 - 28.8 - 26.25 - 24.9 - 24.9 - - - external - O.8 - 26.95 - 24.75 - 22.9 - - - internal - O.8 - 29.2 - 26.65 - 25.3 - 25.3 - - - - M28x5 - M28x5 - 5.0 - - external - O.0 - 27.75 - 24.8 - 22.2 - - - internal - O.0 - 28.4 - 25.1 - 23 - 23 - - - external - O.1 - 27.65 - 24.7 - 22.1 - - - internal - O.1 - 28.5 - 25.2 - 23.1 - 23.1 - - - external - O.2 - 27.55 - 24.6 - 22 - - - internal - O.2 - 28.6 - 25.3 - 23.2 - 23.2 - - - external - O.4 - 27.35 - 24.4 - 21.8 - - - internal - O.4 - 28.8 - 25.5 - 23.4 - 23.4 - - - external - O.8 - 26.95 - 24 - 21.4 - - - internal - O.8 - 29.2 - 25.9 - 23.8 - 23.8 - - - - - 29 - - M29x3.5 - M29x3.5 - 3.5 - - external - O.0 - 28.75 - 26.55 - 24.7 - - - internal - O.0 - 29.4 - 26.85 - 25.5 - 25.5 - - - external - O.1 - 28.65 - 26.45 - 24.6 - - - internal - O.1 - 29.5 - 26.95 - 25.6 - 25.6 - - - external - O.2 - 28.55 - 26.35 - 24.5 - - - internal - O.2 - 29.6 - 27.05 - 25.7 - 25.7 - - - external - O.4 - 28.35 - 26.15 - 24.3 - - - internal - O.4 - 29.8 - 27.25 - 25.9 - 25.9 - - - external - O.8 - 27.95 - 25.75 - 23.9 - - - internal - O.8 - 30.2 - 27.65 - 26.3 - 26.3 - - - - M29x5 - M29x5 - 5.0 - - external - O.0 - 28.75 - 25.8 - 23.2 - - - internal - O.0 - 29.4 - 26.1 - 24 - 24 - - - external - O.1 - 28.65 - 25.7 - 23.1 - - - internal - O.1 - 29.5 - 26.2 - 24.1 - 24.1 - - - external - O.2 - 28.55 - 25.6 - 23 - - - internal - O.2 - 29.6 - 26.3 - 24.2 - 24.2 - - - external - O.4 - 28.35 - 25.4 - 22.8 - - - internal - O.4 - 29.8 - 26.5 - 24.4 - 24.4 - - - external - O.8 - 27.95 - 25 - 22.4 - - - internal - O.8 - 30.2 - 26.9 - 24.8 - 24.8 - - - - - 30 - - M30x3.5 - M30x3.5 - 3.5 - - external - O.0 - 29.75 - 27.55 - 25.7 - - - internal - O.0 - 30.4 - 27.85 - 26.5 - 26.5 - - - external - O.1 - 29.65 - 27.45 - 25.6 - - - internal - O.1 - 30.5 - 27.95 - 26.6 - 26.6 - - - external - O.2 - 29.55 - 27.35 - 25.5 - - - internal - O.2 - 30.6 - 28.05 - 26.7 - 26.7 - - - external - O.4 - 29.35 - 27.15 - 25.3 - - - internal - O.4 - 30.8 - 28.25 - 26.9 - 26.9 - - - external - O.8 - 28.95 - 26.75 - 24.9 - - - internal - O.8 - 31.2 - 28.65 - 27.3 - 27.3 - - - - M30x5 - M30x5 - 5.0 - - external - O.0 - 29.75 - 26.8 - 24.2 - - - internal - O.0 - 30.4 - 27.1 - 25 - 25 - - - external - O.1 - 29.65 - 26.7 - 24.1 - - - internal - O.1 - 30.5 - 27.2 - 25.1 - 25.1 - - - external - O.2 - 29.55 - 26.6 - 24 - - - internal - O.2 - 30.6 - 27.3 - 25.2 - 25.2 - - - external - O.4 - 29.35 - 26.4 - 23.8 - - - internal - O.4 - 30.8 - 27.5 - 25.4 - 25.4 - - - external - O.8 - 28.95 - 26 - 23.4 - - - internal - O.8 - 31.2 - 27.9 - 25.8 - 25.8 - - - - - 31 - - M31x3.5 - M31x3.5 - 3.5 - - external - O.0 - 30.75 - 28.55 - 26.7 - - - internal - O.0 - 31.4 - 28.85 - 27.5 - 27.5 - - - external - O.1 - 30.65 - 28.45 - 26.6 - - - internal - O.1 - 31.5 - 28.95 - 27.6 - 27.6 - - - external - O.2 - 30.55 - 28.35 - 26.5 - - - internal - O.2 - 31.6 - 29.05 - 27.7 - 27.7 - - - external - O.4 - 30.35 - 28.15 - 26.3 - - - internal - O.4 - 31.8 - 29.25 - 27.9 - 27.9 - - - external - O.8 - 29.95 - 27.75 - 25.9 - - - internal - O.8 - 32.2 - 29.65 - 28.3 - 28.3 - - - - M31x5 - M31x5 - 5.0 - - external - O.0 - 30.75 - 27.8 - 25.2 - - - internal - O.0 - 31.4 - 28.1 - 26 - 26 - - - external - O.1 - 30.65 - 27.7 - 25.1 - - - internal - O.1 - 31.5 - 28.2 - 26.1 - 26.1 - - - external - O.2 - 30.55 - 27.6 - 25 - - - internal - O.2 - 31.6 - 28.3 - 26.2 - 26.2 - - - external - O.4 - 30.35 - 27.4 - 24.8 - - - internal - O.4 - 31.8 - 28.5 - 26.4 - 26.4 - - - external - O.8 - 29.95 - 27 - 24.4 - - - internal - O.8 - 32.2 - 28.9 - 26.8 - 26.8 - - - - - 32 - - M32x3.5 - M32x3.5 - 3.5 - - external - O.0 - 31.75 - 29.55 - 27.7 - - - internal - O.0 - 32.4 - 29.85 - 28.5 - 28.5 - - - external - O.1 - 31.65 - 29.45 - 27.6 - - - internal - O.1 - 32.5 - 29.95 - 28.6 - 28.6 - - - external - O.2 - 31.55 - 29.35 - 27.5 - - - internal - O.2 - 32.6 - 30.05 - 28.7 - 28.7 - - - external - O.4 - 31.35 - 29.15 - 27.3 - - - internal - O.4 - 32.8 - 30.25 - 28.9 - 28.9 - - - external - O.8 - 30.95 - 28.75 - 26.9 - - - internal - O.8 - 33.2 - 30.65 - 29.3 - 29.3 - - - - M32x5 - M32x5 - 5.0 - - external - O.0 - 31.75 - 28.8 - 26.2 - - - internal - O.0 - 32.4 - 29.1 - 27 - 27 - - - external - O.1 - 31.65 - 28.7 - 26.1 - - - internal - O.1 - 32.5 - 29.2 - 27.1 - 27.1 - - - external - O.2 - 31.55 - 28.6 - 26 - - - internal - O.2 - 32.6 - 29.3 - 27.2 - 27.2 - - - external - O.4 - 31.35 - 28.4 - 25.8 - - - internal - O.4 - 32.8 - 29.5 - 27.4 - 27.4 - - - external - O.8 - 30.95 - 28 - 25.4 - - - internal - O.8 - 33.2 - 29.9 - 27.8 - 27.8 - - - - - 33 - - M33x3.5 - M33x3.5 - 3.5 - - external - O.0 - 32.75 - 30.55 - 28.7 - - - internal - O.0 - 33.4 - 30.85 - 29.5 - 29.5 - - - external - O.1 - 32.65 - 30.45 - 28.6 - - - internal - O.1 - 33.5 - 30.95 - 29.6 - 29.6 - - - external - O.2 - 32.55 - 30.35 - 28.5 - - - internal - O.2 - 33.6 - 31.05 - 29.7 - 29.7 - - - external - O.4 - 32.35 - 30.15 - 28.3 - - - internal - O.4 - 33.8 - 31.25 - 29.9 - 29.9 - - - external - O.8 - 31.95 - 29.75 - 27.9 - - - internal - O.8 - 34.2 - 31.65 - 30.3 - 30.3 - - - - M33x5 - M33x5 - 5.0 - - external - O.0 - 32.75 - 29.8 - 27.2 - - - internal - O.0 - 33.4 - 30.1 - 28 - 28 - - - external - O.1 - 32.65 - 29.7 - 27.1 - - - internal - O.1 - 33.5 - 30.2 - 28.1 - 28.1 - - - external - O.2 - 32.55 - 29.6 - 27 - - - internal - O.2 - 33.6 - 30.3 - 28.2 - 28.2 - - - external - O.4 - 32.35 - 29.4 - 26.8 - - - internal - O.4 - 33.8 - 30.5 - 28.4 - 28.4 - - - external - O.8 - 31.95 - 29 - 26.4 - - - internal - O.8 - 34.2 - 30.9 - 28.8 - 28.8 - - - - - 34 - - M34x3.5 - M34x3.5 - 3.5 - - external - O.0 - 33.75 - 31.55 - 29.7 - - - internal - O.0 - 34.4 - 31.85 - 30.5 - 30.5 - - - external - O.1 - 33.65 - 31.45 - 29.6 - - - internal - O.1 - 34.5 - 31.95 - 30.6 - 30.6 - - - external - O.2 - 33.55 - 31.35 - 29.5 - - - internal - O.2 - 34.6 - 32.05 - 30.7 - 30.7 - - - external - O.4 - 33.35 - 31.15 - 29.3 - - - internal - O.4 - 34.8 - 32.25 - 30.9 - 30.9 - - - external - O.8 - 32.95 - 30.75 - 28.9 - - - internal - O.8 - 35.2 - 32.65 - 31.3 - 31.3 - - - - M34x5 - M34x5 - 5.0 - - external - O.0 - 33.75 - 30.8 - 28.2 - - - internal - O.0 - 34.4 - 31.1 - 29 - 29 - - - external - O.1 - 33.65 - 30.7 - 28.1 - - - internal - O.1 - 34.5 - 31.2 - 29.1 - 29.1 - - - external - O.2 - 33.55 - 30.6 - 28 - - - internal - O.2 - 34.6 - 31.3 - 29.2 - 29.2 - - - external - O.4 - 33.35 - 30.4 - 27.8 - - - internal - O.4 - 34.8 - 31.5 - 29.4 - 29.4 - - - external - O.8 - 32.95 - 30 - 27.4 - - - internal - O.8 - 35.2 - 31.9 - 29.8 - 29.8 - - - - - 35 - - M35x3.5 - M35x3.5 - 3.5 - - external - O.0 - 34.75 - 32.55 - 30.7 - - - internal - O.0 - 35.4 - 32.85 - 31.5 - 31.5 - - - external - O.1 - 34.65 - 32.45 - 30.6 - - - internal - O.1 - 35.5 - 32.95 - 31.6 - 31.6 - - - external - O.2 - 34.55 - 32.35 - 30.5 - - - internal - O.2 - 35.6 - 33.05 - 31.7 - 31.7 - - - external - O.4 - 34.35 - 32.15 - 30.3 - - - internal - O.4 - 35.8 - 33.25 - 31.9 - 31.9 - - - external - O.8 - 33.95 - 31.75 - 29.9 - - - internal - O.8 - 36.2 - 33.65 - 32.3 - 32.3 - - - - M35x5 - M35x5 - 5.0 - - external - O.0 - 34.75 - 31.8 - 29.2 - - - internal - O.0 - 35.4 - 32.1 - 30 - 30 - - - external - O.1 - 34.65 - 31.7 - 29.1 - - - internal - O.1 - 35.5 - 32.2 - 30.1 - 30.1 - - - external - O.2 - 34.55 - 31.6 - 29 - - - internal - O.2 - 35.6 - 32.3 - 30.2 - 30.2 - - - external - O.4 - 34.35 - 31.4 - 28.8 - - - internal - O.4 - 35.8 - 32.5 - 30.4 - 30.4 - - - external - O.8 - 33.95 - 31 - 28.4 - - - internal - O.8 - 36.2 - 32.9 - 30.8 - 30.8 - - - - - 36 - - M36x3.5 - M36x3.5 - 3.5 - - external - O.0 - 35.75 - 33.55 - 31.7 - - - internal - O.0 - 36.4 - 33.85 - 32.5 - 32.5 - - - external - O.1 - 35.65 - 33.45 - 31.6 - - - internal - O.1 - 36.5 - 33.95 - 32.6 - 32.6 - - - external - O.2 - 35.55 - 33.35 - 31.5 - - - internal - O.2 - 36.6 - 34.05 - 32.7 - 32.7 - - - external - O.4 - 35.35 - 33.15 - 31.3 - - - internal - O.4 - 36.8 - 34.25 - 32.9 - 32.9 - - - external - O.8 - 34.95 - 32.75 - 30.9 - - - internal - O.8 - 37.2 - 34.65 - 33.3 - 33.3 - - - - M36x5 - M36x5 - 5.0 - - external - O.0 - 35.75 - 32.8 - 30.2 - - - internal - O.0 - 36.4 - 33.1 - 31 - 31 - - - external - O.1 - 35.65 - 32.7 - 30.1 - - - internal - O.1 - 36.5 - 33.2 - 31.1 - 31.1 - - - external - O.2 - 35.55 - 32.6 - 30 - - - internal - O.2 - 36.6 - 33.3 - 31.2 - 31.2 - - - external - O.4 - 35.35 - 32.4 - 29.8 - - - internal - O.4 - 36.8 - 33.5 - 31.4 - 31.4 - - - external - O.8 - 34.95 - 32 - 29.4 - - - internal - O.8 - 37.2 - 33.9 - 31.8 - 31.8 - - - - - 37 - - M37x3.5 - M37x3.5 - 3.5 - - external - O.0 - 36.75 - 34.55 - 32.7 - - - internal - O.0 - 37.4 - 34.85 - 33.5 - 33.5 - - - external - O.1 - 36.65 - 34.45 - 32.6 - - - internal - O.1 - 37.5 - 34.95 - 33.6 - 33.6 - - - external - O.2 - 36.55 - 34.35 - 32.5 - - - internal - O.2 - 37.6 - 35.05 - 33.7 - 33.7 - - - external - O.4 - 36.35 - 34.15 - 32.3 - - - internal - O.4 - 37.8 - 35.25 - 33.9 - 33.9 - - - external - O.8 - 35.95 - 33.75 - 31.9 - - - internal - O.8 - 38.2 - 35.65 - 34.3 - 34.3 - - - - M37x5 - M37x5 - 5.0 - - external - O.0 - 36.75 - 33.8 - 31.2 - - - internal - O.0 - 37.4 - 34.1 - 32 - 32 - - - external - O.1 - 36.65 - 33.7 - 31.1 - - - internal - O.1 - 37.5 - 34.2 - 32.1 - 32.1 - - - external - O.2 - 36.55 - 33.6 - 31 - - - internal - O.2 - 37.6 - 34.3 - 32.2 - 32.2 - - - external - O.4 - 36.35 - 33.4 - 30.8 - - - internal - O.4 - 37.8 - 34.5 - 32.4 - 32.4 - - - external - O.8 - 35.95 - 33 - 30.4 - - - internal - O.8 - 38.2 - 34.9 - 32.8 - 32.8 - - - - - 38 - - M38x3.5 - M38x3.5 - 3.5 - - external - O.0 - 37.75 - 35.55 - 33.7 - - - internal - O.0 - 38.4 - 35.85 - 34.5 - 34.5 - - - external - O.1 - 37.65 - 35.45 - 33.6 - - - internal - O.1 - 38.5 - 35.95 - 34.6 - 34.6 - - - external - O.2 - 37.55 - 35.35 - 33.5 - - - internal - O.2 - 38.6 - 36.05 - 34.7 - 34.7 - - - external - O.4 - 37.35 - 35.15 - 33.3 - - - internal - O.4 - 38.8 - 36.25 - 34.9 - 34.9 - - - external - O.8 - 36.95 - 34.75 - 32.9 - - - internal - O.8 - 39.2 - 36.65 - 35.3 - 35.3 - - - - M38x5 - M38x5 - 5.0 - - external - O.0 - 37.75 - 34.8 - 32.2 - - - internal - O.0 - 38.4 - 35.1 - 33 - 33 - - - external - O.1 - 37.65 - 34.7 - 32.1 - - - internal - O.1 - 38.5 - 35.2 - 33.1 - 33.1 - - - external - O.2 - 37.55 - 34.6 - 32 - - - internal - O.2 - 38.6 - 35.3 - 33.2 - 33.2 - - - external - O.4 - 37.35 - 34.4 - 31.8 - - - internal - O.4 - 38.8 - 35.5 - 33.4 - 33.4 - - - external - O.8 - 36.95 - 34 - 31.4 - - - internal - O.8 - 39.2 - 35.9 - 33.8 - 33.8 - - - - - 39 - - M39x3.5 - M39x3.5 - 3.5 - - external - O.0 - 38.75 - 36.55 - 34.7 - - - internal - O.0 - 39.4 - 36.85 - 35.5 - 35.5 - - - external - O.1 - 38.65 - 36.45 - 34.6 - - - internal - O.1 - 39.5 - 36.95 - 35.6 - 35.6 - - - external - O.2 - 38.55 - 36.35 - 34.5 - - - internal - O.2 - 39.6 - 37.05 - 35.7 - 35.7 - - - external - O.4 - 38.35 - 36.15 - 34.3 - - - internal - O.4 - 39.8 - 37.25 - 35.9 - 35.9 - - - external - O.8 - 37.95 - 35.75 - 33.9 - - - internal - O.8 - 40.2 - 37.65 - 36.3 - 36.3 - - - - M39x5 - M39x5 - 5.0 - - external - O.0 - 38.75 - 35.8 - 33.2 - - - internal - O.0 - 39.4 - 36.1 - 34 - 34 - - - external - O.1 - 38.65 - 35.7 - 33.1 - - - internal - O.1 - 39.5 - 36.2 - 34.1 - 34.1 - - - external - O.2 - 38.55 - 35.6 - 33 - - - internal - O.2 - 39.6 - 36.3 - 34.2 - 34.2 - - - external - O.4 - 38.35 - 35.4 - 32.8 - - - internal - O.4 - 39.8 - 36.5 - 34.4 - 34.4 - - - external - O.8 - 37.95 - 35 - 32.4 - - - internal - O.8 - 40.2 - 36.9 - 34.8 - 34.8 - - - - - 40 - - M40x3.5 - M40x3.5 - 3.5 - - external - O.0 - 39.75 - 37.55 - 35.7 - - - internal - O.0 - 40.4 - 37.85 - 36.5 - 36.5 - - - external - O.1 - 39.65 - 37.45 - 35.6 - - - internal - O.1 - 40.5 - 37.95 - 36.6 - 36.6 - - - external - O.2 - 39.55 - 37.35 - 35.5 - - - internal - O.2 - 40.6 - 38.05 - 36.7 - 36.7 - - - external - O.4 - 39.35 - 37.15 - 35.3 - - - internal - O.4 - 40.8 - 38.25 - 36.9 - 36.9 - - - external - O.8 - 38.95 - 36.75 - 34.9 - - - internal - O.8 - 41.2 - 38.65 - 37.3 - 37.3 - - - - M40x5 - M40x5 - 5.0 - - external - O.0 - 39.75 - 36.8 - 34.2 - - - internal - O.0 - 40.4 - 37.1 - 35 - 35 - - - external - O.1 - 39.65 - 36.7 - 34.1 - - - internal - O.1 - 40.5 - 37.2 - 35.1 - 35.1 - - - external - O.2 - 39.55 - 36.6 - 34 - - - internal - O.2 - 40.6 - 37.3 - 35.2 - 35.2 - - - external - O.4 - 39.35 - 36.4 - 33.8 - - - internal - O.4 - 40.8 - 37.5 - 35.4 - 35.4 - - - external - O.8 - 38.95 - 36 - 33.4 - - - internal - O.8 - 41.2 - 37.9 - 35.8 - 35.8 - - - - - 41 - - M41x3.5 - M41x3.5 - 3.5 - - external - O.0 - 40.75 - 38.55 - 36.7 - - - internal - O.0 - 41.4 - 38.85 - 37.5 - 37.5 - - - external - O.1 - 40.65 - 38.45 - 36.6 - - - internal - O.1 - 41.5 - 38.95 - 37.6 - 37.6 - - - external - O.2 - 40.55 - 38.35 - 36.5 - - - internal - O.2 - 41.6 - 39.05 - 37.7 - 37.7 - - - external - O.4 - 40.35 - 38.15 - 36.3 - - - internal - O.4 - 41.8 - 39.25 - 37.9 - 37.9 - - - external - O.8 - 39.95 - 37.75 - 35.9 - - - internal - O.8 - 42.2 - 39.65 - 38.3 - 38.3 - - - - M41x5 - M41x5 - 5.0 - - external - O.0 - 40.75 - 37.8 - 35.2 - - - internal - O.0 - 41.4 - 38.1 - 36 - 36 - - - external - O.1 - 40.65 - 37.7 - 35.1 - - - internal - O.1 - 41.5 - 38.2 - 36.1 - 36.1 - - - external - O.2 - 40.55 - 37.6 - 35 - - - internal - O.2 - 41.6 - 38.3 - 36.2 - 36.2 - - - external - O.4 - 40.35 - 37.4 - 34.8 - - - internal - O.4 - 41.8 - 38.5 - 36.4 - 36.4 - - - external - O.8 - 39.95 - 37 - 34.4 - - - internal - O.8 - 42.2 - 38.9 - 36.8 - 36.8 - - - - - 42 - - M42x3.5 - M42x3.5 - 3.5 - - external - O.0 - 41.75 - 39.55 - 37.7 - - - internal - O.0 - 42.4 - 39.85 - 38.5 - 38.5 - - - external - O.1 - 41.65 - 39.45 - 37.6 - - - internal - O.1 - 42.5 - 39.95 - 38.6 - 38.6 - - - external - O.2 - 41.55 - 39.35 - 37.5 - - - internal - O.2 - 42.6 - 40.05 - 38.7 - 38.7 - - - external - O.4 - 41.35 - 39.15 - 37.3 - - - internal - O.4 - 42.8 - 40.25 - 38.9 - 38.9 - - - external - O.8 - 40.95 - 38.75 - 36.9 - - - internal - O.8 - 43.2 - 40.65 - 39.3 - 39.3 - - - - M42x5 - M42x5 - 5.0 - - external - O.0 - 41.75 - 38.8 - 36.2 - - - internal - O.0 - 42.4 - 39.1 - 37 - 37 - - - external - O.1 - 41.65 - 38.7 - 36.1 - - - internal - O.1 - 42.5 - 39.2 - 37.1 - 37.1 - - - external - O.2 - 41.55 - 38.6 - 36 - - - internal - O.2 - 42.6 - 39.3 - 37.2 - 37.2 - - - external - O.4 - 41.35 - 38.4 - 35.8 - - - internal - O.4 - 42.8 - 39.5 - 37.4 - 37.4 - - - external - O.8 - 40.95 - 38 - 35.4 - - - internal - O.8 - 43.2 - 39.9 - 37.8 - 37.8 - - - - - 43 - - M43x3.5 - M43x3.5 - 3.5 - - external - O.0 - 42.75 - 40.55 - 38.7 - - - internal - O.0 - 43.4 - 40.85 - 39.5 - 39.5 - - - external - O.1 - 42.65 - 40.45 - 38.6 - - - internal - O.1 - 43.5 - 40.95 - 39.6 - 39.6 - - - external - O.2 - 42.55 - 40.35 - 38.5 - - - internal - O.2 - 43.6 - 41.05 - 39.7 - 39.7 - - - external - O.4 - 42.35 - 40.15 - 38.3 - - - internal - O.4 - 43.8 - 41.25 - 39.9 - 39.9 - - - external - O.8 - 41.95 - 39.75 - 37.9 - - - internal - O.8 - 44.2 - 41.65 - 40.3 - 40.3 - - - - M43x5 - M43x5 - 5.0 - - external - O.0 - 42.75 - 39.8 - 37.2 - - - internal - O.0 - 43.4 - 40.1 - 38 - 38 - - - external - O.1 - 42.65 - 39.7 - 37.1 - - - internal - O.1 - 43.5 - 40.2 - 38.1 - 38.1 - - - external - O.2 - 42.55 - 39.6 - 37 - - - internal - O.2 - 43.6 - 40.3 - 38.2 - 38.2 - - - external - O.4 - 42.35 - 39.4 - 36.8 - - - internal - O.4 - 43.8 - 40.5 - 38.4 - 38.4 - - - external - O.8 - 41.95 - 39 - 36.4 - - - internal - O.8 - 44.2 - 40.9 - 38.8 - 38.8 - - - - - 44 - - M44x3.5 - M44x3.5 - 3.5 - - external - O.0 - 43.75 - 41.55 - 39.7 - - - internal - O.0 - 44.4 - 41.85 - 40.5 - 40.5 - - - external - O.1 - 43.65 - 41.45 - 39.6 - - - internal - O.1 - 44.5 - 41.95 - 40.6 - 40.6 - - - external - O.2 - 43.55 - 41.35 - 39.5 - - - internal - O.2 - 44.6 - 42.05 - 40.7 - 40.7 - - - external - O.4 - 43.35 - 41.15 - 39.3 - - - internal - O.4 - 44.8 - 42.25 - 40.9 - 40.9 - - - external - O.8 - 42.95 - 40.75 - 38.9 - - - internal - O.8 - 45.2 - 42.65 - 41.3 - 41.3 - - - - M44x5 - M44x5 - 5.0 - - external - O.0 - 43.75 - 40.8 - 38.2 - - - internal - O.0 - 44.4 - 41.1 - 39 - 39 - - - external - O.1 - 43.65 - 40.7 - 38.1 - - - internal - O.1 - 44.5 - 41.2 - 39.1 - 39.1 - - - external - O.2 - 43.55 - 40.6 - 38 - - - internal - O.2 - 44.6 - 41.3 - 39.2 - 39.2 - - - external - O.4 - 43.35 - 40.4 - 37.8 - - - internal - O.4 - 44.8 - 41.5 - 39.4 - 39.4 - - - external - O.8 - 42.95 - 40 - 37.4 - - - internal - O.8 - 45.2 - 41.9 - 39.8 - 39.8 - - - - - 45 - - M45x3.5 - M45x3.5 - 3.5 - - external - O.0 - 44.75 - 42.55 - 40.7 - - - internal - O.0 - 45.4 - 42.85 - 41.5 - 41.5 - - - external - O.1 - 44.65 - 42.45 - 40.6 - - - internal - O.1 - 45.5 - 42.95 - 41.6 - 41.6 - - - external - O.2 - 44.55 - 42.35 - 40.5 - - - internal - O.2 - 45.6 - 43.05 - 41.7 - 41.7 - - - external - O.4 - 44.35 - 42.15 - 40.3 - - - internal - O.4 - 45.8 - 43.25 - 41.9 - 41.9 - - - external - O.8 - 43.95 - 41.75 - 39.9 - - - internal - O.8 - 46.2 - 43.65 - 42.3 - 42.3 - - - - M45x5 - M45x5 - 5.0 - - external - O.0 - 44.75 - 41.8 - 39.2 - - - internal - O.0 - 45.4 - 42.1 - 40 - 40 - - - external - O.1 - 44.65 - 41.7 - 39.1 - - - internal - O.1 - 45.5 - 42.2 - 40.1 - 40.1 - - - external - O.2 - 44.55 - 41.6 - 39 - - - internal - O.2 - 45.6 - 42.3 - 40.2 - 40.2 - - - external - O.4 - 44.35 - 41.4 - 38.8 - - - internal - O.4 - 45.8 - 42.5 - 40.4 - 40.4 - - - external - O.8 - 43.95 - 41 - 38.4 - - - internal - O.8 - 46.2 - 42.9 - 40.8 - 40.8 - - - - - 46 - - M46x3.5 - M46x3.5 - 3.5 - - external - O.0 - 45.75 - 43.55 - 41.7 - - - internal - O.0 - 46.4 - 43.85 - 42.5 - 42.5 - - - external - O.1 - 45.65 - 43.45 - 41.6 - - - internal - O.1 - 46.5 - 43.95 - 42.6 - 42.6 - - - external - O.2 - 45.55 - 43.35 - 41.5 - - - internal - O.2 - 46.6 - 44.05 - 42.7 - 42.7 - - - external - O.4 - 45.35 - 43.15 - 41.3 - - - internal - O.4 - 46.8 - 44.25 - 42.9 - 42.9 - - - external - O.8 - 44.95 - 42.75 - 40.9 - - - internal - O.8 - 47.2 - 44.65 - 43.3 - 43.3 - - - - M46x5 - M46x5 - 5.0 - - external - O.0 - 45.75 - 42.8 - 40.2 - - - internal - O.0 - 46.4 - 43.1 - 41 - 41 - - - external - O.1 - 45.65 - 42.7 - 40.1 - - - internal - O.1 - 46.5 - 43.2 - 41.1 - 41.1 - - - external - O.2 - 45.55 - 42.6 - 40 - - - internal - O.2 - 46.6 - 43.3 - 41.2 - 41.2 - - - external - O.4 - 45.35 - 42.4 - 39.8 - - - internal - O.4 - 46.8 - 43.5 - 41.4 - 41.4 - - - external - O.8 - 44.95 - 42 - 39.4 - - - internal - O.8 - 47.2 - 43.9 - 41.8 - 41.8 - - - - - 47 - - M47x3.5 - M47x3.5 - 3.5 - - external - O.0 - 46.75 - 44.55 - 42.7 - - - internal - O.0 - 47.4 - 44.85 - 43.5 - 43.5 - - - external - O.1 - 46.65 - 44.45 - 42.6 - - - internal - O.1 - 47.5 - 44.95 - 43.6 - 43.6 - - - external - O.2 - 46.55 - 44.35 - 42.5 - - - internal - O.2 - 47.6 - 45.05 - 43.7 - 43.7 - - - external - O.4 - 46.35 - 44.15 - 42.3 - - - internal - O.4 - 47.8 - 45.25 - 43.9 - 43.9 - - - external - O.8 - 45.95 - 43.75 - 41.9 - - - internal - O.8 - 48.2 - 45.65 - 44.3 - 44.3 - - - - M47x5 - M47x5 - 5.0 - - external - O.0 - 46.75 - 43.8 - 41.2 - - - internal - O.0 - 47.4 - 44.1 - 42 - 42 - - - external - O.1 - 46.65 - 43.7 - 41.1 - - - internal - O.1 - 47.5 - 44.2 - 42.1 - 42.1 - - - external - O.2 - 46.55 - 43.6 - 41 - - - internal - O.2 - 47.6 - 44.3 - 42.2 - 42.2 - - - external - O.4 - 46.35 - 43.4 - 40.8 - - - internal - O.4 - 47.8 - 44.5 - 42.4 - 42.4 - - - external - O.8 - 45.95 - 43 - 40.4 - - - internal - O.8 - 48.2 - 44.9 - 42.8 - 42.8 - - - - - 48 - - M48x3.5 - M48x3.5 - 3.5 - - external - O.0 - 47.75 - 45.55 - 43.7 - - - internal - O.0 - 48.4 - 45.85 - 44.5 - 44.5 - - - external - O.1 - 47.65 - 45.45 - 43.6 - - - internal - O.1 - 48.5 - 45.95 - 44.6 - 44.6 - - - external - O.2 - 47.55 - 45.35 - 43.5 - - - internal - O.2 - 48.6 - 46.05 - 44.7 - 44.7 - - - external - O.4 - 47.35 - 45.15 - 43.3 - - - internal - O.4 - 48.8 - 46.25 - 44.9 - 44.9 - - - external - O.8 - 46.95 - 44.75 - 42.9 - - - internal - O.8 - 49.2 - 46.65 - 45.3 - 45.3 - - - - M48x5 - M48x5 - 5.0 - - external - O.0 - 47.75 - 44.8 - 42.2 - - - internal - O.0 - 48.4 - 45.1 - 43 - 43 - - - external - O.1 - 47.65 - 44.7 - 42.1 - - - internal - O.1 - 48.5 - 45.2 - 43.1 - 43.1 - - - external - O.2 - 47.55 - 44.6 - 42 - - - internal - O.2 - 48.6 - 45.3 - 43.2 - 43.2 - - - external - O.4 - 47.35 - 44.4 - 41.8 - - - internal - O.4 - 48.8 - 45.5 - 43.4 - 43.4 - - - external - O.8 - 46.95 - 44 - 41.4 - - - internal - O.8 - 49.2 - 45.9 - 43.8 - 43.8 - - - - - 49 - - M49x3.5 - M49x3.5 - 3.5 - - external - O.0 - 48.75 - 46.55 - 44.7 - - - internal - O.0 - 49.4 - 46.85 - 45.5 - 45.5 - - - external - O.1 - 48.65 - 46.45 - 44.6 - - - internal - O.1 - 49.5 - 46.95 - 45.6 - 45.6 - - - external - O.2 - 48.55 - 46.35 - 44.5 - - - internal - O.2 - 49.6 - 47.05 - 45.7 - 45.7 - - - external - O.4 - 48.35 - 46.15 - 44.3 - - - internal - O.4 - 49.8 - 47.25 - 45.9 - 45.9 - - - external - O.8 - 47.95 - 45.75 - 43.9 - - - internal - O.8 - 50.2 - 47.65 - 46.3 - 46.3 - - - - M49x5 - M49x5 - 5.0 - - external - O.0 - 48.75 - 45.8 - 43.2 - - - internal - O.0 - 49.4 - 46.1 - 44 - 44 - - - external - O.1 - 48.65 - 45.7 - 43.1 - - - internal - O.1 - 49.5 - 46.2 - 44.1 - 44.1 - - - external - O.2 - 48.55 - 45.6 - 43 - - - internal - O.2 - 49.6 - 46.3 - 44.2 - 44.2 - - - external - O.4 - 48.35 - 45.4 - 42.8 - - - internal - O.4 - 49.8 - 46.5 - 44.4 - 44.4 - - - external - O.8 - 47.95 - 45 - 42.4 - - - internal - O.8 - 50.2 - 46.9 - 44.8 - 44.8 - - - - - 50 - - M50x3.5 - M50x3.5 - 3.5 - - external - O.0 - 49.75 - 47.55 - 45.7 - - - internal - O.0 - 50.4 - 47.85 - 46.5 - 46.5 - - - external - O.1 - 49.65 - 47.45 - 45.6 - - - internal - O.1 - 50.5 - 47.95 - 46.6 - 46.6 - - - external - O.2 - 49.55 - 47.35 - 45.5 - - - internal - O.2 - 50.6 - 48.05 - 46.7 - 46.7 - - - external - O.4 - 49.35 - 47.15 - 45.3 - - - internal - O.4 - 50.8 - 48.25 - 46.9 - 46.9 - - - external - O.8 - 48.95 - 46.75 - 44.9 - - - internal - O.8 - 51.2 - 48.65 - 47.3 - 47.3 - - - - M50x5 - M50x5 - 5.0 - - external - O.0 - 49.75 - 46.8 - 44.2 - - - internal - O.0 - 50.4 - 47.1 - 45 - 45 - - - external - O.1 - 49.65 - 46.7 - 44.1 - - - internal - O.1 - 50.5 - 47.2 - 45.1 - 45.1 - - - external - O.2 - 49.55 - 46.6 - 44 - - - internal - O.2 - 50.6 - 47.3 - 45.2 - 45.2 - - - external - O.4 - 49.35 - 46.4 - 43.8 - - - internal - O.4 - 50.8 - 47.5 - 45.4 - 45.4 - - - external - O.8 - 48.95 - 46 - 43.4 - - - internal - O.8 - 51.2 - 47.9 - 45.8 - 45.8 - - - - \ No newline at end of file diff --git a/3DPrintedMetricV2.xml b/3DPrintedMetricV2.xml deleted file mode 100644 index 36507c2..0000000 --- a/3DPrintedMetricV2.xml +++ /dev/null @@ -1,7017 +0,0 @@ - - - 3D-printed Metric Threads V2 - 3D-printed Metric Threads V2 - mm - 60.0 - 3 - - 8 - - M8x3.5 - M8x3.5 - 3.5 - - external - O.0 - 8 - 6.484 - 6.106 - - - internal - O.0 - 8 - 6.484 - 6.106 - 4.5 - - - external - O.1 - 7.9 - 6.384 - 6.006 - - - internal - O.1 - 8.1 - 6.584 - 6.206 - 4.5 - - - external - O.2 - 7.8 - 6.284 - 5.906 - - - internal - O.2 - 8.2 - 6.684 - 6.306 - 4.5 - - - external - O.4 - 7.6 - 6.084 - 5.706 - - - internal - O.4 - 8.4 - 6.884 - 6.506 - 4.5 - - - external - O.8 - 7.2 - 5.684 - 5.306 - - - internal - O.8 - 8.8 - 7.284 - 6.906 - 4.5 - - - - M8x5 - M8x5 - 5.0 - - external - O.0 - 8 - 5.835 - 5.294 - - - internal - O.0 - 8 - 5.835 - 5.294 - 3 - - - external - O.1 - 7.9 - 5.735 - 5.194 - - - internal - O.1 - 8.1 - 5.935 - 5.394 - 3 - - - external - O.2 - 7.8 - 5.635 - 5.094 - - - internal - O.2 - 8.2 - 6.035 - 5.494 - 3 - - - external - O.4 - 7.6 - 5.435 - 4.894 - - - internal - O.4 - 8.4 - 6.235 - 5.694 - 3 - - - external - O.8 - 7.2 - 5.035 - 4.494 - - - internal - O.8 - 8.8 - 6.635 - 6.094 - 3 - - - - - 9 - - M9x3.5 - M9x3.5 - 3.5 - - external - O.0 - 9 - 7.484 - 7.106 - - - internal - O.0 - 9 - 7.484 - 7.106 - 5.5 - - - external - O.1 - 8.9 - 7.384 - 7.006 - - - internal - O.1 - 9.1 - 7.584 - 7.206 - 5.5 - - - external - O.2 - 8.8 - 7.284 - 6.906 - - - internal - O.2 - 9.2 - 7.684 - 7.306 - 5.5 - - - external - O.4 - 8.6 - 7.084 - 6.706 - - - internal - O.4 - 9.4 - 7.884 - 7.506 - 5.5 - - - external - O.8 - 8.2 - 6.684 - 6.306 - - - internal - O.8 - 9.8 - 8.284 - 7.906 - 5.5 - - - - M9x5 - M9x5 - 5.0 - - external - O.0 - 9 - 6.835 - 6.294 - - - internal - O.0 - 9 - 6.835 - 6.294 - 4 - - - external - O.1 - 8.9 - 6.735 - 6.194 - - - internal - O.1 - 9.1 - 6.935 - 6.394 - 4 - - - external - O.2 - 8.8 - 6.635 - 6.094 - - - internal - O.2 - 9.2 - 7.035 - 6.494 - 4 - - - external - O.4 - 8.6 - 6.435 - 5.894 - - - internal - O.4 - 9.4 - 7.235 - 6.694 - 4 - - - external - O.8 - 8.2 - 6.035 - 5.494 - - - internal - O.8 - 9.8 - 7.635 - 7.094 - 4 - - - - - 10 - - M10x3.5 - M10x3.5 - 3.5 - - external - O.0 - 10 - 8.484 - 8.106 - - - internal - O.0 - 10 - 8.484 - 8.106 - 6.5 - - - external - O.1 - 9.9 - 8.384 - 8.006 - - - internal - O.1 - 10.1 - 8.584 - 8.206 - 6.5 - - - external - O.2 - 9.8 - 8.284 - 7.906 - - - internal - O.2 - 10.2 - 8.684 - 8.306 - 6.5 - - - external - O.4 - 9.6 - 8.084 - 7.706 - - - internal - O.4 - 10.4 - 8.884 - 8.506 - 6.5 - - - external - O.8 - 9.2 - 7.684 - 7.306 - - - internal - O.8 - 10.8 - 9.284 - 8.906 - 6.5 - - - - M10x5 - M10x5 - 5.0 - - external - O.0 - 10 - 7.835 - 7.294 - - - internal - O.0 - 10 - 7.835 - 7.294 - 5 - - - external - O.1 - 9.9 - 7.735 - 7.194 - - - internal - O.1 - 10.1 - 7.935 - 7.394 - 5 - - - external - O.2 - 9.8 - 7.635 - 7.094 - - - internal - O.2 - 10.2 - 8.035 - 7.494 - 5 - - - external - O.4 - 9.6 - 7.435 - 6.894 - - - internal - O.4 - 10.4 - 8.235 - 7.694 - 5 - - - external - O.8 - 9.2 - 7.035 - 6.494 - - - internal - O.8 - 10.8 - 8.635 - 8.094 - 5 - - - - - 11 - - M11x3.5 - M11x3.5 - 3.5 - - external - O.0 - 11 - 9.484 - 9.106 - - - internal - O.0 - 11 - 9.484 - 9.106 - 7.5 - - - external - O.1 - 10.9 - 9.384 - 9.006 - - - internal - O.1 - 11.1 - 9.584 - 9.206 - 7.5 - - - external - O.2 - 10.8 - 9.284 - 8.906 - - - internal - O.2 - 11.2 - 9.684 - 9.306 - 7.5 - - - external - O.4 - 10.6 - 9.084 - 8.706 - - - internal - O.4 - 11.4 - 9.884 - 9.506 - 7.5 - - - external - O.8 - 10.2 - 8.684 - 8.306 - - - internal - O.8 - 11.8 - 10.28 - 9.906 - 7.5 - - - - M11x5 - M11x5 - 5.0 - - external - O.0 - 11 - 8.835 - 8.294 - - - internal - O.0 - 11 - 8.835 - 8.294 - 6 - - - external - O.1 - 10.9 - 8.735 - 8.194 - - - internal - O.1 - 11.1 - 8.935 - 8.394 - 6 - - - external - O.2 - 10.8 - 8.635 - 8.094 - - - internal - O.2 - 11.2 - 9.035 - 8.494 - 6 - - - external - O.4 - 10.6 - 8.435 - 7.894 - - - internal - O.4 - 11.4 - 9.235 - 8.694 - 6 - - - external - O.8 - 10.2 - 8.035 - 7.494 - - - internal - O.8 - 11.8 - 9.635 - 9.094 - 6 - - - - - 12 - - M12x3.5 - M12x3.5 - 3.5 - - external - O.0 - 12 - 10.48 - 10.11 - - - internal - O.0 - 12 - 10.48 - 10.11 - 8.5 - - - external - O.1 - 11.9 - 10.38 - 10.01 - - - internal - O.1 - 12.1 - 10.58 - 10.21 - 8.5 - - - external - O.2 - 11.8 - 10.28 - 9.906 - - - internal - O.2 - 12.2 - 10.68 - 10.31 - 8.5 - - - external - O.4 - 11.6 - 10.08 - 9.706 - - - internal - O.4 - 12.4 - 10.88 - 10.51 - 8.5 - - - external - O.8 - 11.2 - 9.684 - 9.306 - - - internal - O.8 - 12.8 - 11.28 - 10.91 - 8.5 - - - - M12x5 - M12x5 - 5.0 - - external - O.0 - 12 - 9.835 - 9.294 - - - internal - O.0 - 12 - 9.835 - 9.294 - 7 - - - external - O.1 - 11.9 - 9.735 - 9.194 - - - internal - O.1 - 12.1 - 9.935 - 9.394 - 7 - - - external - O.2 - 11.8 - 9.635 - 9.094 - - - internal - O.2 - 12.2 - 10.03 - 9.494 - 7 - - - external - O.4 - 11.6 - 9.435 - 8.894 - - - internal - O.4 - 12.4 - 10.23 - 9.694 - 7 - - - external - O.8 - 11.2 - 9.035 - 8.494 - - - internal - O.8 - 12.8 - 10.63 - 10.09 - 7 - - - - - 13 - - M13x3.5 - M13x3.5 - 3.5 - - external - O.0 - 13 - 11.48 - 11.11 - - - internal - O.0 - 13 - 11.48 - 11.11 - 9.5 - - - external - O.1 - 12.9 - 11.38 - 11.01 - - - internal - O.1 - 13.1 - 11.58 - 11.21 - 9.5 - - - external - O.2 - 12.8 - 11.28 - 10.91 - - - internal - O.2 - 13.2 - 11.68 - 11.31 - 9.5 - - - external - O.4 - 12.6 - 11.08 - 10.71 - - - internal - O.4 - 13.4 - 11.88 - 11.51 - 9.5 - - - external - O.8 - 12.2 - 10.68 - 10.31 - - - internal - O.8 - 13.8 - 12.28 - 11.91 - 9.5 - - - - M13x5 - M13x5 - 5.0 - - external - O.0 - 13 - 10.83 - 10.29 - - - internal - O.0 - 13 - 10.83 - 10.29 - 8 - - - external - O.1 - 12.9 - 10.73 - 10.19 - - - internal - O.1 - 13.1 - 10.93 - 10.39 - 8 - - - external - O.2 - 12.8 - 10.63 - 10.09 - - - internal - O.2 - 13.2 - 11.03 - 10.49 - 8 - - - external - O.4 - 12.6 - 10.43 - 9.894 - - - internal - O.4 - 13.4 - 11.23 - 10.69 - 8 - - - external - O.8 - 12.2 - 10.03 - 9.494 - - - internal - O.8 - 13.8 - 11.63 - 11.09 - 8 - - - - - 14 - - M14x3.5 - M14x3.5 - 3.5 - - external - O.0 - 14 - 12.48 - 12.11 - - - internal - O.0 - 14 - 12.48 - 12.11 - 10.5 - - - external - O.1 - 13.9 - 12.38 - 12.01 - - - internal - O.1 - 14.1 - 12.58 - 12.21 - 10.5 - - - external - O.2 - 13.8 - 12.28 - 11.91 - - - internal - O.2 - 14.2 - 12.68 - 12.31 - 10.5 - - - external - O.4 - 13.6 - 12.08 - 11.71 - - - internal - O.4 - 14.4 - 12.88 - 12.51 - 10.5 - - - external - O.8 - 13.2 - 11.68 - 11.31 - - - internal - O.8 - 14.8 - 13.28 - 12.91 - 10.5 - - - - M14x5 - M14x5 - 5.0 - - external - O.0 - 14 - 11.83 - 11.29 - - - internal - O.0 - 14 - 11.83 - 11.29 - 9 - - - external - O.1 - 13.9 - 11.73 - 11.19 - - - internal - O.1 - 14.1 - 11.93 - 11.39 - 9 - - - external - O.2 - 13.8 - 11.63 - 11.09 - - - internal - O.2 - 14.2 - 12.03 - 11.49 - 9 - - - external - O.4 - 13.6 - 11.43 - 10.89 - - - internal - O.4 - 14.4 - 12.23 - 11.69 - 9 - - - external - O.8 - 13.2 - 11.03 - 10.49 - - - internal - O.8 - 14.8 - 12.63 - 12.09 - 9 - - - - - 15 - - M15x3.5 - M15x3.5 - 3.5 - - external - O.0 - 15 - 13.48 - 13.11 - - - internal - O.0 - 15 - 13.48 - 13.11 - 11.5 - - - external - O.1 - 14.9 - 13.38 - 13.01 - - - internal - O.1 - 15.1 - 13.58 - 13.21 - 11.5 - - - external - O.2 - 14.8 - 13.28 - 12.91 - - - internal - O.2 - 15.2 - 13.68 - 13.31 - 11.5 - - - external - O.4 - 14.6 - 13.08 - 12.71 - - - internal - O.4 - 15.4 - 13.88 - 13.51 - 11.5 - - - external - O.8 - 14.2 - 12.68 - 12.31 - - - internal - O.8 - 15.8 - 14.28 - 13.91 - 11.5 - - - - M15x5 - M15x5 - 5.0 - - external - O.0 - 15 - 12.83 - 12.29 - - - internal - O.0 - 15 - 12.83 - 12.29 - 10 - - - external - O.1 - 14.9 - 12.73 - 12.19 - - - internal - O.1 - 15.1 - 12.93 - 12.39 - 10 - - - external - O.2 - 14.8 - 12.63 - 12.09 - - - internal - O.2 - 15.2 - 13.03 - 12.49 - 10 - - - external - O.4 - 14.6 - 12.43 - 11.89 - - - internal - O.4 - 15.4 - 13.23 - 12.69 - 10 - - - external - O.8 - 14.2 - 12.03 - 11.49 - - - internal - O.8 - 15.8 - 13.63 - 13.09 - 10 - - - - - 16 - - M16x3.5 - M16x3.5 - 3.5 - - external - O.0 - 16 - 14.48 - 14.11 - - - internal - O.0 - 16 - 14.48 - 14.11 - 12.5 - - - external - O.1 - 15.9 - 14.38 - 14.01 - - - internal - O.1 - 16.1 - 14.58 - 14.21 - 12.5 - - - external - O.2 - 15.8 - 14.28 - 13.91 - - - internal - O.2 - 16.2 - 14.68 - 14.31 - 12.5 - - - external - O.4 - 15.6 - 14.08 - 13.71 - - - internal - O.4 - 16.4 - 14.88 - 14.51 - 12.5 - - - external - O.8 - 15.2 - 13.68 - 13.31 - - - internal - O.8 - 16.8 - 15.28 - 14.91 - 12.5 - - - - M16x5 - M16x5 - 5.0 - - external - O.0 - 16 - 13.83 - 13.29 - - - internal - O.0 - 16 - 13.83 - 13.29 - 11 - - - external - O.1 - 15.9 - 13.73 - 13.19 - - - internal - O.1 - 16.1 - 13.93 - 13.39 - 11 - - - external - O.2 - 15.8 - 13.63 - 13.09 - - - internal - O.2 - 16.2 - 14.03 - 13.49 - 11 - - - external - O.4 - 15.6 - 13.43 - 12.89 - - - internal - O.4 - 16.4 - 14.23 - 13.69 - 11 - - - external - O.8 - 15.2 - 13.03 - 12.49 - - - internal - O.8 - 16.8 - 14.63 - 14.09 - 11 - - - - - 17 - - M17x3.5 - M17x3.5 - 3.5 - - external - O.0 - 17 - 15.48 - 15.11 - - - internal - O.0 - 17 - 15.48 - 15.11 - 13.5 - - - external - O.1 - 16.9 - 15.38 - 15.01 - - - internal - O.1 - 17.1 - 15.58 - 15.21 - 13.5 - - - external - O.2 - 16.8 - 15.28 - 14.91 - - - internal - O.2 - 17.2 - 15.68 - 15.31 - 13.5 - - - external - O.4 - 16.6 - 15.08 - 14.71 - - - internal - O.4 - 17.4 - 15.88 - 15.51 - 13.5 - - - external - O.8 - 16.2 - 14.68 - 14.31 - - - internal - O.8 - 17.8 - 16.28 - 15.91 - 13.5 - - - - M17x5 - M17x5 - 5.0 - - external - O.0 - 17 - 14.83 - 14.29 - - - internal - O.0 - 17 - 14.83 - 14.29 - 12 - - - external - O.1 - 16.9 - 14.73 - 14.19 - - - internal - O.1 - 17.1 - 14.93 - 14.39 - 12 - - - external - O.2 - 16.8 - 14.63 - 14.09 - - - internal - O.2 - 17.2 - 15.03 - 14.49 - 12 - - - external - O.4 - 16.6 - 14.43 - 13.89 - - - internal - O.4 - 17.4 - 15.23 - 14.69 - 12 - - - external - O.8 - 16.2 - 14.03 - 13.49 - - - internal - O.8 - 17.8 - 15.63 - 15.09 - 12 - - - - - 18 - - M18x3.5 - M18x3.5 - 3.5 - - external - O.0 - 18 - 16.48 - 16.11 - - - internal - O.0 - 18 - 16.48 - 16.11 - 14.5 - - - external - O.1 - 17.9 - 16.38 - 16.01 - - - internal - O.1 - 18.1 - 16.58 - 16.21 - 14.5 - - - external - O.2 - 17.8 - 16.28 - 15.91 - - - internal - O.2 - 18.2 - 16.68 - 16.31 - 14.5 - - - external - O.4 - 17.6 - 16.08 - 15.71 - - - internal - O.4 - 18.4 - 16.88 - 16.51 - 14.5 - - - external - O.8 - 17.2 - 15.68 - 15.31 - - - internal - O.8 - 18.8 - 17.28 - 16.91 - 14.5 - - - - M18x5 - M18x5 - 5.0 - - external - O.0 - 18 - 15.83 - 15.29 - - - internal - O.0 - 18 - 15.83 - 15.29 - 13 - - - external - O.1 - 17.9 - 15.73 - 15.19 - - - internal - O.1 - 18.1 - 15.93 - 15.39 - 13 - - - external - O.2 - 17.8 - 15.63 - 15.09 - - - internal - O.2 - 18.2 - 16.03 - 15.49 - 13 - - - external - O.4 - 17.6 - 15.43 - 14.89 - - - internal - O.4 - 18.4 - 16.23 - 15.69 - 13 - - - external - O.8 - 17.2 - 15.03 - 14.49 - - - internal - O.8 - 18.8 - 16.63 - 16.09 - 13 - - - - - 19 - - M19x3.5 - M19x3.5 - 3.5 - - external - O.0 - 19 - 17.48 - 17.11 - - - internal - O.0 - 19 - 17.48 - 17.11 - 15.5 - - - external - O.1 - 18.9 - 17.38 - 17.01 - - - internal - O.1 - 19.1 - 17.58 - 17.21 - 15.5 - - - external - O.2 - 18.8 - 17.28 - 16.91 - - - internal - O.2 - 19.2 - 17.68 - 17.31 - 15.5 - - - external - O.4 - 18.6 - 17.08 - 16.71 - - - internal - O.4 - 19.4 - 17.88 - 17.51 - 15.5 - - - external - O.8 - 18.2 - 16.68 - 16.31 - - - internal - O.8 - 19.8 - 18.28 - 17.91 - 15.5 - - - - M19x5 - M19x5 - 5.0 - - external - O.0 - 19 - 16.83 - 16.29 - - - internal - O.0 - 19 - 16.83 - 16.29 - 14 - - - external - O.1 - 18.9 - 16.73 - 16.19 - - - internal - O.1 - 19.1 - 16.93 - 16.39 - 14 - - - external - O.2 - 18.8 - 16.63 - 16.09 - - - internal - O.2 - 19.2 - 17.03 - 16.49 - 14 - - - external - O.4 - 18.6 - 16.43 - 15.89 - - - internal - O.4 - 19.4 - 17.23 - 16.69 - 14 - - - external - O.8 - 18.2 - 16.03 - 15.49 - - - internal - O.8 - 19.8 - 17.63 - 17.09 - 14 - - - - - 20 - - M20x3.5 - M20x3.5 - 3.5 - - external - O.0 - 20 - 18.48 - 18.11 - - - internal - O.0 - 20 - 18.48 - 18.11 - 16.5 - - - external - O.1 - 19.9 - 18.38 - 18.01 - - - internal - O.1 - 20.1 - 18.58 - 18.21 - 16.5 - - - external - O.2 - 19.8 - 18.28 - 17.91 - - - internal - O.2 - 20.2 - 18.68 - 18.31 - 16.5 - - - external - O.4 - 19.6 - 18.08 - 17.71 - - - internal - O.4 - 20.4 - 18.88 - 18.51 - 16.5 - - - external - O.8 - 19.2 - 17.68 - 17.31 - - - internal - O.8 - 20.8 - 19.28 - 18.91 - 16.5 - - - - M20x5 - M20x5 - 5.0 - - external - O.0 - 20 - 17.83 - 17.29 - - - internal - O.0 - 20 - 17.83 - 17.29 - 15 - - - external - O.1 - 19.9 - 17.73 - 17.19 - - - internal - O.1 - 20.1 - 17.93 - 17.39 - 15 - - - external - O.2 - 19.8 - 17.63 - 17.09 - - - internal - O.2 - 20.2 - 18.03 - 17.49 - 15 - - - external - O.4 - 19.6 - 17.43 - 16.89 - - - internal - O.4 - 20.4 - 18.23 - 17.69 - 15 - - - external - O.8 - 19.2 - 17.03 - 16.49 - - - internal - O.8 - 20.8 - 18.63 - 18.09 - 15 - - - - - 21 - - M21x3.5 - M21x3.5 - 3.5 - - external - O.0 - 21 - 19.48 - 19.11 - - - internal - O.0 - 21 - 19.48 - 19.11 - 17.5 - - - external - O.1 - 20.9 - 19.38 - 19.01 - - - internal - O.1 - 21.1 - 19.58 - 19.21 - 17.5 - - - external - O.2 - 20.8 - 19.28 - 18.91 - - - internal - O.2 - 21.2 - 19.68 - 19.31 - 17.5 - - - external - O.4 - 20.6 - 19.08 - 18.71 - - - internal - O.4 - 21.4 - 19.88 - 19.51 - 17.5 - - - external - O.8 - 20.2 - 18.68 - 18.31 - - - internal - O.8 - 21.8 - 20.28 - 19.91 - 17.5 - - - - M21x5 - M21x5 - 5.0 - - external - O.0 - 21 - 18.83 - 18.29 - - - internal - O.0 - 21 - 18.83 - 18.29 - 16 - - - external - O.1 - 20.9 - 18.73 - 18.19 - - - internal - O.1 - 21.1 - 18.93 - 18.39 - 16 - - - external - O.2 - 20.8 - 18.63 - 18.09 - - - internal - O.2 - 21.2 - 19.03 - 18.49 - 16 - - - external - O.4 - 20.6 - 18.43 - 17.89 - - - internal - O.4 - 21.4 - 19.23 - 18.69 - 16 - - - external - O.8 - 20.2 - 18.03 - 17.49 - - - internal - O.8 - 21.8 - 19.63 - 19.09 - 16 - - - - - 22 - - M22x3.5 - M22x3.5 - 3.5 - - external - O.0 - 22 - 20.48 - 20.11 - - - internal - O.0 - 22 - 20.48 - 20.11 - 18.5 - - - external - O.1 - 21.9 - 20.38 - 20.01 - - - internal - O.1 - 22.1 - 20.58 - 20.21 - 18.5 - - - external - O.2 - 21.8 - 20.28 - 19.91 - - - internal - O.2 - 22.2 - 20.68 - 20.31 - 18.5 - - - external - O.4 - 21.6 - 20.08 - 19.71 - - - internal - O.4 - 22.4 - 20.88 - 20.51 - 18.5 - - - external - O.8 - 21.2 - 19.68 - 19.31 - - - internal - O.8 - 22.8 - 21.28 - 20.91 - 18.5 - - - - M22x5 - M22x5 - 5.0 - - external - O.0 - 22 - 19.83 - 19.29 - - - internal - O.0 - 22 - 19.83 - 19.29 - 17 - - - external - O.1 - 21.9 - 19.73 - 19.19 - - - internal - O.1 - 22.1 - 19.93 - 19.39 - 17 - - - external - O.2 - 21.8 - 19.63 - 19.09 - - - internal - O.2 - 22.2 - 20.03 - 19.49 - 17 - - - external - O.4 - 21.6 - 19.43 - 18.89 - - - internal - O.4 - 22.4 - 20.23 - 19.69 - 17 - - - external - O.8 - 21.2 - 19.03 - 18.49 - - - internal - O.8 - 22.8 - 20.63 - 20.09 - 17 - - - - - 23 - - M23x3.5 - M23x3.5 - 3.5 - - external - O.0 - 23 - 21.48 - 21.11 - - - internal - O.0 - 23 - 21.48 - 21.11 - 19.5 - - - external - O.1 - 22.9 - 21.38 - 21.01 - - - internal - O.1 - 23.1 - 21.58 - 21.21 - 19.5 - - - external - O.2 - 22.8 - 21.28 - 20.91 - - - internal - O.2 - 23.2 - 21.68 - 21.31 - 19.5 - - - external - O.4 - 22.6 - 21.08 - 20.71 - - - internal - O.4 - 23.4 - 21.88 - 21.51 - 19.5 - - - external - O.8 - 22.2 - 20.68 - 20.31 - - - internal - O.8 - 23.8 - 22.28 - 21.91 - 19.5 - - - - M23x5 - M23x5 - 5.0 - - external - O.0 - 23 - 20.83 - 20.29 - - - internal - O.0 - 23 - 20.83 - 20.29 - 18 - - - external - O.1 - 22.9 - 20.73 - 20.19 - - - internal - O.1 - 23.1 - 20.93 - 20.39 - 18 - - - external - O.2 - 22.8 - 20.63 - 20.09 - - - internal - O.2 - 23.2 - 21.03 - 20.49 - 18 - - - external - O.4 - 22.6 - 20.43 - 19.89 - - - internal - O.4 - 23.4 - 21.23 - 20.69 - 18 - - - external - O.8 - 22.2 - 20.03 - 19.49 - - - internal - O.8 - 23.8 - 21.63 - 21.09 - 18 - - - - - 24 - - M24x3.5 - M24x3.5 - 3.5 - - external - O.0 - 24 - 22.48 - 22.11 - - - internal - O.0 - 24 - 22.48 - 22.11 - 20.5 - - - external - O.1 - 23.9 - 22.38 - 22.01 - - - internal - O.1 - 24.1 - 22.58 - 22.21 - 20.5 - - - external - O.2 - 23.8 - 22.28 - 21.91 - - - internal - O.2 - 24.2 - 22.68 - 22.31 - 20.5 - - - external - O.4 - 23.6 - 22.08 - 21.71 - - - internal - O.4 - 24.4 - 22.88 - 22.51 - 20.5 - - - external - O.8 - 23.2 - 21.68 - 21.31 - - - internal - O.8 - 24.8 - 23.28 - 22.91 - 20.5 - - - - M24x5 - M24x5 - 5.0 - - external - O.0 - 24 - 21.83 - 21.29 - - - internal - O.0 - 24 - 21.83 - 21.29 - 19 - - - external - O.1 - 23.9 - 21.73 - 21.19 - - - internal - O.1 - 24.1 - 21.93 - 21.39 - 19 - - - external - O.2 - 23.8 - 21.63 - 21.09 - - - internal - O.2 - 24.2 - 22.03 - 21.49 - 19 - - - external - O.4 - 23.6 - 21.43 - 20.89 - - - internal - O.4 - 24.4 - 22.23 - 21.69 - 19 - - - external - O.8 - 23.2 - 21.03 - 20.49 - - - internal - O.8 - 24.8 - 22.63 - 22.09 - 19 - - - - - 25 - - M25x3.5 - M25x3.5 - 3.5 - - external - O.0 - 25 - 23.48 - 23.11 - - - internal - O.0 - 25 - 23.48 - 23.11 - 21.5 - - - external - O.1 - 24.9 - 23.38 - 23.01 - - - internal - O.1 - 25.1 - 23.58 - 23.21 - 21.5 - - - external - O.2 - 24.8 - 23.28 - 22.91 - - - internal - O.2 - 25.2 - 23.68 - 23.31 - 21.5 - - - external - O.4 - 24.6 - 23.08 - 22.71 - - - internal - O.4 - 25.4 - 23.88 - 23.51 - 21.5 - - - external - O.8 - 24.2 - 22.68 - 22.31 - - - internal - O.8 - 25.8 - 24.28 - 23.91 - 21.5 - - - - M25x5 - M25x5 - 5.0 - - external - O.0 - 25 - 22.83 - 22.29 - - - internal - O.0 - 25 - 22.83 - 22.29 - 20 - - - external - O.1 - 24.9 - 22.73 - 22.19 - - - internal - O.1 - 25.1 - 22.93 - 22.39 - 20 - - - external - O.2 - 24.8 - 22.63 - 22.09 - - - internal - O.2 - 25.2 - 23.03 - 22.49 - 20 - - - external - O.4 - 24.6 - 22.43 - 21.89 - - - internal - O.4 - 25.4 - 23.23 - 22.69 - 20 - - - external - O.8 - 24.2 - 22.03 - 21.49 - - - internal - O.8 - 25.8 - 23.63 - 23.09 - 20 - - - - - 26 - - M26x3.5 - M26x3.5 - 3.5 - - external - O.0 - 26 - 24.48 - 24.11 - - - internal - O.0 - 26 - 24.48 - 24.11 - 22.5 - - - external - O.1 - 25.9 - 24.38 - 24.01 - - - internal - O.1 - 26.1 - 24.58 - 24.21 - 22.5 - - - external - O.2 - 25.8 - 24.28 - 23.91 - - - internal - O.2 - 26.2 - 24.68 - 24.31 - 22.5 - - - external - O.4 - 25.6 - 24.08 - 23.71 - - - internal - O.4 - 26.4 - 24.88 - 24.51 - 22.5 - - - external - O.8 - 25.2 - 23.68 - 23.31 - - - internal - O.8 - 26.8 - 25.28 - 24.91 - 22.5 - - - - M26x5 - M26x5 - 5.0 - - external - O.0 - 26 - 23.83 - 23.29 - - - internal - O.0 - 26 - 23.83 - 23.29 - 21 - - - external - O.1 - 25.9 - 23.73 - 23.19 - - - internal - O.1 - 26.1 - 23.93 - 23.39 - 21 - - - external - O.2 - 25.8 - 23.63 - 23.09 - - - internal - O.2 - 26.2 - 24.03 - 23.49 - 21 - - - external - O.4 - 25.6 - 23.43 - 22.89 - - - internal - O.4 - 26.4 - 24.23 - 23.69 - 21 - - - external - O.8 - 25.2 - 23.03 - 22.49 - - - internal - O.8 - 26.8 - 24.63 - 24.09 - 21 - - - - - 27 - - M27x3.5 - M27x3.5 - 3.5 - - external - O.0 - 27 - 25.48 - 25.11 - - - internal - O.0 - 27 - 25.48 - 25.11 - 23.5 - - - external - O.1 - 26.9 - 25.38 - 25.01 - - - internal - O.1 - 27.1 - 25.58 - 25.21 - 23.5 - - - external - O.2 - 26.8 - 25.28 - 24.91 - - - internal - O.2 - 27.2 - 25.68 - 25.31 - 23.5 - - - external - O.4 - 26.6 - 25.08 - 24.71 - - - internal - O.4 - 27.4 - 25.88 - 25.51 - 23.5 - - - external - O.8 - 26.2 - 24.68 - 24.31 - - - internal - O.8 - 27.8 - 26.28 - 25.91 - 23.5 - - - - M27x5 - M27x5 - 5.0 - - external - O.0 - 27 - 24.83 - 24.29 - - - internal - O.0 - 27 - 24.83 - 24.29 - 22 - - - external - O.1 - 26.9 - 24.73 - 24.19 - - - internal - O.1 - 27.1 - 24.93 - 24.39 - 22 - - - external - O.2 - 26.8 - 24.63 - 24.09 - - - internal - O.2 - 27.2 - 25.03 - 24.49 - 22 - - - external - O.4 - 26.6 - 24.43 - 23.89 - - - internal - O.4 - 27.4 - 25.23 - 24.69 - 22 - - - external - O.8 - 26.2 - 24.03 - 23.49 - - - internal - O.8 - 27.8 - 25.63 - 25.09 - 22 - - - - - 28 - - M28x3.5 - M28x3.5 - 3.5 - - external - O.0 - 28 - 26.48 - 26.11 - - - internal - O.0 - 28 - 26.48 - 26.11 - 24.5 - - - external - O.1 - 27.9 - 26.38 - 26.01 - - - internal - O.1 - 28.1 - 26.58 - 26.21 - 24.5 - - - external - O.2 - 27.8 - 26.28 - 25.91 - - - internal - O.2 - 28.2 - 26.68 - 26.31 - 24.5 - - - external - O.4 - 27.6 - 26.08 - 25.71 - - - internal - O.4 - 28.4 - 26.88 - 26.51 - 24.5 - - - external - O.8 - 27.2 - 25.68 - 25.31 - - - internal - O.8 - 28.8 - 27.28 - 26.91 - 24.5 - - - - M28x5 - M28x5 - 5.0 - - external - O.0 - 28 - 25.83 - 25.29 - - - internal - O.0 - 28 - 25.83 - 25.29 - 23 - - - external - O.1 - 27.9 - 25.73 - 25.19 - - - internal - O.1 - 28.1 - 25.93 - 25.39 - 23 - - - external - O.2 - 27.8 - 25.63 - 25.09 - - - internal - O.2 - 28.2 - 26.03 - 25.49 - 23 - - - external - O.4 - 27.6 - 25.43 - 24.89 - - - internal - O.4 - 28.4 - 26.23 - 25.69 - 23 - - - external - O.8 - 27.2 - 25.03 - 24.49 - - - internal - O.8 - 28.8 - 26.63 - 26.09 - 23 - - - - - 29 - - M29x3.5 - M29x3.5 - 3.5 - - external - O.0 - 29 - 27.48 - 27.11 - - - internal - O.0 - 29 - 27.48 - 27.11 - 25.5 - - - external - O.1 - 28.9 - 27.38 - 27.01 - - - internal - O.1 - 29.1 - 27.58 - 27.21 - 25.5 - - - external - O.2 - 28.8 - 27.28 - 26.91 - - - internal - O.2 - 29.2 - 27.68 - 27.31 - 25.5 - - - external - O.4 - 28.6 - 27.08 - 26.71 - - - internal - O.4 - 29.4 - 27.88 - 27.51 - 25.5 - - - external - O.8 - 28.2 - 26.68 - 26.31 - - - internal - O.8 - 29.8 - 28.28 - 27.91 - 25.5 - - - - M29x5 - M29x5 - 5.0 - - external - O.0 - 29 - 26.83 - 26.29 - - - internal - O.0 - 29 - 26.83 - 26.29 - 24 - - - external - O.1 - 28.9 - 26.73 - 26.19 - - - internal - O.1 - 29.1 - 26.93 - 26.39 - 24 - - - external - O.2 - 28.8 - 26.63 - 26.09 - - - internal - O.2 - 29.2 - 27.03 - 26.49 - 24 - - - external - O.4 - 28.6 - 26.43 - 25.89 - - - internal - O.4 - 29.4 - 27.23 - 26.69 - 24 - - - external - O.8 - 28.2 - 26.03 - 25.49 - - - internal - O.8 - 29.8 - 27.63 - 27.09 - 24 - - - - - 30 - - M30x3.5 - M30x3.5 - 3.5 - - external - O.0 - 30 - 28.48 - 28.11 - - - internal - O.0 - 30 - 28.48 - 28.11 - 26.5 - - - external - O.1 - 29.9 - 28.38 - 28.01 - - - internal - O.1 - 30.1 - 28.58 - 28.21 - 26.5 - - - external - O.2 - 29.8 - 28.28 - 27.91 - - - internal - O.2 - 30.2 - 28.68 - 28.31 - 26.5 - - - external - O.4 - 29.6 - 28.08 - 27.71 - - - internal - O.4 - 30.4 - 28.88 - 28.51 - 26.5 - - - external - O.8 - 29.2 - 27.68 - 27.31 - - - internal - O.8 - 30.8 - 29.28 - 28.91 - 26.5 - - - - M30x5 - M30x5 - 5.0 - - external - O.0 - 30 - 27.83 - 27.29 - - - internal - O.0 - 30 - 27.83 - 27.29 - 25 - - - external - O.1 - 29.9 - 27.73 - 27.19 - - - internal - O.1 - 30.1 - 27.93 - 27.39 - 25 - - - external - O.2 - 29.8 - 27.63 - 27.09 - - - internal - O.2 - 30.2 - 28.03 - 27.49 - 25 - - - external - O.4 - 29.6 - 27.43 - 26.89 - - - internal - O.4 - 30.4 - 28.23 - 27.69 - 25 - - - external - O.8 - 29.2 - 27.03 - 26.49 - - - internal - O.8 - 30.8 - 28.63 - 28.09 - 25 - - - - - 31 - - M31x3.5 - M31x3.5 - 3.5 - - external - O.0 - 31 - 29.48 - 29.11 - - - internal - O.0 - 31 - 29.48 - 29.11 - 27.5 - - - external - O.1 - 30.9 - 29.38 - 29.01 - - - internal - O.1 - 31.1 - 29.58 - 29.21 - 27.5 - - - external - O.2 - 30.8 - 29.28 - 28.91 - - - internal - O.2 - 31.2 - 29.68 - 29.31 - 27.5 - - - external - O.4 - 30.6 - 29.08 - 28.71 - - - internal - O.4 - 31.4 - 29.88 - 29.51 - 27.5 - - - external - O.8 - 30.2 - 28.68 - 28.31 - - - internal - O.8 - 31.8 - 30.28 - 29.91 - 27.5 - - - - M31x5 - M31x5 - 5.0 - - external - O.0 - 31 - 28.83 - 28.29 - - - internal - O.0 - 31 - 28.83 - 28.29 - 26 - - - external - O.1 - 30.9 - 28.73 - 28.19 - - - internal - O.1 - 31.1 - 28.93 - 28.39 - 26 - - - external - O.2 - 30.8 - 28.63 - 28.09 - - - internal - O.2 - 31.2 - 29.03 - 28.49 - 26 - - - external - O.4 - 30.6 - 28.43 - 27.89 - - - internal - O.4 - 31.4 - 29.23 - 28.69 - 26 - - - external - O.8 - 30.2 - 28.03 - 27.49 - - - internal - O.8 - 31.8 - 29.63 - 29.09 - 26 - - - - - 32 - - M32x3.5 - M32x3.5 - 3.5 - - external - O.0 - 32 - 30.48 - 30.11 - - - internal - O.0 - 32 - 30.48 - 30.11 - 28.5 - - - external - O.1 - 31.9 - 30.38 - 30.01 - - - internal - O.1 - 32.1 - 30.58 - 30.21 - 28.5 - - - external - O.2 - 31.8 - 30.28 - 29.91 - - - internal - O.2 - 32.2 - 30.68 - 30.31 - 28.5 - - - external - O.4 - 31.6 - 30.08 - 29.71 - - - internal - O.4 - 32.4 - 30.88 - 30.51 - 28.5 - - - external - O.8 - 31.2 - 29.68 - 29.31 - - - internal - O.8 - 32.8 - 31.28 - 30.91 - 28.5 - - - - M32x5 - M32x5 - 5.0 - - external - O.0 - 32 - 29.83 - 29.29 - - - internal - O.0 - 32 - 29.83 - 29.29 - 27 - - - external - O.1 - 31.9 - 29.73 - 29.19 - - - internal - O.1 - 32.1 - 29.93 - 29.39 - 27 - - - external - O.2 - 31.8 - 29.63 - 29.09 - - - internal - O.2 - 32.2 - 30.03 - 29.49 - 27 - - - external - O.4 - 31.6 - 29.43 - 28.89 - - - internal - O.4 - 32.4 - 30.23 - 29.69 - 27 - - - external - O.8 - 31.2 - 29.03 - 28.49 - - - internal - O.8 - 32.8 - 30.63 - 30.09 - 27 - - - - - 33 - - M33x3.5 - M33x3.5 - 3.5 - - external - O.0 - 33 - 31.48 - 31.11 - - - internal - O.0 - 33 - 31.48 - 31.11 - 29.5 - - - external - O.1 - 32.9 - 31.38 - 31.01 - - - internal - O.1 - 33.1 - 31.58 - 31.21 - 29.5 - - - external - O.2 - 32.8 - 31.28 - 30.91 - - - internal - O.2 - 33.2 - 31.68 - 31.31 - 29.5 - - - external - O.4 - 32.6 - 31.08 - 30.71 - - - internal - O.4 - 33.4 - 31.88 - 31.51 - 29.5 - - - external - O.8 - 32.2 - 30.68 - 30.31 - - - internal - O.8 - 33.8 - 32.28 - 31.91 - 29.5 - - - - M33x5 - M33x5 - 5.0 - - external - O.0 - 33 - 30.83 - 30.29 - - - internal - O.0 - 33 - 30.83 - 30.29 - 28 - - - external - O.1 - 32.9 - 30.73 - 30.19 - - - internal - O.1 - 33.1 - 30.93 - 30.39 - 28 - - - external - O.2 - 32.8 - 30.63 - 30.09 - - - internal - O.2 - 33.2 - 31.03 - 30.49 - 28 - - - external - O.4 - 32.6 - 30.43 - 29.89 - - - internal - O.4 - 33.4 - 31.23 - 30.69 - 28 - - - external - O.8 - 32.2 - 30.03 - 29.49 - - - internal - O.8 - 33.8 - 31.63 - 31.09 - 28 - - - - - 34 - - M34x3.5 - M34x3.5 - 3.5 - - external - O.0 - 34 - 32.48 - 32.11 - - - internal - O.0 - 34 - 32.48 - 32.11 - 30.5 - - - external - O.1 - 33.9 - 32.38 - 32.01 - - - internal - O.1 - 34.1 - 32.58 - 32.21 - 30.5 - - - external - O.2 - 33.8 - 32.28 - 31.91 - - - internal - O.2 - 34.2 - 32.68 - 32.31 - 30.5 - - - external - O.4 - 33.6 - 32.08 - 31.71 - - - internal - O.4 - 34.4 - 32.88 - 32.51 - 30.5 - - - external - O.8 - 33.2 - 31.68 - 31.31 - - - internal - O.8 - 34.8 - 33.28 - 32.91 - 30.5 - - - - M34x5 - M34x5 - 5.0 - - external - O.0 - 34 - 31.83 - 31.29 - - - internal - O.0 - 34 - 31.83 - 31.29 - 29 - - - external - O.1 - 33.9 - 31.73 - 31.19 - - - internal - O.1 - 34.1 - 31.93 - 31.39 - 29 - - - external - O.2 - 33.8 - 31.63 - 31.09 - - - internal - O.2 - 34.2 - 32.03 - 31.49 - 29 - - - external - O.4 - 33.6 - 31.43 - 30.89 - - - internal - O.4 - 34.4 - 32.23 - 31.69 - 29 - - - external - O.8 - 33.2 - 31.03 - 30.49 - - - internal - O.8 - 34.8 - 32.63 - 32.09 - 29 - - - - - 35 - - M35x3.5 - M35x3.5 - 3.5 - - external - O.0 - 35 - 33.48 - 33.11 - - - internal - O.0 - 35 - 33.48 - 33.11 - 31.5 - - - external - O.1 - 34.9 - 33.38 - 33.01 - - - internal - O.1 - 35.1 - 33.58 - 33.21 - 31.5 - - - external - O.2 - 34.8 - 33.28 - 32.91 - - - internal - O.2 - 35.2 - 33.68 - 33.31 - 31.5 - - - external - O.4 - 34.6 - 33.08 - 32.71 - - - internal - O.4 - 35.4 - 33.88 - 33.51 - 31.5 - - - external - O.8 - 34.2 - 32.68 - 32.31 - - - internal - O.8 - 35.8 - 34.28 - 33.91 - 31.5 - - - - M35x5 - M35x5 - 5.0 - - external - O.0 - 35 - 32.83 - 32.29 - - - internal - O.0 - 35 - 32.83 - 32.29 - 30 - - - external - O.1 - 34.9 - 32.73 - 32.19 - - - internal - O.1 - 35.1 - 32.93 - 32.39 - 30 - - - external - O.2 - 34.8 - 32.63 - 32.09 - - - internal - O.2 - 35.2 - 33.03 - 32.49 - 30 - - - external - O.4 - 34.6 - 32.43 - 31.89 - - - internal - O.4 - 35.4 - 33.23 - 32.69 - 30 - - - external - O.8 - 34.2 - 32.03 - 31.49 - - - internal - O.8 - 35.8 - 33.63 - 33.09 - 30 - - - - - 36 - - M36x3.5 - M36x3.5 - 3.5 - - external - O.0 - 36 - 34.48 - 34.11 - - - internal - O.0 - 36 - 34.48 - 34.11 - 32.5 - - - external - O.1 - 35.9 - 34.38 - 34.01 - - - internal - O.1 - 36.1 - 34.58 - 34.21 - 32.5 - - - external - O.2 - 35.8 - 34.28 - 33.91 - - - internal - O.2 - 36.2 - 34.68 - 34.31 - 32.5 - - - external - O.4 - 35.6 - 34.08 - 33.71 - - - internal - O.4 - 36.4 - 34.88 - 34.51 - 32.5 - - - external - O.8 - 35.2 - 33.68 - 33.31 - - - internal - O.8 - 36.8 - 35.28 - 34.91 - 32.5 - - - - M36x5 - M36x5 - 5.0 - - external - O.0 - 36 - 33.83 - 33.29 - - - internal - O.0 - 36 - 33.83 - 33.29 - 31 - - - external - O.1 - 35.9 - 33.73 - 33.19 - - - internal - O.1 - 36.1 - 33.93 - 33.39 - 31 - - - external - O.2 - 35.8 - 33.63 - 33.09 - - - internal - O.2 - 36.2 - 34.03 - 33.49 - 31 - - - external - O.4 - 35.6 - 33.43 - 32.89 - - - internal - O.4 - 36.4 - 34.23 - 33.69 - 31 - - - external - O.8 - 35.2 - 33.03 - 32.49 - - - internal - O.8 - 36.8 - 34.63 - 34.09 - 31 - - - - - 37 - - M37x3.5 - M37x3.5 - 3.5 - - external - O.0 - 37 - 35.48 - 35.11 - - - internal - O.0 - 37 - 35.48 - 35.11 - 33.5 - - - external - O.1 - 36.9 - 35.38 - 35.01 - - - internal - O.1 - 37.1 - 35.58 - 35.21 - 33.5 - - - external - O.2 - 36.8 - 35.28 - 34.91 - - - internal - O.2 - 37.2 - 35.68 - 35.31 - 33.5 - - - external - O.4 - 36.6 - 35.08 - 34.71 - - - internal - O.4 - 37.4 - 35.88 - 35.51 - 33.5 - - - external - O.8 - 36.2 - 34.68 - 34.31 - - - internal - O.8 - 37.8 - 36.28 - 35.91 - 33.5 - - - - M37x5 - M37x5 - 5.0 - - external - O.0 - 37 - 34.83 - 34.29 - - - internal - O.0 - 37 - 34.83 - 34.29 - 32 - - - external - O.1 - 36.9 - 34.73 - 34.19 - - - internal - O.1 - 37.1 - 34.93 - 34.39 - 32 - - - external - O.2 - 36.8 - 34.63 - 34.09 - - - internal - O.2 - 37.2 - 35.03 - 34.49 - 32 - - - external - O.4 - 36.6 - 34.43 - 33.89 - - - internal - O.4 - 37.4 - 35.23 - 34.69 - 32 - - - external - O.8 - 36.2 - 34.03 - 33.49 - - - internal - O.8 - 37.8 - 35.63 - 35.09 - 32 - - - - - 38 - - M38x3.5 - M38x3.5 - 3.5 - - external - O.0 - 38 - 36.48 - 36.11 - - - internal - O.0 - 38 - 36.48 - 36.11 - 34.5 - - - external - O.1 - 37.9 - 36.38 - 36.01 - - - internal - O.1 - 38.1 - 36.58 - 36.21 - 34.5 - - - external - O.2 - 37.8 - 36.28 - 35.91 - - - internal - O.2 - 38.2 - 36.68 - 36.31 - 34.5 - - - external - O.4 - 37.6 - 36.08 - 35.71 - - - internal - O.4 - 38.4 - 36.88 - 36.51 - 34.5 - - - external - O.8 - 37.2 - 35.68 - 35.31 - - - internal - O.8 - 38.8 - 37.28 - 36.91 - 34.5 - - - - M38x5 - M38x5 - 5.0 - - external - O.0 - 38 - 35.83 - 35.29 - - - internal - O.0 - 38 - 35.83 - 35.29 - 33 - - - external - O.1 - 37.9 - 35.73 - 35.19 - - - internal - O.1 - 38.1 - 35.93 - 35.39 - 33 - - - external - O.2 - 37.8 - 35.63 - 35.09 - - - internal - O.2 - 38.2 - 36.03 - 35.49 - 33 - - - external - O.4 - 37.6 - 35.43 - 34.89 - - - internal - O.4 - 38.4 - 36.23 - 35.69 - 33 - - - external - O.8 - 37.2 - 35.03 - 34.49 - - - internal - O.8 - 38.8 - 36.63 - 36.09 - 33 - - - - - 39 - - M39x3.5 - M39x3.5 - 3.5 - - external - O.0 - 39 - 37.48 - 37.11 - - - internal - O.0 - 39 - 37.48 - 37.11 - 35.5 - - - external - O.1 - 38.9 - 37.38 - 37.01 - - - internal - O.1 - 39.1 - 37.58 - 37.21 - 35.5 - - - external - O.2 - 38.8 - 37.28 - 36.91 - - - internal - O.2 - 39.2 - 37.68 - 37.31 - 35.5 - - - external - O.4 - 38.6 - 37.08 - 36.71 - - - internal - O.4 - 39.4 - 37.88 - 37.51 - 35.5 - - - external - O.8 - 38.2 - 36.68 - 36.31 - - - internal - O.8 - 39.8 - 38.28 - 37.91 - 35.5 - - - - M39x5 - M39x5 - 5.0 - - external - O.0 - 39 - 36.83 - 36.29 - - - internal - O.0 - 39 - 36.83 - 36.29 - 34 - - - external - O.1 - 38.9 - 36.73 - 36.19 - - - internal - O.1 - 39.1 - 36.93 - 36.39 - 34 - - - external - O.2 - 38.8 - 36.63 - 36.09 - - - internal - O.2 - 39.2 - 37.03 - 36.49 - 34 - - - external - O.4 - 38.6 - 36.43 - 35.89 - - - internal - O.4 - 39.4 - 37.23 - 36.69 - 34 - - - external - O.8 - 38.2 - 36.03 - 35.49 - - - internal - O.8 - 39.8 - 37.63 - 37.09 - 34 - - - - - 40 - - M40x3.5 - M40x3.5 - 3.5 - - external - O.0 - 40 - 38.48 - 38.11 - - - internal - O.0 - 40 - 38.48 - 38.11 - 36.5 - - - external - O.1 - 39.9 - 38.38 - 38.01 - - - internal - O.1 - 40.1 - 38.58 - 38.21 - 36.5 - - - external - O.2 - 39.8 - 38.28 - 37.91 - - - internal - O.2 - 40.2 - 38.68 - 38.31 - 36.5 - - - external - O.4 - 39.6 - 38.08 - 37.71 - - - internal - O.4 - 40.4 - 38.88 - 38.51 - 36.5 - - - external - O.8 - 39.2 - 37.68 - 37.31 - - - internal - O.8 - 40.8 - 39.28 - 38.91 - 36.5 - - - - M40x5 - M40x5 - 5.0 - - external - O.0 - 40 - 37.83 - 37.29 - - - internal - O.0 - 40 - 37.83 - 37.29 - 35 - - - external - O.1 - 39.9 - 37.73 - 37.19 - - - internal - O.1 - 40.1 - 37.93 - 37.39 - 35 - - - external - O.2 - 39.8 - 37.63 - 37.09 - - - internal - O.2 - 40.2 - 38.03 - 37.49 - 35 - - - external - O.4 - 39.6 - 37.43 - 36.89 - - - internal - O.4 - 40.4 - 38.23 - 37.69 - 35 - - - external - O.8 - 39.2 - 37.03 - 36.49 - - - internal - O.8 - 40.8 - 38.63 - 38.09 - 35 - - - - - 41 - - M41x3.5 - M41x3.5 - 3.5 - - external - O.0 - 41 - 39.48 - 39.11 - - - internal - O.0 - 41 - 39.48 - 39.11 - 37.5 - - - external - O.1 - 40.9 - 39.38 - 39.01 - - - internal - O.1 - 41.1 - 39.58 - 39.21 - 37.5 - - - external - O.2 - 40.8 - 39.28 - 38.91 - - - internal - O.2 - 41.2 - 39.68 - 39.31 - 37.5 - - - external - O.4 - 40.6 - 39.08 - 38.71 - - - internal - O.4 - 41.4 - 39.88 - 39.51 - 37.5 - - - external - O.8 - 40.2 - 38.68 - 38.31 - - - internal - O.8 - 41.8 - 40.28 - 39.91 - 37.5 - - - - M41x5 - M41x5 - 5.0 - - external - O.0 - 41 - 38.83 - 38.29 - - - internal - O.0 - 41 - 38.83 - 38.29 - 36 - - - external - O.1 - 40.9 - 38.73 - 38.19 - - - internal - O.1 - 41.1 - 38.93 - 38.39 - 36 - - - external - O.2 - 40.8 - 38.63 - 38.09 - - - internal - O.2 - 41.2 - 39.03 - 38.49 - 36 - - - external - O.4 - 40.6 - 38.43 - 37.89 - - - internal - O.4 - 41.4 - 39.23 - 38.69 - 36 - - - external - O.8 - 40.2 - 38.03 - 37.49 - - - internal - O.8 - 41.8 - 39.63 - 39.09 - 36 - - - - - 42 - - M42x3.5 - M42x3.5 - 3.5 - - external - O.0 - 42 - 40.48 - 40.11 - - - internal - O.0 - 42 - 40.48 - 40.11 - 38.5 - - - external - O.1 - 41.9 - 40.38 - 40.01 - - - internal - O.1 - 42.1 - 40.58 - 40.21 - 38.5 - - - external - O.2 - 41.8 - 40.28 - 39.91 - - - internal - O.2 - 42.2 - 40.68 - 40.31 - 38.5 - - - external - O.4 - 41.6 - 40.08 - 39.71 - - - internal - O.4 - 42.4 - 40.88 - 40.51 - 38.5 - - - external - O.8 - 41.2 - 39.68 - 39.31 - - - internal - O.8 - 42.8 - 41.28 - 40.91 - 38.5 - - - - M42x5 - M42x5 - 5.0 - - external - O.0 - 42 - 39.83 - 39.29 - - - internal - O.0 - 42 - 39.83 - 39.29 - 37 - - - external - O.1 - 41.9 - 39.73 - 39.19 - - - internal - O.1 - 42.1 - 39.93 - 39.39 - 37 - - - external - O.2 - 41.8 - 39.63 - 39.09 - - - internal - O.2 - 42.2 - 40.03 - 39.49 - 37 - - - external - O.4 - 41.6 - 39.43 - 38.89 - - - internal - O.4 - 42.4 - 40.23 - 39.69 - 37 - - - external - O.8 - 41.2 - 39.03 - 38.49 - - - internal - O.8 - 42.8 - 40.63 - 40.09 - 37 - - - - - 43 - - M43x3.5 - M43x3.5 - 3.5 - - external - O.0 - 43 - 41.48 - 41.11 - - - internal - O.0 - 43 - 41.48 - 41.11 - 39.5 - - - external - O.1 - 42.9 - 41.38 - 41.01 - - - internal - O.1 - 43.1 - 41.58 - 41.21 - 39.5 - - - external - O.2 - 42.8 - 41.28 - 40.91 - - - internal - O.2 - 43.2 - 41.68 - 41.31 - 39.5 - - - external - O.4 - 42.6 - 41.08 - 40.71 - - - internal - O.4 - 43.4 - 41.88 - 41.51 - 39.5 - - - external - O.8 - 42.2 - 40.68 - 40.31 - - - internal - O.8 - 43.8 - 42.28 - 41.91 - 39.5 - - - - M43x5 - M43x5 - 5.0 - - external - O.0 - 43 - 40.83 - 40.29 - - - internal - O.0 - 43 - 40.83 - 40.29 - 38 - - - external - O.1 - 42.9 - 40.73 - 40.19 - - - internal - O.1 - 43.1 - 40.93 - 40.39 - 38 - - - external - O.2 - 42.8 - 40.63 - 40.09 - - - internal - O.2 - 43.2 - 41.03 - 40.49 - 38 - - - external - O.4 - 42.6 - 40.43 - 39.89 - - - internal - O.4 - 43.4 - 41.23 - 40.69 - 38 - - - external - O.8 - 42.2 - 40.03 - 39.49 - - - internal - O.8 - 43.8 - 41.63 - 41.09 - 38 - - - - - 44 - - M44x3.5 - M44x3.5 - 3.5 - - external - O.0 - 44 - 42.48 - 42.11 - - - internal - O.0 - 44 - 42.48 - 42.11 - 40.5 - - - external - O.1 - 43.9 - 42.38 - 42.01 - - - internal - O.1 - 44.1 - 42.58 - 42.21 - 40.5 - - - external - O.2 - 43.8 - 42.28 - 41.91 - - - internal - O.2 - 44.2 - 42.68 - 42.31 - 40.5 - - - external - O.4 - 43.6 - 42.08 - 41.71 - - - internal - O.4 - 44.4 - 42.88 - 42.51 - 40.5 - - - external - O.8 - 43.2 - 41.68 - 41.31 - - - internal - O.8 - 44.8 - 43.28 - 42.91 - 40.5 - - - - M44x5 - M44x5 - 5.0 - - external - O.0 - 44 - 41.83 - 41.29 - - - internal - O.0 - 44 - 41.83 - 41.29 - 39 - - - external - O.1 - 43.9 - 41.73 - 41.19 - - - internal - O.1 - 44.1 - 41.93 - 41.39 - 39 - - - external - O.2 - 43.8 - 41.63 - 41.09 - - - internal - O.2 - 44.2 - 42.03 - 41.49 - 39 - - - external - O.4 - 43.6 - 41.43 - 40.89 - - - internal - O.4 - 44.4 - 42.23 - 41.69 - 39 - - - external - O.8 - 43.2 - 41.03 - 40.49 - - - internal - O.8 - 44.8 - 42.63 - 42.09 - 39 - - - - - 45 - - M45x3.5 - M45x3.5 - 3.5 - - external - O.0 - 45 - 43.48 - 43.11 - - - internal - O.0 - 45 - 43.48 - 43.11 - 41.5 - - - external - O.1 - 44.9 - 43.38 - 43.01 - - - internal - O.1 - 45.1 - 43.58 - 43.21 - 41.5 - - - external - O.2 - 44.8 - 43.28 - 42.91 - - - internal - O.2 - 45.2 - 43.68 - 43.31 - 41.5 - - - external - O.4 - 44.6 - 43.08 - 42.71 - - - internal - O.4 - 45.4 - 43.88 - 43.51 - 41.5 - - - external - O.8 - 44.2 - 42.68 - 42.31 - - - internal - O.8 - 45.8 - 44.28 - 43.91 - 41.5 - - - - M45x5 - M45x5 - 5.0 - - external - O.0 - 45 - 42.83 - 42.29 - - - internal - O.0 - 45 - 42.83 - 42.29 - 40 - - - external - O.1 - 44.9 - 42.73 - 42.19 - - - internal - O.1 - 45.1 - 42.93 - 42.39 - 40 - - - external - O.2 - 44.8 - 42.63 - 42.09 - - - internal - O.2 - 45.2 - 43.03 - 42.49 - 40 - - - external - O.4 - 44.6 - 42.43 - 41.89 - - - internal - O.4 - 45.4 - 43.23 - 42.69 - 40 - - - external - O.8 - 44.2 - 42.03 - 41.49 - - - internal - O.8 - 45.8 - 43.63 - 43.09 - 40 - - - - - 46 - - M46x3.5 - M46x3.5 - 3.5 - - external - O.0 - 46 - 44.48 - 44.11 - - - internal - O.0 - 46 - 44.48 - 44.11 - 42.5 - - - external - O.1 - 45.9 - 44.38 - 44.01 - - - internal - O.1 - 46.1 - 44.58 - 44.21 - 42.5 - - - external - O.2 - 45.8 - 44.28 - 43.91 - - - internal - O.2 - 46.2 - 44.68 - 44.31 - 42.5 - - - external - O.4 - 45.6 - 44.08 - 43.71 - - - internal - O.4 - 46.4 - 44.88 - 44.51 - 42.5 - - - external - O.8 - 45.2 - 43.68 - 43.31 - - - internal - O.8 - 46.8 - 45.28 - 44.91 - 42.5 - - - - M46x5 - M46x5 - 5.0 - - external - O.0 - 46 - 43.83 - 43.29 - - - internal - O.0 - 46 - 43.83 - 43.29 - 41 - - - external - O.1 - 45.9 - 43.73 - 43.19 - - - internal - O.1 - 46.1 - 43.93 - 43.39 - 41 - - - external - O.2 - 45.8 - 43.63 - 43.09 - - - internal - O.2 - 46.2 - 44.03 - 43.49 - 41 - - - external - O.4 - 45.6 - 43.43 - 42.89 - - - internal - O.4 - 46.4 - 44.23 - 43.69 - 41 - - - external - O.8 - 45.2 - 43.03 - 42.49 - - - internal - O.8 - 46.8 - 44.63 - 44.09 - 41 - - - - - 47 - - M47x3.5 - M47x3.5 - 3.5 - - external - O.0 - 47 - 45.48 - 45.11 - - - internal - O.0 - 47 - 45.48 - 45.11 - 43.5 - - - external - O.1 - 46.9 - 45.38 - 45.01 - - - internal - O.1 - 47.1 - 45.58 - 45.21 - 43.5 - - - external - O.2 - 46.8 - 45.28 - 44.91 - - - internal - O.2 - 47.2 - 45.68 - 45.31 - 43.5 - - - external - O.4 - 46.6 - 45.08 - 44.71 - - - internal - O.4 - 47.4 - 45.88 - 45.51 - 43.5 - - - external - O.8 - 46.2 - 44.68 - 44.31 - - - internal - O.8 - 47.8 - 46.28 - 45.91 - 43.5 - - - - M47x5 - M47x5 - 5.0 - - external - O.0 - 47 - 44.83 - 44.29 - - - internal - O.0 - 47 - 44.83 - 44.29 - 42 - - - external - O.1 - 46.9 - 44.73 - 44.19 - - - internal - O.1 - 47.1 - 44.93 - 44.39 - 42 - - - external - O.2 - 46.8 - 44.63 - 44.09 - - - internal - O.2 - 47.2 - 45.03 - 44.49 - 42 - - - external - O.4 - 46.6 - 44.43 - 43.89 - - - internal - O.4 - 47.4 - 45.23 - 44.69 - 42 - - - external - O.8 - 46.2 - 44.03 - 43.49 - - - internal - O.8 - 47.8 - 45.63 - 45.09 - 42 - - - - - 48 - - M48x3.5 - M48x3.5 - 3.5 - - external - O.0 - 48 - 46.48 - 46.11 - - - internal - O.0 - 48 - 46.48 - 46.11 - 44.5 - - - external - O.1 - 47.9 - 46.38 - 46.01 - - - internal - O.1 - 48.1 - 46.58 - 46.21 - 44.5 - - - external - O.2 - 47.8 - 46.28 - 45.91 - - - internal - O.2 - 48.2 - 46.68 - 46.31 - 44.5 - - - external - O.4 - 47.6 - 46.08 - 45.71 - - - internal - O.4 - 48.4 - 46.88 - 46.51 - 44.5 - - - external - O.8 - 47.2 - 45.68 - 45.31 - - - internal - O.8 - 48.8 - 47.28 - 46.91 - 44.5 - - - - M48x5 - M48x5 - 5.0 - - external - O.0 - 48 - 45.83 - 45.29 - - - internal - O.0 - 48 - 45.83 - 45.29 - 43 - - - external - O.1 - 47.9 - 45.73 - 45.19 - - - internal - O.1 - 48.1 - 45.93 - 45.39 - 43 - - - external - O.2 - 47.8 - 45.63 - 45.09 - - - internal - O.2 - 48.2 - 46.03 - 45.49 - 43 - - - external - O.4 - 47.6 - 45.43 - 44.89 - - - internal - O.4 - 48.4 - 46.23 - 45.69 - 43 - - - external - O.8 - 47.2 - 45.03 - 44.49 - - - internal - O.8 - 48.8 - 46.63 - 46.09 - 43 - - - - - 49 - - M49x3.5 - M49x3.5 - 3.5 - - external - O.0 - 49 - 47.48 - 47.11 - - - internal - O.0 - 49 - 47.48 - 47.11 - 45.5 - - - external - O.1 - 48.9 - 47.38 - 47.01 - - - internal - O.1 - 49.1 - 47.58 - 47.21 - 45.5 - - - external - O.2 - 48.8 - 47.28 - 46.91 - - - internal - O.2 - 49.2 - 47.68 - 47.31 - 45.5 - - - external - O.4 - 48.6 - 47.08 - 46.71 - - - internal - O.4 - 49.4 - 47.88 - 47.51 - 45.5 - - - external - O.8 - 48.2 - 46.68 - 46.31 - - - internal - O.8 - 49.8 - 48.28 - 47.91 - 45.5 - - - - M49x5 - M49x5 - 5.0 - - external - O.0 - 49 - 46.83 - 46.29 - - - internal - O.0 - 49 - 46.83 - 46.29 - 44 - - - external - O.1 - 48.9 - 46.73 - 46.19 - - - internal - O.1 - 49.1 - 46.93 - 46.39 - 44 - - - external - O.2 - 48.8 - 46.63 - 46.09 - - - internal - O.2 - 49.2 - 47.03 - 46.49 - 44 - - - external - O.4 - 48.6 - 46.43 - 45.89 - - - internal - O.4 - 49.4 - 47.23 - 46.69 - 44 - - - external - O.8 - 48.2 - 46.03 - 45.49 - - - internal - O.8 - 49.8 - 47.63 - 47.09 - 44 - - - - - 50 - - M50x3.5 - M50x3.5 - 3.5 - - external - O.0 - 50 - 48.48 - 48.11 - - - internal - O.0 - 50 - 48.48 - 48.11 - 46.5 - - - external - O.1 - 49.9 - 48.38 - 48.01 - - - internal - O.1 - 50.1 - 48.58 - 48.21 - 46.5 - - - external - O.2 - 49.8 - 48.28 - 47.91 - - - internal - O.2 - 50.2 - 48.68 - 48.31 - 46.5 - - - external - O.4 - 49.6 - 48.08 - 47.71 - - - internal - O.4 - 50.4 - 48.88 - 48.51 - 46.5 - - - external - O.8 - 49.2 - 47.68 - 47.31 - - - internal - O.8 - 50.8 - 49.28 - 48.91 - 46.5 - - - - M50x5 - M50x5 - 5.0 - - external - O.0 - 50 - 47.83 - 47.29 - - - internal - O.0 - 50 - 47.83 - 47.29 - 45 - - - external - O.1 - 49.9 - 47.73 - 47.19 - - - internal - O.1 - 50.1 - 47.93 - 47.39 - 45 - - - external - O.2 - 49.8 - 47.63 - 47.09 - - - internal - O.2 - 50.2 - 48.03 - 47.49 - 45 - - - external - O.4 - 49.6 - 47.43 - 46.89 - - - internal - O.4 - 50.4 - 48.23 - 47.69 - 45 - - - external - O.8 - 49.2 - 47.03 - 46.49 - - - internal - O.8 - 50.8 - 48.63 - 48.09 - 45 - - - - \ No newline at end of file diff --git a/3DPrintedMetricV3.xml b/3DPrintedMetricV3.xml deleted file mode 100644 index a3524a9..0000000 --- a/3DPrintedMetricV3.xml +++ /dev/null @@ -1,7018 +0,0 @@ - - - 3D-printed Metric Threads V3 - 3D-printed Metric Threads V3 - mm - 60.0 - 8 - 3 - - 8 - - M8x3.5 - M8x3.5 - 3.5 - - external - O.0 - 8 - 5.727 - 4.211 - - - internal - O.0 - 8 - 5.727 - 4.211 - 4.5 - - - external - O.1 - 7.9 - 5.627 - 4.111 - - - internal - O.1 - 8.1 - 5.827 - 4.311 - 4.5 - - - external - O.2 - 7.8 - 5.527 - 4.011 - - - internal - O.2 - 8.2 - 5.927 - 4.411 - 4.5 - - - external - O.4 - 7.6 - 5.327 - 3.811 - - - internal - O.4 - 8.4 - 6.127 - 4.611 - 4.5 - - - external - O.8 - 7.2 - 4.927 - 3.411 - - - internal - O.8 - 8.8 - 6.527 - 5.011 - 4.5 - - - - M8x5 - M8x5 - 5.0 - - external - O.0 - 8 - 4.752 - 2.587 - - - internal - O.0 - 8 - 4.752 - 2.587 - 3 - - - external - O.1 - 7.9 - 4.652 - 2.487 - - - internal - O.1 - 8.1 - 4.852 - 2.687 - 3 - - - external - O.2 - 7.8 - 4.552 - 2.387 - - - internal - O.2 - 8.2 - 4.952 - 2.787 - 3 - - - external - O.4 - 7.6 - 4.352 - 2.187 - - - internal - O.4 - 8.4 - 5.152 - 2.987 - 3 - - - external - O.8 - 7.2 - 3.952 - 1.787 - - - internal - O.8 - 8.8 - 5.552 - 3.387 - 3 - - - - - 9 - - M9x3.5 - M9x3.5 - 3.5 - - external - O.0 - 9 - 6.727 - 5.211 - - - internal - O.0 - 9 - 6.727 - 5.211 - 5.5 - - - external - O.1 - 8.9 - 6.627 - 5.111 - - - internal - O.1 - 9.1 - 6.827 - 5.311 - 5.5 - - - external - O.2 - 8.8 - 6.527 - 5.011 - - - internal - O.2 - 9.2 - 6.927 - 5.411 - 5.5 - - - external - O.4 - 8.6 - 6.327 - 4.811 - - - internal - O.4 - 9.4 - 7.127 - 5.611 - 5.5 - - - external - O.8 - 8.2 - 5.927 - 4.411 - - - internal - O.8 - 9.8 - 7.527 - 6.011 - 5.5 - - - - M9x5 - M9x5 - 5.0 - - external - O.0 - 9 - 5.752 - 3.587 - - - internal - O.0 - 9 - 5.752 - 3.587 - 4 - - - external - O.1 - 8.9 - 5.652 - 3.487 - - - internal - O.1 - 9.1 - 5.852 - 3.687 - 4 - - - external - O.2 - 8.8 - 5.552 - 3.387 - - - internal - O.2 - 9.2 - 5.952 - 3.787 - 4 - - - external - O.4 - 8.6 - 5.352 - 3.187 - - - internal - O.4 - 9.4 - 6.152 - 3.987 - 4 - - - external - O.8 - 8.2 - 4.952 - 2.787 - - - internal - O.8 - 9.8 - 6.552 - 4.387 - 4 - - - - - 10 - - M10x3.5 - M10x3.5 - 3.5 - - external - O.0 - 10 - 7.727 - 6.211 - - - internal - O.0 - 10 - 7.727 - 6.211 - 6.5 - - - external - O.1 - 9.9 - 7.627 - 6.111 - - - internal - O.1 - 10.1 - 7.827 - 6.311 - 6.5 - - - external - O.2 - 9.8 - 7.527 - 6.011 - - - internal - O.2 - 10.2 - 7.927 - 6.411 - 6.5 - - - external - O.4 - 9.6 - 7.327 - 5.811 - - - internal - O.4 - 10.4 - 8.127 - 6.611 - 6.5 - - - external - O.8 - 9.2 - 6.927 - 5.411 - - - internal - O.8 - 10.8 - 8.527 - 7.011 - 6.5 - - - - M10x5 - M10x5 - 5.0 - - external - O.0 - 10 - 6.752 - 4.587 - - - internal - O.0 - 10 - 6.752 - 4.587 - 5 - - - external - O.1 - 9.9 - 6.652 - 4.487 - - - internal - O.1 - 10.1 - 6.852 - 4.687 - 5 - - - external - O.2 - 9.8 - 6.552 - 4.387 - - - internal - O.2 - 10.2 - 6.952 - 4.787 - 5 - - - external - O.4 - 9.6 - 6.352 - 4.187 - - - internal - O.4 - 10.4 - 7.152 - 4.987 - 5 - - - external - O.8 - 9.2 - 5.952 - 3.787 - - - internal - O.8 - 10.8 - 7.552 - 5.387 - 5 - - - - - 11 - - M11x3.5 - M11x3.5 - 3.5 - - external - O.0 - 11 - 8.727 - 7.211 - - - internal - O.0 - 11 - 8.727 - 7.211 - 7.5 - - - external - O.1 - 10.9 - 8.627 - 7.111 - - - internal - O.1 - 11.1 - 8.827 - 7.311 - 7.5 - - - external - O.2 - 10.8 - 8.527 - 7.011 - - - internal - O.2 - 11.2 - 8.927 - 7.411 - 7.5 - - - external - O.4 - 10.6 - 8.327 - 6.811 - - - internal - O.4 - 11.4 - 9.127 - 7.611 - 7.5 - - - external - O.8 - 10.2 - 7.927 - 6.411 - - - internal - O.8 - 11.8 - 9.527 - 8.011 - 7.5 - - - - M11x5 - M11x5 - 5.0 - - external - O.0 - 11 - 7.752 - 5.587 - - - internal - O.0 - 11 - 7.752 - 5.587 - 6 - - - external - O.1 - 10.9 - 7.652 - 5.487 - - - internal - O.1 - 11.1 - 7.852 - 5.687 - 6 - - - external - O.2 - 10.8 - 7.552 - 5.387 - - - internal - O.2 - 11.2 - 7.952 - 5.787 - 6 - - - external - O.4 - 10.6 - 7.352 - 5.187 - - - internal - O.4 - 11.4 - 8.152 - 5.987 - 6 - - - external - O.8 - 10.2 - 6.952 - 4.787 - - - internal - O.8 - 11.8 - 8.552 - 6.387 - 6 - - - - - 12 - - M12x3.5 - M12x3.5 - 3.5 - - external - O.0 - 12 - 9.727 - 8.211 - - - internal - O.0 - 12 - 9.727 - 8.211 - 8.5 - - - external - O.1 - 11.9 - 9.627 - 8.111 - - - internal - O.1 - 12.1 - 9.827 - 8.311 - 8.5 - - - external - O.2 - 11.8 - 9.527 - 8.011 - - - internal - O.2 - 12.2 - 9.927 - 8.411 - 8.5 - - - external - O.4 - 11.6 - 9.327 - 7.811 - - - internal - O.4 - 12.4 - 10.13 - 8.611 - 8.5 - - - external - O.8 - 11.2 - 8.927 - 7.411 - - - internal - O.8 - 12.8 - 10.53 - 9.011 - 8.5 - - - - M12x5 - M12x5 - 5.0 - - external - O.0 - 12 - 8.752 - 6.587 - - - internal - O.0 - 12 - 8.752 - 6.587 - 7 - - - external - O.1 - 11.9 - 8.652 - 6.487 - - - internal - O.1 - 12.1 - 8.852 - 6.687 - 7 - - - external - O.2 - 11.8 - 8.552 - 6.387 - - - internal - O.2 - 12.2 - 8.952 - 6.787 - 7 - - - external - O.4 - 11.6 - 8.352 - 6.187 - - - internal - O.4 - 12.4 - 9.152 - 6.987 - 7 - - - external - O.8 - 11.2 - 7.952 - 5.787 - - - internal - O.8 - 12.8 - 9.552 - 7.387 - 7 - - - - - 13 - - M13x3.5 - M13x3.5 - 3.5 - - external - O.0 - 13 - 10.73 - 9.211 - - - internal - O.0 - 13 - 10.73 - 9.211 - 9.5 - - - external - O.1 - 12.9 - 10.63 - 9.111 - - - internal - O.1 - 13.1 - 10.83 - 9.311 - 9.5 - - - external - O.2 - 12.8 - 10.53 - 9.011 - - - internal - O.2 - 13.2 - 10.93 - 9.411 - 9.5 - - - external - O.4 - 12.6 - 10.33 - 8.811 - - - internal - O.4 - 13.4 - 11.13 - 9.611 - 9.5 - - - external - O.8 - 12.2 - 9.927 - 8.411 - - - internal - O.8 - 13.8 - 11.53 - 10.01 - 9.5 - - - - M13x5 - M13x5 - 5.0 - - external - O.0 - 13 - 9.752 - 7.587 - - - internal - O.0 - 13 - 9.752 - 7.587 - 8 - - - external - O.1 - 12.9 - 9.652 - 7.487 - - - internal - O.1 - 13.1 - 9.852 - 7.687 - 8 - - - external - O.2 - 12.8 - 9.552 - 7.387 - - - internal - O.2 - 13.2 - 9.952 - 7.787 - 8 - - - external - O.4 - 12.6 - 9.352 - 7.187 - - - internal - O.4 - 13.4 - 10.15 - 7.987 - 8 - - - external - O.8 - 12.2 - 8.952 - 6.787 - - - internal - O.8 - 13.8 - 10.55 - 8.387 - 8 - - - - - 14 - - M14x3.5 - M14x3.5 - 3.5 - - external - O.0 - 14 - 11.73 - 10.21 - - - internal - O.0 - 14 - 11.73 - 10.21 - 10.5 - - - external - O.1 - 13.9 - 11.63 - 10.11 - - - internal - O.1 - 14.1 - 11.83 - 10.31 - 10.5 - - - external - O.2 - 13.8 - 11.53 - 10.01 - - - internal - O.2 - 14.2 - 11.93 - 10.41 - 10.5 - - - external - O.4 - 13.6 - 11.33 - 9.811 - - - internal - O.4 - 14.4 - 12.13 - 10.61 - 10.5 - - - external - O.8 - 13.2 - 10.93 - 9.411 - - - internal - O.8 - 14.8 - 12.53 - 11.01 - 10.5 - - - - M14x5 - M14x5 - 5.0 - - external - O.0 - 14 - 10.75 - 8.587 - - - internal - O.0 - 14 - 10.75 - 8.587 - 9 - - - external - O.1 - 13.9 - 10.65 - 8.487 - - - internal - O.1 - 14.1 - 10.85 - 8.687 - 9 - - - external - O.2 - 13.8 - 10.55 - 8.387 - - - internal - O.2 - 14.2 - 10.95 - 8.787 - 9 - - - external - O.4 - 13.6 - 10.35 - 8.187 - - - internal - O.4 - 14.4 - 11.15 - 8.987 - 9 - - - external - O.8 - 13.2 - 9.952 - 7.787 - - - internal - O.8 - 14.8 - 11.55 - 9.387 - 9 - - - - - 15 - - M15x3.5 - M15x3.5 - 3.5 - - external - O.0 - 15 - 12.73 - 11.21 - - - internal - O.0 - 15 - 12.73 - 11.21 - 11.5 - - - external - O.1 - 14.9 - 12.63 - 11.11 - - - internal - O.1 - 15.1 - 12.83 - 11.31 - 11.5 - - - external - O.2 - 14.8 - 12.53 - 11.01 - - - internal - O.2 - 15.2 - 12.93 - 11.41 - 11.5 - - - external - O.4 - 14.6 - 12.33 - 10.81 - - - internal - O.4 - 15.4 - 13.13 - 11.61 - 11.5 - - - external - O.8 - 14.2 - 11.93 - 10.41 - - - internal - O.8 - 15.8 - 13.53 - 12.01 - 11.5 - - - - M15x5 - M15x5 - 5.0 - - external - O.0 - 15 - 11.75 - 9.587 - - - internal - O.0 - 15 - 11.75 - 9.587 - 10 - - - external - O.1 - 14.9 - 11.65 - 9.487 - - - internal - O.1 - 15.1 - 11.85 - 9.687 - 10 - - - external - O.2 - 14.8 - 11.55 - 9.387 - - - internal - O.2 - 15.2 - 11.95 - 9.787 - 10 - - - external - O.4 - 14.6 - 11.35 - 9.187 - - - internal - O.4 - 15.4 - 12.15 - 9.987 - 10 - - - external - O.8 - 14.2 - 10.95 - 8.787 - - - internal - O.8 - 15.8 - 12.55 - 10.39 - 10 - - - - - 16 - - M16x3.5 - M16x3.5 - 3.5 - - external - O.0 - 16 - 13.73 - 12.21 - - - internal - O.0 - 16 - 13.73 - 12.21 - 12.5 - - - external - O.1 - 15.9 - 13.63 - 12.11 - - - internal - O.1 - 16.1 - 13.83 - 12.31 - 12.5 - - - external - O.2 - 15.8 - 13.53 - 12.01 - - - internal - O.2 - 16.2 - 13.93 - 12.41 - 12.5 - - - external - O.4 - 15.6 - 13.33 - 11.81 - - - internal - O.4 - 16.4 - 14.13 - 12.61 - 12.5 - - - external - O.8 - 15.2 - 12.93 - 11.41 - - - internal - O.8 - 16.8 - 14.53 - 13.01 - 12.5 - - - - M16x5 - M16x5 - 5.0 - - external - O.0 - 16 - 12.75 - 10.59 - - - internal - O.0 - 16 - 12.75 - 10.59 - 11 - - - external - O.1 - 15.9 - 12.65 - 10.49 - - - internal - O.1 - 16.1 - 12.85 - 10.69 - 11 - - - external - O.2 - 15.8 - 12.55 - 10.39 - - - internal - O.2 - 16.2 - 12.95 - 10.79 - 11 - - - external - O.4 - 15.6 - 12.35 - 10.19 - - - internal - O.4 - 16.4 - 13.15 - 10.99 - 11 - - - external - O.8 - 15.2 - 11.95 - 9.787 - - - internal - O.8 - 16.8 - 13.55 - 11.39 - 11 - - - - - 17 - - M17x3.5 - M17x3.5 - 3.5 - - external - O.0 - 17 - 14.73 - 13.21 - - - internal - O.0 - 17 - 14.73 - 13.21 - 13.5 - - - external - O.1 - 16.9 - 14.63 - 13.11 - - - internal - O.1 - 17.1 - 14.83 - 13.31 - 13.5 - - - external - O.2 - 16.8 - 14.53 - 13.01 - - - internal - O.2 - 17.2 - 14.93 - 13.41 - 13.5 - - - external - O.4 - 16.6 - 14.33 - 12.81 - - - internal - O.4 - 17.4 - 15.13 - 13.61 - 13.5 - - - external - O.8 - 16.2 - 13.93 - 12.41 - - - internal - O.8 - 17.8 - 15.53 - 14.01 - 13.5 - - - - M17x5 - M17x5 - 5.0 - - external - O.0 - 17 - 13.75 - 11.59 - - - internal - O.0 - 17 - 13.75 - 11.59 - 12 - - - external - O.1 - 16.9 - 13.65 - 11.49 - - - internal - O.1 - 17.1 - 13.85 - 11.69 - 12 - - - external - O.2 - 16.8 - 13.55 - 11.39 - - - internal - O.2 - 17.2 - 13.95 - 11.79 - 12 - - - external - O.4 - 16.6 - 13.35 - 11.19 - - - internal - O.4 - 17.4 - 14.15 - 11.99 - 12 - - - external - O.8 - 16.2 - 12.95 - 10.79 - - - internal - O.8 - 17.8 - 14.55 - 12.39 - 12 - - - - - 18 - - M18x3.5 - M18x3.5 - 3.5 - - external - O.0 - 18 - 15.73 - 14.21 - - - internal - O.0 - 18 - 15.73 - 14.21 - 14.5 - - - external - O.1 - 17.9 - 15.63 - 14.11 - - - internal - O.1 - 18.1 - 15.83 - 14.31 - 14.5 - - - external - O.2 - 17.8 - 15.53 - 14.01 - - - internal - O.2 - 18.2 - 15.93 - 14.41 - 14.5 - - - external - O.4 - 17.6 - 15.33 - 13.81 - - - internal - O.4 - 18.4 - 16.13 - 14.61 - 14.5 - - - external - O.8 - 17.2 - 14.93 - 13.41 - - - internal - O.8 - 18.8 - 16.53 - 15.01 - 14.5 - - - - M18x5 - M18x5 - 5.0 - - external - O.0 - 18 - 14.75 - 12.59 - - - internal - O.0 - 18 - 14.75 - 12.59 - 13 - - - external - O.1 - 17.9 - 14.65 - 12.49 - - - internal - O.1 - 18.1 - 14.85 - 12.69 - 13 - - - external - O.2 - 17.8 - 14.55 - 12.39 - - - internal - O.2 - 18.2 - 14.95 - 12.79 - 13 - - - external - O.4 - 17.6 - 14.35 - 12.19 - - - internal - O.4 - 18.4 - 15.15 - 12.99 - 13 - - - external - O.8 - 17.2 - 13.95 - 11.79 - - - internal - O.8 - 18.8 - 15.55 - 13.39 - 13 - - - - - 19 - - M19x3.5 - M19x3.5 - 3.5 - - external - O.0 - 19 - 16.73 - 15.21 - - - internal - O.0 - 19 - 16.73 - 15.21 - 15.5 - - - external - O.1 - 18.9 - 16.63 - 15.11 - - - internal - O.1 - 19.1 - 16.83 - 15.31 - 15.5 - - - external - O.2 - 18.8 - 16.53 - 15.01 - - - internal - O.2 - 19.2 - 16.93 - 15.41 - 15.5 - - - external - O.4 - 18.6 - 16.33 - 14.81 - - - internal - O.4 - 19.4 - 17.13 - 15.61 - 15.5 - - - external - O.8 - 18.2 - 15.93 - 14.41 - - - internal - O.8 - 19.8 - 17.53 - 16.01 - 15.5 - - - - M19x5 - M19x5 - 5.0 - - external - O.0 - 19 - 15.75 - 13.59 - - - internal - O.0 - 19 - 15.75 - 13.59 - 14 - - - external - O.1 - 18.9 - 15.65 - 13.49 - - - internal - O.1 - 19.1 - 15.85 - 13.69 - 14 - - - external - O.2 - 18.8 - 15.55 - 13.39 - - - internal - O.2 - 19.2 - 15.95 - 13.79 - 14 - - - external - O.4 - 18.6 - 15.35 - 13.19 - - - internal - O.4 - 19.4 - 16.15 - 13.99 - 14 - - - external - O.8 - 18.2 - 14.95 - 12.79 - - - internal - O.8 - 19.8 - 16.55 - 14.39 - 14 - - - - - 20 - - M20x3.5 - M20x3.5 - 3.5 - - external - O.0 - 20 - 17.73 - 16.21 - - - internal - O.0 - 20 - 17.73 - 16.21 - 16.5 - - - external - O.1 - 19.9 - 17.63 - 16.11 - - - internal - O.1 - 20.1 - 17.83 - 16.31 - 16.5 - - - external - O.2 - 19.8 - 17.53 - 16.01 - - - internal - O.2 - 20.2 - 17.93 - 16.41 - 16.5 - - - external - O.4 - 19.6 - 17.33 - 15.81 - - - internal - O.4 - 20.4 - 18.13 - 16.61 - 16.5 - - - external - O.8 - 19.2 - 16.93 - 15.41 - - - internal - O.8 - 20.8 - 18.53 - 17.01 - 16.5 - - - - M20x5 - M20x5 - 5.0 - - external - O.0 - 20 - 16.75 - 14.59 - - - internal - O.0 - 20 - 16.75 - 14.59 - 15 - - - external - O.1 - 19.9 - 16.65 - 14.49 - - - internal - O.1 - 20.1 - 16.85 - 14.69 - 15 - - - external - O.2 - 19.8 - 16.55 - 14.39 - - - internal - O.2 - 20.2 - 16.95 - 14.79 - 15 - - - external - O.4 - 19.6 - 16.35 - 14.19 - - - internal - O.4 - 20.4 - 17.15 - 14.99 - 15 - - - external - O.8 - 19.2 - 15.95 - 13.79 - - - internal - O.8 - 20.8 - 17.55 - 15.39 - 15 - - - - - 21 - - M21x3.5 - M21x3.5 - 3.5 - - external - O.0 - 21 - 18.73 - 17.21 - - - internal - O.0 - 21 - 18.73 - 17.21 - 17.5 - - - external - O.1 - 20.9 - 18.63 - 17.11 - - - internal - O.1 - 21.1 - 18.83 - 17.31 - 17.5 - - - external - O.2 - 20.8 - 18.53 - 17.01 - - - internal - O.2 - 21.2 - 18.93 - 17.41 - 17.5 - - - external - O.4 - 20.6 - 18.33 - 16.81 - - - internal - O.4 - 21.4 - 19.13 - 17.61 - 17.5 - - - external - O.8 - 20.2 - 17.93 - 16.41 - - - internal - O.8 - 21.8 - 19.53 - 18.01 - 17.5 - - - - M21x5 - M21x5 - 5.0 - - external - O.0 - 21 - 17.75 - 15.59 - - - internal - O.0 - 21 - 17.75 - 15.59 - 16 - - - external - O.1 - 20.9 - 17.65 - 15.49 - - - internal - O.1 - 21.1 - 17.85 - 15.69 - 16 - - - external - O.2 - 20.8 - 17.55 - 15.39 - - - internal - O.2 - 21.2 - 17.95 - 15.79 - 16 - - - external - O.4 - 20.6 - 17.35 - 15.19 - - - internal - O.4 - 21.4 - 18.15 - 15.99 - 16 - - - external - O.8 - 20.2 - 16.95 - 14.79 - - - internal - O.8 - 21.8 - 18.55 - 16.39 - 16 - - - - - 22 - - M22x3.5 - M22x3.5 - 3.5 - - external - O.0 - 22 - 19.73 - 18.21 - - - internal - O.0 - 22 - 19.73 - 18.21 - 18.5 - - - external - O.1 - 21.9 - 19.63 - 18.11 - - - internal - O.1 - 22.1 - 19.83 - 18.31 - 18.5 - - - external - O.2 - 21.8 - 19.53 - 18.01 - - - internal - O.2 - 22.2 - 19.93 - 18.41 - 18.5 - - - external - O.4 - 21.6 - 19.33 - 17.81 - - - internal - O.4 - 22.4 - 20.13 - 18.61 - 18.5 - - - external - O.8 - 21.2 - 18.93 - 17.41 - - - internal - O.8 - 22.8 - 20.53 - 19.01 - 18.5 - - - - M22x5 - M22x5 - 5.0 - - external - O.0 - 22 - 18.75 - 16.59 - - - internal - O.0 - 22 - 18.75 - 16.59 - 17 - - - external - O.1 - 21.9 - 18.65 - 16.49 - - - internal - O.1 - 22.1 - 18.85 - 16.69 - 17 - - - external - O.2 - 21.8 - 18.55 - 16.39 - - - internal - O.2 - 22.2 - 18.95 - 16.79 - 17 - - - external - O.4 - 21.6 - 18.35 - 16.19 - - - internal - O.4 - 22.4 - 19.15 - 16.99 - 17 - - - external - O.8 - 21.2 - 17.95 - 15.79 - - - internal - O.8 - 22.8 - 19.55 - 17.39 - 17 - - - - - 23 - - M23x3.5 - M23x3.5 - 3.5 - - external - O.0 - 23 - 20.73 - 19.21 - - - internal - O.0 - 23 - 20.73 - 19.21 - 19.5 - - - external - O.1 - 22.9 - 20.63 - 19.11 - - - internal - O.1 - 23.1 - 20.83 - 19.31 - 19.5 - - - external - O.2 - 22.8 - 20.53 - 19.01 - - - internal - O.2 - 23.2 - 20.93 - 19.41 - 19.5 - - - external - O.4 - 22.6 - 20.33 - 18.81 - - - internal - O.4 - 23.4 - 21.13 - 19.61 - 19.5 - - - external - O.8 - 22.2 - 19.93 - 18.41 - - - internal - O.8 - 23.8 - 21.53 - 20.01 - 19.5 - - - - M23x5 - M23x5 - 5.0 - - external - O.0 - 23 - 19.75 - 17.59 - - - internal - O.0 - 23 - 19.75 - 17.59 - 18 - - - external - O.1 - 22.9 - 19.65 - 17.49 - - - internal - O.1 - 23.1 - 19.85 - 17.69 - 18 - - - external - O.2 - 22.8 - 19.55 - 17.39 - - - internal - O.2 - 23.2 - 19.95 - 17.79 - 18 - - - external - O.4 - 22.6 - 19.35 - 17.19 - - - internal - O.4 - 23.4 - 20.15 - 17.99 - 18 - - - external - O.8 - 22.2 - 18.95 - 16.79 - - - internal - O.8 - 23.8 - 20.55 - 18.39 - 18 - - - - - 24 - - M24x3.5 - M24x3.5 - 3.5 - - external - O.0 - 24 - 21.73 - 20.21 - - - internal - O.0 - 24 - 21.73 - 20.21 - 20.5 - - - external - O.1 - 23.9 - 21.63 - 20.11 - - - internal - O.1 - 24.1 - 21.83 - 20.31 - 20.5 - - - external - O.2 - 23.8 - 21.53 - 20.01 - - - internal - O.2 - 24.2 - 21.93 - 20.41 - 20.5 - - - external - O.4 - 23.6 - 21.33 - 19.81 - - - internal - O.4 - 24.4 - 22.13 - 20.61 - 20.5 - - - external - O.8 - 23.2 - 20.93 - 19.41 - - - internal - O.8 - 24.8 - 22.53 - 21.01 - 20.5 - - - - M24x5 - M24x5 - 5.0 - - external - O.0 - 24 - 20.75 - 18.59 - - - internal - O.0 - 24 - 20.75 - 18.59 - 19 - - - external - O.1 - 23.9 - 20.65 - 18.49 - - - internal - O.1 - 24.1 - 20.85 - 18.69 - 19 - - - external - O.2 - 23.8 - 20.55 - 18.39 - - - internal - O.2 - 24.2 - 20.95 - 18.79 - 19 - - - external - O.4 - 23.6 - 20.35 - 18.19 - - - internal - O.4 - 24.4 - 21.15 - 18.99 - 19 - - - external - O.8 - 23.2 - 19.95 - 17.79 - - - internal - O.8 - 24.8 - 21.55 - 19.39 - 19 - - - - - 25 - - M25x3.5 - M25x3.5 - 3.5 - - external - O.0 - 25 - 22.73 - 21.21 - - - internal - O.0 - 25 - 22.73 - 21.21 - 21.5 - - - external - O.1 - 24.9 - 22.63 - 21.11 - - - internal - O.1 - 25.1 - 22.83 - 21.31 - 21.5 - - - external - O.2 - 24.8 - 22.53 - 21.01 - - - internal - O.2 - 25.2 - 22.93 - 21.41 - 21.5 - - - external - O.4 - 24.6 - 22.33 - 20.81 - - - internal - O.4 - 25.4 - 23.13 - 21.61 - 21.5 - - - external - O.8 - 24.2 - 21.93 - 20.41 - - - internal - O.8 - 25.8 - 23.53 - 22.01 - 21.5 - - - - M25x5 - M25x5 - 5.0 - - external - O.0 - 25 - 21.75 - 19.59 - - - internal - O.0 - 25 - 21.75 - 19.59 - 20 - - - external - O.1 - 24.9 - 21.65 - 19.49 - - - internal - O.1 - 25.1 - 21.85 - 19.69 - 20 - - - external - O.2 - 24.8 - 21.55 - 19.39 - - - internal - O.2 - 25.2 - 21.95 - 19.79 - 20 - - - external - O.4 - 24.6 - 21.35 - 19.19 - - - internal - O.4 - 25.4 - 22.15 - 19.99 - 20 - - - external - O.8 - 24.2 - 20.95 - 18.79 - - - internal - O.8 - 25.8 - 22.55 - 20.39 - 20 - - - - - 26 - - M26x3.5 - M26x3.5 - 3.5 - - external - O.0 - 26 - 23.73 - 22.21 - - - internal - O.0 - 26 - 23.73 - 22.21 - 22.5 - - - external - O.1 - 25.9 - 23.63 - 22.11 - - - internal - O.1 - 26.1 - 23.83 - 22.31 - 22.5 - - - external - O.2 - 25.8 - 23.53 - 22.01 - - - internal - O.2 - 26.2 - 23.93 - 22.41 - 22.5 - - - external - O.4 - 25.6 - 23.33 - 21.81 - - - internal - O.4 - 26.4 - 24.13 - 22.61 - 22.5 - - - external - O.8 - 25.2 - 22.93 - 21.41 - - - internal - O.8 - 26.8 - 24.53 - 23.01 - 22.5 - - - - M26x5 - M26x5 - 5.0 - - external - O.0 - 26 - 22.75 - 20.59 - - - internal - O.0 - 26 - 22.75 - 20.59 - 21 - - - external - O.1 - 25.9 - 22.65 - 20.49 - - - internal - O.1 - 26.1 - 22.85 - 20.69 - 21 - - - external - O.2 - 25.8 - 22.55 - 20.39 - - - internal - O.2 - 26.2 - 22.95 - 20.79 - 21 - - - external - O.4 - 25.6 - 22.35 - 20.19 - - - internal - O.4 - 26.4 - 23.15 - 20.99 - 21 - - - external - O.8 - 25.2 - 21.95 - 19.79 - - - internal - O.8 - 26.8 - 23.55 - 21.39 - 21 - - - - - 27 - - M27x3.5 - M27x3.5 - 3.5 - - external - O.0 - 27 - 24.73 - 23.21 - - - internal - O.0 - 27 - 24.73 - 23.21 - 23.5 - - - external - O.1 - 26.9 - 24.63 - 23.11 - - - internal - O.1 - 27.1 - 24.83 - 23.31 - 23.5 - - - external - O.2 - 26.8 - 24.53 - 23.01 - - - internal - O.2 - 27.2 - 24.93 - 23.41 - 23.5 - - - external - O.4 - 26.6 - 24.33 - 22.81 - - - internal - O.4 - 27.4 - 25.13 - 23.61 - 23.5 - - - external - O.8 - 26.2 - 23.93 - 22.41 - - - internal - O.8 - 27.8 - 25.53 - 24.01 - 23.5 - - - - M27x5 - M27x5 - 5.0 - - external - O.0 - 27 - 23.75 - 21.59 - - - internal - O.0 - 27 - 23.75 - 21.59 - 22 - - - external - O.1 - 26.9 - 23.65 - 21.49 - - - internal - O.1 - 27.1 - 23.85 - 21.69 - 22 - - - external - O.2 - 26.8 - 23.55 - 21.39 - - - internal - O.2 - 27.2 - 23.95 - 21.79 - 22 - - - external - O.4 - 26.6 - 23.35 - 21.19 - - - internal - O.4 - 27.4 - 24.15 - 21.99 - 22 - - - external - O.8 - 26.2 - 22.95 - 20.79 - - - internal - O.8 - 27.8 - 24.55 - 22.39 - 22 - - - - - 28 - - M28x3.5 - M28x3.5 - 3.5 - - external - O.0 - 28 - 25.73 - 24.21 - - - internal - O.0 - 28 - 25.73 - 24.21 - 24.5 - - - external - O.1 - 27.9 - 25.63 - 24.11 - - - internal - O.1 - 28.1 - 25.83 - 24.31 - 24.5 - - - external - O.2 - 27.8 - 25.53 - 24.01 - - - internal - O.2 - 28.2 - 25.93 - 24.41 - 24.5 - - - external - O.4 - 27.6 - 25.33 - 23.81 - - - internal - O.4 - 28.4 - 26.13 - 24.61 - 24.5 - - - external - O.8 - 27.2 - 24.93 - 23.41 - - - internal - O.8 - 28.8 - 26.53 - 25.01 - 24.5 - - - - M28x5 - M28x5 - 5.0 - - external - O.0 - 28 - 24.75 - 22.59 - - - internal - O.0 - 28 - 24.75 - 22.59 - 23 - - - external - O.1 - 27.9 - 24.65 - 22.49 - - - internal - O.1 - 28.1 - 24.85 - 22.69 - 23 - - - external - O.2 - 27.8 - 24.55 - 22.39 - - - internal - O.2 - 28.2 - 24.95 - 22.79 - 23 - - - external - O.4 - 27.6 - 24.35 - 22.19 - - - internal - O.4 - 28.4 - 25.15 - 22.99 - 23 - - - external - O.8 - 27.2 - 23.95 - 21.79 - - - internal - O.8 - 28.8 - 25.55 - 23.39 - 23 - - - - - 29 - - M29x3.5 - M29x3.5 - 3.5 - - external - O.0 - 29 - 26.73 - 25.21 - - - internal - O.0 - 29 - 26.73 - 25.21 - 25.5 - - - external - O.1 - 28.9 - 26.63 - 25.11 - - - internal - O.1 - 29.1 - 26.83 - 25.31 - 25.5 - - - external - O.2 - 28.8 - 26.53 - 25.01 - - - internal - O.2 - 29.2 - 26.93 - 25.41 - 25.5 - - - external - O.4 - 28.6 - 26.33 - 24.81 - - - internal - O.4 - 29.4 - 27.13 - 25.61 - 25.5 - - - external - O.8 - 28.2 - 25.93 - 24.41 - - - internal - O.8 - 29.8 - 27.53 - 26.01 - 25.5 - - - - M29x5 - M29x5 - 5.0 - - external - O.0 - 29 - 25.75 - 23.59 - - - internal - O.0 - 29 - 25.75 - 23.59 - 24 - - - external - O.1 - 28.9 - 25.65 - 23.49 - - - internal - O.1 - 29.1 - 25.85 - 23.69 - 24 - - - external - O.2 - 28.8 - 25.55 - 23.39 - - - internal - O.2 - 29.2 - 25.95 - 23.79 - 24 - - - external - O.4 - 28.6 - 25.35 - 23.19 - - - internal - O.4 - 29.4 - 26.15 - 23.99 - 24 - - - external - O.8 - 28.2 - 24.95 - 22.79 - - - internal - O.8 - 29.8 - 26.55 - 24.39 - 24 - - - - - 30 - - M30x3.5 - M30x3.5 - 3.5 - - external - O.0 - 30 - 27.73 - 26.21 - - - internal - O.0 - 30 - 27.73 - 26.21 - 26.5 - - - external - O.1 - 29.9 - 27.63 - 26.11 - - - internal - O.1 - 30.1 - 27.83 - 26.31 - 26.5 - - - external - O.2 - 29.8 - 27.53 - 26.01 - - - internal - O.2 - 30.2 - 27.93 - 26.41 - 26.5 - - - external - O.4 - 29.6 - 27.33 - 25.81 - - - internal - O.4 - 30.4 - 28.13 - 26.61 - 26.5 - - - external - O.8 - 29.2 - 26.93 - 25.41 - - - internal - O.8 - 30.8 - 28.53 - 27.01 - 26.5 - - - - M30x5 - M30x5 - 5.0 - - external - O.0 - 30 - 26.75 - 24.59 - - - internal - O.0 - 30 - 26.75 - 24.59 - 25 - - - external - O.1 - 29.9 - 26.65 - 24.49 - - - internal - O.1 - 30.1 - 26.85 - 24.69 - 25 - - - external - O.2 - 29.8 - 26.55 - 24.39 - - - internal - O.2 - 30.2 - 26.95 - 24.79 - 25 - - - external - O.4 - 29.6 - 26.35 - 24.19 - - - internal - O.4 - 30.4 - 27.15 - 24.99 - 25 - - - external - O.8 - 29.2 - 25.95 - 23.79 - - - internal - O.8 - 30.8 - 27.55 - 25.39 - 25 - - - - - 31 - - M31x3.5 - M31x3.5 - 3.5 - - external - O.0 - 31 - 28.73 - 27.21 - - - internal - O.0 - 31 - 28.73 - 27.21 - 27.5 - - - external - O.1 - 30.9 - 28.63 - 27.11 - - - internal - O.1 - 31.1 - 28.83 - 27.31 - 27.5 - - - external - O.2 - 30.8 - 28.53 - 27.01 - - - internal - O.2 - 31.2 - 28.93 - 27.41 - 27.5 - - - external - O.4 - 30.6 - 28.33 - 26.81 - - - internal - O.4 - 31.4 - 29.13 - 27.61 - 27.5 - - - external - O.8 - 30.2 - 27.93 - 26.41 - - - internal - O.8 - 31.8 - 29.53 - 28.01 - 27.5 - - - - M31x5 - M31x5 - 5.0 - - external - O.0 - 31 - 27.75 - 25.59 - - - internal - O.0 - 31 - 27.75 - 25.59 - 26 - - - external - O.1 - 30.9 - 27.65 - 25.49 - - - internal - O.1 - 31.1 - 27.85 - 25.69 - 26 - - - external - O.2 - 30.8 - 27.55 - 25.39 - - - internal - O.2 - 31.2 - 27.95 - 25.79 - 26 - - - external - O.4 - 30.6 - 27.35 - 25.19 - - - internal - O.4 - 31.4 - 28.15 - 25.99 - 26 - - - external - O.8 - 30.2 - 26.95 - 24.79 - - - internal - O.8 - 31.8 - 28.55 - 26.39 - 26 - - - - - 32 - - M32x3.5 - M32x3.5 - 3.5 - - external - O.0 - 32 - 29.73 - 28.21 - - - internal - O.0 - 32 - 29.73 - 28.21 - 28.5 - - - external - O.1 - 31.9 - 29.63 - 28.11 - - - internal - O.1 - 32.1 - 29.83 - 28.31 - 28.5 - - - external - O.2 - 31.8 - 29.53 - 28.01 - - - internal - O.2 - 32.2 - 29.93 - 28.41 - 28.5 - - - external - O.4 - 31.6 - 29.33 - 27.81 - - - internal - O.4 - 32.4 - 30.13 - 28.61 - 28.5 - - - external - O.8 - 31.2 - 28.93 - 27.41 - - - internal - O.8 - 32.8 - 30.53 - 29.01 - 28.5 - - - - M32x5 - M32x5 - 5.0 - - external - O.0 - 32 - 28.75 - 26.59 - - - internal - O.0 - 32 - 28.75 - 26.59 - 27 - - - external - O.1 - 31.9 - 28.65 - 26.49 - - - internal - O.1 - 32.1 - 28.85 - 26.69 - 27 - - - external - O.2 - 31.8 - 28.55 - 26.39 - - - internal - O.2 - 32.2 - 28.95 - 26.79 - 27 - - - external - O.4 - 31.6 - 28.35 - 26.19 - - - internal - O.4 - 32.4 - 29.15 - 26.99 - 27 - - - external - O.8 - 31.2 - 27.95 - 25.79 - - - internal - O.8 - 32.8 - 29.55 - 27.39 - 27 - - - - - 33 - - M33x3.5 - M33x3.5 - 3.5 - - external - O.0 - 33 - 30.73 - 29.21 - - - internal - O.0 - 33 - 30.73 - 29.21 - 29.5 - - - external - O.1 - 32.9 - 30.63 - 29.11 - - - internal - O.1 - 33.1 - 30.83 - 29.31 - 29.5 - - - external - O.2 - 32.8 - 30.53 - 29.01 - - - internal - O.2 - 33.2 - 30.93 - 29.41 - 29.5 - - - external - O.4 - 32.6 - 30.33 - 28.81 - - - internal - O.4 - 33.4 - 31.13 - 29.61 - 29.5 - - - external - O.8 - 32.2 - 29.93 - 28.41 - - - internal - O.8 - 33.8 - 31.53 - 30.01 - 29.5 - - - - M33x5 - M33x5 - 5.0 - - external - O.0 - 33 - 29.75 - 27.59 - - - internal - O.0 - 33 - 29.75 - 27.59 - 28 - - - external - O.1 - 32.9 - 29.65 - 27.49 - - - internal - O.1 - 33.1 - 29.85 - 27.69 - 28 - - - external - O.2 - 32.8 - 29.55 - 27.39 - - - internal - O.2 - 33.2 - 29.95 - 27.79 - 28 - - - external - O.4 - 32.6 - 29.35 - 27.19 - - - internal - O.4 - 33.4 - 30.15 - 27.99 - 28 - - - external - O.8 - 32.2 - 28.95 - 26.79 - - - internal - O.8 - 33.8 - 30.55 - 28.39 - 28 - - - - - 34 - - M34x3.5 - M34x3.5 - 3.5 - - external - O.0 - 34 - 31.73 - 30.21 - - - internal - O.0 - 34 - 31.73 - 30.21 - 30.5 - - - external - O.1 - 33.9 - 31.63 - 30.11 - - - internal - O.1 - 34.1 - 31.83 - 30.31 - 30.5 - - - external - O.2 - 33.8 - 31.53 - 30.01 - - - internal - O.2 - 34.2 - 31.93 - 30.41 - 30.5 - - - external - O.4 - 33.6 - 31.33 - 29.81 - - - internal - O.4 - 34.4 - 32.13 - 30.61 - 30.5 - - - external - O.8 - 33.2 - 30.93 - 29.41 - - - internal - O.8 - 34.8 - 32.53 - 31.01 - 30.5 - - - - M34x5 - M34x5 - 5.0 - - external - O.0 - 34 - 30.75 - 28.59 - - - internal - O.0 - 34 - 30.75 - 28.59 - 29 - - - external - O.1 - 33.9 - 30.65 - 28.49 - - - internal - O.1 - 34.1 - 30.85 - 28.69 - 29 - - - external - O.2 - 33.8 - 30.55 - 28.39 - - - internal - O.2 - 34.2 - 30.95 - 28.79 - 29 - - - external - O.4 - 33.6 - 30.35 - 28.19 - - - internal - O.4 - 34.4 - 31.15 - 28.99 - 29 - - - external - O.8 - 33.2 - 29.95 - 27.79 - - - internal - O.8 - 34.8 - 31.55 - 29.39 - 29 - - - - - 35 - - M35x3.5 - M35x3.5 - 3.5 - - external - O.0 - 35 - 32.73 - 31.21 - - - internal - O.0 - 35 - 32.73 - 31.21 - 31.5 - - - external - O.1 - 34.9 - 32.63 - 31.11 - - - internal - O.1 - 35.1 - 32.83 - 31.31 - 31.5 - - - external - O.2 - 34.8 - 32.53 - 31.01 - - - internal - O.2 - 35.2 - 32.93 - 31.41 - 31.5 - - - external - O.4 - 34.6 - 32.33 - 30.81 - - - internal - O.4 - 35.4 - 33.13 - 31.61 - 31.5 - - - external - O.8 - 34.2 - 31.93 - 30.41 - - - internal - O.8 - 35.8 - 33.53 - 32.01 - 31.5 - - - - M35x5 - M35x5 - 5.0 - - external - O.0 - 35 - 31.75 - 29.59 - - - internal - O.0 - 35 - 31.75 - 29.59 - 30 - - - external - O.1 - 34.9 - 31.65 - 29.49 - - - internal - O.1 - 35.1 - 31.85 - 29.69 - 30 - - - external - O.2 - 34.8 - 31.55 - 29.39 - - - internal - O.2 - 35.2 - 31.95 - 29.79 - 30 - - - external - O.4 - 34.6 - 31.35 - 29.19 - - - internal - O.4 - 35.4 - 32.15 - 29.99 - 30 - - - external - O.8 - 34.2 - 30.95 - 28.79 - - - internal - O.8 - 35.8 - 32.55 - 30.39 - 30 - - - - - 36 - - M36x3.5 - M36x3.5 - 3.5 - - external - O.0 - 36 - 33.73 - 32.21 - - - internal - O.0 - 36 - 33.73 - 32.21 - 32.5 - - - external - O.1 - 35.9 - 33.63 - 32.11 - - - internal - O.1 - 36.1 - 33.83 - 32.31 - 32.5 - - - external - O.2 - 35.8 - 33.53 - 32.01 - - - internal - O.2 - 36.2 - 33.93 - 32.41 - 32.5 - - - external - O.4 - 35.6 - 33.33 - 31.81 - - - internal - O.4 - 36.4 - 34.13 - 32.61 - 32.5 - - - external - O.8 - 35.2 - 32.93 - 31.41 - - - internal - O.8 - 36.8 - 34.53 - 33.01 - 32.5 - - - - M36x5 - M36x5 - 5.0 - - external - O.0 - 36 - 32.75 - 30.59 - - - internal - O.0 - 36 - 32.75 - 30.59 - 31 - - - external - O.1 - 35.9 - 32.65 - 30.49 - - - internal - O.1 - 36.1 - 32.85 - 30.69 - 31 - - - external - O.2 - 35.8 - 32.55 - 30.39 - - - internal - O.2 - 36.2 - 32.95 - 30.79 - 31 - - - external - O.4 - 35.6 - 32.35 - 30.19 - - - internal - O.4 - 36.4 - 33.15 - 30.99 - 31 - - - external - O.8 - 35.2 - 31.95 - 29.79 - - - internal - O.8 - 36.8 - 33.55 - 31.39 - 31 - - - - - 37 - - M37x3.5 - M37x3.5 - 3.5 - - external - O.0 - 37 - 34.73 - 33.21 - - - internal - O.0 - 37 - 34.73 - 33.21 - 33.5 - - - external - O.1 - 36.9 - 34.63 - 33.11 - - - internal - O.1 - 37.1 - 34.83 - 33.31 - 33.5 - - - external - O.2 - 36.8 - 34.53 - 33.01 - - - internal - O.2 - 37.2 - 34.93 - 33.41 - 33.5 - - - external - O.4 - 36.6 - 34.33 - 32.81 - - - internal - O.4 - 37.4 - 35.13 - 33.61 - 33.5 - - - external - O.8 - 36.2 - 33.93 - 32.41 - - - internal - O.8 - 37.8 - 35.53 - 34.01 - 33.5 - - - - M37x5 - M37x5 - 5.0 - - external - O.0 - 37 - 33.75 - 31.59 - - - internal - O.0 - 37 - 33.75 - 31.59 - 32 - - - external - O.1 - 36.9 - 33.65 - 31.49 - - - internal - O.1 - 37.1 - 33.85 - 31.69 - 32 - - - external - O.2 - 36.8 - 33.55 - 31.39 - - - internal - O.2 - 37.2 - 33.95 - 31.79 - 32 - - - external - O.4 - 36.6 - 33.35 - 31.19 - - - internal - O.4 - 37.4 - 34.15 - 31.99 - 32 - - - external - O.8 - 36.2 - 32.95 - 30.79 - - - internal - O.8 - 37.8 - 34.55 - 32.39 - 32 - - - - - 38 - - M38x3.5 - M38x3.5 - 3.5 - - external - O.0 - 38 - 35.73 - 34.21 - - - internal - O.0 - 38 - 35.73 - 34.21 - 34.5 - - - external - O.1 - 37.9 - 35.63 - 34.11 - - - internal - O.1 - 38.1 - 35.83 - 34.31 - 34.5 - - - external - O.2 - 37.8 - 35.53 - 34.01 - - - internal - O.2 - 38.2 - 35.93 - 34.41 - 34.5 - - - external - O.4 - 37.6 - 35.33 - 33.81 - - - internal - O.4 - 38.4 - 36.13 - 34.61 - 34.5 - - - external - O.8 - 37.2 - 34.93 - 33.41 - - - internal - O.8 - 38.8 - 36.53 - 35.01 - 34.5 - - - - M38x5 - M38x5 - 5.0 - - external - O.0 - 38 - 34.75 - 32.59 - - - internal - O.0 - 38 - 34.75 - 32.59 - 33 - - - external - O.1 - 37.9 - 34.65 - 32.49 - - - internal - O.1 - 38.1 - 34.85 - 32.69 - 33 - - - external - O.2 - 37.8 - 34.55 - 32.39 - - - internal - O.2 - 38.2 - 34.95 - 32.79 - 33 - - - external - O.4 - 37.6 - 34.35 - 32.19 - - - internal - O.4 - 38.4 - 35.15 - 32.99 - 33 - - - external - O.8 - 37.2 - 33.95 - 31.79 - - - internal - O.8 - 38.8 - 35.55 - 33.39 - 33 - - - - - 39 - - M39x3.5 - M39x3.5 - 3.5 - - external - O.0 - 39 - 36.73 - 35.21 - - - internal - O.0 - 39 - 36.73 - 35.21 - 35.5 - - - external - O.1 - 38.9 - 36.63 - 35.11 - - - internal - O.1 - 39.1 - 36.83 - 35.31 - 35.5 - - - external - O.2 - 38.8 - 36.53 - 35.01 - - - internal - O.2 - 39.2 - 36.93 - 35.41 - 35.5 - - - external - O.4 - 38.6 - 36.33 - 34.81 - - - internal - O.4 - 39.4 - 37.13 - 35.61 - 35.5 - - - external - O.8 - 38.2 - 35.93 - 34.41 - - - internal - O.8 - 39.8 - 37.53 - 36.01 - 35.5 - - - - M39x5 - M39x5 - 5.0 - - external - O.0 - 39 - 35.75 - 33.59 - - - internal - O.0 - 39 - 35.75 - 33.59 - 34 - - - external - O.1 - 38.9 - 35.65 - 33.49 - - - internal - O.1 - 39.1 - 35.85 - 33.69 - 34 - - - external - O.2 - 38.8 - 35.55 - 33.39 - - - internal - O.2 - 39.2 - 35.95 - 33.79 - 34 - - - external - O.4 - 38.6 - 35.35 - 33.19 - - - internal - O.4 - 39.4 - 36.15 - 33.99 - 34 - - - external - O.8 - 38.2 - 34.95 - 32.79 - - - internal - O.8 - 39.8 - 36.55 - 34.39 - 34 - - - - - 40 - - M40x3.5 - M40x3.5 - 3.5 - - external - O.0 - 40 - 37.73 - 36.21 - - - internal - O.0 - 40 - 37.73 - 36.21 - 36.5 - - - external - O.1 - 39.9 - 37.63 - 36.11 - - - internal - O.1 - 40.1 - 37.83 - 36.31 - 36.5 - - - external - O.2 - 39.8 - 37.53 - 36.01 - - - internal - O.2 - 40.2 - 37.93 - 36.41 - 36.5 - - - external - O.4 - 39.6 - 37.33 - 35.81 - - - internal - O.4 - 40.4 - 38.13 - 36.61 - 36.5 - - - external - O.8 - 39.2 - 36.93 - 35.41 - - - internal - O.8 - 40.8 - 38.53 - 37.01 - 36.5 - - - - M40x5 - M40x5 - 5.0 - - external - O.0 - 40 - 36.75 - 34.59 - - - internal - O.0 - 40 - 36.75 - 34.59 - 35 - - - external - O.1 - 39.9 - 36.65 - 34.49 - - - internal - O.1 - 40.1 - 36.85 - 34.69 - 35 - - - external - O.2 - 39.8 - 36.55 - 34.39 - - - internal - O.2 - 40.2 - 36.95 - 34.79 - 35 - - - external - O.4 - 39.6 - 36.35 - 34.19 - - - internal - O.4 - 40.4 - 37.15 - 34.99 - 35 - - - external - O.8 - 39.2 - 35.95 - 33.79 - - - internal - O.8 - 40.8 - 37.55 - 35.39 - 35 - - - - - 41 - - M41x3.5 - M41x3.5 - 3.5 - - external - O.0 - 41 - 38.73 - 37.21 - - - internal - O.0 - 41 - 38.73 - 37.21 - 37.5 - - - external - O.1 - 40.9 - 38.63 - 37.11 - - - internal - O.1 - 41.1 - 38.83 - 37.31 - 37.5 - - - external - O.2 - 40.8 - 38.53 - 37.01 - - - internal - O.2 - 41.2 - 38.93 - 37.41 - 37.5 - - - external - O.4 - 40.6 - 38.33 - 36.81 - - - internal - O.4 - 41.4 - 39.13 - 37.61 - 37.5 - - - external - O.8 - 40.2 - 37.93 - 36.41 - - - internal - O.8 - 41.8 - 39.53 - 38.01 - 37.5 - - - - M41x5 - M41x5 - 5.0 - - external - O.0 - 41 - 37.75 - 35.59 - - - internal - O.0 - 41 - 37.75 - 35.59 - 36 - - - external - O.1 - 40.9 - 37.65 - 35.49 - - - internal - O.1 - 41.1 - 37.85 - 35.69 - 36 - - - external - O.2 - 40.8 - 37.55 - 35.39 - - - internal - O.2 - 41.2 - 37.95 - 35.79 - 36 - - - external - O.4 - 40.6 - 37.35 - 35.19 - - - internal - O.4 - 41.4 - 38.15 - 35.99 - 36 - - - external - O.8 - 40.2 - 36.95 - 34.79 - - - internal - O.8 - 41.8 - 38.55 - 36.39 - 36 - - - - - 42 - - M42x3.5 - M42x3.5 - 3.5 - - external - O.0 - 42 - 39.73 - 38.21 - - - internal - O.0 - 42 - 39.73 - 38.21 - 38.5 - - - external - O.1 - 41.9 - 39.63 - 38.11 - - - internal - O.1 - 42.1 - 39.83 - 38.31 - 38.5 - - - external - O.2 - 41.8 - 39.53 - 38.01 - - - internal - O.2 - 42.2 - 39.93 - 38.41 - 38.5 - - - external - O.4 - 41.6 - 39.33 - 37.81 - - - internal - O.4 - 42.4 - 40.13 - 38.61 - 38.5 - - - external - O.8 - 41.2 - 38.93 - 37.41 - - - internal - O.8 - 42.8 - 40.53 - 39.01 - 38.5 - - - - M42x5 - M42x5 - 5.0 - - external - O.0 - 42 - 38.75 - 36.59 - - - internal - O.0 - 42 - 38.75 - 36.59 - 37 - - - external - O.1 - 41.9 - 38.65 - 36.49 - - - internal - O.1 - 42.1 - 38.85 - 36.69 - 37 - - - external - O.2 - 41.8 - 38.55 - 36.39 - - - internal - O.2 - 42.2 - 38.95 - 36.79 - 37 - - - external - O.4 - 41.6 - 38.35 - 36.19 - - - internal - O.4 - 42.4 - 39.15 - 36.99 - 37 - - - external - O.8 - 41.2 - 37.95 - 35.79 - - - internal - O.8 - 42.8 - 39.55 - 37.39 - 37 - - - - - 43 - - M43x3.5 - M43x3.5 - 3.5 - - external - O.0 - 43 - 40.73 - 39.21 - - - internal - O.0 - 43 - 40.73 - 39.21 - 39.5 - - - external - O.1 - 42.9 - 40.63 - 39.11 - - - internal - O.1 - 43.1 - 40.83 - 39.31 - 39.5 - - - external - O.2 - 42.8 - 40.53 - 39.01 - - - internal - O.2 - 43.2 - 40.93 - 39.41 - 39.5 - - - external - O.4 - 42.6 - 40.33 - 38.81 - - - internal - O.4 - 43.4 - 41.13 - 39.61 - 39.5 - - - external - O.8 - 42.2 - 39.93 - 38.41 - - - internal - O.8 - 43.8 - 41.53 - 40.01 - 39.5 - - - - M43x5 - M43x5 - 5.0 - - external - O.0 - 43 - 39.75 - 37.59 - - - internal - O.0 - 43 - 39.75 - 37.59 - 38 - - - external - O.1 - 42.9 - 39.65 - 37.49 - - - internal - O.1 - 43.1 - 39.85 - 37.69 - 38 - - - external - O.2 - 42.8 - 39.55 - 37.39 - - - internal - O.2 - 43.2 - 39.95 - 37.79 - 38 - - - external - O.4 - 42.6 - 39.35 - 37.19 - - - internal - O.4 - 43.4 - 40.15 - 37.99 - 38 - - - external - O.8 - 42.2 - 38.95 - 36.79 - - - internal - O.8 - 43.8 - 40.55 - 38.39 - 38 - - - - - 44 - - M44x3.5 - M44x3.5 - 3.5 - - external - O.0 - 44 - 41.73 - 40.21 - - - internal - O.0 - 44 - 41.73 - 40.21 - 40.5 - - - external - O.1 - 43.9 - 41.63 - 40.11 - - - internal - O.1 - 44.1 - 41.83 - 40.31 - 40.5 - - - external - O.2 - 43.8 - 41.53 - 40.01 - - - internal - O.2 - 44.2 - 41.93 - 40.41 - 40.5 - - - external - O.4 - 43.6 - 41.33 - 39.81 - - - internal - O.4 - 44.4 - 42.13 - 40.61 - 40.5 - - - external - O.8 - 43.2 - 40.93 - 39.41 - - - internal - O.8 - 44.8 - 42.53 - 41.01 - 40.5 - - - - M44x5 - M44x5 - 5.0 - - external - O.0 - 44 - 40.75 - 38.59 - - - internal - O.0 - 44 - 40.75 - 38.59 - 39 - - - external - O.1 - 43.9 - 40.65 - 38.49 - - - internal - O.1 - 44.1 - 40.85 - 38.69 - 39 - - - external - O.2 - 43.8 - 40.55 - 38.39 - - - internal - O.2 - 44.2 - 40.95 - 38.79 - 39 - - - external - O.4 - 43.6 - 40.35 - 38.19 - - - internal - O.4 - 44.4 - 41.15 - 38.99 - 39 - - - external - O.8 - 43.2 - 39.95 - 37.79 - - - internal - O.8 - 44.8 - 41.55 - 39.39 - 39 - - - - - 45 - - M45x3.5 - M45x3.5 - 3.5 - - external - O.0 - 45 - 42.73 - 41.21 - - - internal - O.0 - 45 - 42.73 - 41.21 - 41.5 - - - external - O.1 - 44.9 - 42.63 - 41.11 - - - internal - O.1 - 45.1 - 42.83 - 41.31 - 41.5 - - - external - O.2 - 44.8 - 42.53 - 41.01 - - - internal - O.2 - 45.2 - 42.93 - 41.41 - 41.5 - - - external - O.4 - 44.6 - 42.33 - 40.81 - - - internal - O.4 - 45.4 - 43.13 - 41.61 - 41.5 - - - external - O.8 - 44.2 - 41.93 - 40.41 - - - internal - O.8 - 45.8 - 43.53 - 42.01 - 41.5 - - - - M45x5 - M45x5 - 5.0 - - external - O.0 - 45 - 41.75 - 39.59 - - - internal - O.0 - 45 - 41.75 - 39.59 - 40 - - - external - O.1 - 44.9 - 41.65 - 39.49 - - - internal - O.1 - 45.1 - 41.85 - 39.69 - 40 - - - external - O.2 - 44.8 - 41.55 - 39.39 - - - internal - O.2 - 45.2 - 41.95 - 39.79 - 40 - - - external - O.4 - 44.6 - 41.35 - 39.19 - - - internal - O.4 - 45.4 - 42.15 - 39.99 - 40 - - - external - O.8 - 44.2 - 40.95 - 38.79 - - - internal - O.8 - 45.8 - 42.55 - 40.39 - 40 - - - - - 46 - - M46x3.5 - M46x3.5 - 3.5 - - external - O.0 - 46 - 43.73 - 42.21 - - - internal - O.0 - 46 - 43.73 - 42.21 - 42.5 - - - external - O.1 - 45.9 - 43.63 - 42.11 - - - internal - O.1 - 46.1 - 43.83 - 42.31 - 42.5 - - - external - O.2 - 45.8 - 43.53 - 42.01 - - - internal - O.2 - 46.2 - 43.93 - 42.41 - 42.5 - - - external - O.4 - 45.6 - 43.33 - 41.81 - - - internal - O.4 - 46.4 - 44.13 - 42.61 - 42.5 - - - external - O.8 - 45.2 - 42.93 - 41.41 - - - internal - O.8 - 46.8 - 44.53 - 43.01 - 42.5 - - - - M46x5 - M46x5 - 5.0 - - external - O.0 - 46 - 42.75 - 40.59 - - - internal - O.0 - 46 - 42.75 - 40.59 - 41 - - - external - O.1 - 45.9 - 42.65 - 40.49 - - - internal - O.1 - 46.1 - 42.85 - 40.69 - 41 - - - external - O.2 - 45.8 - 42.55 - 40.39 - - - internal - O.2 - 46.2 - 42.95 - 40.79 - 41 - - - external - O.4 - 45.6 - 42.35 - 40.19 - - - internal - O.4 - 46.4 - 43.15 - 40.99 - 41 - - - external - O.8 - 45.2 - 41.95 - 39.79 - - - internal - O.8 - 46.8 - 43.55 - 41.39 - 41 - - - - - 47 - - M47x3.5 - M47x3.5 - 3.5 - - external - O.0 - 47 - 44.73 - 43.21 - - - internal - O.0 - 47 - 44.73 - 43.21 - 43.5 - - - external - O.1 - 46.9 - 44.63 - 43.11 - - - internal - O.1 - 47.1 - 44.83 - 43.31 - 43.5 - - - external - O.2 - 46.8 - 44.53 - 43.01 - - - internal - O.2 - 47.2 - 44.93 - 43.41 - 43.5 - - - external - O.4 - 46.6 - 44.33 - 42.81 - - - internal - O.4 - 47.4 - 45.13 - 43.61 - 43.5 - - - external - O.8 - 46.2 - 43.93 - 42.41 - - - internal - O.8 - 47.8 - 45.53 - 44.01 - 43.5 - - - - M47x5 - M47x5 - 5.0 - - external - O.0 - 47 - 43.75 - 41.59 - - - internal - O.0 - 47 - 43.75 - 41.59 - 42 - - - external - O.1 - 46.9 - 43.65 - 41.49 - - - internal - O.1 - 47.1 - 43.85 - 41.69 - 42 - - - external - O.2 - 46.8 - 43.55 - 41.39 - - - internal - O.2 - 47.2 - 43.95 - 41.79 - 42 - - - external - O.4 - 46.6 - 43.35 - 41.19 - - - internal - O.4 - 47.4 - 44.15 - 41.99 - 42 - - - external - O.8 - 46.2 - 42.95 - 40.79 - - - internal - O.8 - 47.8 - 44.55 - 42.39 - 42 - - - - - 48 - - M48x3.5 - M48x3.5 - 3.5 - - external - O.0 - 48 - 45.73 - 44.21 - - - internal - O.0 - 48 - 45.73 - 44.21 - 44.5 - - - external - O.1 - 47.9 - 45.63 - 44.11 - - - internal - O.1 - 48.1 - 45.83 - 44.31 - 44.5 - - - external - O.2 - 47.8 - 45.53 - 44.01 - - - internal - O.2 - 48.2 - 45.93 - 44.41 - 44.5 - - - external - O.4 - 47.6 - 45.33 - 43.81 - - - internal - O.4 - 48.4 - 46.13 - 44.61 - 44.5 - - - external - O.8 - 47.2 - 44.93 - 43.41 - - - internal - O.8 - 48.8 - 46.53 - 45.01 - 44.5 - - - - M48x5 - M48x5 - 5.0 - - external - O.0 - 48 - 44.75 - 42.59 - - - internal - O.0 - 48 - 44.75 - 42.59 - 43 - - - external - O.1 - 47.9 - 44.65 - 42.49 - - - internal - O.1 - 48.1 - 44.85 - 42.69 - 43 - - - external - O.2 - 47.8 - 44.55 - 42.39 - - - internal - O.2 - 48.2 - 44.95 - 42.79 - 43 - - - external - O.4 - 47.6 - 44.35 - 42.19 - - - internal - O.4 - 48.4 - 45.15 - 42.99 - 43 - - - external - O.8 - 47.2 - 43.95 - 41.79 - - - internal - O.8 - 48.8 - 45.55 - 43.39 - 43 - - - - - 49 - - M49x3.5 - M49x3.5 - 3.5 - - external - O.0 - 49 - 46.73 - 45.21 - - - internal - O.0 - 49 - 46.73 - 45.21 - 45.5 - - - external - O.1 - 48.9 - 46.63 - 45.11 - - - internal - O.1 - 49.1 - 46.83 - 45.31 - 45.5 - - - external - O.2 - 48.8 - 46.53 - 45.01 - - - internal - O.2 - 49.2 - 46.93 - 45.41 - 45.5 - - - external - O.4 - 48.6 - 46.33 - 44.81 - - - internal - O.4 - 49.4 - 47.13 - 45.61 - 45.5 - - - external - O.8 - 48.2 - 45.93 - 44.41 - - - internal - O.8 - 49.8 - 47.53 - 46.01 - 45.5 - - - - M49x5 - M49x5 - 5.0 - - external - O.0 - 49 - 45.75 - 43.59 - - - internal - O.0 - 49 - 45.75 - 43.59 - 44 - - - external - O.1 - 48.9 - 45.65 - 43.49 - - - internal - O.1 - 49.1 - 45.85 - 43.69 - 44 - - - external - O.2 - 48.8 - 45.55 - 43.39 - - - internal - O.2 - 49.2 - 45.95 - 43.79 - 44 - - - external - O.4 - 48.6 - 45.35 - 43.19 - - - internal - O.4 - 49.4 - 46.15 - 43.99 - 44 - - - external - O.8 - 48.2 - 44.95 - 42.79 - - - internal - O.8 - 49.8 - 46.55 - 44.39 - 44 - - - - - 50 - - M50x3.5 - M50x3.5 - 3.5 - - external - O.0 - 50 - 47.73 - 46.21 - - - internal - O.0 - 50 - 47.73 - 46.21 - 46.5 - - - external - O.1 - 49.9 - 47.63 - 46.11 - - - internal - O.1 - 50.1 - 47.83 - 46.31 - 46.5 - - - external - O.2 - 49.8 - 47.53 - 46.01 - - - internal - O.2 - 50.2 - 47.93 - 46.41 - 46.5 - - - external - O.4 - 49.6 - 47.33 - 45.81 - - - internal - O.4 - 50.4 - 48.13 - 46.61 - 46.5 - - - external - O.8 - 49.2 - 46.93 - 45.41 - - - internal - O.8 - 50.8 - 48.53 - 47.01 - 46.5 - - - - M50x5 - M50x5 - 5.0 - - external - O.0 - 50 - 46.75 - 44.59 - - - internal - O.0 - 50 - 46.75 - 44.59 - 45 - - - external - O.1 - 49.9 - 46.65 - 44.49 - - - internal - O.1 - 50.1 - 46.85 - 44.69 - 45 - - - external - O.2 - 49.8 - 46.55 - 44.39 - - - internal - O.2 - 50.2 - 46.95 - 44.79 - 45 - - - external - O.4 - 49.6 - 46.35 - 44.19 - - - internal - O.4 - 50.4 - 47.15 - 44.99 - 45 - - - external - O.8 - 49.2 - 45.95 - 43.79 - - - internal - O.8 - 50.8 - 47.55 - 45.39 - 45 - - - - \ No newline at end of file From 46735119057bb2395625e6556457643df9f70b0f Mon Sep 17 00:00:00 2001 From: Philip <44484441+EckPhi@users.noreply.github.com> Date: Sun, 29 Mar 2026 12:47:34 +0200 Subject: [PATCH 15/31] Updates README and adds web generator placeholder --- README.md | 207 +++++++++++++++++++++++++++++++++++++++----------- web/README.md | 16 ++++ 2 files changed, 177 insertions(+), 46 deletions(-) create mode 100644 web/README.md diff --git a/README.md b/README.md index 24ad29f..2fc73e3 100644 --- a/README.md +++ b/README.md @@ -1,73 +1,188 @@ -# Fusion 360 Thread Profiles for 3D-Printed Threads +# CustomThreads - 3D-Printed Metric Threads Generator -Fusion 360 comes with various thread standards, most of which are not a great fit for 3D printing because they are too fine to work well when printed. -This project defines thread profiles that are aimed at 3D printing so that they work, even when printed with lower detail settings (e.g. 0.30mm layer height). +Generate standardized ISO metric thread profiles optimized for 3D printing in XML format. -## What's in the Box? +## Quick Start -The provided `3DPrintedMetricV3.xml` file contains the thread profiles listed below. -Additional profiles can be easily generated from the included Python script. +### Download Pre-Generated XML Files -**Shape:** 60ยฐ whitworth -**Size (OD):** 8-50mm -**Pitch:** 3.5mm, 5mm -**Classes:** O.0, O.1, O.2, O.4, O.8 +The latest XML thread profiles are automatically generated and available as GitHub Releases: -The classes specify tolerances. -O.0 has the tightest tolerances which are loosely based on ISO M30x3.5 6g/6H. -The number after the 'O.' specifies, in tenths of a millimeter, how much the major, minor and pitch diameters are offset compared to O.0. -For example, O.2 would have a 0.2mm looser tolerance than O.0. +๐Ÿ‘‰ **[Download Latest Release](../../releases/latest)** -`3DPrintedMetric.xml` contains old thread definitions and is provided for compatibility with older prints or models. +Simply download the `3DPrintedMetricThreads.xml` file and follow the installation instructions below. -## Install and Use +### Install in Fusion 360 -If you just want to use the profiles with no customization, download the file `3DPrintedMetricV3.xml`, save it in the following directory and then restart Fusion 360. +1. Download `3DPrintedMetricThreads.xml` from the latest release +2. Save it to your Fusion 360 thread data directory: + - **Windows:** `%localappdata%\Autodesk\webdeploy\Production\\Fusion\Server\Fusion\Configuration\ThreadData` + - **macOS:** `~/Library/Application Support/Autodesk/Webdeploy/production//Autodesk Fusion 360/Contents/Libraries/Applications/Fusion/Fusion/Server/Fusion/Configuration/ThreadData` +3. Restart Fusion 360 +4. When creating threads, select **"3D-Printed Metric Threads V3"** from the thread type dropdown -**On Windows:** -``` -%localappdata%\Autodesk\webdeploy\Production\\Fusion\Server\Fusion\Configuration\ThreadData -``` +> **Note:** You can automate this installation with the [ThreadKeeper](https://github.com/thomasa88/ThreadKeeper) plugin, which reinstalls profiles automatically after each Fusion 360 update. -**On Mac OS:** +## Project Structure -*Macintosh HD> Users> [Username] > Library > Application Support > Autodesk > Webdeploy > production > [Version specific ID] > Then right click on "Autodesk Fusion 360" and choose Show Package Contents > Contents > Libraries > Applications > Fusion > Fusion > Server > Fusion > Configuration >ThreadData* +``` +. +โ”œโ”€โ”€ .github/workflows/ # CI/CD pipeline +โ”‚ โ””โ”€โ”€ generate-xml-release.yml +โ”œโ”€โ”€ src/customthreads/ # Python package (core logic) +โ”‚ โ”œโ”€โ”€ __init__.py +โ”‚ โ”œโ”€โ”€ models.py # Thread model classes +โ”‚ โ”œโ”€โ”€ generator.py # XML generation utilities +โ”‚ โ””โ”€โ”€ cli.py # Command-line interface +โ”œโ”€โ”€ tests/ # Test suite +โ”‚ โ””โ”€โ”€ test_main.py +โ”œโ”€โ”€ web/ # JavaScript/web generator (future) +โ”œโ”€โ”€ pyproject.toml # Poetry configuration +โ”œโ”€โ”€ README.md +โ””โ”€โ”€ .gitignore +``` -(see also [here](https://knowledge.autodesk.com/support/fusion-360/learn-explore/caas/sfdcarticles/sfdcarticles/Custom-Threads-in-Fusion-360.html)) +## Development -When you now create or edit a *Thread* feature, you should be able to select the Thread Type *"3D-printed Metric Threads"*. +### Installation -![Select Thread Type in Fusion 360](ss_fusion.png) +Install dependencies with Poetry: -> Note: Profiles will need to be reinstalled after every Fusion 360 update. To do this automatically, check out the [ThreadKeeper](https://github.com/thomasa88/ThreadKeeper) plugin. +```bash +poetry install +``` -## Generating Customized Profiles +### Generate XML Locally -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: +Run the thread generator: ```bash -python main.py +poetry run python -m customthreads.cli ``` -This will create a file named `3DPrintedMetricV3.xml` in the working directory which you can then rename and install in Fusion as described above. +This creates `3DPrintedMetricThreads.xml` with: +- **Diameters:** 8-50mm +- **Pitches:** 1.0mm to 6.0mm (0.5mm steps) +- **Tolerance Classes:** O.0, O.1, O.2, O.4, O.8 -To customize the generated profiles, simply edit the values defined at the top of `main.py`. +### Customize Configuration -To change the profile of the thread change the "THREAD_FORM" variable and reference the below chart. +Edit `src/customthreads/cli.py` to customize parameters: +- Pitch range (pitch_start, pitch_end, pitch_step) +- Thread sizes (thread_sizes) +- Tolerance offsets (tolerance_offsets) +- Output filename (output_filename) +- Thread metadata (thread_name, unit, thread_angle, thread_form) -0 = Trapezoidal -1 = sharp V shape -6 = Square -8 = whitworth +### Run Tests ```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] -THREAD_FORM = 8 +poetry run pytest -v +``` + +All 12 tests should pass โœ“ + +### Use as a Library + +```python +from customthreads import generate_xml + +# Generate custom thread profiles +generate_xml( + output_filename="custom_threads.xml", + thread_name="My Custom Threads", + unit="mm", + thread_angle=60.0, + thread_form=8, + pitch_start=1.0, + pitch_end=6.0, + pitch_step=0.5, + thread_sizes=list(range(8, 51)), + tolerance_offsets=[0.0, 0.1, 0.2, 0.4, 0.8], +) +``` + +## Automated Release Pipeline + +### How It Works + +This project uses **GitHub Actions** to automatically generate and publish thread profiles as releases. + +**Workflow File:** `.github/workflows/generate-xml-release.yml` + +**Trigger Events:** +- โœ… Every push to `main` branch +- โœ… Manual trigger via GitHub Actions UI (workflow_dispatch) + +**Pipeline Steps:** +1. Set up Ubuntu environment with Python 3.11 +2. Install dependencies using Poetry +3. Run test suite to verify code quality +4. Generate XML thread profiles +5. Create a GitHub Release with datetime-based version +6. Upload XML file as release artifact + +### Release Versioning + +- **Format:** `v{YYYY}.{MM}.{DD}-{HHMM}` (UTC time) +- **Examples:** + - `v2026.03.29-1000` = March 29, 2026 at 10:00 UTC + - `v2026.03.29-1245` = March 29, 2026 at 12:45 UTC + +### Key Features + +- โœ… **No committed XML files** โ€” generated automatically on every push +- โœ… **Version history** โ€” easy to compare profiles across releases +- โœ… **Quality assured** โ€” tests run before each release +- โœ… **One-click downloads** โ€” users get latest profiles from Releases page + +### Disable Auto-Release (Optional) + +To switch to manual-only releases: + +1. Edit `.github/workflows/generate-xml-release.yml` +2. Change the trigger to: + ```yaml + on: + workflow_dispatch: # Manual trigger only + ``` +3. Commit and push +4. Releases can now be triggered manually from the Actions tab + +## Thread Specifications + +### Tolerance Classes + +These offset values from nominal dimensions allow fine-tuning for different printing conditions: + +| Class | Offset | Use Case | +|-------|--------|----------| +| O.0 | 0.0mm | Tightest fit, best accuracy | +| O.1 | +0.1mm | Standard fit, most prints | +| O.2 | +0.2mm | Loose fit, easier to print | +| O.4 | +0.4mm | Very loose, coarse prints | +| O.8 | +0.8mm | Safety margin, worst-case prints | + +### Generated Profile Range + +| Parameter | Value | +|-----------|-------| +| **Diameters** | 8-50mm (all integer values) | +| **Pitches** | 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0, 4.5, 5.0, 5.5, 6.0mm | +| **Thread Type** | ISO Metric (60ยฐ Whitworth) | +| **Optimized for** | FDM 3D printing (0.2-0.3mm layer height) | + +## Future: JavaScript Implementation + +The `web/` directory is reserved for an interactive JavaScript-based generator that will be deployable on GitHub Pages, allowing users to generate custom profiles directly in their browser. + +## License + +See LICENSE file for details + +## References + +- [Fusion 360 Custom Threads Documentation](https://knowledge.autodesk.com/support/fusion-360/learn-explore/caas/sfdcarticles/sfdcarticles/Custom-Threads-in-Fusion-360.html) +- [ISO Metric Screw Thread (Wikipedia)](https://en.wikipedia.org/wiki/ISO_metric_screw_thread) +- [ThreadKeeper - Fusion 360 Plugin](https://github.com/thomasa88/ThreadKeeper) ``` diff --git a/web/README.md b/web/README.md new file mode 100644 index 0000000..049a3e5 --- /dev/null +++ b/web/README.md @@ -0,0 +1,16 @@ +# Web Generator (Future) + +This directory will contain the JavaScript/web implementation of the thread generator for deployment on GitHub Pages. + +## Planned Structure + +- `index.html` - Main web interface +- `app.js` - Core generator logic (ported from Python) +- `style.css` - Styling +- `lib/` - Dependencies and utilities + +## Notes + +- The core thread generation algorithm will be ported from Python to JavaScript +- Users will be able to configure pitch ranges, thread sizes, and tolerance offsets in a browser +- Generated XML can be downloaded directly From 7bc5bceb7e6e3a60eb8269f80e51229b5e8d6d28 Mon Sep 17 00:00:00 2001 From: Philip <44484441+EckPhi@users.noreply.github.com> Date: Sun, 29 Mar 2026 13:02:35 +0200 Subject: [PATCH 16/31] Add GitHub Actions CI/CD workflows for XML and Pages --- .github/workflows/deploy-pages.yml | 43 ++++++++++ .github/workflows/generate-xml-release.yml | 97 ++++++++++++++++++++++ 2 files changed, 140 insertions(+) create mode 100644 .github/workflows/deploy-pages.yml create mode 100644 .github/workflows/generate-xml-release.yml diff --git a/.github/workflows/deploy-pages.yml b/.github/workflows/deploy-pages.yml new file mode 100644 index 0000000..b7d77c9 --- /dev/null +++ b/.github/workflows/deploy-pages.yml @@ -0,0 +1,43 @@ +name: Deploy GitHub Pages Generator + +on: + push: + branches: + - main + paths: + - web/** + - .github/workflows/deploy-pages.yml + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: pages + cancel-in-progress: true + +jobs: + deploy: + runs-on: ubuntu-latest + + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Configure Pages + uses: actions/configure-pages@v5 + + - name: Upload web artifact + uses: actions/upload-pages-artifact@v3 + with: + path: ./web + + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.github/workflows/generate-xml-release.yml b/.github/workflows/generate-xml-release.yml new file mode 100644 index 0000000..a56e64e --- /dev/null +++ b/.github/workflows/generate-xml-release.yml @@ -0,0 +1,97 @@ +name: Generate and Release XML Thread Profiles + +on: + push: + branches: + - main + workflow_dispatch: # Allow manual trigger + +jobs: + generate-and-release: + runs-on: ubuntu-latest + + permissions: + contents: write # Permission to create releases + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Install Poetry + uses: snok/install-poetry@v1 + with: + version: latest + virtualenvs-create: true + virtualenvs-in-project: true + + - name: Cache Poetry dependencies + uses: actions/cache@v3 + with: + path: .venv + key: ${{ runner.os }}-poetry-${{ hashFiles('**/poetry.lock') }} + restore-keys: | + ${{ runner.os }}-poetry- + + - name: Install dependencies + run: poetry install + + - name: Generate XML thread profiles + run: | + poetry run python -m customthreads.cli + # Also generate with alternative configurations (optional) + mkdir -p build/xml + mv 3DPrintedMetricThreads.xml build/xml/ + + - name: Create datetime-based version tag + id: version + run: | + VERSION=$(date -u +'v%Y.%m.%d-%H%M') + echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "Version: $VERSION" + + - name: Create Release + uses: actions/create-release@v1 + id: create_release + with: + draft: false + prerelease: false + release_name: XML Thread Profiles ${{ steps.version.outputs.version }} + tag_name: ${{ steps.version.outputs.version }} + body: | + Automatically generated 3D-printed metric thread profiles in XML format. + + **Generated:** ${{ github.event.head_commit.timestamp }} + **Commit:** ${{ github.sha }} + + ## Contents + - `3DPrintedMetricThreads.xml` - Thread profiles from 3.5mm to 50mm diameter, pitches 1.0mm to 6.0mm, 5 tolerance classes (O.0 to O.8) + + ## Usage + 1. Download the XML file + 2. Place in Fusion 360 thread data directory (see README) + 3. Restart Fusion 360 + + Use these profiles for 3D-printed metric threads optimized for FDM printing. + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Upload Release Asset + uses: actions/upload-release-asset@v1 + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: ./build/xml/3DPrintedMetricThreads.xml + asset_name: 3DPrintedMetricThreads.xml + asset_content_type: application/xml + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Test generated content + run: | + poetry run pytest -v + # Verify XML is valid + python3 -c "import xml.etree.ElementTree as ET; ET.parse('build/xml/3DPrintedMetricThreads.xml'); print('โœ… Generated XML is valid')" From 9c7dc11c178158ea833a39bd238d0a1836031b0d Mon Sep 17 00:00:00 2001 From: Philip <44484441+EckPhi@users.noreply.github.com> Date: Sun, 29 Mar 2026 13:02:35 +0200 Subject: [PATCH 17/31] Update .gitignore for workflow and poetry lock handling --- .gitignore | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.gitignore b/.gitignore index 1201370..52d42de 100644 --- a/.gitignore +++ b/.gitignore @@ -45,9 +45,6 @@ coverage.xml .hypothesis/ .pytest_cache/ -# Poetry -poetry.lock - # Virtual environments venv/ ENV/ @@ -70,5 +67,3 @@ env/ /dist/ /output/ -# GitHub Actions -.github/workflows/ From ca520c7a568d521a99616afcb3086f0bb2b4a18c Mon Sep 17 00:00:00 2001 From: Philip <44484441+EckPhi@users.noreply.github.com> Date: Sun, 29 Mar 2026 13:02:35 +0200 Subject: [PATCH 18/31] Remove legacy Fusion 360 thread XSD schema file --- Fusion360ThreadProfile.xsd | 66 -------------------------------------- 1 file changed, 66 deletions(-) delete mode 100644 Fusion360ThreadProfile.xsd diff --git a/Fusion360ThreadProfile.xsd b/Fusion360ThreadProfile.xsd deleted file mode 100644 index 5ed6511..0000000 --- a/Fusion360ThreadProfile.xsd +++ /dev/null @@ -1,66 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - 0=Trapezoidal, 1=Sharp, 5=Square, 7=WithWorth - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file From 71fb41632b1dadb333c22f240038beb909fe1516 Mon Sep 17 00:00:00 2001 From: Philip <44484441+EckPhi@users.noreply.github.com> Date: Sun, 29 Mar 2026 13:02:35 +0200 Subject: [PATCH 19/31] Revise README for web generator and deployment details --- README.md | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 2fc73e3..f0feeca 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,20 @@ Generate standardized ISO metric thread profiles optimized for 3D printing in XM ## Quick Start +### Use the Web Generator + +The browser generator is in [web/index.html](web/index.html) and is deployed through GitHub Pages. + +- Local preview: + +```bash +cd web +python3 -m http.server 8080 +``` + +- Open `http://localhost:8080` +- Generate and download `3DPrintedMetricThreads.xml` + ### Download Pre-Generated XML Files The latest XML thread profiles are automatically generated and available as GitHub Releases: @@ -28,7 +42,8 @@ Simply download the `3DPrintedMetricThreads.xml` file and follow the installatio ``` . โ”œโ”€โ”€ .github/workflows/ # CI/CD pipeline -โ”‚ โ””โ”€โ”€ generate-xml-release.yml +โ”‚ โ”œโ”€โ”€ generate-xml-release.yml +โ”‚ โ””โ”€โ”€ deploy-pages.yml โ”œโ”€โ”€ src/customthreads/ # Python package (core logic) โ”‚ โ”œโ”€โ”€ __init__.py โ”‚ โ”œโ”€โ”€ models.py # Thread model classes @@ -36,7 +51,7 @@ Simply download the `3DPrintedMetricThreads.xml` file and follow the installatio โ”‚ โ””โ”€โ”€ cli.py # Command-line interface โ”œโ”€โ”€ tests/ # Test suite โ”‚ โ””โ”€โ”€ test_main.py -โ”œโ”€โ”€ web/ # JavaScript/web generator (future) +โ”œโ”€โ”€ web/ # GitHub Pages generator app โ”œโ”€โ”€ pyproject.toml # Poetry configuration โ”œโ”€โ”€ README.md โ””โ”€โ”€ .gitignore @@ -76,7 +91,7 @@ Edit `src/customthreads/cli.py` to customize parameters: ### Run Tests -```python +```bash poetry run pytest -v ``` @@ -172,9 +187,15 @@ These offset values from nominal dimensions allow fine-tuning for different prin | **Thread Type** | ISO Metric (60ยฐ Whitworth) | | **Optimized for** | FDM 3D printing (0.2-0.3mm layer height) | -## Future: JavaScript Implementation +## GitHub Pages Deployment -The `web/` directory is reserved for an interactive JavaScript-based generator that will be deployable on GitHub Pages, allowing users to generate custom profiles directly in their browser. +The web app is deployed by [.github/workflows/deploy-pages.yml](.github/workflows/deploy-pages.yml). + +- Trigger: push to `main` when files under [web](web) change +- Source artifact: [web](web) +- Publish target: GitHub Pages environment + +If Pages is not enabled yet in repository settings, enable it once and keep source as GitHub Actions. ## License @@ -185,4 +206,3 @@ See LICENSE file for details - [Fusion 360 Custom Threads Documentation](https://knowledge.autodesk.com/support/fusion-360/learn-explore/caas/sfdcarticles/sfdcarticles/Custom-Threads-in-Fusion-360.html) - [ISO Metric Screw Thread (Wikipedia)](https://en.wikipedia.org/wiki/ISO_metric_screw_thread) - [ThreadKeeper - Fusion 360 Plugin](https://github.com/thomasa88/ThreadKeeper) -``` From fc341eab4f616fb18403421e37e69b23d4a387b9 Mon Sep 17 00:00:00 2001 From: Philip <44484441+EckPhi@users.noreply.github.com> Date: Sun, 29 Mar 2026 13:02:35 +0200 Subject: [PATCH 20/31] Add poetry.lock to enable consistent dependency installs --- poetry.lock | 80 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 poetry.lock diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..bf37c4a --- /dev/null +++ b/poetry.lock @@ -0,0 +1,80 @@ +# This file is automatically @generated by Poetry 2.3.2 and should not be changed by hand. + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["dev"] +markers = "sys_platform == \"win32\"" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "iniconfig" +version = "2.3.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.10" +groups = ["dev"] +files = [ + {file = "iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12"}, + {file = "iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730"}, +] + +[[package]] +name = "packaging" +version = "26.0" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529"}, + {file = "packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4"}, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"}, + {file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["coverage", "pytest", "pytest-benchmark"] + +[[package]] +name = "pytest" +version = "7.4.4" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.7" +groups = ["dev"] +files = [ + {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"}, + {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=0.12,<2.0" + +[package.extras] +testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] + +[metadata] +lock-version = "2.1" +python-versions = "^3.11" +content-hash = "a90a99984f5620be3b1732353abf5d343d3316684b2f1a669cda5d881ea706c1" From c07811cdf3ec621a37420bbc9d88af96d14a8835 Mon Sep 17 00:00:00 2001 From: Philip <44484441+EckPhi@users.noreply.github.com> Date: Sun, 29 Mar 2026 13:02:35 +0200 Subject: [PATCH 21/31] Rewrite web app for in-browser thread XML generation --- web/README.md | 52 ++++++++-- web/index.html | 97 ++++++++++++++++++ web/js/app.js | 144 +++++++++++++++++++++++++++ web/js/calculator.js | 105 ++++++++++++++++++++ web/js/xml.js | 66 ++++++++++++ web/style.css | 232 +++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 685 insertions(+), 11 deletions(-) create mode 100644 web/index.html create mode 100644 web/js/app.js create mode 100644 web/js/calculator.js create mode 100644 web/js/xml.js create mode 100644 web/style.css diff --git a/web/README.md b/web/README.md index 049a3e5..ad9a9cb 100644 --- a/web/README.md +++ b/web/README.md @@ -1,16 +1,46 @@ -# Web Generator (Future) +# Web Generator -This directory will contain the JavaScript/web implementation of the thread generator for deployment on GitHub Pages. +This folder contains the GitHub Pages web app for generating Fusion 360 thread XML directly in the browser. -## Planned Structure +## Files -- `index.html` - Main web interface -- `app.js` - Core generator logic (ported from Python) -- `style.css` - Styling -- `lib/` - Dependencies and utilities +- `index.html`: UI layout and form inputs +- `style.css`: App styling and responsive layout +- `js/calculator.js`: Ported thread calculation logic from Python +- `js/xml.js`: XML assembly and file download helpers +- `js/app.js`: Form wiring, validation, preview, and download actions -## Notes +## Local Run -- The core thread generation algorithm will be ported from Python to JavaScript -- Users will be able to configure pitch ranges, thread sizes, and tolerance offsets in a browser -- Generated XML can be downloaded directly +Because the app uses ES modules, serve it through a local web server instead of opening `index.html` directly. + +Example with Python: + +```bash +cd web +python3 -m http.server 8080 +``` + +Then open `http://localhost:8080`. + +## Deploy + +Deployment is handled by `.github/workflows/deploy-pages.yml`. + +- Trigger: push to `main` when files in `web/` change +- Output: published GitHub Pages site from this folder + +## Behavior Parity + +The browser generator mirrors Python logic from: + +- `src/customthreads/generator.py` +- `src/customthreads/models.py` + +It preserves: + +- Pitch range generation with floating-point guard +- Designation format: `M{size}x{pitch}` +- Tolerance class labels: `O.x` +- Metric geometry formulas for major/pitch/minor diameters +- XML node structure compatible with Fusion 360 thread files diff --git a/web/index.html b/web/index.html new file mode 100644 index 0000000..6520e52 --- /dev/null +++ b/web/index.html @@ -0,0 +1,97 @@ + + + + + + CustomThreads Generator + + + + + + +
+
+

GitHub Pages Tool

+

3D-Printed Metric Thread XML Generator

+

Generate Fusion 360 compatible XML profiles directly in your browser. No local Python required.

+
+ +
+
+

Generator Settings

+ +
+ + +
+ +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+ +
+ + +
+ +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+ + +
+ +
    +
    + +
    +

    Output Preview

    +
    No output generated yet.
    +
    
    +      
    +
    +
    + + + + diff --git a/web/js/app.js b/web/js/app.js new file mode 100644 index 0000000..76a14dd --- /dev/null +++ b/web/js/app.js @@ -0,0 +1,144 @@ +import { calculateSummary, createModel, createThreadSizes } from "./calculator.js"; +import { buildXml, downloadTextFile } from "./xml.js"; + +const form = document.querySelector("#config-form"); +const errorsEl = document.querySelector("#errors"); +const summaryEl = document.querySelector("#summary"); +const previewEl = document.querySelector("#xmlPreview"); +const downloadBtn = document.querySelector("#downloadBtn"); + +let generated = null; + +form.addEventListener("submit", (event) => { + event.preventDefault(); + const parseResult = parseConfig(form); + + if (!parseResult.ok) { + renderErrors(parseResult.errors); + return; + } + + const config = parseResult.config; + const structure = createModel(config); + const xml = buildXml(config, structure); + const summary = calculateSummary(structure); + + generated = { + filename: config.filename, + xml, + summary, + firstDesignation: structure.model[0]?.designations[0]?.name ?? "n/a", + lastDesignation: + structure.model[structure.model.length - 1]?.designations[ + structure.model[structure.model.length - 1]?.designations.length - 1 + ]?.name ?? "n/a", + }; + + renderErrors([]); + renderOutput(generated); + downloadBtn.disabled = false; +}); + +downloadBtn.addEventListener("click", () => { + if (!generated) { + return; + } + downloadTextFile(generated.filename, generated.xml); +}); + +function parseConfig(formElement) { + const values = new FormData(formElement); + const errors = []; + + const pitchStart = parseFloat(values.get("pitchStart")); + const pitchEnd = parseFloat(values.get("pitchEnd")); + const pitchStep = parseFloat(values.get("pitchStep")); + const sizeMin = parseInt(values.get("sizeMin"), 10); + const sizeMax = parseInt(values.get("sizeMax"), 10); + const threadAngle = parseFloat(values.get("angle")); + const threadForm = parseInt(values.get("threadForm"), 10); + const offsetsRaw = String(values.get("offsets") ?? ""); + + if (!(pitchStep > 0)) { + errors.push("Pitch step must be greater than 0."); + } + + if (!(pitchEnd >= pitchStart)) { + errors.push("Pitch end must be greater than or equal to pitch start."); + } + + if (!(sizeMin > 0 && sizeMax >= sizeMin)) { + errors.push("Diameter range is invalid. Ensure max >= min and both are positive."); + } + + if (!(threadAngle > 0)) { + errors.push("Thread angle must be greater than 0."); + } + + const toleranceOffsets = offsetsRaw + .split(",") + .map((part) => part.trim()) + .filter(Boolean) + .map((part) => Number(part)); + + if (toleranceOffsets.length === 0 || toleranceOffsets.some((value) => Number.isNaN(value))) { + errors.push("Tolerance offsets must be comma-separated numeric values."); + } + + if (errors.length > 0) { + return { ok: false, errors }; + } + + return { + ok: true, + config: { + threadName: String(values.get("threadName") ?? "3D-Printed Metric Threads V3"), + unit: String(values.get("unit") ?? "mm"), + filename: ensureXmlFilename(String(values.get("filename") ?? "3DPrintedMetricThreads.xml")), + threadAngle, + threadForm, + pitchStart, + pitchEnd, + pitchStep, + threadSizes: createThreadSizes(sizeMin, sizeMax), + toleranceOffsets, + }, + }; +} + +function renderErrors(errors) { + errorsEl.innerHTML = ""; + if (errors.length === 0) { + return; + } + + for (const error of errors) { + const item = document.createElement("li"); + item.textContent = error; + errorsEl.appendChild(item); + } + + summaryEl.textContent = "Fix the validation errors to generate XML."; + summaryEl.className = "summary"; + previewEl.textContent = ""; + downloadBtn.disabled = true; +} + +function renderOutput(result) { + summaryEl.className = "summary ok"; + summaryEl.innerHTML = [ + `Generated: ${result.filename}`, + `Thread Sizes: ${result.summary.sizeCount}`, + `Designations: ${result.summary.designationCount}`, + `Thread Nodes: ${result.summary.threadCount}`, + `Range: ${result.firstDesignation} to ${result.lastDesignation}`, + ].join("
    "); + + const previewLines = result.xml.split("\n").slice(0, 80).join("\n"); + previewEl.textContent = previewLines; +} + +function ensureXmlFilename(filename) { + const clean = filename.trim() || "3DPrintedMetricThreads.xml"; + return clean.toLowerCase().endsWith(".xml") ? clean : `${clean}.xml`; +} diff --git a/web/js/calculator.js b/web/js/calculator.js new file mode 100644 index 0000000..7571c20 --- /dev/null +++ b/web/js/calculator.js @@ -0,0 +1,105 @@ +export function generatePitchList(start, end, step) { + const pitches = []; + let current = start; + + while (current <= end + 1e-9) { + pitches.push(roundTo(current, 4)); + current += step; + } + + return pitches; +} + +export function formatNumber(value) { + return Number.isInteger(value) ? String(value) : String(value); +} + +export function format4g(value) { + return parseFloat(Number(value).toPrecision(4)).toString(); +} + +export function createThreadSizes(minSize, maxSize) { + const sizes = []; + for (let size = minSize; size <= maxSize; size += 1) { + sizes.push(size); + } + return sizes; +} + +export function createModel(config) { + const pitches = generatePitchList(config.pitchStart, config.pitchEnd, config.pitchStep); + const model = []; + + for (const size of config.threadSizes) { + const designations = []; + for (const pitch of pitches) { + const designation = { + nominalDiameter: size, + pitch, + name: `M${formatNumber(size)}x${formatNumber(pitch)}`, + threads: calculateThreads(size, pitch, config.threadAngle, config.toleranceOffsets), + }; + designations.push(designation); + } + + model.push({ + size, + designations, + }); + } + + return { pitches, model }; +} + +export function calculateSummary(structure) { + const sizeCount = structure.model.length; + const designationCount = structure.model.reduce((sum, item) => sum + item.designations.length, 0); + const threadCount = structure.model.reduce( + (sum, item) => + sum + item.designations.reduce((inner, des) => inner + des.threads.length, 0), + 0 + ); + + return { sizeCount, designationCount, threadCount }; +} + +function calculateThreads(diameter, pitch, threadAngle, toleranceOffsets) { + const threads = []; + + const h = (1 / Math.tan(toRadians(threadAngle / 2))) * (pitch / 2); + const pitchDiaBase = diameter - 2 * ((3 * h) / 8); + const minorDiaBase = diameter - 2 * ((5 * h) / 8); + + for (const offset of toleranceOffsets) { + const classLabel = `O.${String(offset).split(".")[1] ?? "0"}`; + + threads.push({ + gender: "external", + clazz: classLabel, + majorDia: diameter - offset, + pitchDia: pitchDiaBase - offset, + minorDia: minorDiaBase - offset, + tapDrill: null, + }); + + threads.push({ + gender: "internal", + clazz: classLabel, + majorDia: diameter + offset, + pitchDia: pitchDiaBase + offset, + minorDia: minorDiaBase + offset, + tapDrill: diameter - pitch, + }); + } + + return threads; +} + +function toRadians(value) { + return (Math.PI / 180) * value; +} + +function roundTo(value, places) { + const scale = 10 ** places; + return Math.round(value * scale) / scale; +} diff --git a/web/js/xml.js b/web/js/xml.js new file mode 100644 index 0000000..f3a49d0 --- /dev/null +++ b/web/js/xml.js @@ -0,0 +1,66 @@ +import { format4g } from "./calculator.js"; + +export function buildXml(config, structure) { + const lines = []; + lines.push(""); + lines.push(""); + lines.push(` ${escapeXml(config.threadName)}`); + lines.push(` ${escapeXml(config.threadName)}`); + lines.push(` ${escapeXml(config.unit)}`); + lines.push(` ${escapeXml(String(config.threadAngle))}`); + lines.push(` ${escapeXml(String(config.threadForm))}`); + lines.push(" 3"); + + for (const sizeItem of structure.model) { + lines.push(" "); + lines.push(` ${sizeItem.size}`); + + for (const designation of sizeItem.designations) { + lines.push(" "); + lines.push(` ${escapeXml(designation.name)}`); + lines.push(` ${escapeXml(designation.name)}`); + lines.push(` ${designation.pitch}`); + + for (const thread of designation.threads) { + lines.push(" "); + lines.push(` ${thread.gender}`); + lines.push(` ${thread.clazz}`); + lines.push(` ${format4g(thread.majorDia)}`); + lines.push(` ${format4g(thread.pitchDia)}`); + lines.push(` ${format4g(thread.minorDia)}`); + if (thread.tapDrill) { + lines.push(` ${format4g(thread.tapDrill)}`); + } + lines.push(" "); + } + + lines.push(" "); + } + + lines.push(" "); + } + + lines.push(""); + return lines.join("\n"); +} + +export function downloadTextFile(filename, text) { + const blob = new Blob([text], { type: "application/xml;charset=utf-8" }); + const url = URL.createObjectURL(blob); + const anchor = document.createElement("a"); + anchor.href = url; + anchor.download = filename; + document.body.appendChild(anchor); + anchor.click(); + anchor.remove(); + URL.revokeObjectURL(url); +} + +function escapeXml(value) { + return value + .replaceAll("&", "&") + .replaceAll("<", "<") + .replaceAll(">", ">") + .replaceAll('"', """) + .replaceAll("'", "'"); +} diff --git a/web/style.css b/web/style.css new file mode 100644 index 0000000..dc95c50 --- /dev/null +++ b/web/style.css @@ -0,0 +1,232 @@ +:root { + --bg-0: #f7f2e7; + --bg-1: #ffe4b9; + --ink-0: #17212f; + --ink-1: #2f3c4d; + --accent-a: #007f7c; + --accent-b: #e95f2b; + --ok: #0e8f5e; + --warn: #b73a2f; + --card: #fffdf8; + --line: #d8d2c7; + --shadow: 0 14px 40px rgba(20, 27, 40, 0.12); +} + +* { + box-sizing: border-box; +} + +body { + margin: 0; + min-height: 100vh; + font-family: "Space Grotesk", sans-serif; + color: var(--ink-0); + background: + radial-gradient(circle at 10% 15%, rgba(0, 127, 124, 0.18), transparent 28%), + radial-gradient(circle at 85% 5%, rgba(233, 95, 43, 0.2), transparent 30%), + linear-gradient(160deg, var(--bg-0), var(--bg-1)); +} + +.page { + width: min(1200px, 94vw); + margin: 2rem auto 3rem; +} + +.hero { + animation: fadeUp 420ms ease-out; +} + +.eyebrow { + font-size: 0.84rem; + letter-spacing: 0.12em; + text-transform: uppercase; + color: var(--accent-a); + margin: 0; +} + +h1 { + margin: 0.4rem 0 0.5rem; + font-size: clamp(1.8rem, 3.8vw, 3rem); + line-height: 1.05; +} + +.subtitle { + margin: 0; + color: var(--ink-1); + max-width: 70ch; +} + +.grid { + display: grid; + grid-template-columns: 1.15fr 1fr; + gap: 1rem; + margin-top: 1.2rem; +} + +.card { + background: var(--card); + border: 1px solid var(--line); + border-radius: 16px; + box-shadow: var(--shadow); +} + +.controls, +.output { + padding: 1rem; +} + +h2 { + margin-top: 0; + margin-bottom: 0.8rem; + font-size: 1.1rem; +} + +.field-row { + display: grid; + gap: 0.4rem; + margin-bottom: 0.8rem; +} + +.split-2 { + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 0.7rem; +} + +.split-3 { + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 0.7rem; +} + +.split-4 { + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: 0.7rem; +} + +label { + font-size: 0.83rem; + color: var(--ink-1); +} + +input { + width: 100%; + border: 1px solid var(--line); + border-radius: 10px; + background: #fff; + color: var(--ink-0); + padding: 0.62rem 0.7rem; + font: inherit; +} + +input:focus { + outline: 2px solid color-mix(in oklab, var(--accent-a), white 65%); + border-color: var(--accent-a); +} + +.actions { + display: flex; + gap: 0.6rem; + margin-top: 0.8rem; +} + +button { + border: 0; + border-radius: 10px; + font: inherit; + font-weight: 600; + cursor: pointer; + padding: 0.66rem 0.95rem; + transition: transform 120ms ease, opacity 120ms ease; +} + +button:hover { + transform: translateY(-1px); +} + +#generateBtn { + background: var(--accent-a); + color: #fff; +} + +#downloadBtn { + background: var(--accent-b); + color: #fff; +} + +button[disabled] { + cursor: not-allowed; + opacity: 0.45; +} + +.errors { + margin: 0.8rem 0 0; + padding-left: 1.1rem; + color: var(--warn); + min-height: 1.2rem; +} + +.summary { + border: 1px dashed var(--line); + border-radius: 12px; + padding: 0.8rem; + margin-bottom: 0.8rem; + background: #fff; +} + +.summary.ok { + border-color: color-mix(in oklab, var(--ok), white 50%); +} + +.summary.empty { + color: var(--ink-1); +} + +.xml-preview { + margin: 0; + background: #11161f; + color: #dfebff; + border-radius: 12px; + min-height: 300px; + max-height: 520px; + overflow: auto; + padding: 0.8rem; + font-family: "IBM Plex Mono", monospace; + font-size: 0.8rem; + line-height: 1.45; +} + +@keyframes fadeUp { + from { + opacity: 0; + transform: translateY(8px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +@media (max-width: 950px) { + .grid { + grid-template-columns: 1fr; + } + + .split-4 { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } +} + +@media (max-width: 640px) { + .page { + width: 95vw; + } + + .split-2, + .split-3, + .split-4 { + grid-template-columns: 1fr; + } + + .actions { + flex-direction: column; + } +} From ef895f068875b6512523349cc8883b6826ce8df8 Mon Sep 17 00:00:00 2001 From: Philip <44484441+EckPhi@users.noreply.github.com> Date: Sun, 29 Mar 2026 13:17:36 +0200 Subject: [PATCH 22/31] Add shareable URL feature to web app --- README.md | 1 + web/README.md | 2 ++ web/index.html | 1 + web/js/app.js | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 72 insertions(+) diff --git a/README.md b/README.md index f0feeca..665fbaa 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ python3 -m http.server 8080 - Open `http://localhost:8080` - Generate and download `3DPrintedMetricThreads.xml` +- Use Copy Share Link to share a prefilled generator configuration via URL ### Download Pre-Generated XML Files diff --git a/web/README.md b/web/README.md index ad9a9cb..70e6a38 100644 --- a/web/README.md +++ b/web/README.md @@ -10,6 +10,8 @@ This folder contains the GitHub Pages web app for generating Fusion 360 thread X - `js/xml.js`: XML assembly and file download helpers - `js/app.js`: Form wiring, validation, preview, and download actions +Phase 2 UX includes shareable URLs for prefilled configurations. + ## Local Run Because the app uses ES modules, serve it through a local web server instead of opening `index.html` directly. diff --git a/web/index.html b/web/index.html index 6520e52..debfded 100644 --- a/web/index.html +++ b/web/index.html @@ -79,6 +79,7 @@

    Generator Settings

    +
      diff --git a/web/js/app.js b/web/js/app.js index 76a14dd..6dbfbe9 100644 --- a/web/js/app.js +++ b/web/js/app.js @@ -6,9 +6,12 @@ const errorsEl = document.querySelector("#errors"); const summaryEl = document.querySelector("#summary"); const previewEl = document.querySelector("#xmlPreview"); const downloadBtn = document.querySelector("#downloadBtn"); +const shareBtn = document.querySelector("#shareBtn"); let generated = null; +hydrateFormFromUrl(); + form.addEventListener("submit", (event) => { event.preventDefault(); const parseResult = parseConfig(form); @@ -36,6 +39,7 @@ form.addEventListener("submit", (event) => { renderErrors([]); renderOutput(generated); + writeUrlFromConfig(config); downloadBtn.disabled = false; }); @@ -46,6 +50,25 @@ downloadBtn.addEventListener("click", () => { downloadTextFile(generated.filename, generated.xml); }); +shareBtn.addEventListener("click", async () => { + const parseResult = parseConfig(form); + if (!parseResult.ok) { + renderErrors(parseResult.errors); + return; + } + + writeUrlFromConfig(parseResult.config); + const shareUrl = window.location.href; + + try { + await navigator.clipboard.writeText(shareUrl); + summaryEl.className = "summary ok"; + summaryEl.innerHTML += "
      Share link copied to clipboard."; + } catch { + window.prompt("Copy this share URL:", shareUrl); + } +}); + function parseConfig(formElement) { const values = new FormData(formElement); const errors = []; @@ -142,3 +165,48 @@ function ensureXmlFilename(filename) { const clean = filename.trim() || "3DPrintedMetricThreads.xml"; return clean.toLowerCase().endsWith(".xml") ? clean : `${clean}.xml`; } + +function hydrateFormFromUrl() { + const params = new URLSearchParams(window.location.search); + if (params.size === 0) { + return; + } + + setIfPresent("threadName", params.get("threadName")); + setIfPresent("pitchStart", params.get("pitchStart")); + setIfPresent("pitchEnd", params.get("pitchEnd")); + setIfPresent("pitchStep", params.get("pitchStep")); + setIfPresent("sizeMin", params.get("sizeMin")); + setIfPresent("sizeMax", params.get("sizeMax")); + setIfPresent("offsets", params.get("offsets")); + setIfPresent("unit", params.get("unit")); + setIfPresent("angle", params.get("angle")); + setIfPresent("threadForm", params.get("threadForm")); + setIfPresent("filename", params.get("filename")); +} + +function setIfPresent(fieldName, value) { + if (value === null) { + return; + } + const field = form.querySelector(`[name="${fieldName}"]`); + if (field) { + field.value = value; + } +} + +function writeUrlFromConfig(config) { + const url = new URL(window.location.href); + url.searchParams.set("threadName", config.threadName); + url.searchParams.set("pitchStart", String(config.pitchStart)); + url.searchParams.set("pitchEnd", String(config.pitchEnd)); + url.searchParams.set("pitchStep", String(config.pitchStep)); + url.searchParams.set("sizeMin", String(config.threadSizes[0])); + url.searchParams.set("sizeMax", String(config.threadSizes[config.threadSizes.length - 1])); + url.searchParams.set("offsets", config.toleranceOffsets.join(",")); + url.searchParams.set("unit", config.unit); + url.searchParams.set("angle", String(config.threadAngle)); + url.searchParams.set("threadForm", String(config.threadForm)); + url.searchParams.set("filename", config.filename); + window.history.replaceState({}, "", url); +} From aa7c6a20b2d7f7291c64adf1a9f47c9229e5d6d8 Mon Sep 17 00:00:00 2001 From: Philip <44484441+EckPhi@users.noreply.github.com> Date: Sun, 29 Mar 2026 13:17:36 +0200 Subject: [PATCH 23/31] Add live app links to documentation and UI --- README.md | 2 ++ web/README.md | 2 ++ web/index.html | 2 +- web/style.css | 9 +++++++++ 4 files changed, 14 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 665fbaa..1a352ab 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,8 @@ Generate standardized ISO metric thread profiles optimized for 3D printing in XM The browser generator is in [web/index.html](web/index.html) and is deployed through GitHub Pages. +- Live URL: [https://eckphi.github.io/CustomThreads/](https://eckphi.github.io/CustomThreads/) + - Local preview: ```bash diff --git a/web/README.md b/web/README.md index 70e6a38..b06ac6e 100644 --- a/web/README.md +++ b/web/README.md @@ -2,6 +2,8 @@ This folder contains the GitHub Pages web app for generating Fusion 360 thread XML directly in the browser. +Live site: [https://eckphi.github.io/CustomThreads/](https://eckphi.github.io/CustomThreads/) + ## Files - `index.html`: UI layout and form inputs diff --git a/web/index.html b/web/index.html index debfded..b139187 100644 --- a/web/index.html +++ b/web/index.html @@ -14,7 +14,7 @@

      GitHub Pages Tool

      3D-Printed Metric Thread XML Generator

      -

      Generate Fusion 360 compatible XML profiles directly in your browser. No local Python required.

      +

      Generate Fusion 360 compatible XML profiles directly in your browser. No local Python required. Live app: https://eckphi.github.io/CustomThreads/

      diff --git a/web/style.css b/web/style.css index dc95c50..83718af 100644 --- a/web/style.css +++ b/web/style.css @@ -56,6 +56,15 @@ h1 { max-width: 70ch; } +.live-link { + color: var(--accent-a); + font-weight: 600; +} + +.live-link:hover { + color: var(--accent-b); +} + .grid { display: grid; grid-template-columns: 1.15fr 1fr; From 5571ddbd4d28f0f1981fdd876fe040cc89737072 Mon Sep 17 00:00:00 2001 From: Philip <44484441+EckPhi@users.noreply.github.com> Date: Sun, 29 Mar 2026 13:17:37 +0200 Subject: [PATCH 24/31] Update workflow actions to latest major versions and set Node 24 env --- .github/workflows/deploy-pages.yml | 11 +++++++---- .github/workflows/generate-xml-release.yml | 5 ++++- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/.github/workflows/deploy-pages.yml b/.github/workflows/deploy-pages.yml index b7d77c9..f8c0f98 100644 --- a/.github/workflows/deploy-pages.yml +++ b/.github/workflows/deploy-pages.yml @@ -9,6 +9,9 @@ on: - .github/workflows/deploy-pages.yml workflow_dispatch: +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true" + permissions: contents: read pages: write @@ -28,16 +31,16 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Configure Pages - uses: actions/configure-pages@v5 + uses: actions/configure-pages@v6 - name: Upload web artifact - uses: actions/upload-pages-artifact@v3 + uses: actions/upload-pages-artifact@v4 with: path: ./web - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@v4 + uses: actions/deploy-pages@v5 diff --git a/.github/workflows/generate-xml-release.yml b/.github/workflows/generate-xml-release.yml index a56e64e..0fbc585 100644 --- a/.github/workflows/generate-xml-release.yml +++ b/.github/workflows/generate-xml-release.yml @@ -6,6 +6,9 @@ on: - main workflow_dispatch: # Allow manual trigger +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true" + jobs: generate-and-release: runs-on: ubuntu-latest @@ -15,7 +18,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Set up Python uses: actions/setup-python@v5 From 3f4815e4a71a7895bbefcfc4518f7c7ea0c1fd4e Mon Sep 17 00:00:00 2001 From: Philip <44484441+EckPhi@users.noreply.github.com> Date: Sun, 29 Mar 2026 13:17:37 +0200 Subject: [PATCH 25/31] Simplify XML release workflow with unified release asset upload --- .github/workflows/generate-xml-release.yml | 29 ++++++++-------------- 1 file changed, 10 insertions(+), 19 deletions(-) diff --git a/.github/workflows/generate-xml-release.yml b/.github/workflows/generate-xml-release.yml index 0fbc585..b611e4b 100644 --- a/.github/workflows/generate-xml-release.yml +++ b/.github/workflows/generate-xml-release.yml @@ -57,39 +57,30 @@ jobs: echo "version=$VERSION" >> $GITHUB_OUTPUT echo "Version: $VERSION" - - name: Create Release - uses: actions/create-release@v1 - id: create_release + - name: Create release and upload asset + uses: softprops/action-gh-release@v2 with: draft: false prerelease: false - release_name: XML Thread Profiles ${{ steps.version.outputs.version }} + name: XML Thread Profiles ${{ steps.version.outputs.version }} tag_name: ${{ steps.version.outputs.version }} body: | Automatically generated 3D-printed metric thread profiles in XML format. - + **Generated:** ${{ github.event.head_commit.timestamp }} **Commit:** ${{ github.sha }} - + ## Contents - - `3DPrintedMetricThreads.xml` - Thread profiles from 3.5mm to 50mm diameter, pitches 1.0mm to 6.0mm, 5 tolerance classes (O.0 to O.8) - + - `3DPrintedMetricThreads.xml` - Thread profiles from 8mm to 50mm diameter, pitches 1.0mm to 6.0mm, 5 tolerance classes (O.0 to O.8) + ## Usage 1. Download the XML file 2. Place in Fusion 360 thread data directory (see README) 3. Restart Fusion 360 - + Use these profiles for 3D-printed metric threads optimized for FDM printing. - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Upload Release Asset - uses: actions/upload-release-asset@v1 - with: - upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: ./build/xml/3DPrintedMetricThreads.xml - asset_name: 3DPrintedMetricThreads.xml - asset_content_type: application/xml + files: | + ./build/xml/3DPrintedMetricThreads.xml env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 32a3b13e0c56542139e3327ac7eb4ad75056165c Mon Sep 17 00:00:00 2001 From: Philip <44484441+EckPhi@users.noreply.github.com> Date: Sun, 29 Mar 2026 13:19:38 +0200 Subject: [PATCH 26/31] Switches workflow triggers from main to master branch --- .github/workflows/deploy-pages.yml | 2 +- .github/workflows/generate-xml-release.yml | 2 +- README.md | 4 ++-- web/README.md | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/deploy-pages.yml b/.github/workflows/deploy-pages.yml index f8c0f98..b5126d8 100644 --- a/.github/workflows/deploy-pages.yml +++ b/.github/workflows/deploy-pages.yml @@ -3,7 +3,7 @@ name: Deploy GitHub Pages Generator on: push: branches: - - main + - master paths: - web/** - .github/workflows/deploy-pages.yml diff --git a/.github/workflows/generate-xml-release.yml b/.github/workflows/generate-xml-release.yml index b611e4b..246350d 100644 --- a/.github/workflows/generate-xml-release.yml +++ b/.github/workflows/generate-xml-release.yml @@ -3,7 +3,7 @@ name: Generate and Release XML Thread Profiles on: push: branches: - - main + - master workflow_dispatch: # Allow manual trigger env: diff --git a/README.md b/README.md index 1a352ab..5236431 100644 --- a/README.md +++ b/README.md @@ -129,7 +129,7 @@ This project uses **GitHub Actions** to automatically generate and publish threa **Workflow File:** `.github/workflows/generate-xml-release.yml` **Trigger Events:** -- โœ… Every push to `main` branch +- โœ… Every push to `master` branch - โœ… Manual trigger via GitHub Actions UI (workflow_dispatch) **Pipeline Steps:** @@ -194,7 +194,7 @@ These offset values from nominal dimensions allow fine-tuning for different prin The web app is deployed by [.github/workflows/deploy-pages.yml](.github/workflows/deploy-pages.yml). -- Trigger: push to `main` when files under [web](web) change +- Trigger: push to `master` when files under [web](web) change - Source artifact: [web](web) - Publish target: GitHub Pages environment diff --git a/web/README.md b/web/README.md index b06ac6e..be40dc7 100644 --- a/web/README.md +++ b/web/README.md @@ -31,7 +31,7 @@ Then open `http://localhost:8080`. Deployment is handled by `.github/workflows/deploy-pages.yml`. -- Trigger: push to `main` when files in `web/` change +- Trigger: push to `master` when files in `web/` change - Output: published GitHub Pages site from this folder ## Behavior Parity From 5ae4f3b2cdc097f16b1b86617a50ca3a3bfcb3cf Mon Sep 17 00:00:00 2001 From: Philip <44484441+EckPhi@users.noreply.github.com> Date: Sun, 29 Mar 2026 13:26:15 +0200 Subject: [PATCH 27/31] Adds source code link to project overview --- web/index.html | 1 + 1 file changed, 1 insertion(+) diff --git a/web/index.html b/web/index.html index b139187..81f3b99 100644 --- a/web/index.html +++ b/web/index.html @@ -15,6 +15,7 @@

      GitHub Pages Tool

      3D-Printed Metric Thread XML Generator

      Generate Fusion 360 compatible XML profiles directly in your browser. No local Python required. Live app: https://eckphi.github.io/CustomThreads/

      +

      Source code: https://github.com/EckPhi/CustomThreads

      From 780ed3c4c4c8139a6024854e7d1a05f893049e07 Mon Sep 17 00:00:00 2001 From: Philip <44484441+EckPhi@users.noreply.github.com> Date: Sun, 29 Mar 2026 18:54:49 +0200 Subject: [PATCH 28/31] Add utility for formatting numbers for display --- src/customthreads/utils.py | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 src/customthreads/utils.py diff --git a/src/customthreads/utils.py b/src/customthreads/utils.py new file mode 100644 index 0000000..57fcda3 --- /dev/null +++ b/src/customthreads/utils.py @@ -0,0 +1,6 @@ +"""Shared utility helpers for the customthreads package.""" + + +def format_number(val): + """Format numbers for display (e.g., 5.0 -> "5").""" + return str(int(val)) if val == int(val) else str(val) From 23eb6539f9169fc7a27e1023b6282546690cc9c8 Mon Sep 17 00:00:00 2001 From: Philip <44484441+EckPhi@users.noreply.github.com> Date: Sun, 29 Mar 2026 18:55:49 +0200 Subject: [PATCH 29/31] =?UTF-8?q?Update=20documentation=20for=20main?= =?UTF-8?q?=E2=86=92master=20branch=20and=20versioning?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/CI_GUIDE.md | 34 +++++++++++++++++----------------- README.md | 8 ++++---- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/.github/CI_GUIDE.md b/.github/CI_GUIDE.md index 2540010..a0b28c6 100644 --- a/.github/CI_GUIDE.md +++ b/.github/CI_GUIDE.md @@ -6,7 +6,7 @@ This document explains the GitHub Actions workflow for automatically generating The automated pipeline ensures that: 1. โœ… XML files are **never committed** to the repository -2. โœ… Every push to `main` triggers a new release +2. โœ… Every push to `master` triggers a new release 3. โœ… Tests run before each release to ensure quality 4. โœ… Releases are timestamped with UTC datetime 5. โœ… Users always have access to the latest profiles @@ -23,7 +23,7 @@ The workflow runs on: on: push: branches: - - main # Automatic on every push to main + - master # Automatic on every push to master workflow_dispatch: # Manual trigger via GitHub UI ``` @@ -36,29 +36,29 @@ on: - Cache dependencies #### 2. Generate (automatic) -- Run all tests to verify code quality - Generate XML files using `poetry run python -m customthreads.cli` - Place artifacts in `build/xml/` directory -#### 3. Release (automatic) -- Create Git tag with datetime format: `v{YYYY}.{MM}.{DD}-{HHMM}` +#### 3. Verify (automatic) +- Run all tests to verify code quality +- Validate generated XML structure + +#### 4. Release (automatic) +- Create Git tag with datetime format: `v{YYYY}.{MM}.{DD}-{HHMMSS}-r{RUN_NUMBER}` - Create GitHub Release with metadata - Upload XML file as downloadable artifact -#### 4. Verify (automatic) -- Validate generated XML structure -- Ensure all tests passed - ### Versioning Scheme -Release versions use **calendar versioning with minute precision**: +Release versions use **calendar versioning with second precision + run number**: ``` -v2026.03.29-1245 +v2026.03.29-124530-r42 โ””โ”€ 2026: Year โ””โ”€ 03: Month (March) โ””โ”€ 29: Day - โ””โ”€ 1245: Time in UTC (12:45 UTC) + โ””โ”€ 124530: Time in UTC (12:45:30 UTC) + โ””โ”€ r42: GitHub Actions run number ``` **Advantages:** @@ -68,9 +68,9 @@ v2026.03.29-1245 - Preserves full timestamp accuracy **Examples:** -- `v2026.03.29-0000` = March 29, 2026 at midnight UTC -- `v2026.03.29-1200` = March 29, 2026 at noon UTC -- `v2026.12.31-2359` = December 31, 2026 at 23:59 UTC +- `v2026.03.29-000000-r7` = March 29, 2026 at midnight UTC, run 7 +- `v2026.03.29-120000-r12` = March 29, 2026 at noon UTC, run 12 +- `v2026.12.31-235959-r128` = December 31, 2026 at 23:59:59 UTC, run 128 ## Using Releases @@ -160,7 +160,7 @@ Edit the `body` section of `create-release` step: ### Release Not Created 1. Check "Actions" tab for workflow errors -2. Verify branch is `main` (not a feature branch) +2. Verify branch is `master` (not a feature branch) 3. Ensure `pyproject.toml` is present 4. Check Poetry dependencies are correct @@ -172,7 +172,7 @@ Edit the `body` section of `create-release` step: ### Workflow Not Triggering -1. Verify `.github/workflows/generate-xml-release.yml` is in `main` branch +1. Verify `.github/workflows/generate-xml-release.yml` is in `master` branch 2. Check repository settings โ†’ Actions permissions are enabled 3. Ensure your GitHub token has write access to releases diff --git a/README.md b/README.md index 5236431..5414fd2 100644 --- a/README.md +++ b/README.md @@ -142,10 +142,10 @@ This project uses **GitHub Actions** to automatically generate and publish threa ### Release Versioning -- **Format:** `v{YYYY}.{MM}.{DD}-{HHMM}` (UTC time) +- **Format:** `v{YYYY}.{MM}.{DD}-{HHMMSS}-r{RUN_NUMBER}` (UTC time + workflow run number) - **Examples:** - - `v2026.03.29-1000` = March 29, 2026 at 10:00 UTC - - `v2026.03.29-1245` = March 29, 2026 at 12:45 UTC + - `v2026.03.29-100000-r5` = March 29, 2026 at 10:00:00 UTC, run 5 + - `v2026.03.29-124530-r9` = March 29, 2026 at 12:45:30 UTC, run 9 ### Key Features @@ -187,7 +187,7 @@ These offset values from nominal dimensions allow fine-tuning for different prin |-----------|-------| | **Diameters** | 8-50mm (all integer values) | | **Pitches** | 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0, 4.5, 5.0, 5.5, 6.0mm | -| **Thread Type** | ISO Metric (60ยฐ Whitworth) | +| **Thread Type** | ISO Metric (60ยฐ flank angle) | | **Optimized for** | FDM 3D printing (0.2-0.3mm layer height) | ## GitHub Pages Deployment From 0316559b5f44deba91db4b3f289ca10458e8dd36 Mon Sep 17 00:00:00 2001 From: Philip <44484441+EckPhi@users.noreply.github.com> Date: Sun, 29 Mar 2026 18:55:49 +0200 Subject: [PATCH 30/31] Refactor XML generation and validation in workflow and code --- .github/workflows/generate-xml-release.yml | 17 ++++---- src/customthreads/__init__.py | 3 +- src/customthreads/generator.py | 15 +------ src/customthreads/models.py | 2 +- tests/test_main.py | 51 ++++++++-------------- web/js/xml.js | 2 +- 6 files changed, 34 insertions(+), 56 deletions(-) diff --git a/.github/workflows/generate-xml-release.yml b/.github/workflows/generate-xml-release.yml index 246350d..22b89f9 100644 --- a/.github/workflows/generate-xml-release.yml +++ b/.github/workflows/generate-xml-release.yml @@ -49,12 +49,19 @@ jobs: # Also generate with alternative configurations (optional) mkdir -p build/xml mv 3DPrintedMetricThreads.xml build/xml/ + + - name: Run tests and validate generated XML + run: | + poetry run pytest -v + python3 -c "import xml.etree.ElementTree as ET; ET.parse('build/xml/3DPrintedMetricThreads.xml'); print('โœ… Generated XML is valid')" - name: Create datetime-based version tag id: version run: | - VERSION=$(date -u +'v%Y.%m.%d-%H%M') + VERSION=$(date -u +"v%Y.%m.%d-%H%M%S-r${{ github.run_number }}") + GENERATED_AT=$(date -u +"%Y-%m-%dT%H:%M:%SZ") echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "generated_at=$GENERATED_AT" >> $GITHUB_OUTPUT echo "Version: $VERSION" - name: Create release and upload asset @@ -67,7 +74,7 @@ jobs: body: | Automatically generated 3D-printed metric thread profiles in XML format. - **Generated:** ${{ github.event.head_commit.timestamp }} + **Generated:** ${{ steps.version.outputs.generated_at }} **Commit:** ${{ github.sha }} ## Contents @@ -83,9 +90,3 @@ jobs: ./build/xml/3DPrintedMetricThreads.xml env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Test generated content - run: | - poetry run pytest -v - # Verify XML is valid - python3 -c "import xml.etree.ElementTree as ET; ET.parse('build/xml/3DPrintedMetricThreads.xml'); print('โœ… Generated XML is valid')" diff --git a/src/customthreads/__init__.py b/src/customthreads/__init__.py index 01b9246..2d759ac 100644 --- a/src/customthreads/__init__.py +++ b/src/customthreads/__init__.py @@ -1,7 +1,8 @@ """3D-Printed Metric Threads Generator - Core package.""" from .models import Thread, ThreadProfile, MetricThreadGenerator -from .generator import generate_pitch_list, format_number, generate_xml +from .generator import generate_pitch_list, generate_xml +from .utils import format_number __version__ = "1.0.0" __all__ = [ diff --git a/src/customthreads/generator.py b/src/customthreads/generator.py index f5dda84..211b6c8 100644 --- a/src/customthreads/generator.py +++ b/src/customthreads/generator.py @@ -2,6 +2,7 @@ import xml.etree.ElementTree as ET from .models import MetricThreadGenerator +from .utils import format_number def generate_pitch_list(start, end, step): @@ -23,18 +24,6 @@ def generate_pitch_list(start, end, step): return pitches -def format_number(val): - """Format numbers for display (e.g., 5.0 โ†’ "5"). - - Args: - val: Numeric value to format - - Returns: - String representation without unnecessary decimals - """ - return str(int(val)) if val == int(val) else str(val) - - def generate_xml( output_filename, thread_name, @@ -91,7 +80,7 @@ def generate_xml( ET.SubElement(thread_element, "MajorDia").text = f"{thread.majorDia:.4g}" ET.SubElement(thread_element, "PitchDia").text = f"{thread.pitchDia:.4g}" ET.SubElement(thread_element, "MinorDia").text = f"{thread.minorDia:.4g}" - if thread.tapDrill: + if thread.tapDrill is not None: ET.SubElement(thread_element, "TapDrill").text = f"{thread.tapDrill:.4g}" ET.indent(tree) diff --git a/src/customthreads/models.py b/src/customthreads/models.py index 27067a8..353e903 100644 --- a/src/customthreads/models.py +++ b/src/customthreads/models.py @@ -2,6 +2,7 @@ import math from abc import ABC, abstractmethod +from .utils import format_number class Thread: @@ -44,7 +45,6 @@ class Designation: def __init__(self, diameter, pitch): self.nominalDiameter = diameter self.pitch = pitch - from .generator import format_number self.name = f"M{format_number(diameter)}x{format_number(pitch)}" def __init__(self, pitch_list, thread_sizes, tolerance_offsets, thread_angle): diff --git a/tests/test_main.py b/tests/test_main.py index 7ef9e30..f21890f 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -1,7 +1,5 @@ import pytest -import os import xml.etree.ElementTree as ET -from pathlib import Path from customthreads import ( MetricThreadGenerator, generate_pitch_list, @@ -13,21 +11,6 @@ class TestThreadGeneration: """Integration tests for thread XML generation.""" - @pytest.fixture(autouse=True) - def cleanup(self): - """Clean up generated XML files after each test.""" - yield - # Clean up test files - test_files = [ - "test_output_1.xml", - "test_output_2.xml", - "test_output_3.xml", - "test_output_4.xml", - ] - for f in test_files: - if os.path.exists(f): - os.remove(f) - def test_pitch_list_generation(self): """Test that pitch_list is generated correctly.""" pitches = generate_pitch_list(1.0, 2.0, 0.5) @@ -50,11 +33,12 @@ def test_format_number_decimal(self): assert format_number(5.5) == "5.5" assert format_number(2.75) == "2.75" - def test_xml_generation_default_config(self): + def test_xml_generation_default_config(self, tmp_path): """Test XML generation with default configuration.""" + output = tmp_path / "test_output_1.xml" # Generate XML with test config generate_xml( - output_filename="test_output_1.xml", + output_filename=str(output), thread_name="Test Threads", unit="mm", thread_angle=60.0, @@ -67,10 +51,10 @@ def test_xml_generation_default_config(self): ) # Verify file exists - assert os.path.exists("test_output_1.xml") + assert output.exists() # Parse and verify structure - tree = ET.parse("test_output_1.xml") + tree = ET.parse(output) root = tree.getroot() assert root.tag == "ThreadType" @@ -79,10 +63,11 @@ def test_xml_generation_default_config(self): assert root.find("Angle").text == "60.0" assert root.find("ThreadForm").text == "8" - def test_xml_thread_sizes(self): + def test_xml_thread_sizes(self, tmp_path): """Test that XML contains expected thread sizes.""" + output = tmp_path / "test_output_2.xml" generate_xml( - output_filename="test_output_2.xml", + output_filename=str(output), thread_name="Test Threads", unit="mm", thread_angle=60.0, @@ -94,7 +79,7 @@ def test_xml_thread_sizes(self): tolerance_offsets=[0.0], ) - tree = ET.parse("test_output_2.xml") + tree = ET.parse(output) root = tree.getroot() thread_sizes = root.findall("ThreadSize") @@ -106,10 +91,11 @@ def test_xml_thread_sizes(self): assert size is not None assert int(size) >= 8 - def test_xml_thread_designations(self): + def test_xml_thread_designations(self, tmp_path): """Test that thread designations are correctly named.""" + output = tmp_path / "test_output_3.xml" generate_xml( - output_filename="test_output_3.xml", + output_filename=str(output), thread_name="Test Threads", unit="mm", thread_angle=60.0, @@ -121,7 +107,7 @@ def test_xml_thread_designations(self): tolerance_offsets=[0.0], ) - tree = ET.parse("test_output_3.xml") + tree = ET.parse(output) root = tree.getroot() # Find designations @@ -134,10 +120,11 @@ def test_xml_thread_designations(self): assert "M" in des_text assert "x" in des_text - def test_xml_contains_threads(self): + def test_xml_contains_threads(self, tmp_path): """Test that XML contains external and internal threads.""" + output = tmp_path / "test_output_4.xml" generate_xml( - output_filename="test_output_4.xml", + output_filename=str(output), thread_name="Test Threads", unit="mm", thread_angle=60.0, @@ -149,15 +136,15 @@ def test_xml_contains_threads(self): tolerance_offsets=[0.0, 0.1], ) - tree = ET.parse("test_output_4.xml") + tree = ET.parse(output) root = tree.getroot() threads = root.findall(".//Thread") assert len(threads) > 0 # Check for both genders - genders = {thread.find("Gender").text for thread in threads[:4]} - assert "external" in genders or "internal" in genders + genders = {thread.find("Gender").text for thread in threads} + assert {"external", "internal"}.issubset(genders) class TestThreadProfile: diff --git a/web/js/xml.js b/web/js/xml.js index f3a49d0..b4dc267 100644 --- a/web/js/xml.js +++ b/web/js/xml.js @@ -28,7 +28,7 @@ export function buildXml(config, structure) { lines.push(` ${format4g(thread.majorDia)}`); lines.push(` ${format4g(thread.pitchDia)}`); lines.push(` ${format4g(thread.minorDia)}`); - if (thread.tapDrill) { + if (thread.tapDrill != null) { lines.push(` ${format4g(thread.tapDrill)}`); } lines.push(" "); From e609e2e2a87b87d8c4df4ca2eaaa3184b7ad1f25 Mon Sep 17 00:00:00 2001 From: Philip <44484441+EckPhi@users.noreply.github.com> Date: Sun, 29 Mar 2026 18:55:49 +0200 Subject: [PATCH 31/31] Enhance UI feedback and output rendering for share action --- web/js/app.js | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/web/js/app.js b/web/js/app.js index 6dbfbe9..48988fb 100644 --- a/web/js/app.js +++ b/web/js/app.js @@ -62,8 +62,7 @@ shareBtn.addEventListener("click", async () => { try { await navigator.clipboard.writeText(shareUrl); - summaryEl.className = "summary ok"; - summaryEl.innerHTML += "
      Share link copied to clipboard."; + renderOutput(generated, "Share link copied to clipboard."); } catch { window.prompt("Copy this share URL:", shareUrl); } @@ -147,15 +146,21 @@ function renderErrors(errors) { downloadBtn.disabled = true; } -function renderOutput(result) { +function renderOutput(result, notice = "") { summaryEl.className = "summary ok"; - summaryEl.innerHTML = [ + const lines = [ `Generated: ${result.filename}`, `Thread Sizes: ${result.summary.sizeCount}`, `Designations: ${result.summary.designationCount}`, `Thread Nodes: ${result.summary.threadCount}`, `Range: ${result.firstDesignation} to ${result.lastDesignation}`, - ].join("
      "); + ]; + + if (notice) { + lines.push(`${notice}`); + } + + summaryEl.innerHTML = lines.join("
      "); const previewLines = result.xml.split("\n").slice(0, 80).join("\n"); previewEl.textContent = previewLines;