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()
303 size
.x
= size
.x
+ canvas
:get_left_margin() + canvas
:get_right_margin()
304 size
.y
= size
.y
+ canvas
:get_top_margin() + canvas
:get_bottom_margin()
306 -- Create the cairo surface
308 if format == 'png' and cairo
.ImageSurface
then
309 surface
= cairo
.ImageSurface
.create(cairo
.Format
.RGB24
, size
.x
, size
.y
)
310 elseif format == 'svg' and cairo
.SvgSurface
then
311 surface
= cairo
.SvgSurface
.create(file
, size
.x
, size
.y
)
312 elseif format == 'pdf' and cairo
.PdfSurface
then
313 surface
= cairo
.PdfSurface
.create(file
, size
.x
, size
.y
)
314 elseif format == 'ps' and cairo
.PsSurface
then
315 -- Pull request: http://github.com/pavouk/lgi/pull/46
316 surface
= cairo
.PsSurface
.create(file
, size
.x
, size
.y
)
317 surface
:dsc_comment('%%Title: adg-lua demonstration program')
318 surface
:dsc_comment('%%Copyright: Copyleft (C) 2013 Fontana Nicola')
319 surface
:dsc_comment('%%Orientation: Portrait')
320 surface
:dsc_begin_setup()
321 surface
:dsc_begin_page_setup()
322 surface
:dsc_comment('%%IncludeFeature: *PageSize A4')
323 elseif not format then
327 return nil, 'Requested format not supported (' .. format .. ')'
330 -- Render the canvas content
331 local cr
= cairo
.Context
.create(surface
)
335 if cairo
.Surface
.get_type(surface
) == 'IMAGE' then
336 status
= cairo
.Surface
.write_to_png(surface
, file
)
342 if status
~= 'SUCCESS' then
343 return nil, cairo
.Status
.to_string(cairo
.Status
[status
])
347 local function add_title_block(canvas
)
348 canvas
:set_title_block(Adg
.TitleBlock
{
354 projection
= Adg
.Projection
{ scheme
= Adg
.ProjectionScheme
.FIRST_ANGLE
},
359 local function add_dimensions(canvas
, model
)
360 local body
= model
.body
361 local hole
= model
.hole
367 dim
= Adg
.LDim
.new_full_from_model(body
, '-D3I_X', '-D3F_X', '-D3F_Y', -math
.pi
/2)
368 dim
:set_outside(Adg
.ThreeState
.OFF
)
371 dim
= Adg
.LDim
.new_full_from_model(body
, '-D6I_X', '-D67', '-East', -math
.pi
/2)
373 dim
:switch_extension1(false)
376 dim
= Adg
.LDim
.new_full_from_model(body
, '-D6I_X', '-D7F', '-East', -math
.pi
/2)
377 dim
:set_limits('-0.06', nil)
380 dim
= Adg
.ADim
.new_full_from_model(body
, '-D6I_Y', '-D6F', '-D6F', '-D67', '-D6F')
384 dim
= Adg
.RDim
.new_full_from_model(body
, '-RD34', '-RD34_R', '-RD34_XY')
387 dim
= Adg
.LDim
.new_full_from_model(body
, '-DGROOVEI_X', '-DGROOVEF_X', '-DGROOVEX_POS', -math
.pi
/2)
390 dim
= Adg
.LDim
.new_full_from_model(body
, 'D2I', '-D2I', '-D2_POS', math
.pi
)
391 dim
:set_limits('-0.1', nil)
392 dim
:set_outside(Adg
.ThreeState
.OFF
)
393 dim
:set_value('\226\140\128 <>')
396 dim
= Adg
.LDim
.new_full_from_model(body
, 'DGROOVEI_Y', '-DGROOVEI_Y', '-DGROOVEY_POS', math
.pi
)
397 dim
:set_limits('-0.1', nil)
398 dim
:set_outside(Adg
.ThreeState
.OFF
)
399 dim
:set_value('\226\140\128 <>')
405 dim
= Adg
.ADim
.new_full_from_model(body
, 'D1F', 'D1I', 'D2I', 'D1F', 'D1F')
407 dim
:switch_extension2(false)
410 dim
= Adg
.LDim
.new_full_from_model(body
, 'D1I', nil, 'West', math
.pi
/ 2)
411 dim
:set_ref2_from_model(hole
, '-LHOLE')
412 dim
:switch_extension1(false)
415 dim
= Adg
.LDim
.new_full_from_model(body
, 'D1I', 'DGROOVEI_X', 'West', math
.pi
/ 2)
416 dim
:switch_extension1(false)
420 dim
= Adg
.LDim
.new_full_from_model(body
, 'D4F', 'D6I_X', 'D4_POS', math
.pi
/ 2)
421 dim
:set_limits(nil, '+0.2')
422 dim
:set_outside(Adg
.ThreeState
.OFF
)
425 dim
= Adg
.LDim
.new_full_from_model(body
, 'D1F', 'D3I_X', 'D2_POS', math
.pi
/ 2)
427 dim
:switch_extension2(false)
428 dim
:set_outside(Adg
.ThreeState
.OFF
)
431 dim
= Adg
.LDim
.new_full_from_model(body
, 'D3I_X', 'D7F', 'East', math
.pi
/ 2)
432 dim
:set_limits(nil, '+0.1')
434 dim
:set_outside(Adg
.ThreeState
.OFF
)
435 dim
:switch_extension2(false)
438 dim
= Adg
.LDim
.new_full_from_model(body
, 'D1I', 'D7F', 'D3F_Y', math
.pi
/ 2)
439 dim
:set_limits('-0.05', '+0.05')
443 dim
= Adg
.ADim
.new_full_from_model(body
, 'D4F', 'D4I', 'D5I', 'D4F', 'D4F')
445 dim
:switch_extension2(false)
451 dim
= Adg
.LDim
.new_full_from_model(body
, 'D6F', '-D6F', 'East', 0)
452 dim
:set_limits('-0.1', nil)
454 dim
:set_value('\226\140\128 <>')
457 dim
= Adg
.LDim
.new_full_from_model(body
, 'D4F', '-D4F', 'East', 0)
459 dim
:set_value('\226\140\128 <>')
462 dim
= Adg
.LDim
.new_full_from_model(body
, 'D5F', '-D5F', 'East', 0)
463 dim
:set_limits('-0.1', nil)
465 dim
:set_value('\226\140\128 <>')
468 dim
= Adg
.LDim
.new_full_from_model(body
, 'D7F', '-D7F', 'East', 0)
469 dim
:set_value('\226\140\128 <>')
475 dim
= Adg
.LDim
.new_full_from_model(hole
, 'DHOLE', '-DHOLE', nil, math
.pi
)
476 dim
:set_pos_from_model(body
, '-West')
477 dim
:set_value('\226\140\128 <>')
480 dim
= Adg
.LDim
.new_full_from_model(body
, 'D1I', '-D1I', '-West', math
.pi
)
481 dim
:set_limits('-0.05', '+0.05')
483 dim
:set_value('\226\140\128 <>')
486 dim
= Adg
.LDim
.new_full_from_model(body
, 'D3I_Y', '-D3I_Y', '-West', math
.pi
)
487 dim
:set_limits('-0.25', nil)
489 dim
:set_value('\226\140\128 <>')
493 function generator
.view
.detailed(part
)
494 local canvas
= Adg
.Canvas
{}
495 local model
= part
.model
497 add_title_block(canvas
)
498 canvas
:add(Adg
.Stroke
{ trail
= model
.body
})
499 canvas
:add(Adg
.Stroke
{ trail
= model
.edges
})
500 canvas
:add(Adg
.Hatch
{ trail
= model
.hole
})
501 canvas
:add(Adg
.Stroke
{ trail
= model
.hole
})
502 canvas
:add(Adg
.Stroke
{
504 line_dress
= Adg
.Dress
.LINE_AXIS
506 add_dimensions(canvas
, model
)
513 -----------------------------------------------------------------
515 local controller
= {}
517 function controller
.new(data
)
520 local function generate(class
, method
)
521 local constructor
= generator
[class
][method
]
522 local result
= constructor
and constructor(part
) or false
523 part
[class
][method
] = result
527 -- data: numbers and strings needed to define the whole part
528 part
.data
= data
or {}
530 -- model: different models (AdgModel instances) generated from data
532 setmetatable(part
.model
, {
533 __index
= function (self
, key
)
534 return generate('model', key
)
538 -- view: drawings (AdgCanvas) availables for a single set of data
540 setmetatable(part
.view
, {
541 __index
= function (self
, key
)
542 return generate('view', key
)
546 part
.refresh
= function (self
)
547 -- Regenerate all the models
548 for _
, model
in pairs(self
.model
) do
550 model
:regenerate(self
)
554 -- Update the title block of all the views
555 for _
, view
in pairs(self
.view
) do
556 local title_block
= view
.title_block
557 for field
in pairs(Adg
.TitleBlock
._property
) do
558 local value
= self
.data
[field
:upper()]
559 if value
then title_block
[field
] = value
end