-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
-
11
-
12
-
13
-
14
-
15
-
16
-
17
-
18
-
19
-
20
-
21
-
22
-
23
-
24
-
25
-
26
-
27
-
28
-
29
-
30
-
31
-
32
-
33
-
34
-
35
-
36
-
37
-
38
-
39
-
40
-
41
-
42
-
43
-
44
-
45
-
46
-
47
-
48
-
49
-
50
-
51
-
52
-
53
-
54
-
55
-
56
-
57
-
58
-
59
-
60
-
61
-
62
-
63
-
64
-
65
-
66
-
67
-
68
-
69
-
70
-
71
-
72
-
73
-
74
-
75
-
76
-
77
-
78
-
79
-
80
-
81
-
82
-
83
-
84
-
85
-
86
-
87
-
88
-
89
-
90
-
91
-
92
-
93
-
94
-
95
-
96
-
97
-
98
-
99
-
100
-
101
-
102
-
103
-
104
-
105
-
106
-
107
-
108
-
109
-
110
-
111
-
112
-
113
-
114
-
115
-
116
-
117
-
118
-
119
-
120
-
121
-
122
-
123
-
124
-
125
-
126
-
127
-
128
-
129
-
130
-
131
-
132
-
133
-
134
-
135
-
136
-
137
-
138
-
139
-
140
-
141
-
142
-
143
-
144
-
145
-
146
-
147
-
148
-
149
-
150
-
151
-
152
-
153
-
154
-
155
-
156
-
157
-
158
-
159
-
160
-
161
-
162
-
163
-
164
-
165
-
166
-
167
-
168
-
169
-
170
-
171
-
172
-
173
-
174
-
175
-
176
-
177
-
178
-
179
-
180
-
181
-
182
-
183
-
184
-
185
-
186
-
187
-
188
-
189
-
190
-
191
-
192
-
193
-
194
-
195
-
196
-
197
-
198
-
199
-
200
-
201
-
202
-
203
-
204
-
205
-
206
-
207
-
208
-
209
-
210
-
211
-
212
-
213
-
214
-
215
-
216
-
217
-
218
-
219
-
220
-
221
-
222
-
223
-
224
-
225
-
226
-
227
-
228
-
229
-
230
-
231
-
232
-
233
-
234
-
235
-
236
-
237
-
238
-
239
-
240
-
241
-
242
-
243
-
244
-
245
-
246
-
247
-
248
-
249
-
250
-
251
-
252
-
253
-
254
-
255
-
256
-
257
-
258
-
259
-
260
-
261
-
262
-
263
-
264
-
265
-
266
-
267
-
268
-
269
-
270
-
271
-
272
-
273
-
274
-
275
-
276
-
277
-
278
-
279
-
280
-
281
-
282
-
283
-
284
-
285
-
286
-
287
-
288
-
289
-
290
-
291
-
292
-
293
-
294
-
295
-
296
-
297
-
298
-
299
-
300
-
301
-
302
-
303
-
304
-
305
-
306
-
307
-
308
-
309
-
310
-
311
-
312
-
313
-
314
-
315
-
316
-
317
-
318
-
319
-
320
-
321
-
322
-
323
-
324
-
325
-
326
-- Copyright 2026 Shota FUJI
--
-- This Source Code Form is subject to the terms of the Mozilla Public
-- License, v. 2.0. If a copy of the MPL was not distributed with this
-- file, You can obtain one at https://mozilla.org/MPL/2.0/.
--
-- SPDX-License-Identifier: MPL-2.0
module Template exposing (template)
import Length exposing (Length, toMM)
import Parameters exposing (Parameters, canvasSizeDimension)
import String
import Svg exposing (..)
import Svg.Attributes exposing (..)
import Svg.Path exposing (LargeArcFlag(..), PathCommand(..), PointMode(..), SweepFlag(..))
type Anchor
= TopLeft ( Float, Float )
| TopRight ( Float, Float )
| BottomLeft ( Float, Float )
| BottomRight ( Float, Float )
topLeftFor : Anchor -> ( Float, Float ) -> ( Float, Float )
topLeftFor anchor ( width, height ) =
case anchor of
TopLeft ( x, y ) ->
( x, y )
TopRight ( x, y ) ->
( x - width, y )
BottomLeft ( x, y ) ->
( x, y - height )
BottomRight ( x, y ) ->
( x - width, y - height )
template : Parameters -> List (Svg.Attribute msg) -> Svg.Svg msg
template params attrs =
let
( canvasWidth, canvasHeight ) =
canvasSizeDimension params.canvas.size
|> Tuple.mapBoth toMM toMM
margin =
toMM params.canvas.margin
in
svg
(viewBox (String.join " " [ "0", "0", String.fromInt (ceiling canvasWidth), String.fromInt (ceiling canvasHeight) ])
:: class "print"
:: attrs
)
[ scaleChecker (BottomLeft ( margin, canvasHeight - margin ))
, defs
[]
[ pattern
[ id "SkivingPattern"
, x "0"
, y "0"
, width "3"
, height "3"
, patternUnits "userSpaceOnUse"
]
[ Svg.path
[ Svg.Path.d
[ MoveTo Absolute ( -1, -1 )
, LineTo Absolute ( 4, 4 )
]
, fill "none"
, stroke "currentColor"
, strokeWidth "0.1"
, strokeDasharray "1"
]
[]
]
]
, longPiece params (TopLeft ( margin, margin ))
, shortPiece params (TopLeft ( toMM params.lugWidth + margin + 10, margin ))
]
scaleChecker : Anchor -> Svg msg
scaleChecker anchor =
let
( ox, oy ) =
topLeftFor anchor ( 10, 10 )
px : Float -> String
px n =
String.fromFloat (ox + n)
py : Float -> String
py n =
String.fromFloat (oy + n)
in
g
[]
[ g
[ stroke "currentColor", strokeWidth "0.2" ]
[ line
[ x1 (px 0), y1 (py 8), x2 (px 0), y2 (py 10) ]
[]
, line
[ x1 (px 0), y1 (py 9), x2 (px 10), y2 (py 9) ]
[]
, line
[ x1 (px 10), y1 (py 8), x2 (px 10), y2 (py 10) ]
[]
]
, text_
[ x (px 5)
, y (py 7)
, fontSize "3"
, fontWeight "100"
, textAnchor "middle"
, fill "currentColor"
]
[ text "10mm" ]
]
skivingSeamStroke : List (Svg.Attribute msg)
skivingSeamStroke =
[ fill "none", stroke "currentColor", strokeWidth "0.3", strokeDasharray "1 0.5" ]
longPiece : Parameters -> Anchor -> Svg msg
longPiece params anchor =
let
lugWidth =
toMM params.lugWidth
length =
toMM params.longPiece.length
flap =
toMM params.longPiece.flap
( ox, oy ) =
topLeftFor anchor ( lugWidth, length )
in
g
[]
(Svg.path
[ Svg.Path.d
[ MoveTo Absolute ( ox, oy )
, VerticalLineTo Relative (length + flap - lugWidth)
, EllipticalArcCurve Relative
{ rx = lugWidth / 2
, ry = lugWidth / 2
, angle = 0.0
, largeArcFlag = LargeArc
, sweepFlag = Counterclockwise
, x = lugWidth
, y = 0
}
[]
, VerticalLineTo Relative -(length + flap - lugWidth)
, ClosePath
]
, fill "none"
, stroke "currentColor"
, strokeWidth "0.3"
]
[]
:: Svg.path
(Svg.Path.d
[ MoveTo Absolute ( ox, oy + flap )
, HorizontalLineTo Relative lugWidth
]
:: skivingSeamStroke
)
[]
:: Svg.path
[ Svg.Path.d
[ MoveTo Absolute ( ox, oy )
, HorizontalLineTo Relative lugWidth
, VerticalLineTo Relative flap
, HorizontalLineTo Relative -lugWidth
, ClosePath
]
, fill "url(#SkivingPattern)"
]
[]
:: (List.range 0 (params.longPiece.buckleHole.count - 1)
|> List.map
(\index ->
let
{ buckleHole } =
params.longPiece
offset =
toMM buckleHole.offset
interval =
toMM buckleHole.interval
in
hole
buckleHole.diameter
( ox + lugWidth / 2
, oy + offset + interval * toFloat index
)
)
)
)
hole : Length -> ( Float, Float ) -> Svg msg
hole diameter ( cx, cy ) =
let
radius =
toMM diameter / 2
in
g
[]
[ circle
[ Svg.Attributes.cx (String.fromFloat cx)
, Svg.Attributes.cy (String.fromFloat cy)
, r (String.fromFloat radius)
, fill "none"
, stroke "currentColor"
, strokeWidth "0.3"
]
[]
, g
[ fill "none"
, stroke "currentColor"
, strokeWidth "0.1"
]
[ Svg.path
[ Svg.Path.d
[ MoveTo Absolute ( cx - radius / 2, cy )
, HorizontalLineTo Relative radius
]
]
[]
, Svg.path
[ Svg.Path.d
[ MoveTo Absolute ( cx, cy - radius / 2 )
, VerticalLineTo Relative radius
]
]
[]
]
]
shortPiece : Parameters -> Anchor -> Svg msg
shortPiece params anchor =
let
lugWidth =
toMM params.lugWidth
length =
toMM params.shortPiece.length
caseSideFlap =
toMM params.shortPiece.caseSideFlap
claspSideFlap =
toMM params.shortPiece.claspSideFlap
( ox, oy ) =
topLeftFor anchor ( lugWidth, length )
in
g
[]
[ Svg.path
[ Svg.Path.d
[ MoveTo Absolute ( ox, oy )
, VerticalLineTo Relative (length + caseSideFlap + claspSideFlap)
, HorizontalLineTo Relative lugWidth
, VerticalLineTo Relative -(length + caseSideFlap + claspSideFlap)
, ClosePath
]
, fill "none"
, stroke "currentColor"
, strokeWidth "0.3"
]
[]
, g
skivingSeamStroke
[ Svg.path
[ Svg.Path.d
[ MoveTo Absolute ( ox, oy + caseSideFlap )
, HorizontalLineTo Relative lugWidth
]
]
[]
, Svg.path
[ Svg.Path.d
[ MoveTo Absolute ( ox, oy + caseSideFlap + length )
, HorizontalLineTo Relative lugWidth
]
]
[]
]
, g
[ fill "url(#SkivingPattern)" ]
[ Svg.path
[ Svg.Path.d
[ MoveTo Absolute ( ox, oy )
, HorizontalLineTo Relative lugWidth
, VerticalLineTo Relative caseSideFlap
, HorizontalLineTo Relative -lugWidth
, ClosePath
]
]
[]
, Svg.path
[ Svg.Path.d
[ MoveTo Absolute ( ox, oy + caseSideFlap + length )
, HorizontalLineTo Relative lugWidth
, VerticalLineTo Relative claspSideFlap
, HorizontalLineTo Relative -lugWidth
, ClosePath
]
]
[]
]
]