Line data Source code
1 : # TODO: split file and move format independent methods
2 :
3 : # TODO: silently ignore uv argument if not supported (change!)
4 :
5 :
6 : # TODO: alternatively specify function VHnd -> pos (write)
7 : # application "2d height field [xy ; z] -> 3d coordinate"
8 : # (note: splitting for read is more difficult)
9 :
10 : # TODO: OFF support uv section ... see pmp
11 : # requires map attribute class to attribute
12 :
13 4 : function _read(io::IO,
14 : ::Val{:off},
15 : template::Mesh{V, F, H, E};
16 : position=Val(:x), uv::Nothing=nothing) where {V, F, H, E}
17 2 : match(r"^\s*OFF\s*$", readline(io)) !== nothing ||
18 : error("invalid header")
19 :
20 8 : n, m, e = map(w -> parse(Int, w), split(readline(io)))
21 :
22 2 : (m>=0 && n>=0) || error("negative size")
23 2 : e==0 || error("expect 0 edges")
24 :
25 2 : mesh = similar(template)
26 :
27 2 : sizehint!(mesh, (n, m))
28 2 : ax = vattr(mesh, position)
29 2 : isenabled(ax) || error("require positions :x")
30 :
31 2 : vtyp = eltype(ax)
32 2 : typ = eltype(vtyp)
33 :
34 2 : d = length(vtyp)
35 :
36 2 : for i = 1:n
37 48 : coords = map(w -> parse(typ, w), split(readline(io)))
38 12 : length(coords) == 3 || error("invalid coordinate")
39 12 : x = vtyp(coords[1:d]...)
40 12 : v = addvertex!(mesh)
41 12 : ax[v] = x
42 22 : end
43 :
44 2 : for i = 1:m
45 20 : tf = map(w -> parse(Int, w), split(readline(io)))
46 4 : (length(tf) >= 3) || error("invalid face")
47 :
48 4 : d = tf[1]
49 4 : (length(tf) == 1 + d) || error("inconsistent face")
50 :
51 4 : vs = VHnd.(tf[2:end] .+ 1)
52 :
53 4 : all(1 <= Int(v) <= n for v in vs) || error("invalid index")
54 :
55 4 : f = addface!(mesh, vs)
56 4 : f !== nothing || error("cannot add face $(vs)")
57 6 : end
58 :
59 2 : mesh
60 : end
61 :
62 4 : function Base.read(fmt::Val{S}, filename::AbstractString,
63 : template::Mesh{V, F, H, E};
64 : position=Val(:x), uv=nothing,
65 : exceptions::Bool=false) where {S, V, F, H, E}
66 2 : try
67 2 : open(filename) do f
68 2 : _read(f, fmt, template; position, uv)
69 : end
70 : catch e
71 0 : exceptions && rethrow()
72 0 : showerror(stderr, e, Base.catch_backtrace())
73 0 : nothing
74 : end
75 : end
76 :
77 2 : Base.read(fmt::Symbol, filename::AbstractString,
78 : template::Mesh{V, F, H, E};
79 : exceptions::Bool=false, options...) where {V, F, H, E} =
80 : read(Val(fmt), filename, template;
81 : exceptions, options...)
82 :
83 2 : _format_from_extension(filename) =
84 : splitext(filename)[2][2:end] |> lowercase |> Symbol |> Val
85 :
86 2 : function Base.read(filename::AbstractString, template::Mesh{V, F, H, E};
87 : exceptions::Bool=false,
88 : options...) where {V, F, H, E}
89 1 : fmt = _format_from_extension(filename)
90 1 : read(fmt, filename, template; exceptions, options...)
91 : end
92 :
93 : """
94 : mesh = read([format,] filename, template::Mesh[;
95 : position=Val(:x), uv=:nothing, exceptions=false])
96 :
97 : Read a polygonal mesh from file `filename` assuming `format`, where
98 : `template` defines the concrete type of mesh (e.g., which attributes
99 : exist) and state of attributes (i.e., which attributes are enabled).
100 :
101 : In case `read` fails, the optional argument `exceptions` determines
102 : whether an exception is thrown or `read` return `nothing`.
103 :
104 : The following formats are currently supported
105 :
106 : - `:off` OFF file, only vertex positions
107 : - `:obj` OBJ file, vertex positions and texture coordinates (optional)
108 :
109 : See also [`write`](@ref)
110 : """ read
111 :
112 4 : function _write(io::IO,
113 : ::Val{:off}, mesh::Mesh{V, F, H, E};
114 : position=Val(:x), uv::Nothing=nothing) where {V, F, H, E}
115 2 : print(io, "OFF\n$(nv(mesh)) $(nf(mesh)) 0\n")
116 12 : vidx = zeros(Int, n_total(vattr(mesh)))
117 :
118 2 : ax = vattr(mesh, position)
119 2 : isenabled(ax) || error("require positions :x")
120 :
121 6 : x = zeros(3)
122 2 : d = min(3, length(zeros(eltype(ax))))
123 :
124 4 : for (i, v) in enumerate(vertices(mesh))
125 12 : vidx[Int(v)] = i
126 : # Note: ax is not necessarily a 3-vector
127 12 : for j in 1:d
128 36 : x[j] = ax[v][j]
129 60 : end
130 12 : print(io, "$(x[1]) $(x[2]) $(x[3])\n")
131 22 : end
132 :
133 4 : for f in faces(mesh)
134 4 : vtx = collect(vertices(mesh, f))
135 4 : print(io, length(vtx))
136 4 : for v in vtx
137 12 : idx = vidx[Int(v)]
138 12 : @assert idx > 0
139 12 : print(io, " $(idx-1)")
140 12 : end
141 4 : print(io, "\n")
142 6 : end
143 :
144 2 : io
145 : end
146 :
147 4 : function Base.write(fmt::Val{S}, filename::AbstractString,
148 : mesh::Mesh{V, F, H, E};
149 : exceptions::Bool=false,
150 : position=Val(:x), uv=nothing) where {S, V, F, H, E}
151 2 : try
152 2 : open(filename, "w+") do f
153 2 : _write(f, fmt, mesh; position, uv)
154 : end
155 : catch e
156 0 : exceptions && rethrow()
157 0 : showerror(stderr, e, Base.catch_backtrace())
158 0 : false
159 : end
160 :
161 0 : true
162 : end
163 :
164 2 : Base.write(fmt::Symbol, filename::AbstractString,
165 : mesh::Mesh{V, F, H, E};
166 : exceptions::Bool=false,
167 : position=Val(:x), uv=nothing) where {V, F, H, E} =
168 : write(Val(fmt), filename, mesh; exceptions, position, uv)
169 :
170 2 : function Base.write(filename::AbstractString, mesh::Mesh{V, F, H, E};
171 : exceptions::Bool=false,
172 : position=Val(:x), uv=nothing) where {V, F, H, E}
173 1 : fmt = _format_from_extension(filename)
174 1 : write(fmt, filename, mesh; exceptions, position, uv)
175 : end
176 :
177 : """
178 : success = write([format,] filename, mesh::Mesh[;
179 : position=Val(:x), uv=nothing, exceptions=false])
180 :
181 : Write a polygonal `mesh` to file `filename` using `format`. Returns
182 : `true` on success.
183 :
184 : In case `write` fails, the optional argument `exceptions` determines
185 : whether `an exception is thrown or `write` returns `false`.
186 :
187 : The following formats are currently supported
188 :
189 : - `:off` OFF file, only vertex positions
190 : - `:obj` OBJ file, vertex positions and texture coordinates (optional)
191 :
192 :
193 : See also [`read`](@ref)
194 : """ write
|