pyextrusion 0.17.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,124 @@
1
+ """PyExtrusion - engineering calculation toolkit for aluminium extrusion."""
2
+ from ._meta import __author__, __description__, __license__, __title__, __url__, __version__
3
+ from .api import calculate_case, calculate_process, calculate_simple, compare_presses
4
+ from .comparison import (
5
+ ProcessComparisonResult, PlanningComparisonResult, ProductionSequenceComparisonResult,
6
+ compare_processes, compare_planning, compare_production_sequences,
7
+ )
8
+ from .demand import (
9
+ AnnualDemandSpec, NormalizedAnnualDemand, AnnualCalculationResult,
10
+ normalize_annual_demand, calculate_annual_demand,
11
+ )
12
+ from .planning import (
13
+ PlanningMode, PlanningRequest, PlanningResult,
14
+ validate_planning_request, calculate_planning,
15
+ )
16
+ from .sequence import (
17
+ ProductionOrder, ProductionSequenceEntry, ProductionSequenceResult,
18
+ calculate_production_sequence,
19
+ )
20
+ from .core import calculate, section_from_linear_weight, extrusion_ratio, extrusion_ratio_status, ram_speed
21
+ from .io import (
22
+ case_from_dict,
23
+ case_to_dict,
24
+ load_case_json,
25
+ load_press_json,
26
+ load_study_json,
27
+ press_from_dict,
28
+ press_to_dict,
29
+ process_from_dict,
30
+ planning_case_from_dict,
31
+ planning_case_to_dict,
32
+ load_planning_case_json,
33
+ save_planning_case_json,
34
+ save_case_json,
35
+ save_press_json,
36
+ save_study_json,
37
+ study_from_dict,
38
+ study_to_dict,
39
+ annual_demand_from_dict, annual_demand_to_dict,
40
+ load_annual_demand_json, save_annual_demand_json,
41
+ production_order_from_dict, production_order_to_dict,
42
+ production_sequence_from_dict, production_sequence_to_dict,
43
+ load_production_sequence_json, save_production_sequence_json,
44
+ )
45
+ from .models import (
46
+ PressSpec, ProfileSpec, ProcessSpec, PlanningCase, ProductionSpec, StudyCase, StudyInput,
47
+ CalculationResult, ProcessResult, ComparisonResult, ConfigurationResult,
48
+ StatusResult, GeometryResult, BilletResult, ProductionResult, ScrapResult,
49
+ ProductivityResult, TimingResult, ProcessTraceResult,
50
+ SawSpec, ButtRule, ProfileType, CanonicalProfileType, PROFILE_TYPE_ALIASES,
51
+ PROFILE_TYPE_VALID_VALUES, normalize_profile_type,
52
+ )
53
+ from .reporting import (
54
+ format_annual_result, format_comparison, format_press, format_result,
55
+ format_planning_result, format_production_sequence,
56
+ format_process_comparison, format_planning_comparison,
57
+ format_production_sequence_comparison,
58
+ )
59
+ from .fields import FieldInfo, FIELD_GLOSSARY, RESULT_SECTIONS, describe_field, list_fields
60
+ from .errors import (
61
+ ErrorInfo, ERROR_CATALOG, PyExtrusionError, InvalidPressConfigurationError,
62
+ InvalidProfileInputError, InvalidProductionInputError, UnknownResultFieldError,
63
+ UnsupportedSchemaVersionError, InvalidAnnualDemandError, InputFileError,
64
+ UnknownResultSectionError, InvalidResultSelectionError, InvalidStudyAdjustmentError,
65
+ InvalidPlanningRequestError, InvalidProductionSequenceError, InvalidComparisonError,
66
+ get_error_info, list_error_info,
67
+ )
68
+ from .validation import (
69
+ ValidationMessage,
70
+ validate_case,
71
+ validate_case_json,
72
+ validate_planning_case,
73
+ validate_planning_case_json,
74
+ validate_press,
75
+ validate_press_json,
76
+ validate_study,
77
+ validate_study_json,
78
+ validate_annual_demand, validate_annual_demand_json,
79
+ validate_production_sequence_json,
80
+ )
81
+
82
+ Press = PressSpec
83
+ Profile = ProfileSpec
84
+ Process = ProcessSpec
85
+ Production = ProductionSpec
86
+
87
+ __all__ = [
88
+ "calculate", "calculate_case", "calculate_process", "calculate_simple", "compare_presses",
89
+ "compare_processes", "compare_planning", "compare_production_sequences",
90
+ "normalize_annual_demand", "calculate_annual_demand", "calculate_planning",
91
+ "validate_planning_request", "calculate_production_sequence",
92
+ "section_from_linear_weight", "extrusion_ratio", "extrusion_ratio_status", "ram_speed",
93
+ "Press", "Profile", "Process", "Production", "PressSpec", "ProfileSpec", "ProcessSpec", "PlanningCase", "ProductionSpec",
94
+ "StudyCase", "StudyInput", "CalculationResult", "ProcessResult", "ComparisonResult", "ConfigurationResult",
95
+ "StatusResult", "GeometryResult", "BilletResult", "ProductionResult", "ScrapResult",
96
+ "ProductivityResult", "TimingResult", "ProcessTraceResult",
97
+ "SawSpec", "ButtRule", "ProfileType", "CanonicalProfileType", "PROFILE_TYPE_ALIASES",
98
+ "PROFILE_TYPE_VALID_VALUES", "normalize_profile_type",
99
+ "AnnualDemandSpec", "NormalizedAnnualDemand", "AnnualCalculationResult",
100
+ "PlanningMode", "PlanningRequest", "PlanningResult",
101
+ "ProductionOrder", "ProductionSequenceEntry", "ProductionSequenceResult",
102
+ "ProcessComparisonResult", "PlanningComparisonResult", "ProductionSequenceComparisonResult",
103
+ "load_press_json", "load_case_json", "load_planning_case_json", "load_study_json",
104
+ "save_press_json", "save_case_json", "save_planning_case_json", "save_study_json",
105
+ "load_annual_demand_json", "save_annual_demand_json",
106
+ "load_production_sequence_json", "save_production_sequence_json",
107
+ "press_from_dict", "process_from_dict", "planning_case_from_dict", "case_from_dict", "study_from_dict", "annual_demand_from_dict",
108
+ "production_order_from_dict", "production_sequence_from_dict",
109
+ "press_to_dict", "planning_case_to_dict", "case_to_dict", "study_to_dict", "annual_demand_to_dict",
110
+ "production_order_to_dict", "production_sequence_to_dict",
111
+ "format_press", "format_result", "format_comparison", "format_annual_result", "format_planning_result", "format_production_sequence",
112
+ "format_process_comparison", "format_planning_comparison", "format_production_sequence_comparison",
113
+ "FieldInfo", "FIELD_GLOSSARY", "RESULT_SECTIONS", "describe_field", "list_fields",
114
+ "ErrorInfo", "ERROR_CATALOG", "PyExtrusionError", "InvalidPressConfigurationError",
115
+ "InvalidProfileInputError", "InvalidProductionInputError", "UnknownResultFieldError",
116
+ "UnsupportedSchemaVersionError", "InvalidAnnualDemandError", "InputFileError",
117
+ "UnknownResultSectionError", "InvalidResultSelectionError", "InvalidStudyAdjustmentError",
118
+ "InvalidPlanningRequestError", "InvalidProductionSequenceError", "InvalidComparisonError",
119
+ "get_error_info", "list_error_info",
120
+ "ValidationMessage", "validate_press", "validate_case", "validate_planning_case", "validate_study",
121
+ "validate_press_json", "validate_case_json", "validate_planning_case_json", "validate_study_json",
122
+ "validate_annual_demand", "validate_annual_demand_json", "validate_production_sequence_json",
123
+ "__title__", "__version__", "__description__", "__author__", "__license__", "__url__",
124
+ ]
pyextrusion/_meta.py ADDED
@@ -0,0 +1,8 @@
1
+ """Public project metadata for PyExtrusion."""
2
+
3
+ __title__ = "PyExtrusion"
4
+ __version__ = "0.17.0"
5
+ __description__ = "Engineering calculation toolkit for aluminium extrusion"
6
+ __author__ = "Enrique Calvo Ordonez"
7
+ __license__ = "Apache-2.0"
8
+ __url__ = "https://pyextrusion.com"
pyextrusion/_strict.py ADDED
@@ -0,0 +1,93 @@
1
+ from __future__ import annotations
2
+
3
+ import math
4
+ from typing import Any, TypeVar
5
+
6
+ E = TypeVar("E", bound=Exception)
7
+
8
+
9
+ def _raise(exc_type: type[E], message: str) -> None:
10
+ raise exc_type(message)
11
+
12
+
13
+ def require_number(
14
+ value: Any,
15
+ field: str,
16
+ exc_type: type[E],
17
+ *,
18
+ minimum: float | None = None,
19
+ maximum: float | None = None,
20
+ exclusive_minimum: bool = False,
21
+ allow_none: bool = False,
22
+ ) -> float | None:
23
+ if value is None:
24
+ if allow_none:
25
+ return None
26
+ _raise(exc_type, f"{field} is required")
27
+ if isinstance(value, bool) or not isinstance(value, (int, float)):
28
+ _raise(exc_type, f"{field} must be a number, not {type(value).__name__}")
29
+ result = float(value)
30
+ if not math.isfinite(result):
31
+ _raise(exc_type, f"{field} must be a finite number")
32
+ if minimum is not None:
33
+ if exclusive_minimum:
34
+ if not result > minimum:
35
+ _raise(exc_type, f"{field} must be > {minimum:g}")
36
+ elif result < minimum:
37
+ _raise(exc_type, f"{field} must be >= {minimum:g}")
38
+ if maximum is not None and result > maximum:
39
+ _raise(exc_type, f"{field} must be <= {maximum:g}")
40
+ return result
41
+
42
+
43
+ def require_int(
44
+ value: Any,
45
+ field: str,
46
+ exc_type: type[E],
47
+ *,
48
+ minimum: int | None = None,
49
+ maximum: int | None = None,
50
+ allow_none: bool = False,
51
+ ) -> int | None:
52
+ if value is None:
53
+ if allow_none:
54
+ return None
55
+ _raise(exc_type, f"{field} is required")
56
+ if isinstance(value, bool) or not isinstance(value, int):
57
+ _raise(exc_type, f"{field} must be an integer")
58
+ if minimum is not None and value < minimum:
59
+ _raise(exc_type, f"{field} must be >= {minimum}")
60
+ if maximum is not None and value > maximum:
61
+ _raise(exc_type, f"{field} must be <= {maximum}")
62
+ return value
63
+
64
+
65
+ def require_bool(value: Any, field: str, exc_type: type[E]) -> bool:
66
+ if not isinstance(value, bool):
67
+ _raise(exc_type, f"{field} must be true or false")
68
+ return value
69
+
70
+
71
+ def require_string(value: Any, field: str, exc_type: type[E], *, nonempty: bool = True) -> str:
72
+ if not isinstance(value, str):
73
+ _raise(exc_type, f"{field} must be a string")
74
+ if nonempty and not value.strip():
75
+ _raise(exc_type, f"{field} must not be empty")
76
+ return value
77
+
78
+
79
+ def require_kerf(value: Any, field: str, exc_type: type[E]) -> float:
80
+ result = require_number(value, field, exc_type, minimum=0.0)
81
+ assert result is not None
82
+ if result == 0.0:
83
+ return 0.0
84
+ if result < 3.0 or result > 10.0:
85
+ _raise(exc_type, f"{field} must be 0 mm or between 3 and 10 mm")
86
+ return result
87
+
88
+
89
+ def finite_product(a: float, b: float, field: str, exc_type: type[E]) -> float:
90
+ value = a * b
91
+ if not math.isfinite(value):
92
+ _raise(exc_type, f"{field} is outside the supported finite numeric range")
93
+ return value
pyextrusion/api.py ADDED
@@ -0,0 +1,78 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Iterable
4
+
5
+ from .core import calculate, calculate_process as _calculate_process
6
+ from .models import (
7
+ CalculationResult,
8
+ ComparisonResult,
9
+ Complexity,
10
+ PressSpec,
11
+ PlanningCase,
12
+ ProcessResult,
13
+ ProductionSpec,
14
+ ProfileSpec,
15
+ ProfileType,
16
+ StudyCase,
17
+ StudyInput,
18
+ )
19
+
20
+
21
+ def calculate_case(press: PressSpec, case: StudyCase) -> CalculationResult:
22
+ """Run a reusable StudyCase on one press."""
23
+ return calculate(case.to_study_input(press))
24
+
25
+
26
+ def calculate_process(press: PressSpec, case: PlanningCase) -> ProcessResult:
27
+ """Evaluate Press + Profile + ProcessSpec without any production quantity."""
28
+ return _calculate_process(case.to_process_input(press))
29
+
30
+
31
+ def compare_presses(presses: Iterable[PressSpec], case: StudyCase) -> ComparisonResult:
32
+ """Evaluate one case on several presses. Results include the orientative v2.4 productivity index; no final industrial decision is made automatically."""
33
+ press_tuple = tuple(presses)
34
+ if not press_tuple:
35
+ raise ValueError("compare_presses requires at least one press")
36
+ return ComparisonResult(tuple(calculate_case(press, case) for press in press_tuple))
37
+
38
+
39
+ def calculate_simple(
40
+ press: PressSpec,
41
+ *,
42
+ linear_weight_kg_m: float,
43
+ exits: int,
44
+ exit_speed_m_min: float | None = None,
45
+ cut_length_mm: float,
46
+ bars_requested: int,
47
+ profile_type: ProfileType | None = None,
48
+ front_scrap_m: float = 0.0,
49
+ complexity: Complexity = "normal",
50
+ section_per_exit_m2: float | None = None,
51
+ cuts: int | None = None,
52
+ butt_mm: float | None = None,
53
+ multi_billet_front_scrap_m: float | None = None,
54
+ supplement_10_pct: bool = False,
55
+ ram_speed_mm_s: float | None = None,
56
+ ) -> CalculationResult:
57
+ """Low-friction API backed by the same deterministic engine as StudyInput."""
58
+ case = StudyCase(
59
+ profile=ProfileSpec(
60
+ linear_weight_kg_m=linear_weight_kg_m,
61
+ exits=exits,
62
+ profile_type=profile_type,
63
+ section_per_exit_m2=section_per_exit_m2,
64
+ ),
65
+ production=ProductionSpec(
66
+ exit_speed_m_min=exit_speed_m_min,
67
+ cut_length_mm=cut_length_mm,
68
+ bars_requested=bars_requested,
69
+ front_scrap_m=front_scrap_m,
70
+ complexity=complexity,
71
+ cuts=cuts,
72
+ butt_mm=butt_mm,
73
+ multi_billet_front_scrap_m=multi_billet_front_scrap_m,
74
+ supplement_10_pct=supplement_10_pct,
75
+ ram_speed_mm_s=ram_speed_mm_s,
76
+ ),
77
+ )
78
+ return calculate_case(press, case)