1 /*
2 * OpenSCAD (www.openscad.org)
3 * Copyright (C) 2009-2011 Clifford Wolf <clifford@clifford.at> and
4 * Marius Kintel <marius@kintel.net>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * As a special exception, you have permission to link this program
12 * with the CGAL library and distribute executables, as long as you
13 * follow the requirements of the GNU GPL in regard to all of the
14 * software in the executable aside from CGAL.
15 *
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
20 *
21 * You should have received a copy of the GNU General Public License
22 * along with this program; if not, write to the Free Software
23 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24 *
25 */
26
27 #include "module.h"
28 #include "ModuleInstantiation.h"
29 #include "node.h"
30 #include "polyset.h"
31 #include "evalcontext.h"
32 #include "builtin.h"
33 #include "printutils.h"
34 #include "fileutils.h"
35 #include "handle_dep.h"
36 #include "ext/lodepng/lodepng.h"
37
38 #include <cstdint>
39 #include <array>
40 #include <sstream>
41 #include <fstream>
42 #include <unordered_map>
43 #include "boost-utils.h"
44 #include <boost/functional/hash.hpp>
45 #include <boost/tokenizer.hpp>
46 #include <boost/lexical_cast.hpp>
47 #include <boost/algorithm/string.hpp>
48 #include <boost/assign/std/vector.hpp>
49 using namespace boost::assign; // bring 'operator+=()' into scope
50
51 #include <boost/filesystem.hpp>
52 namespace fs = boost::filesystem;
53
54 class SurfaceModule : public AbstractModule
55 {
56 public:
SurfaceModule()57 SurfaceModule() { }
58 AbstractNode *instantiate(const std::shared_ptr<Context>& ctx, const ModuleInstantiation *inst, const std::shared_ptr<EvalContext>& evalctx) const override;
59 };
60
61 typedef std::unordered_map<std::pair<int,int>, double, boost::hash<std::pair<int,int>>> img_data_t;
62
63 class SurfaceNode : public LeafNode
64 {
65 public:
66 VISITABLE();
SurfaceNode(const ModuleInstantiation * mi,const std::shared_ptr<EvalContext> & ctx)67 SurfaceNode(const ModuleInstantiation *mi, const std::shared_ptr<EvalContext> &ctx) : LeafNode(mi, ctx), center(false), invert(false), convexity(1) { }
68 std::string toString() const override;
name() const69 std::string name() const override { return "surface"; }
70
71 Filename filename;
72 bool center;
73 bool invert;
74 int convexity;
75
76 const Geometry *createGeometry() const override;
77 private:
78 void convert_image(img_data_t &data, std::vector<uint8_t> &img, unsigned int width, unsigned int height) const;
79 bool is_png(std::vector<uint8_t> &img) const;
80 img_data_t read_dat(std::string filename) const;
81 img_data_t read_png_or_dat(std::string filename) const;
82 };
83
instantiate(const std::shared_ptr<Context> & ctx,const ModuleInstantiation * inst,const std::shared_ptr<EvalContext> & evalctx) const84 AbstractNode *SurfaceModule::instantiate(const std::shared_ptr<Context>& ctx, const ModuleInstantiation *inst, const std::shared_ptr<EvalContext>& evalctx) const
85 {
86 auto node = new SurfaceNode(inst, evalctx);
87
88 AssignmentList args{assignment("file"), assignment("center"), assignment("convexity")};
89 AssignmentList optargs{assignment("center"),assignment("invert")};
90
91 ContextHandle<Context> c{Context::create<Context>(ctx)};
92 c->setVariables(evalctx, args, optargs);
93
94 const auto &fileval = c->lookup_variable("file");
95 auto filename = lookup_file(fileval.isUndefined() ? "" : fileval.toString(), inst->path(), c->documentPath());
96 node->filename = filename;
97 handle_dep(fs::path(filename).generic_string());
98
99 const auto ¢er = c->lookup_variable("center", true);
100 if (center.type() == Value::Type::BOOL) {
101 node->center = center.toBool();
102 }
103
104 const auto &convexity = c->lookup_variable("convexity", true);
105 if (convexity.type() == Value::Type::NUMBER) {
106 node->convexity = static_cast<int>(convexity.toDouble());
107 }
108
109 const auto &invert = c->lookup_variable("invert", true);
110 if (invert.type() == Value::Type::BOOL) {
111 node->invert = invert.toBool();
112 }
113
114 return node;
115 }
116
convert_image(img_data_t & data,std::vector<uint8_t> & img,unsigned int width,unsigned int height) const117 void SurfaceNode::convert_image(img_data_t &data, std::vector<uint8_t> &img, unsigned int width, unsigned int height) const
118 {
119 for (unsigned int y = 0; y < height; ++y) {
120 for (unsigned int x = 0; x < width; ++x) {
121 long idx = 4 * (y * width + x);
122 double pixel = 0.2126 * img[idx] + 0.7152 * img[idx + 1] + 0.0722 * img[idx + 2];
123 double z = 100.0/255 * (invert ? 1 - pixel : pixel);
124 data[std::make_pair(height - 1 - y, x)] = z;
125 }
126 }
127 }
128
is_png(std::vector<uint8_t> & png) const129 bool SurfaceNode::is_png(std::vector<uint8_t> &png) const
130 {
131 return (png.size() >= 8 &&
132 std::memcmp(png.data(),
133 std::array<uint8_t, 8>({{0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a}}).data(), 8) == 0);
134 }
135
read_png_or_dat(std::string filename) const136 img_data_t SurfaceNode::read_png_or_dat(std::string filename) const
137 {
138 img_data_t data;
139 std::vector<uint8_t> png;
140 int ret_val = 0;
141 try{
142 ret_val = lodepng::load_file(png, filename);
143 }catch(std::bad_alloc &ba){
144
145 LOG(message_group::Warning,Location::NONE,"","bad_alloc caught for '%1$s'.",ba.what());
146 return data;
147 }
148
149 if(ret_val == 78){
150 LOG(message_group::Warning,Location::NONE,"","The file '%1$s' couldn't be opened.",filename);
151 return data;
152 }
153
154 if (!is_png(png)) {
155 png.clear();
156 return read_dat(filename);
157 }
158
159 unsigned int width, height;
160 std::vector<uint8_t> img;
161 auto error = lodepng::decode(img, width, height, png);
162 if (error) {
163 LOG(message_group::Warning,Location::NONE,"","Can't read PNG image '%1$s'",filename);
164 data.clear();
165 return data;
166 }
167
168 convert_image(data, img, width, height);
169
170 return data;
171 }
172
read_dat(std::string filename) const173 img_data_t SurfaceNode::read_dat(std::string filename) const
174 {
175 img_data_t data;
176 std::ifstream stream(filename.c_str());
177
178 if (!stream.good()) {
179 LOG(message_group::Warning,Location::NONE,"","Can't open DAT file '%1$s'.",filename);
180 return data;
181 }
182
183 int lines = 0, columns = 0;
184 double min_val = 0;
185
186 typedef boost::tokenizer<boost::char_separator<char>> tokenizer;
187 boost::char_separator<char> sep(" \t");
188
189 while (!stream.eof()) {
190 std::string line;
191 while (!stream.eof() && (line.size() == 0 || line[0] == '#')) {
192 std::getline(stream, line);
193 boost::trim(line);
194 }
195 if (line.size() == 0 && stream.eof()) break;
196
197 int col = 0;
198 tokenizer tokens(line, sep);
199 try {
200 for(const auto &token : tokens) {
201 auto v = boost::lexical_cast<double>(token);
202 data[std::make_pair(lines, col++)] = v;
203 if (col > columns) columns = col;
204 min_val = std::min(v-1, min_val);
205 }
206 }
207 catch (const boost::bad_lexical_cast &blc) {
208 if (!stream.eof()) {
209 LOG(message_group::Warning,Location::NONE,"","Illegal value in '%1$s': %2$s",filename,blc.what());
210 }
211 break;
212 }
213 lines++;
214 }
215
216 return data;
217 }
218
createGeometry() const219 const Geometry *SurfaceNode::createGeometry() const
220 {
221 auto data = read_png_or_dat(filename);
222
223 auto p = new PolySet(3);
224 p->setConvexity(convexity);
225
226 int lines = 0;
227 int columns = 0;
228 double min_val = 0;
229 for (const auto &entry : data) {
230 lines = std::max(lines, entry.first.first + 1);
231 columns = std::max(columns, entry.first.second + 1);
232 min_val = std::min(entry.second - 1, min_val);
233 }
234
235 double ox = center ? -(columns-1)/2.0 : 0;
236 double oy = center ? -(lines-1)/2.0 : 0;
237
238 for (int i = 1; i < lines; ++i)
239 for (int j = 1; j < columns; ++j)
240 {
241 double v1 = data[std::make_pair(i-1, j-1)];
242 double v2 = data[std::make_pair(i-1, j)];
243 double v3 = data[std::make_pair(i, j-1)];
244 double v4 = data[std::make_pair(i, j)];
245 double vx = (v1 + v2 + v3 + v4) / 4;
246
247 p->append_poly();
248 p->append_vertex(ox + j-1, oy + i-1, v1);
249 p->append_vertex(ox + j, oy + i-1, v2);
250 p->append_vertex(ox + j-0.5, oy + i-0.5, vx);
251
252 p->append_poly();
253 p->append_vertex(ox + j, oy + i-1, v2);
254 p->append_vertex(ox + j, oy + i, v4);
255 p->append_vertex(ox + j-0.5, oy + i-0.5, vx);
256
257 p->append_poly();
258 p->append_vertex(ox + j, oy + i, v4);
259 p->append_vertex(ox + j-1, oy + i, v3);
260 p->append_vertex(ox + j-0.5, oy + i-0.5, vx);
261
262 p->append_poly();
263 p->append_vertex(ox + j-1, oy + i, v3);
264 p->append_vertex(ox + j-1, oy + i-1, v1);
265 p->append_vertex(ox + j-0.5, oy + i-0.5, vx);
266 }
267
268 for (int i = 1; i < lines; ++i)
269 {
270 p->append_poly();
271 p->append_vertex(ox + 0, oy + i-1, min_val);
272 p->append_vertex(ox + 0, oy + i-1, data[std::make_pair(i-1, 0)]);
273 p->append_vertex(ox + 0, oy + i, data[std::make_pair(i, 0)]);
274 p->append_vertex(ox + 0, oy + i, min_val);
275
276 p->append_poly();
277 p->insert_vertex(ox + columns-1, oy + i-1, min_val);
278 p->insert_vertex(ox + columns-1, oy + i-1, data[std::make_pair(i-1, columns-1)]);
279 p->insert_vertex(ox + columns-1, oy + i, data[std::make_pair(i, columns-1)]);
280 p->insert_vertex(ox + columns-1, oy + i, min_val);
281 }
282
283 for (int i = 1; i < columns; ++i)
284 {
285 p->append_poly();
286 p->insert_vertex(ox + i-1, oy + 0, min_val);
287 p->insert_vertex(ox + i-1, oy + 0, data[std::make_pair(0, i-1)]);
288 p->insert_vertex(ox + i, oy + 0, data[std::make_pair(0, i)]);
289 p->insert_vertex(ox + i, oy + 0, min_val);
290
291 p->append_poly();
292 p->append_vertex(ox + i-1, oy + lines-1, min_val);
293 p->append_vertex(ox + i-1, oy + lines-1, data[std::make_pair(lines-1, i-1)]);
294 p->append_vertex(ox + i, oy + lines-1, data[std::make_pair(lines-1, i)]);
295 p->append_vertex(ox + i, oy + lines-1, min_val);
296 }
297
298 if (columns > 1 && lines > 1) {
299 p->append_poly();
300 for (int i = 0; i < columns-1; ++i)
301 p->insert_vertex(ox + i, oy + 0, min_val);
302 for (int i = 0; i < lines-1; ++i)
303 p->insert_vertex(ox + columns-1, oy + i, min_val);
304 for (int i = columns-1; i > 0; i--)
305 p->insert_vertex(ox + i, oy + lines-1, min_val);
306 for (int i = lines-1; i > 0; i--)
307 p->insert_vertex(ox + 0, oy + i, min_val);
308 }
309
310 return p;
311 }
312
toString() const313 std::string SurfaceNode::toString() const
314 {
315 std::ostringstream stream;
316 fs::path path{static_cast<std::string>(this->filename)}; // gcc-4.6
317
318 stream << this->name() << "(file = " << this->filename
319 << ", center = " << (this->center ? "true" : "false")
320 << ", invert = " << (this->invert ? "true" : "false")
321 << ", " "timestamp = " << (fs::exists(path) ? fs::last_write_time(path) : 0)
322 << ")";
323
324 return stream.str();
325 }
326
register_builtin_surface()327 void register_builtin_surface()
328 {
329 Builtins::init("surface", new SurfaceModule(),
330 {
331 "surface(string, center = false, invert = false, number)",
332 });
333 }
334