3 This file is part of adg-lua.
4 Copyright (C) 2012-2013 Nicola Fontana <ntd at entidi.it>
6 adg-lua is free software; you can redistribute it and/or modify
7 it under the terms of the GNU Lesser General Public License as
8 published by the Free Software Foundation; either version 2 of
9 the License, or (at your option) any later version.
11 adg-lua is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU Lesser General Public License for more details.
16 You should have received a copy of the GNU Lesser General
17 Public License along with adg-lua; if not, write to the Free
18 Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19 Boston, MA 02110-1301, USA.
23 local lgi
= require
'lgi'
24 local cairo
= lgi
.require
'cairo'
25 local Cpml
= lgi
.require
'Cpml'
26 local Adg
= lgi
.require
'Adg'
28 local SQRT3
= math
.sqrt(3)
32 -- Backward compatibility
34 if not cairo
.Status
.to_string
then
35 -- Pull request: http://github.com/pavouk/lgi/pull/44
36 local core
= require
'lgi.core'
37 local ffi
= require
'lgi.ffi'
40 cairo
._enum
.Status
.to_string
= core
.callable
.new
{
41 addr
= cairo
._module
.cairo_status_to_string
,
49 -----------------------------------------------------------------
52 local constructor
= {}
54 -- Inject the regenerate method into Adg.Model
56 -- Rebuilding the model *without* destroying it is the quickest method
57 -- to change a drawing: the notification mechanism will change only the
58 -- entities that effectively need to be modified.
60 -- Another (easier) option would be to regenerate everything - that is
61 -- models and views - from scratch.
62 rawset(Adg
.Model
, 'regenerate', function (model
, part
)
63 -- Call the original constructor of model, registered during the first call
64 -- to the same constructor, to regenerate it with the data stored in part.
65 constructor
[model
](part
, model
)
68 function generator
.model
.hole(part
, path
)
69 path
= path
or Adg
.Path
{}
70 constructor
[path
] = generator
.model
.hole
72 local data
= part
.data
74 local pair
= Cpml
.Pair
{ x
= data
.LHOLE
, y
= 0 }
76 path
:set_named_pair('LHOLE', pair
)
78 pair
.y
= data
.DHOLE
/ 2
79 pair
.x
= pair
.x
- pair
.y
/ SQRT3
81 local edge
= pair
:dup()
85 path
:set_named_pair('DHOLE', pair
)
87 path
:line_to_explicit(0, (data
.D1
+ data
.DHOLE
) / 4)
88 path
:curve_to_explicit(data
.LHOLE
/ 2, data
.DHOLE
/ 2,
89 data
.LHOLE
+ 2, data
.D1
/ 2,
94 -- No need to incomodate an AdgEdge model for two reasons:
95 -- it is only a single line and it is always needed
103 local function add_groove(path
, part
)
104 local data
= part
.data
105 local pair
= Cpml
.Pair
{ x
= data
.ZGROOVE
, y
= data
.D1
/ 2 }
108 path
:set_named_pair('DGROOVEI_X', pair
)
111 path
:set_named_pair('DGROOVEY_POS', pair
)
113 pair
.y
= data
.DGROOVE
/ 2
115 path
:set_named_pair('DGROOVEI_Y', pair
)
117 pair
.x
= pair
.x
+ data
.LGROOVE
121 path
:set_named_pair('DGROOVEX_POS', pair
)
125 path
:set_named_pair('DGROOVEF_X', pair
)
128 function generator
.model
.body(part
, path
)
129 path
= path
or Adg
.Path
{}
130 constructor
[path
] = generator
.model
.body
132 local data
= part
.data
134 local pair
= Cpml
.Pair
{ x
= 0, y
= data
.D1
/ 2 }
136 path
:set_named_pair('D1I', pair
)
138 if data
.GROOVE
then add_groove(path
, part
) end
140 pair
.x
= data
.A
- data
.B
- data
.LD2
144 path
:set_named_pair('D2_POS', pair
)
146 pair
.x
= pair
.x
+ (data
.D1
- data
.D2
) / 2
149 path
:set_named_pair('D2I', pair
)
151 pair
.x
= data
.A
- data
.B
155 pair
.x
= data
.A
- data
.B
158 path
:set_named_pair('D3I', pair
)
161 path
:set_named_pair('East', pair
)
164 path
:set_named_pair('West', pair
)
166 path
:chamfer(data
.CHAMFER
, data
.CHAMFER
)
168 pair
.x
= data
.A
- data
.B
+ data
.LD3
172 local primitive
= path
:over_primitive()
173 local tmp
= primitive
:put_point(0)
174 path
:set_named_pair('D3I_X', tmp
)
176 tmp
= primitive
:put_point(-1)
177 path
:set_named_pair('D3I_Y', tmp
)
179 path
:chamfer(data
.CHAMFER
, data
.CHAMFER
)
184 primitive
= path
:over_primitive()
185 tmp
= primitive
:put_point(0)
186 path
:set_named_pair('D3F_Y', tmp
)
187 tmp
= primitive
:put_point(-1)
188 path
:set_named_pair('D3F_X', tmp
)
190 path
:fillet(data
.RD34
)
192 pair
.x
= pair
.x
+ data
.RD34
193 path
:set_named_pair('D4I', pair
)
195 pair
.x
= data
.A
- data
.C
- data
.LD5
197 path
:set_named_pair('D4F', pair
)
200 path
:set_named_pair('D4_POS', pair
)
202 primitive
= path
:over_primitive()
203 tmp
= primitive
:put_point(0)
204 tmp
.x
= tmp
.x
+ data
.RD34
205 path
:set_named_pair('RD34', tmp
)
207 tmp
.x
= tmp
.x
- math
.cos(math
.pi
/ 4) * data
.RD34
208 tmp
.y
= tmp
.y
- math
.sin(math
.pi
/ 4) * data
.RD34
209 path
:set_named_pair('RD34_R', tmp
)
211 tmp
.x
= tmp
.x
+ data
.RD34
212 tmp
.y
= tmp
.y
+ data
.RD34
213 path
:set_named_pair('RD34_XY', tmp
)
215 pair
.x
= pair
.x
+ (data
.D4
- data
.D5
) / 2
218 path
:set_named_pair('D5I', pair
)
220 pair
.x
= data
.A
- data
.C
228 primitive
= path
:over_primitive()
229 tmp
= primitive
:put_point(0)
230 path
:set_named_pair('D5F', tmp
)
234 pair
.x
= pair
.x
+ data
.LD6
236 path
:set_named_pair('D6F', pair
)
238 primitive
= path
:over_primitive()
239 tmp
= primitive
:put_point(0)
240 path
:set_named_pair('D6I_X', tmp
)
242 primitive
= path
:over_primitive()
243 tmp
= primitive
:put_point(-1)
244 path
:set_named_pair('D6I_Y', tmp
)
246 pair
.x
= data
.A
- data
.LD7
247 pair
.y
= pair
.y
- (data
.C
- data
.LD7
- data
.LD6
) / SQRT3
249 path
:set_named_pair('D67', pair
)
256 path
:set_named_pair('D7F', pair
)
258 path
:reflect_explicit(1, 0)
264 function generator
.model
.edges(part
, edges
)
265 edges
= edges
or Adg
.Edges
{}
266 constructor
[edges
] = generator
.model
.edges
268 edges
:set_source(part
.model
.body
)
273 function generator
.model
.axis(part
, path
)
275 XXX: actually the end points can extend outside the body
276 only in local space. The proper extension values should be
277 expressed in global space but actually is impossible to
278 combine local and global space in the AdgPath API.
280 path
= path
or Adg
.Path
{}
281 constructor
[path
] = generator
.model
.axis
283 local data
= part
.data
285 path
:move_to_explicit(-1, 0)
286 path
:line_to_explicit(data
.A
+ 1, 0)
293 -----------------------------------------------------------------
297 -- Inject the export method into Adg.Canvas
298 rawset(Adg
.Canvas
, 'export', function (canvas
, file
, format)
299 -- The not explicitely set, the export format is guessed from the file suffix
300 if not format then format = file
:match('%.([^.]+)$') end
302 local size
= canvas
:get_size():dup()
303 size
:transform(canvas
:get_global_map())
305 -- Create the cairo surface
307 if format == 'png' and cairo
.ImageSurface
then
308 surface
= cairo
.ImageSurface
.create(cairo
.Format
.RGB24
, size
.x
, size
.y
)
309 elseif format == 'svg' and cairo
.SvgSurface
then
310 surface
= cairo
.SvgSurface
.create(file
, size
.x
, size
.y
)
311 elseif format == 'pdf' and cairo
.PdfSurface
then
312 surface
= cairo
.PdfSurface
.create(file
, size
.x
, size
.y
)
313 elseif format == 'ps' and cairo
.PsSurface
then
314 -- Pull request: http://github.com/pavouk/lgi/pull/46
315 surface
= cairo
.PsSurface
.create(file
, size
.x
, size
.y
)
316 surface
:dsc_comment('%%Title: adg-lua demonstration program')
317 surface
:dsc_comment('%%Copyright: Copyleft (C) 2013 Fontana Nicola')
318 surface
:dsc_comment('%%Orientation: Portrait')
319 surface
:dsc_begin_setup()
320 surface
:dsc_begin_page_setup()
321 surface
:dsc_comment('%%IncludeFeature: *PageSize A4')
322 elseif not format then
326 return nil, 'Requested format not supported (' .. format .. ')'
329 -- Render the canvas content
330 local cr
= cairo
.Context
.create(surface
)
334 if cairo
.Surface
.get_type(surface
) == 'IMAGE' then
335 status
= cairo
.Surface
.write_to_png(surface
, file
)
341 if status
~= 'SUCCESS' then
342 return nil, cairo
.Status
.to_string(cairo
.Status
[status
])
346 local function add_title_block(canvas
)
347 canvas
:set_title_block(Adg
.TitleBlock
{
353 projection
= Adg
.Projection
{ scheme
= Adg
.ProjectionScheme
.FIRST_ANGLE
},
358 local function add_dimensions(canvas
, model
)
359 local body
= model
.body
360 local hole
= model
.hole
366 dim
= Adg
.LDim
.new_full_from_model(body
, '-D3I_X', '-D3F_X', '-D3F_Y', -math
.pi
/2)
367 dim
:set_outside(Adg
.ThreeState
.OFF
)
370 dim
= Adg
.LDim
.new_full_from_model(body
, '-D6I_X', '-D67', '-East', -math
.pi
/2)
372 dim
:switch_extension1(false)
375 dim
= Adg
.LDim
.new_full_from_model(body
, '-D6I_X', '-D7F', '-East', -math
.pi
/2)
376 dim
:set_limits('-0.06', nil)
379 dim
= Adg
.ADim
.new_full_from_model(body
, '-D6I_Y', '-D6F', '-D6F', '-D67', '-D6F')
383 dim
= Adg
.RDim
.new_full_from_model(body
, '-RD34', '-RD34_R', '-RD34_XY')
386 dim
= Adg
.LDim
.new_full_from_model(body
, '-DGROOVEI_X', '-DGROOVEF_X', '-DGROOVEX_POS', -math
.pi
/2)
389 dim
= Adg
.LDim
.new_full_from_model(body
, 'D2I', '-D2I', '-D2_POS', math
.pi
)
390 dim
:set_limits('-0.1', nil)
391 dim
:set_outside(Adg
.ThreeState
.OFF
)
392 dim
:set_value('\226\140\128 <>')
395 dim
= Adg
.LDim
.new_full_from_model(body
, 'DGROOVEI_Y', '-DGROOVEI_Y', '-DGROOVEY_POS', math
.pi
)
396 dim
:set_limits('-0.1', nil)
397 dim
:set_outside(Adg
.ThreeState
.OFF
)
398 dim
:set_value('\226\140\128 <>')
404 dim
= Adg
.ADim
.new_full_from_model(body
, 'D1F', 'D1I', 'D2I', 'D1F', 'D1F')
406 dim
:switch_extension2(false)
409 dim
= Adg
.LDim
.new_full_from_model(body
, 'D1I', nil, 'West', math
.pi
/ 2)
410 dim
:set_ref2_from_model(hole
, '-LHOLE')
411 dim
:switch_extension1(false)
414 dim
= Adg
.LDim
.new_full_from_model(body
, 'D1I', 'DGROOVEI_X', 'West', math
.pi
/ 2)
415 dim
:switch_extension1(false)
419 dim
= Adg
.LDim
.new_full_from_model(body
, 'D4F', 'D6I_X', 'D4_POS', math
.pi
/ 2)
420 dim
:set_limits(nil, '+0.2')
421 dim
:set_outside(Adg
.ThreeState
.OFF
)
424 dim
= Adg
.LDim
.new_full_from_model(body
, 'D1F', 'D3I_X', 'D2_POS', math
.pi
/ 2)
426 dim
:switch_extension2(false)
427 dim
:set_outside(Adg
.ThreeState
.OFF
)
430 dim
= Adg
.LDim
.new_full_from_model(body
, 'D3I_X', 'D7F', 'East', math
.pi
/ 2)
431 dim
:set_limits(nil, '+0.1')
433 dim
:set_outside(Adg
.ThreeState
.OFF
)
434 dim
:switch_extension2(false)
437 dim
= Adg
.LDim
.new_full_from_model(body
, 'D1I', 'D7F', 'D3F_Y', math
.pi
/ 2)
438 dim
:set_limits('-0.05', '+0.05')
442 dim
= Adg
.ADim
.new_full_from_model(body
, 'D4F', 'D4I', 'D5I', 'D4F', 'D4F')
444 dim
:switch_extension2(false)
450 dim
= Adg
.LDim
.new_full_from_model(body
, 'D6F', '-D6F', 'East', 0)
451 dim
:set_limits('-0.1', nil)
453 dim
:set_value('\226\140\128 <>')
456 dim
= Adg
.LDim
.new_full_from_model(body
, 'D4F', '-D4F', 'East', 0)
458 dim
:set_value('\226\140\128 <>')
461 dim
= Adg
.LDim
.new_full_from_model(body
, 'D5F', '-D5F', 'East', 0)
462 dim
:set_limits('-0.1', nil)
464 dim
:set_value('\226\140\128 <>')
467 dim
= Adg
.LDim
.new_full_from_model(body
, 'D7F', '-D7F', 'East', 0)
468 dim
:set_value('\226\140\128 <>')
474 dim
= Adg
.LDim
.new_full_from_model(hole
, 'DHOLE', '-DHOLE', nil, math
.pi
)
475 dim
:set_pos_from_model(body
, '-West')
476 dim
:set_value('\226\140\128 <>')
479 dim
= Adg
.LDim
.new_full_from_model(body
, 'D1I', '-D1I', '-West', math
.pi
)
480 dim
:set_limits('-0.05', '+0.05')
482 dim
:set_value('\226\140\128 <>')
485 dim
= Adg
.LDim
.new_full_from_model(body
, 'D3I_Y', '-D3I_Y', '-West', math
.pi
)
486 dim
:set_limits('-0.25', nil)
488 dim
:set_value('\226\140\128 <>')
492 function generator
.view
.detailed(part
)
493 local canvas
= Adg
.Canvas
{}
494 local model
= part
.model
496 add_title_block(canvas
)
497 canvas
:add(Adg
.Stroke
{ trail
= model
.body
})
498 canvas
:add(Adg
.Stroke
{ trail
= model
.edges
})
499 canvas
:add(Adg
.Hatch
{ trail
= model
.hole
})
500 canvas
:add(Adg
.Stroke
{ trail
= model
.hole
})
501 canvas
:add(Adg
.Stroke
{
503 line_dress
= Adg
.Dress
.LINE_AXIS
505 add_dimensions(canvas
, model
)
512 -----------------------------------------------------------------
514 local controller
= {}
516 function controller
.new(data
)
519 local function generate(class
, method
)
520 local constructor
= generator
[class
][method
]
521 local result
= constructor
and constructor(part
) or false
522 part
[class
][method
] = result
526 -- data: numbers and strings needed to define the whole part
527 part
.data
= data
or {}
529 -- model: different models (AdgModel instances) generated from data
531 setmetatable(part
.model
, {
532 __index
= function (self
, key
)
533 return generate('model', key
)
537 -- view: drawings (AdgCanvas) availables for a single set of data
539 setmetatable(part
.view
, {
540 __index
= function (self
, key
)
541 return generate('view', key
)
545 part
.refresh
= function (self
)
546 -- Regenerate all the models
547 for _
, model
in pairs(self
.model
) do
549 model
:regenerate(self
)
553 -- Update the title block of all the views
554 for _
, view
in pairs(self
.view
) do
555 local title_block
= view
.title_block
556 for field
in pairs(Adg
.TitleBlock
._property
) do
557 local value
= self
.data
[field
:upper()]
558 if value
then title_block
[field
] = value
end