shipshape 0.1.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.
shipshape/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ """shipshape - Open-source parametric vessel design and validation library."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1,5 @@
1
+ """Parameter computation for parametric vessel designs."""
2
+
3
+ from .__main__ import compute_derived
4
+
5
+ __all__ = ["compute_derived"]
@@ -0,0 +1,177 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Compute derived parameters from base parameters.
4
+ This module loads base parameters from JSON and computes all derived values.
5
+ """
6
+
7
+ import json
8
+ import os
9
+ import argparse
10
+ from typing import Dict, Any
11
+
12
+ def compute_derived(base: Dict[str, Any]) -> Dict[str, Any]:
13
+ """
14
+ Compute all derived parameters from base parameters.
15
+ Returns a complete parameter dictionary with both base and derived values.
16
+ """
17
+ params = base.copy()
18
+
19
+ # Constants
20
+ mm_in_one_inch = 25.4
21
+
22
+ # Derived dimensions
23
+ params['mm_in_one_inch'] = mm_in_one_inch
24
+ params['stringer_width'] = base['stringer_width_inches'] * mm_in_one_inch
25
+ params['clamp_width'] = base['clamp_width_inches'] * mm_in_one_inch
26
+ params['clamp_height'] = base['clamp_height_inches'] * mm_in_one_inch
27
+ params['vaka_stringer_width'] = base['vaka_stringer_width_inches'] * mm_in_one_inch
28
+ params['vaka_stringer_height'] = base['vaka_stringer_height_inches'] * mm_in_one_inch
29
+ params['frame_width'] = base['frame_width_inches'] * mm_in_one_inch
30
+ params['frame_depth'] = base['frame_depth_inches'] * mm_in_one_inch
31
+ params['bottom_height'] = base['bottom_height_inches'] * mm_in_one_inch
32
+
33
+ # Aka length depends on panels and deck
34
+ params['aka_length'] = (base['panel_length'] * base['panels_transversal'] +
35
+ base['deck_width'])
36
+
37
+ # Bottom thickness same as vaka
38
+ params['bottom_thickness'] = base['vaka_thickness']
39
+
40
+ # Crossdeck dimensions
41
+ params['crossdeck_width'] = base['panel_width'] / base['akas_per_panel']
42
+ params['crossdeck_thickness'] = base['deck_thickness']
43
+ params['crossdeck_length'] = (base['panels_transversal'] * base['panel_length'] +
44
+ (base['deck_width'] - base['vaka_width']) / 2 +
45
+ params['stringer_width'])
46
+
47
+ # Cockpit length: distance from center to first aka's inner edge, doubled
48
+ # First aka Y position depends on akas_per_panel:
49
+ # - Single aka: centered in panel at crossdeck_width/2 + panel_width/2
50
+ # - Multiple akas: at rim distance from panel edge at crossdeck_width/2 + aka_rim
51
+ if base.get('akas_per_panel', 1) == 1:
52
+ first_aka_y = params['crossdeck_width'] / 2 + base['panel_width'] / 2
53
+ else:
54
+ first_aka_y = params['crossdeck_width'] / 2 + base['aka_rim']
55
+ params['cockpit_length'] = 2 * first_aka_y - base['aka_width']
56
+
57
+ # Panel stringer calculations
58
+ params['panel_stringer_offset'] = (base['panel_length'] / 4 -
59
+ params['stringer_width'] / 2)
60
+ params['panel_stringer_length'] = (params['crossdeck_width'] +
61
+ base['panels_longitudinal'] * base['panel_width'])
62
+
63
+ # Vertical levels (build up from bottom)
64
+ params['clamp_base_level'] = (params['bottom_height'] + base['freeboard'] -
65
+ params['clamp_height'])
66
+ params['vaka_stringer_base_level'] = params['clamp_base_level'] - params['freeboard'] / 2
67
+ params['overhead_base_level'] = (params['clamp_base_level'] +
68
+ params['clamp_height'])
69
+ params['aka_base_level'] = (params['overhead_base_level'] +
70
+ base['overhead_thickness'])
71
+ params['stringer_base_level'] = params['aka_base_level'] + base['aka_height']
72
+ params['panel_base_level'] = params['stringer_base_level'] + params['stringer_width']
73
+ params['deck_base_level'] = params['panel_base_level']
74
+ params['deck_level'] = params['deck_base_level'] + base['deck_thickness']
75
+
76
+ # Spine (uses same sizes as aka)
77
+ params['spine_thickness'] = base['aka_thickness']
78
+ params['spine_width'] = base['aka_width']
79
+ params['spine_base_level'] = params['aka_base_level'] - params['spine_width']
80
+ params['spine_length'] = (base['panel_width'] * base['panels_longitudinal'] +
81
+ params['crossdeck_width'] + base['spine_length_extension'])
82
+
83
+ # Beam calculation
84
+ params['beam'] = (params['aka_length'] + base['aka_cap_thickness'] -
85
+ params['spine_width'] + base['ama_diameter'] / 2)
86
+
87
+ # Pillar (uses same sizes as aka)
88
+ params['pillar_thickness'] = base['aka_thickness']
89
+ params['pillar_width'] = base['aka_width']
90
+ params['pillar_height'] = params['spine_base_level'] - base['ama_thickness']
91
+
92
+ # Ama cone length: cone starts at outer edge of outermost pillar
93
+ # Calculate Y position of the outermost (last) aka
94
+ last_panel_index = base['panels_longitudinal'] // 2 - 1
95
+ last_aka_index = base.get('akas_per_panel', 1) - 1
96
+ if base.get('akas_per_panel', 1) == 1:
97
+ last_aka_y = (params['crossdeck_width'] / 2
98
+ + last_panel_index * base['panel_width']
99
+ + base['panel_width'] / 2)
100
+ else:
101
+ aka_spacing = ((base['panel_width'] - 2 * base['aka_rim'])
102
+ / (base['akas_per_panel'] - 1))
103
+ last_aka_y = (params['crossdeck_width'] / 2
104
+ + last_panel_index * base['panel_width']
105
+ + base['aka_rim'] + last_aka_index * aka_spacing)
106
+
107
+ # Vaka x offset (distance from ama centerline to vaka centerline)
108
+ params['vaka_x_offset'] = (- params['pillar_width'] / 2
109
+ + base['panel_length'] * base['panels_transversal']
110
+ + base['deck_width'] / 2)
111
+
112
+ # Mast calculations
113
+ params['mast_distance_from_center'] = (base['vaka_length'] / 4 +
114
+ base['mast_distance_from_center_offset'])
115
+ params['mast_base_level'] = params['bottom_height'] + base['sole_thickness']
116
+ params['mast_partner_length'] = (base['vaka_width'] -
117
+ base['mast_partner_vaka_clearance'])
118
+ params['mast_partner_width'] = (base['mast_diameter'] +
119
+ base['mast_partner_width_offset'])
120
+ params['mast_step_outer_diameter'] = (base['mast_diameter'] +
121
+ base['mast_step_diameter_offset'])
122
+ params['mast_step_inner_diameter'] = base['mast_diameter']
123
+
124
+ # Yard spar height
125
+ params['yard_spar_height'] = (base['mast_height'] -
126
+ base['yard_spar_distance_from_top'])
127
+
128
+ # Boom length matches yard length (rectangular sails)
129
+ params['boom_length'] = base['yard_length']
130
+ params['sail_area_m2'] = (2 * base['sail_height'] / 1000
131
+ * base['sail_width'] / 1000)
132
+
133
+ # Rudder calculations
134
+ params['rudder_bearing_block_height'] = params['stringer_width']
135
+ params['rudder_vaka_mount_base_level'] = ((params['bottom_height'] +
136
+ base['freeboard']) / 2)
137
+ params['rudder_rib_length'] = (base['rudder_blade_length'] -
138
+ base['rudder_rib_clearance'])
139
+
140
+ # Tiller dimensions (uses stringer sizes)
141
+ params['tiller_width'] = params['stringer_width']
142
+ params['tiller_thickness'] = base['stringer_thickness']
143
+ params['tiller_length'] = 490 # This was hardcoded in original
144
+
145
+ return params
146
+
147
+ def main():
148
+ parser = argparse.ArgumentParser(description='Compute parameters')
149
+ parser.add_argument('--boat', required=True, help='Path to boat constants')
150
+ parser.add_argument('--configuration', required=True, help='Path to configuration constants')
151
+ parser.add_argument('--output', required=True, help='Path to output JSON artifact')
152
+
153
+ args = parser.parse_args()
154
+
155
+ # Load boat parameters
156
+ with open(args.boat, 'r') as b:
157
+ boat_data = json.load(b)
158
+
159
+ with open(args.configuration, 'r') as c:
160
+ configuration_data = json.load(c)
161
+
162
+ data = boat_data | configuration_data
163
+
164
+ data = compute_derived(data)
165
+
166
+
167
+
168
+ # Write JSON output
169
+ os.makedirs(os.path.dirname(args.output) or '.', exist_ok=True)
170
+ with open(args.output, 'w') as f:
171
+ json.dump(data, f, indent=2)
172
+
173
+ print(f"✓ Parameters complete")
174
+ print(f" Output: {args.output}")
175
+
176
+ if __name__ == "__main__":
177
+ main()
@@ -0,0 +1,5 @@
1
+ """Static load validation for vessel structures."""
2
+
3
+ from .__main__ import run_validation
4
+
5
+ __all__ = ["run_validation"]