├── README.md
├── binvox_rw.py
├── blender_utils.py
├── examples
├── 0.binvox
├── 0.off
├── 0.txt
├── render_binvox.sh
├── render_off.sh
├── render_off_txt.sh
└── render_txt.sh
├── import_off.py
├── mesh.py
├── obj_to_off.py
├── off_to_obj.py
├── point_cloud.py
├── render_binvox.py
├── render_off.py
├── render_off_txt.py
├── render_txt.py
├── screenshot.jpg
├── txt_to_ply.py
├── write_binvox.py
└── write_txt.py
/README.md:
--------------------------------------------------------------------------------
1 | # Visualizing 3D Data with Blender in Python
2 |
3 | This repository contains various Python scripts for visualizing 3D data,
4 | in the form of triangular meshes, point clouds or occupancy grids,
5 | using [Blender](https://www.blender.org/)'s [Python API](https://docs.blender.org/api/current/).
6 |
7 | **Acknowledgement: The code is based in some parts on work by
8 | [Simon Donné](https://avg.is.tuebingen.mpg.de/person/sdonne).**
9 |
10 | If you use this code, consider citing:
11 |
12 | @inproceedings{Stutz2018CVPR,
13 | title = {Learning 3D Shape Completion from Laser Scan Data with Weak Supervision },
14 | author = {Stutz, David and Geiger, Andreas},
15 | booktitle = {IEEE Conference on Computer Vision and Pattern Recognition (CVPR)},
16 | publisher = {IEEE Computer Society},
17 | year = {2018}
18 | }
19 | @misc{Stutz2017,
20 | author = {David Stutz},
21 | title = {Learning Shape Completion from Bounding Boxes with CAD Shape Priors},
22 | month = {September},
23 | year = {2017},
24 | institution = {RWTH Aachen University},
25 | address = {Aachen, Germany},
26 | howpublished = {http://davidstutz.de/},
27 | }
28 |
29 | 
30 |
31 | ## Requirements
32 |
33 | The code was tested with [Blender](https://www.blender.org/) 2.78 or higher.
34 | Python should be installed locally and should also ship Python 3.5 with
35 | a custom NumPy installation.
36 |
37 | All remaining requirements, essentially [dimatura/binvox-rw-py](https://github.com/dimatura/binvox-rw-py)
38 | and [alextsui05/blender-off-addon](https://github.com/alextsui05/blender-off-addon)
39 | are included.
40 |
41 | ## File Formats
42 |
43 | The following file formats are assumed:
44 |
45 | * [OFF](https://en.wikipedia.org/wiki/OFF_(file_format)): file format for triangular meshes.
46 | * [BINVOX](http://www.patrickmin.com/binvox/): file format for occupancy grids.
47 | * A custom TXT format for point clouds.
48 |
49 | The provided OFF files can be visualized using [MeshLab](http://www.meshlab.net/).
50 | Scripts for converting OBJ files to OFF files as well as writing BINVOX and TXT files
51 | are provided.
52 |
53 | The custom TXT file format looks as follows:
54 |
55 | n_points
56 | p1_x p1_y p2_z
57 | p2_x p2_y p2_z
58 | ...
59 |
60 | Use the following Python scripts for conversion:
61 |
62 | * `off_to_obj.py` and `obj_to_off.py` to conversion between OFF and OBJ.
63 | * `txt_to_ply.py` to convert a TXT point cloud to PLY format.
64 |
65 | Examples of using the included Python code for writing TXT or BINVOX files
66 | are provided in `write_txt.py` and `write_binvox.py`.
67 |
68 | ## Examples
69 |
70 | `examples/` includes some examples; these should be rune out of the root folder:
71 |
72 | ./examples/render_off.sh
73 | ./examples/render_binvox.sh
74 | ./examples/render_txt.sh
75 |
76 | All examples create `examples/0.png`.
77 |
78 | ## Usage
79 |
80 | The provided examples essentially use the functionality provided in `blender_utils.py`;
81 | the general approach is as follows:
82 |
83 | # Import utils.
84 | from blender_utils import *
85 |
86 | # Initialize and set camera target.
87 | # Check the function to adapt the light sources or camera(s).
88 | camera_target = initialize()
89 |
90 | # Create a material.
91 | # First parameter is the name, second is the diffuse color in rgb,
92 | # third argument is the alpha channel and last whether to cast/receive shadow.
93 | off_material = make_material('BRC_Material_Mesh', (0.66, 0.45, 0.23), 0.8, True)
94 |
95 | # Load an OFF file, alternatively, use load_binvox or load_txt.
96 | # In any case, we need the previously defined material.
97 | # The mesh provided in examples/0.off is scaled to [0,32]^3,
98 | # so we scale it by 1/32 = 0.03125 and then translate it to the
99 | # origin using (-0.5, -0.5, -0.5).
100 | # Finally we switch y and z axes.
101 | load_off(args.off, off_material, (-0.5, -0.5, -0.5), 0.03125, 'xzy')
102 |
103 | # Set rotation of camera around the origin, implicitly defines the locatio
104 | # together with the distance.
105 | rotation = (5, 0, -55)
106 | distance = 0.5
107 |
108 | # Render all loaded objects using the camera_target obtained above and
109 | # the output file.
110 | render(camera_target, output_file, rotation, distance)
111 |
112 | ## License
113 |
114 | License for source code corresponding to:
115 |
116 | D. Stutz, A. Geiger. **Learning 3D Shape Completion from Laser Scan Data with Weak Supervision.** IEEE Conference on Computer Vision and Pattern Recognition (CVPR), 2018.
117 |
118 | Note that the source code is based on the following projects for which separate licenses apply:
119 |
120 | * [ModelNet](http://modelnet.cs.princeton.edu/)
121 | * [alextsui05/blender-off-addon](https://github.com/alextsui05/blender-off-addon)
122 | * [dimatura/binvox-rw-py](https://github.com/dimatura/binvox-rw-py)
123 |
124 | Copyright (c) 2018 David Stutz, Max-Planck-Gesellschaft
125 |
126 | **Please read carefully the following terms and conditions and any accompanying documentation before you download and/or use this software and associated documentation files (the "Software").**
127 |
128 | The authors hereby grant you a non-exclusive, non-transferable, free of charge right to copy, modify, merge, publish, distribute, and sublicense the Software for the sole purpose of performing non-commercial scientific research, non-commercial education, or non-commercial artistic projects.
129 |
130 | Any other use, in particular any use for commercial purposes, is prohibited. This includes, without limitation, incorporation in a commercial product, use in a commercial service, or production of other artefacts for commercial purposes.
131 |
132 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
133 |
134 | You understand and agree that the authors are under no obligation to provide either maintenance services, update services, notices of latent defects, or corrections of defects with regard to the Software. The authors nevertheless reserve the right to update, modify, or discontinue the Software at any time.
135 |
136 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. You agree to cite the corresponding papers (see above) in documents and papers that report on research using the Software.
137 |
--------------------------------------------------------------------------------
/binvox_rw.py:
--------------------------------------------------------------------------------
1 | # Copyright (C) 2012 Daniel Maturana
2 | # This file is part of binvox-rw-py.
3 | #
4 | # binvox-rw-py is free software: you can redistribute it and/or modify
5 | # it under the terms of the GNU General Public License as published by
6 | # the Free Software Foundation, either version 3 of the License, or
7 | # (at your option) any later version.
8 | #
9 | # binvox-rw-py is distributed in the hope that it will be useful,
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | # GNU General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU General Public License
15 | # along with binvox-rw-py. If not, see .
16 | #
17 |
18 | """
19 | Binvox to Numpy and back.
20 |
21 |
22 | >>> import numpy as np
23 | >>> import binvox_rw
24 | >>> with open('chair.binvox', 'rb') as f:
25 | ... m1 = binvox_rw.read_as_3d_array(f)
26 | ...
27 | >>> m1.dims
28 | [32, 32, 32]
29 | >>> m1.scale
30 | 41.133000000000003
31 | >>> m1.translate
32 | [0.0, 0.0, 0.0]
33 | >>> with open('chair_out.binvox', 'wb') as f:
34 | ... m1.write(f)
35 | ...
36 | >>> with open('chair_out.binvox', 'rb') as f:
37 | ... m2 = binvox_rw.read_as_3d_array(f)
38 | ...
39 | >>> m1.dims==m2.dims
40 | True
41 | >>> m1.scale==m2.scale
42 | True
43 | >>> m1.translate==m2.translate
44 | True
45 | >>> np.all(m1.data==m2.data)
46 | True
47 |
48 | >>> with open('chair.binvox', 'rb') as f:
49 | ... md = binvox_rw.read_as_3d_array(f)
50 | ...
51 | >>> with open('chair.binvox', 'rb') as f:
52 | ... ms = binvox_rw.read_as_coord_array(f)
53 | ...
54 | >>> data_ds = binvox_rw.dense_to_sparse(md.data)
55 | >>> data_sd = binvox_rw.sparse_to_dense(ms.data, 32)
56 | >>> np.all(data_sd==md.data)
57 | True
58 | >>> # the ordering of elements returned by numpy.nonzero changes with axis
59 | >>> # ordering, so to compare for equality we first lexically sort the voxels.
60 | >>> np.all(ms.data[:, np.lexsort(ms.data)] == data_ds[:, np.lexsort(data_ds)])
61 | True
62 | """
63 |
64 | import numpy as np
65 |
66 | class Voxels(object):
67 | """ Holds a binvox model.
68 | data is either a three-dimensional numpy boolean array (dense representation)
69 | or a two-dimensional numpy float array (coordinate representation).
70 |
71 | dims, translate and scale are the model metadata.
72 |
73 | dims are the voxel dimensions, e.g. [32, 32, 32] for a 32x32x32 model.
74 |
75 | scale and translate relate the voxels to the original model coordinates.
76 |
77 | To translate voxel coordinates i, j, k to original coordinates x, y, z:
78 |
79 | x_n = (i+.5)/dims[0]
80 | y_n = (j+.5)/dims[1]
81 | z_n = (k+.5)/dims[2]
82 | x = scale*x_n + translate[0]
83 | y = scale*y_n + translate[1]
84 | z = scale*z_n + translate[2]
85 |
86 | """
87 |
88 | def __init__(self, data, dims, translate, scale):
89 | self.data = data
90 | self.dims = dims
91 | self.translate = translate
92 | self.scale = scale
93 |
94 | def clone(self):
95 | data = self.data.copy()
96 | dims = self.dims[:]
97 | translate = self.translate[:]
98 | return Voxels(data, dims, translate, self.scale)
99 |
100 | def write(self, fp):
101 | write(self, fp)
102 |
103 | def read_header(fp):
104 | """ Read binvox header. Mostly meant for internal use.
105 | """
106 | line = fp.readline().strip()
107 | if not line.startswith(b'#binvox'):
108 | raise IOError('Not a binvox file')
109 | dims = list(map(int, fp.readline().strip().split(b' ')[1:]))
110 | translate = list(map(float, fp.readline().strip().split(b' ')[1:]))
111 | scale = list(map(float, fp.readline().strip().split(b' ')[1:]))[0]
112 | line = fp.readline()
113 | return dims, translate, scale
114 |
115 | def read_as_3d_array(fp):
116 | """ Read binary binvox format as array.
117 |
118 | Returns the model with accompanying metadata.
119 |
120 | Voxels are stored in a three-dimensional numpy array, which is simple and
121 | direct, but may use a lot of memory for large models. (Storage requirements
122 | are 8*(d^3) bytes, where d is the dimensions of the binvox model. Numpy
123 | boolean arrays use a byte per element).
124 |
125 | Doesn't do any checks on input except for the '#binvox' line.
126 | """
127 | dims, translate, scale = read_header(fp)
128 | raw_data = np.frombuffer(fp.read(), dtype=np.uint8)
129 | # if just using reshape() on the raw data:
130 | # indexing the array as array[i,j,k], the indices map into the
131 | # coords as:
132 | # i -> x
133 | # j -> z
134 | # k -> y
135 | # if fix_coords is true, then data is rearranged so that
136 | # mapping is
137 | # i -> x
138 | # j -> y
139 | # k -> z
140 | values, counts = raw_data[::2], raw_data[1::2]
141 | data = np.repeat(values, counts).astype(np.bool)
142 | data = data.reshape(dims)
143 |
144 | return Voxels(data, dims, translate, scale)
145 |
146 | def read_as_coord_array(fp, fix_coords=True):
147 | """ Read binary binvox format as coordinates.
148 |
149 | Returns binvox model with voxels in a "coordinate" representation, i.e. an
150 | 3 x N array where N is the number of nonzero voxels. Each column
151 | corresponds to a nonzero voxel and the 3 rows are the (x, z, y) coordinates
152 | of the voxel. (The odd ordering is due to the way binvox format lays out
153 | data). Note that coordinates refer to the binvox voxels, without any
154 | scaling or translation.
155 |
156 | Use this to save memory if your model is very sparse (mostly empty).
157 |
158 | Doesn't do any checks on input except for the '#binvox' line.
159 | """
160 | dims, translate, scale = read_header(fp)
161 | raw_data = np.frombuffer(fp.read(), dtype=np.uint8)
162 |
163 | values, counts = raw_data[::2], raw_data[1::2]
164 |
165 | sz = np.prod(dims)
166 | index, end_index = 0, 0
167 | end_indices = np.cumsum(counts)
168 | indices = np.concatenate(([0], end_indices[:-1])).astype(end_indices.dtype)
169 |
170 | values = values.astype(np.bool)
171 | indices = indices[values]
172 | end_indices = end_indices[values]
173 |
174 | nz_voxels = []
175 | for index, end_index in zip(indices, end_indices):
176 | nz_voxels.extend(range(index, end_index))
177 | nz_voxels = np.array(nz_voxels)
178 | # TODO are these dims correct?
179 | # according to docs,
180 | # index = x * wxh + z * width + y; // wxh = width * height = d * d
181 |
182 | x = nz_voxels / (dims[0]*dims[1])
183 | zwpy = nz_voxels % (dims[0]*dims[1]) # z*w + y
184 | z = zwpy / dims[0]
185 | y = zwpy % dims[0]
186 |
187 | data = np.vstack((x, y, z))
188 |
189 | #return Voxels(data, dims, translate, scale)
190 | return Voxels(np.ascontiguousarray(data), dims, translate, scale)
191 |
192 | def dense_to_sparse(voxel_data, dtype=np.int):
193 | """ From dense representation to sparse (coordinate) representation.
194 | No coordinate reordering.
195 | """
196 | if voxel_data.ndim!=3:
197 | raise ValueError('voxel_data is wrong shape; should be 3D array.')
198 | return np.asarray(np.nonzero(voxel_data), dtype)
199 |
200 | def sparse_to_dense(voxel_data, dims, dtype=np.bool):
201 | if voxel_data.ndim!=2 or voxel_data.shape[0]!=3:
202 | raise ValueError('voxel_data is wrong shape; should be 3xN array.')
203 | if np.isscalar(dims):
204 | dims = [dims]*3
205 | dims = np.atleast_2d(dims).T
206 | # truncate to integers
207 | xyz = voxel_data.astype(np.int)
208 | # discard voxels that fall outside dims
209 | valid_ix = ~np.any((xyz < 0) | (xyz >= dims), 0)
210 | xyz = xyz[:,valid_ix]
211 | out = np.zeros(dims.flatten(), dtype=dtype)
212 | out[tuple(xyz)] = True
213 | return out
214 |
215 | #def get_linear_index(x, y, z, dims):
216 | #""" Assuming xzy order. (y increasing fastest.
217 | #TODO ensure this is right when dims are not all same
218 | #"""
219 | #return x*(dims[1]*dims[2]) + z*dims[1] + y
220 |
221 | def write(voxel_model, fp):
222 | """ Write binary binvox format.
223 |
224 | Note that when saving a model in sparse (coordinate) format, it is first
225 | converted to dense format.
226 |
227 | Doesn't check if the model is 'sane'.
228 |
229 | """
230 | if voxel_model.data.ndim==2:
231 | # TODO avoid conversion to dense
232 | dense_voxel_data = sparse_to_dense(voxel_model.data, voxel_model.dims)
233 | else:
234 | dense_voxel_data = voxel_model.data
235 |
236 | fp.write('#binvox 1\n')
237 | fp.write('dim '+' '.join(map(str, voxel_model.dims))+'\n')
238 | fp.write('translate '+' '.join(map(str, voxel_model.translate))+'\n')
239 | fp.write('scale '+str(voxel_model.scale)+'\n')
240 | fp.write('data\n')
241 |
242 | voxels_flat = dense_voxel_data.flatten()
243 |
244 | # keep a sort of state machine for writing run length encoding
245 | state = voxels_flat[0]
246 | ctr = 0
247 | for c in voxels_flat:
248 | if c==state:
249 | ctr += 1
250 | # if ctr hits max, dump
251 | if ctr==255:
252 | fp.write(chr(state))
253 | fp.write(chr(ctr))
254 | ctr = 0
255 | else:
256 | # if switch state, dump
257 | fp.write(chr(state))
258 | fp.write(chr(ctr))
259 | state = c
260 | ctr = 1
261 | # flush out remainders
262 | if ctr > 0:
263 | fp.write(chr(state))
264 | fp.write(chr(ctr))
265 |
266 | if __name__ == '__main__':
267 | import doctest
268 | doctest.testmod()
269 |
--------------------------------------------------------------------------------
/blender_utils.py:
--------------------------------------------------------------------------------
1 | import sys
2 | import os
3 |
4 |
5 | class LogLevel:
6 | """
7 | Defines color of log message.
8 | """
9 |
10 | INFO = '\033[94m'
11 | """ (string) Blue. """
12 | WARNING = '\033[93m'
13 | """ (string) Yellow. """
14 | ERROR = '\033[91m\033[1m'
15 | """ (string) Red. """
16 | ENDC = '\033[0m'
17 | """ (string) End of color. """
18 |
19 |
20 | def log(output,level=LogLevel.INFO):
21 | """
22 | Log message.
23 |
24 | :param output: message
25 | :param level: LogLevel
26 | """
27 |
28 | sys.stderr.write(level)
29 | sys.stderr.write(str(output))
30 | sys.stderr.write(LogLevel.ENDC)
31 | sys.stderr.write("\n")
32 | sys.stderr.flush()
33 |
34 |
35 | # This makes sure that Blender's NumPy is loaded first.
36 | # The path needs to be adapted before usage.
37 | # Example:
38 | # blender_package_path = '~/blender-2.79-linux-glibc219-x86_64/2.79/python/lib/python3.5/site-packages/'
39 | if not 'BLENDER_PACKAGE_PATH' in globals():
40 | BLENDER_PACKAGE_PATH = None
41 | BLENDER_PACKAGE_PATH = '/BS/dstutz/work/dev-box/blender-2.79-linux-glibc219-x86_64/2.79/python/lib/python3.5/site-packages/'
42 | if BLENDER_PACKAGE_PATH is None:
43 | log('Open blender_utils.py and set BLENDER_PACKAGE_PATH before usage, check documentation!', LogLevel.ERROR)
44 | exit()
45 | if not os.path.exists(BLENDER_PACKAGE_PATH):
46 | log('The set BLENDER_PACKAGE_PATH does not exist, check documentation!', LogLevel.ERROR)
47 | exit()
48 |
49 | sys.path.insert(1, os.path.realpath(BLENDER_PACKAGE_PATH))
50 |
51 | import bpy
52 | import bmesh
53 | import math
54 | import numpy as np
55 |
56 | import binvox_rw
57 | import import_off
58 | import_off.register()
59 |
60 | sphere_base_mesh = None
61 | cube_base_mesh = None
62 |
63 |
64 | def initialize(width=512, height=448):
65 | """
66 | Setup scene, camer and lighting.
67 |
68 | :param width: width of rendered image
69 | :param height: height of rendered image
70 | :return: camera target
71 | """
72 |
73 | # First, the base meshes (sphere and cube) are set,
74 | # these are later used to display point clouds or occupancy grids.
75 | bpy.ops.mesh.primitive_ico_sphere_add()
76 | global sphere_base_mesh
77 | sphere_base_mesh = bpy.context.scene.objects.active.data.copy()
78 | for face in sphere_base_mesh.polygons:
79 | face.use_smooth = True
80 |
81 | bpy.ops.mesh.primitive_cube_add()
82 | global cube_base_mesh
83 | cube_base_mesh = bpy.context.scene.objects.active.data.copy()
84 |
85 | # Delete current scene, except for the camera and the lamp
86 | for obj in bpy.data.objects:
87 | if str(obj.name) in ['Camera']:
88 | continue
89 | obj.select = True
90 | bpy.ops.object.delete()
91 |
92 | scene = bpy.context.scene
93 |
94 | # Setup the camera, location can also be influenced later,
95 | # these are only defaults.
96 | cam = scene.objects['Camera']
97 | cam.location = (0, 3.0, 1.0)
98 | cam.data.lens = 35
99 | cam.data.sensor_width = 32
100 | cam.data.sensor_height = 32
101 | cam_constraint = cam.constraints.new(type='TRACK_TO')
102 | cam_constraint.track_axis = 'TRACK_NEGATIVE_Z'
103 | cam_constraint.up_axis = 'UP_Y'
104 |
105 | def parent_obj_to_camera(b_camera):
106 | """
107 | Utility function defining the target of the camera as the origin.
108 |
109 | :param b_camera: camera object
110 | :return: origin object
111 | """
112 |
113 | origin = (0, 0, 0)
114 | b_empty = bpy.data.objects.new('Empty', None)
115 | b_empty.location = origin
116 | b_camera.parent = b_empty # setup parenting
117 |
118 | scn = bpy.context.scene
119 | scn.objects.link(b_empty)
120 | scn.objects.active = b_empty
121 | return b_empty
122 |
123 | # Sets up the camera and defines its target.
124 | camera_target = parent_obj_to_camera(cam)
125 | cam_constraint.target = camera_target
126 |
127 | # For nicer visualization, several light locations are defined.
128 | # See the documentation for details, these should be edited based
129 | # on preferences.
130 | locations = [
131 | (-0.98382, 0.445997, 0.526505),
132 | (-0.421806, -0.870784, 0.524944),
133 | (0.075576, -0.960128, 0.816464),
134 | (0.493553, -0.57716, 0.928208),
135 | (0.787275, -0.256822, 0.635172),
136 | (1.01032, 0.148764, 0.335078)
137 | ]
138 |
139 | for i in range(len(locations)):
140 | # We only use point spot lamps centered at the given locations
141 | # and without any specific rotation (see euler angles below).
142 | lamp_data = bpy.data.lamps.new(name='Point Lamp ' + str(i), type='POINT')
143 | lamp_data.shadow_method = 'RAY_SHADOW'
144 | lamp_data.shadow_ray_sample_method = 'CONSTANT_QMC'
145 | lamp_data.use_shadow = True
146 | lamp_data.shadow_soft_size = 1e6
147 | lamp_data.distance = 2
148 | lamp_data.energy = 0.1
149 | lamp_data.use_diffuse = True
150 | lamp_data.use_specular = True
151 | lamp_data.falloff_type = 'CONSTANT'
152 |
153 | lamp_object = bpy.data.objects.new(name='Spot Lamp ' + str(i), object_data=lamp_data)
154 | scene.objects.link(lamp_object)
155 | lamp_object.location[0] = locations[i][0]
156 | lamp_object.location[1] = locations[i][1]
157 | lamp_object.location[2] = locations[i][2]
158 | lamp_object.rotation_euler[0] = 0
159 | lamp_object.rotation_euler[1] = 0
160 | lamp_object.rotation_euler[2] = 0
161 | lamp_object.parent = camera_target
162 |
163 | # This tries to use CUDA rendering if possible.
164 | try:
165 | if (2, 78, 0) <= bpy.app.version:
166 | # https://blender.stackexchange.com/questions/5281/blender-sets-compute-device-cuda-but-doesnt-use-it-for-actual-render-on-ec2
167 | bpy.context.user_preferences.addons['cycles'].preferences.compute_device_type = 'CUDA'
168 | bpy.context.user_preferences.addons['cycles'].preferences.devices[0].use = True
169 | else:
170 | bpy.context.user_preferences.system.compute_device_type = 'CUDA'
171 | except TypeError:
172 | pass
173 |
174 | scene.render.use_file_extension = False
175 | scene.render.resolution_x = width
176 | scene.render.resolution_y = height
177 | scene.render.resolution_percentage = 100
178 | scene.render.use_antialiasing = True
179 | scene.render.use_shadows = True
180 | world = bpy.context.scene.world
181 | world.zenith_color = [1.0, 1.0, 1.0]
182 | world.horizon_color = [1.0, 1.0, 1.0]
183 | scene.render.alpha_mode = 'SKY'
184 | world.light_settings.use_environment_light = True
185 | world.light_settings.environment_color = 'PLAIN'
186 | world.light_settings.environment_energy = 0.5
187 |
188 | return camera_target
189 |
190 |
191 | def make_material(name, diffuse, alpha, shadow=False):
192 | """
193 | Creates a material with the given diffuse and alpha. If shadow is true the
194 | object casts and receives shadows.
195 |
196 | :param name: name of material
197 | :param diffuse: diffuse color (in rgb)
198 | :param alpha: alpha (float in [0,1])
199 | :param shadow: whether to cast/receive shadows
200 | :return: material
201 | """
202 |
203 | material = bpy.data.materials.new(name)
204 | material.diffuse_color = diffuse
205 | material.diffuse_shader = 'LAMBERT'
206 | material.diffuse_intensity = 1
207 | material.specular_color = (1, 1, 1)
208 | material.specular_shader = 'COOKTORR'
209 | material.specular_intensity = 2
210 | material.alpha = alpha
211 | material.use_transparency = True
212 | material.ambient = 1.0
213 |
214 | material.use_cast_shadows = shadow
215 | material.use_shadows = shadow
216 |
217 | return material
218 |
219 |
220 | def load_off(off_file, material, offset=(0, 0, 0), scale=1, axes='xyz'):
221 | """
222 | Loads a triangular mesh from an OFF file. For pre-processing, mesh.py can be used;
223 | the function still allows to define an offset (to translate the mesh) and a scale.
224 |
225 | The axes parameter defines the order of the axes. Using xzy, for example, assumes
226 | that the first coordinate is x, the second is z and the third is y.
227 |
228 | **Note that first, the axes are swapper, then the OFF is scaled and finally translated.**
229 |
230 | :param off_file: path to OFF file
231 | :param material: previously defined material
232 | :param offset: offset after scaling
233 | :param scale: scaling
234 | :param axes: axes definition
235 | """
236 |
237 | # This used import_off.py, see README for license.
238 | bpy.ops.import_mesh.off(filepath=off_file)
239 |
240 | assert len(offset) == 3
241 | assert scale > 0
242 | assert len(axes) == 3
243 |
244 | x_index = axes.find('x')
245 | y_index = axes.find('y')
246 | z_index = axes.find('z')
247 |
248 | assert x_index >= 0 and x_index < 3
249 | assert y_index >= 0 and y_index < 3
250 | assert z_index >= 0 and z_index < 3
251 | assert x_index != y_index and x_index != z_index and y_index != z_index
252 |
253 | for obj in bpy.context.scene.objects:
254 |
255 | # obj.name contains the group name of a group of faces, see http://paulbourke.net/dataformats/obj/
256 | # every mesh is of type 'MESH', this works not only for ShapeNet but also for 'simple'
257 | # obj files
258 | if obj.type == 'MESH' and not 'BRC' in obj.name:
259 |
260 | # change color
261 | # this is based on https://stackoverflow.com/questions/4644650/blender-how-do-i-add-a-color-to-an-object
262 | # but needed changing a lot of attributes according to documentation
263 | obj.data.materials.append(material)
264 |
265 | for vertex in obj.data.vertices:
266 | # make a copy, otherwise axes switching does not work
267 | vertex_copy = (vertex.co[0], vertex.co[1], vertex.co[2])
268 |
269 | # First, swap the axes, then scale and offset.
270 | vertex.co[0] = vertex_copy[x_index]
271 | vertex.co[1] = vertex_copy[y_index]
272 | vertex.co[2] = vertex_copy[z_index]
273 |
274 | vertex.co[0] = vertex.co[0] * scale + offset[0]
275 | vertex.co[1] = vertex.co[1] * scale + offset[1]
276 | vertex.co[2] = vertex.co[2] * scale + offset[2]
277 |
278 | obj.name = 'BRC_' + obj.name
279 |
280 |
281 | def load_txt(txt_file, radius, material, offset=(0, 0, 0), scale=1, axes='xyz'):
282 | """
283 | Load a point cloud from txt file, see the documentation for the format.
284 | Additionally, the radius of the points, an offset and a scale can be defined, for details
285 | on the parameters also see load_off.
286 |
287 | :param txt_file: path to TXT file
288 | :param radius: radius of rendered points/spheres
289 | :param material: previously defined material
290 | :param offset: offset
291 | :param scale: scale
292 | :param axes: axes definition
293 | :return:
294 | """
295 |
296 | global sphere_base_mesh
297 |
298 | assert len(offset) == 3
299 | assert scale > 0
300 | assert len(axes) == 3
301 |
302 | x_index = axes.find('x')
303 | y_index = axes.find('y')
304 | z_index = axes.find('z')
305 |
306 | assert x_index >= 0 and x_index < 3
307 | assert y_index >= 0 and y_index < 3
308 | assert z_index >= 0 and z_index < 3
309 | assert x_index != y_index and x_index != z_index and y_index != z_index
310 |
311 | voxel_file = open(txt_file, 'r')
312 | voxel_lines = voxel_file.readlines()
313 | voxel_file.close()
314 |
315 | mesh = bmesh.new()
316 | for line in voxel_lines:
317 | vals = line.split(' ')
318 | if not line.startswith('#') and line.strip() != '' and len(vals) >= 3:
319 | location = (
320 | float(vals[x_index]) * scale + offset[0],
321 | float(vals[y_index]) * scale + offset[1],
322 | float(vals[z_index]) * scale + offset[2]
323 | )
324 |
325 | m = sphere_base_mesh.copy()
326 | for vertex in m.vertices:
327 | vertex.co[0] = vertex.co[0] * radius + location[0]
328 | vertex.co[1] = vertex.co[1] * radius + location[1]
329 | vertex.co[2] = vertex.co[2] * radius + location[2]
330 |
331 | mesh.from_mesh(m)
332 |
333 | mesh2 = bpy.data.meshes.new('Mesh')
334 | mesh.to_mesh(mesh2)
335 |
336 | obj = bpy.data.objects.new('BRC_Point_Cloud', mesh2)
337 | obj.data.materials.append(material)
338 | obj.active_material_index = 0
339 | obj.active_material = material
340 |
341 | bpy.context.scene.objects.link(obj)
342 |
343 |
344 | def load_binvox(binvox_file, radius, material, offset, scale, axes):
345 | """
346 | Load a binvox file, see binvox_rw.py for format. Again, radius of the cubes, material, offset and scale
347 | can be defined as in load_off.
348 |
349 | :param binvox_file: path to binvox file
350 | :param radius: radius, i.e. side length, of cubes
351 | :param material: previously defined material
352 | :param offset: offset
353 | :param scale: scale
354 | :param axes: axes definition
355 | :return:
356 | """
357 |
358 | global cube_base_mesh
359 |
360 | assert len(offset) == 3
361 | assert len(scale) == 3
362 | assert len(axes) == 3
363 |
364 | x_index = axes.find("x")
365 | y_index = axes.find("y")
366 | z_index = axes.find("z")
367 |
368 | assert x_index >= 0 and x_index < 3
369 | assert y_index >= 0 and y_index < 3
370 | assert z_index >= 0 and z_index < 3
371 | assert x_index != y_index and x_index != z_index and y_index != z_index
372 |
373 | with open(binvox_file, 'rb') as f:
374 | model = binvox_rw.read_as_3d_array(f)
375 |
376 | points = np.where(model.data)
377 | locations = np.zeros((points[0].shape[0], 3), dtype=float)
378 | locations[:, 0] = (points[x_index][:] + 0.5) / model.data.shape[x_index]
379 | locations[:, 1] = (points[y_index][:] + 0.5) / model.data.shape[y_index]
380 | locations[:, 2] = (points[z_index][:] + 0.5) / model.data.shape[z_index]
381 | locations[:, 0] -= 0.5
382 | locations[:, 1] -= 0.5
383 | locations[:, 2] -= 0.5
384 |
385 | locations[:, 0] = locations[:, 0] * scale[0] + offset[0]
386 | locations[:, 1] = locations[:, 1] * scale[1] + offset[1]
387 | locations[:, 2] = locations[:, 2] * scale[2] + offset[2]
388 |
389 | mesh = bmesh.new()
390 | for i in range(locations.shape[0]):
391 | m = cube_base_mesh.copy()
392 | for vertex in m.vertices:
393 | vertex.co[0] = vertex.co[0] * radius + locations[i, 0]
394 | vertex.co[1] = vertex.co[1] * radius + locations[i, 1]
395 | vertex.co[2] = vertex.co[2] * radius + locations[i, 2]
396 |
397 | mesh.from_mesh(m)
398 |
399 | mesh2 = bpy.data.meshes.new('Mesh')
400 | mesh.to_mesh(mesh2)
401 |
402 | obj = bpy.data.objects.new('BRC_Occupancy', mesh2)
403 | obj.data.materials.append(material)
404 | obj.active_material_index = 0
405 | obj.active_material = material
406 |
407 | bpy.context.scene.objects.link(obj)
408 |
409 |
410 | def render(camera_target, output_file, rotation, distance):
411 | """
412 | Render all loaded objects into the given object files. Additionally, the
413 | rotation of the camera around the origin and the distance can be defined.
414 |
415 | The first argument is the camera_target returned from initialize().
416 |
417 | :param camera_target: returned by initialize()
418 | :param output_file: path to output file
419 | :param rotation: rotation of camera
420 | :param distance: distance to target
421 | """
422 |
423 | bpy.context.scene.render.filepath = output_file
424 |
425 | camera_target.rotation_euler[0] = math.radians(rotation[0])
426 | camera_target.rotation_euler[1] = math.radians(rotation[1])
427 | camera_target.rotation_euler[2] = math.radians(rotation[2])
428 |
429 | cam = bpy.context.scene.objects['Camera']
430 | cam.location = (0, 3.0 * distance, 1.0 * distance)
431 |
432 | bpy.ops.render.render(animation=False, write_still=True)
433 |
--------------------------------------------------------------------------------
/examples/0.binvox:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davidstutz/bpy-visualization-utils/9aec34f4b58d83992c3c3ce7eb6369d9055760bc/examples/0.binvox
--------------------------------------------------------------------------------
/examples/0.txt:
--------------------------------------------------------------------------------
1 | 597
2 | 9.2312 6.01208 11.3019
3 | 8.41012 6.15688 11.2039
4 | 23.1434 5.82715 10.8433
5 | 8.70879 6.97059 11.2857
6 | 8.32762 7.41969 10.6109
7 | 22.8282 6.3528 11.6698
8 | 9.0674 7.835 11.2977
9 | 8.31103 8.02385 11.1044
10 | 23.2571 7.2552 11.7843
11 | 22.767 7.68042 11.1771
12 | 8.53898 8.80688 11.2816
13 | 8.31705 9.39399 10.383
14 | 22.8261 8.40071 11.6476
15 | 8.89255 9.69058 11.3031
16 | 8.30492 10.0097 10.8776
17 | 8.35123 10.8274 9.60242
18 | 7.42347 10.8874 9.61968
19 | 23.2385 9.31776 11.7839
20 | 22.7593 9.76443 11.1533
21 | 20.708 10.7657 9.84308
22 | 19.4961 10.7727 9.96082
23 | 18.3032 10.7767 10.0809
24 | 17.1305 10.7788 10.2018
25 | 15.9884 10.7862 10.3114
26 | 14.8671 10.7931 10.4196
27 | 13.7647 10.7985 10.5282
28 | 12.684 10.8047 10.633
29 | 11.6156 10.8054 10.7451
30 | 10.5727 10.8104 10.8478
31 | 9.25672 10.5927 11.3191
32 | 8.38344 10.6958 11.2543
33 | 7.56107 10.8301 11.1365
34 | 7.34353 11.4521 10.2108
35 | 23.787 10.3218 11.8
36 | 22.7691 10.4667 11.6775
37 | 22.0173 10.7603 11.3074
38 | 20.7928 10.7666 11.4119
39 | 19.5902 10.7715 11.5163
40 | 18.4103 10.7762 11.6191
41 | 17.2565 10.7831 11.716
42 | 16.1219 10.7882 11.8139
43 | 15.0112 10.7951 11.9068
44 | 13.9202 10.8013 11.9989
45 | 12.8463 10.8055 12.0925
46 | 11.7951 10.8119 12.1807
47 | 10.7595 10.8158 12.2712
48 | 9.74419 10.8209 12.3579
49 | 8.74601 10.8254 12.444
50 | 7.77235 10.8354 12.5196
51 | 7.30195 11.2418 11.9542
52 | 7.3212 12.0624 10.7226
53 | 7.34377 12.9115 9.44814
54 | 24.5944 10.7545 12.6665
55 | 23.3278 10.7575 12.7641
56 | 22.0937 10.7648 12.8526
57 | 20.8816 10.7709 12.9411
58 | 19.6913 10.776 13.0293
59 | 18.5216 10.7798 13.1179
60 | 17.376 10.7847 13.2027
61 | 16.2576 10.7934 13.2798
62 | 15.154 10.7982 13.3615
63 | 14.0728 10.8044 13.4394
64 | 13.008 10.8082 13.5194
65 | 11.9684 10.8163 13.5911
66 | 10.9423 10.8208 13.667
67 | 9.93943 10.829 13.7356
68 | 8.94804 10.8325 13.8102
69 | 7.97176 10.8343 13.8861
70 | 7.267 11.0425 13.6362
71 | 7.28484 11.8466 12.4507
72 | 7.30598 12.6782 11.2244
73 | 7.3304 13.5385 9.95556
74 | 24.6488 10.7622 14.2056
75 | 23.3955 10.7652 14.2867
76 | 22.1711 10.7707 14.3622
77 | 20.9707 10.7765 14.4357
78 | 19.7862 10.7779 14.5144
79 | 18.6333 10.785 14.5826
80 | 17.5061 10.7948 14.6453
81 | 9.02739 5.95223 22.2307
82 | 8.17622 6.01113 22.2035
83 | 14.2231 10.8079 14.8501
84 | 13.1742 10.8163 14.9093
85 | 12.1376 10.8202 14.9744
86 | 11.1221 10.8261 15.0351
87 | 10.1233 10.8311 15.0959
88 | 9.13883 10.8338 15.1591
89 | 8.17962 10.8429 15.2113
90 | 7.31298 10.9136 15.1681
91 | 7.24809 11.6362 14.1269
92 | 7.27094 12.4536 12.9425
93 | 7.29117 13.2938 11.7253
94 | 7.31686 14.1649 10.4629
95 | 22.9044 5.89981 22.7383
96 | 23.4597 10.7715 15.779
97 | 22.2509 10.7788 15.8364
98 | 21.0582 10.782 15.8988
99 | 19.8893 10.786 15.9588
100 | 18.7445 10.7912 16.0157
101 | 17.628 10.801 16.0644
102 | 16.5242 10.8058 16.1196
103 | 8.89927 6.42032 22.7846
104 | 8.12755 6.52781 22.6751
105 | 8.08216 7.13788 21.81
106 | 12.3011 10.8223 16.3333
107 | 11.297 10.8303 16.3784
108 | 10.3065 10.8354 16.4267
109 | 9.3319 10.8396 16.4754
110 | 8.37223 10.8425 16.5253
111 | 7.43748 10.8522 16.5639
112 | 7.21476 11.4341 15.7488
113 | 7.23465 12.234 14.6102
114 | 7.25703 13.0601 13.4341
115 | 7.27837 13.9107 12.2233
116 | 7.30208 14.7899 10.9716
117 | 7.32723 15.6982 9.67835
118 | 22.8335 6.4864 23.2685
119 | 22.518 6.93649 22.6534
120 | 23.521 10.7768 17.241
121 | 22.3221 10.7827 17.2859
122 | 21.1452 10.7884 17.3303
123 | 19.9817 10.7886 17.3816
124 | 18.8614 10.8023 17.4123
125 | 17.746 10.8063 17.4563
126 | 16.6507 10.81 17.4997
127 | 15.5755 10.8139 17.542
128 | 14.5212 10.819 17.5818
129 | 8.42792 7.2873 22.789
130 | 8.07037 7.68552 22.2483
131 | 11.4684 10.8342 17.6961
132 | 10.4867 10.8399 17.7318
133 | 9.52206 10.8459 17.7663
134 | 8.56805 10.8474 17.8066
135 | 7.63593 10.8535 17.8394
136 | 7.1855 11.2407 17.3183
137 | 7.20076 12.0222 16.2252
138 | 7.22154 12.8316 15.0929
139 | 7.2426 13.6657 13.926
140 | 7.26512 14.5268 12.7215
141 | 7.28924 15.4161 11.4773
142 | 7.31191 16.3322 10.1958
143 | 23.4358 7.41995 23.2964
144 | 22.5429 7.57468 23.1133
145 | 22.5266 8.19666 22.2592
146 | 23.585 10.7843 18.6691
147 | 22.3935 10.7875 18.7039
148 | 21.2278 10.7929 18.735
149 | 20.0912 10.8029 18.7587
150 | 18.9582 10.8021 18.7971
151 | 17.8585 10.8094 18.8234
152 | 16.7775 10.8159 18.85
153 | 15.7148 10.8218 18.8768
154 | 14.6702 10.8273 18.9037
155 | 13.644 10.8329 18.93
156 | 8.83798 8.13283 22.798
157 | 8.0863 8.25303 22.6576
158 | 8.08944 8.92855 21.7294
159 | 9.70691 10.8506 19.0353
160 | 8.76422 10.8553 19.06
161 | 7.83295 10.8565 19.0889
162 | 7.152 11.0488 18.8472
163 | 7.16734 11.8163 17.7922
164 | 7.18801 12.6109 16.6998
165 | 7.20883 13.4292 15.5748
166 | 7.23037 14.2729 14.4148
167 | 7.25187 15.1425 13.2194
168 | 7.27495 16.0406 11.9847
169 | 7.29975 16.9688 10.7086
170 | 7.3351 17.9371 9.37706
171 | 22.9676 8.4293 23.2733
172 | 22.5161 8.82294 22.7514
173 | 23.6354 10.7846 20.0777
174 | 22.4563 10.7881 20.0985
175 | 21.3077 10.7968 20.1118
176 | 20.1773 10.8038 20.1269
177 | 19.0542 10.8026 20.1529
178 | 17.9683 10.8121 20.1637
179 | 16.896 10.8179 20.1791
180 | 15.8418 10.8232 20.1949
181 | 14.8117 10.8323 20.2049
182 | 13.7905 10.8353 20.2231
183 | 12.7916 10.8416 20.2363
184 | 11.8073 10.8461 20.2516
185 | 8.36565 9.01627 22.7978
186 | 8.06247 9.4717 22.1893
187 | 8.94875 10.8568 20.2993
188 | 8.02858 10.8612 20.3133
189 | 7.19143 10.9207 20.2511
190 | 7.13468 11.6164 19.3127
191 | 7.15469 12.3961 18.2592
192 | 7.17541 13.1993 17.1739
193 | 7.1965 14.0268 16.0559
194 | 7.21697 14.8787 14.9048
195 | 7.23818 15.7574 13.7176
196 | 7.2592 16.6632 12.4936
197 | 7.28475 17.6021 11.2249
198 | 7.31326 18.5742 9.91148
199 | 22.5684 9.48431 23.1959
200 | 22.5168 10.1142 22.3601
201 | 22.5335 10.7978 21.4522
202 | 21.3973 10.8073 21.4517
203 | 20.2733 10.8118 21.4578
204 | 19.1657 10.8139 21.467
205 | 18.0787 10.8165 21.4752
206 | 17.0197 10.8247 21.4757
207 | 15.971 10.8276 21.4832
208 | 14.9406 10.8304 21.4905
209 | 13.9326 10.8365 21.4932
210 | 12.9448 10.8448 21.4929
211 | 11.9748 10.8539 21.4912
212 | 11.0129 10.8571 21.4972
213 | 8.78228 9.89059 22.8046
214 | 8.07377 10.0436 22.609
215 | 8.09839 10.7694 21.645
216 | 7.36815 10.9074 21.4697
217 | 7.1019 11.4214 20.79
218 | 7.12108 12.1867 19.7737
219 | 7.14096 12.9746 18.7272
220 | 7.16125 13.786 17.6496
221 | 7.18218 14.6222 16.5391
222 | 7.20254 15.4831 15.3958
223 | 7.22525 16.3726 14.2144
224 | 7.24558 17.2875 12.9994
225 | 7.26799 18.2333 11.7432
226 | 7.29455 19.2137 10.4411
227 | 23.087 10.4144 23.2919
228 | 22.4634 10.722 22.8904
229 | 21.4839 10.8169 22.7664
230 | 20.371 10.8219 22.7599
231 | 19.2811 10.8288 22.7509
232 | 18.2077 10.834 22.7441
233 | 17.1467 10.8351 22.7427
234 | 16.1128 10.8419 22.7338
235 | 15.092 10.8456 22.729
236 | 14.0901 10.8503 22.7229
237 | 13.109 10.8577 22.7133
238 | 12.1421 10.8635 22.7056
239 | 11.1864 10.8659 22.7026
240 | 10.2437 10.8661 22.7022
241 | 9.31417 10.8645 22.7043
242 | 8.25343 10.748 22.8565
243 | 7.5299 10.8849 22.6777
244 | 7.08476 11.2442 22.2087
245 | 7.09194 11.9863 21.2399
246 | 7.10932 12.7581 20.2323
247 | 7.1285 13.5538 19.1935
248 | 7.14711 14.3723 18.1249
249 | 7.16842 15.2177 17.0213
250 | 7.18918 16.088 15.8851
251 | 7.21129 16.9864 14.7123
252 | 7.23294 17.9122 13.5036
253 | 7.25542 18.8682 12.2556
254 | 7.27953 19.8565 10.9654
255 | 7.30553 20.879 9.63048
256 | 23.8615 10.8319 24.0727
257 | 22.6969 10.8246 24.0697
258 | 21.6121 10.8522 24.0229
259 | 20.482 10.841 24.0253
260 | 19.407 10.8513 24.0009
261 | 18.3476 10.8597 23.9789
262 | 17.3083 10.8693 23.9557
263 | 16.2879 10.8797 23.9316
264 | 15.2951 10.8971 23.8988
265 | 14.2946 10.8981 23.887
266 | 13.327 10.9103 23.8612
267 | 12.3707 10.9193 23.8396
268 | 11.4309 10.9289 23.8173
269 | 10.5037 10.9366 23.7977
270 | 9.58591 10.9402 23.7835
271 | 8.69525 10.9534 23.7571
272 | 7.81089 10.9605 23.7387
273 | 7.08138 11.0826 23.5742
274 | 7.06533 11.793 22.6622
275 | 7.08103 12.55 21.6907
276 | 7.09727 13.329 20.6909
277 | 7.1141 14.1309 19.6617
278 | 7.1349 14.96 18.5977
279 | 7.15701 15.8149 17.5004
280 | 7.17655 16.6933 16.3732
281 | 7.19732 17.5997 15.2098
282 | 7.21999 18.5363 14.0079
283 | 7.24258 19.5023 12.7681
284 | 7.26455 20.4988 11.4892
285 | 7.28962 21.531 10.1645
286 | 24.1855 11.6612 24.3307
287 | 23.0516 11.6701 24.2958
288 | 21.9391 11.6799 24.2602
289 | 20.8464 11.6901 24.2246
290 | 19.7749 11.7019 24.1875
291 | 18.72 11.7127 24.1519
292 | 17.6826 11.7233 24.1168
293 | 16.6621 11.7337 24.0825
294 | 15.6589 11.7443 24.0482
295 | 14.6701 11.7535 24.016
296 | 13.6981 11.7633 23.9834
297 | 12.7447 11.7752 23.9486
298 | 11.8043 11.7854 23.9161
299 | 10.8785 11.7955 23.8841
300 | 9.96624 11.8047 23.8534
301 | 9.06794 11.8138 23.8232
302 | 8.18465 11.8237 23.7923
303 | 7.31647 11.835 23.76
304 | 7.04952 12.3445 23.1114
305 | 7.06832 13.1119 22.1431
306 | 7.08587 13.9001 21.1486
307 | 7.10327 14.7109 20.1255
308 | 7.12181 15.5465 19.0712
309 | 7.14365 16.41 17.9816
310 | 7.16332 17.2976 16.8617
311 | 7.18458 18.2139 15.7055
312 | 7.20534 19.1582 14.514
313 | 7.22828 20.1346 13.2821
314 | 7.25107 21.1422 12.0107
315 | 7.27554 22.1844 10.6957
316 | 7.33256 23.2973 9.29211
317 | 23.6903 12.696 24.3227
318 | 22.5584 12.6962 24.2875
319 | 21.4476 12.6973 24.2518
320 | 20.3588 12.7004 24.2143
321 | 19.2869 12.7024 24.1787
322 | 18.2346 12.7056 24.1422
323 | 17.1987 12.7079 24.1073
324 | 16.1804 12.7106 24.0724
325 | 15.178 12.7127 24.0388
326 | 14.1929 12.7157 24.0047
327 | 13.2216 12.7171 23.9729
328 | 12.2681 12.7202 23.9396
329 | 11.33 12.7235 23.9065
330 | 10.4051 12.7256 23.8753
331 | 9.49434 12.7276 23.8447
332 | 8.59916 12.7307 23.8132
333 | 7.71692 12.7332 23.7828
334 | 7.04929 12.9089 23.5441
335 | 7.05569 13.6735 22.5952
336 | 7.07323 14.4697 21.6074
337 | 7.0893 15.2876 20.5926
338 | 7.10912 16.133 19.5439
339 | 7.12927 17.0037 18.4636
340 | 7.14997 17.9014 17.3501
341 | 7.17104 18.8269 16.2019
342 | 7.19093 19.7799 15.0195
343 | 7.2141 20.7665 13.7956
344 | 7.23679 21.7843 12.5329
345 | 7.25988 22.8357 11.2286
346 | 7.28439 23.9235 9.87905
347 | 24.345 13.7463 24.349
348 | 23.1944 13.7371 24.3133
349 | 22.0646 13.7286 24.2777
350 | 20.9571 13.7219 24.2406
351 | 19.869 13.7157 24.2038
352 | 18.7998 13.7099 24.1672
353 | 17.7485 13.7043 24.1312
354 | 16.7142 13.6984 24.0962
355 | 15.6966 13.6923 24.0622
356 | 14.697 13.6873 24.0275
357 | 13.7131 13.6821 23.9937
358 | 12.7439 13.6761 23.9615
359 | 11.7913 13.671 23.9289
360 | 10.8533 13.6656 23.8972
361 | 9.93085 13.661 23.8652
362 | 9.02364 13.6572 23.8328
363 | 8.13103 13.654 23.8003
364 | 7.25494 13.6534 23.7653
365 | 7.04136 14.2332 23.0488
366 | 7.05997 15.0385 22.0665
367 | 7.07666 15.8652 21.058
368 | 7.09597 16.7186 20.0169
369 | 7.11566 17.5978 18.9445
370 | 7.1363 18.5044 17.8385
371 | 7.15705 19.439 16.6985
372 | 7.17689 20.4016 15.5242
373 | 7.2006 21.3987 14.3079
374 | 7.22285 22.4263 13.0543
375 | 7.24499 23.4873 11.7601
376 | 23.8474 14.8035 24.3379
377 | 22.6967 14.7839 24.3034
378 | 21.5707 14.7676 24.2661
379 | 20.466 14.7528 24.228
380 | 19.3787 14.7373 24.1917
381 | 18.3109 14.7228 24.1553
382 | 17.2611 14.7086 24.1193
383 | 16.2279 14.694 24.0847
384 | 15.2118 14.6797 24.0507
385 | 14.2134 14.6663 24.0163
386 | 13.2309 14.6529 23.9828
387 | 12.2648 14.6402 23.9493
388 | 11.3135 14.6272 23.917
389 | 10.3775 14.6145 23.8849
390 | 9.45687 14.6026 23.8527
391 | 8.55057 14.5909 23.8211
392 | 7.6614 14.5821 23.7867
393 | 7.02769 14.7931 23.5014
394 | 7.04753 15.6076 22.5245
395 | 7.06574 16.444 21.5211
396 | 7.08326 17.3043 20.4891
397 | 7.10351 18.1928 19.4233
398 | 7.12226 19.1067 18.3271
399 | 7.14198 20.0495 17.1961
400 | 7.16437 21.0244 16.0267
401 | 7.18577 22.0291 14.8215
402 | 7.20938 23.0684 13.5748
403 | 7.23377 24.1425 12.2864
404 | 23.3452 15.8646 24.3283
405 | 22.1977 15.8366 24.2926
406 | 21.0758 15.813 24.2533
407 | 19.9707 15.7878 24.2167
408 | 18.8858 15.764 24.1797
409 | 17.8204 15.7414 24.1426
410 | 16.7712 15.7179 24.1075
411 | 15.7383 15.6938 24.0739
412 | 14.7255 15.6724 24.0385
413 | 13.7285 15.6507 24.0043
414 | 12.7483 15.6298 23.9702
415 | 11.7833 15.6089 23.9371
416 | 10.8333 15.5877 23.9051
417 | 9.89835 15.5669 23.8736
418 | 8.97827 15.5465 23.8426
419 | 8.07472 15.5283 23.8099
420 | 7.19489 15.5194 23.7676
421 | 7.03556 16.1768 22.9818
422 | 7.05306 17.0208 21.986
423 | 7.07146 17.8905 20.96
424 | 7.08959 18.7858 19.9039
425 | 7.10801 19.7083 18.8155
426 | 7.1296 20.6624 17.6901
427 | 7.14961 21.6445 16.5316
428 | 7.17095 22.659 15.3348
429 | 7.19627 23.7105 14.0945
430 | 24.009 16.9707 24.3536
431 | 22.8409 16.9315 24.3183
432 | 21.6989 16.8967 24.2796
433 | 20.5765 16.8623 24.2419
434 | 19.4739 16.8287 24.2045
435 | 18.391 16.7962 24.1673
436 | 17.3266 16.7644 24.1305
437 | 16.2777 16.7312 24.0964
438 | 15.2486 16.7002 24.0611
439 | 14.2368 16.67 24.0262
440 | 13.2422 16.6407 23.9913
441 | 12.262 16.6103 23.9587
442 | 11.298 16.5807 23.9263
443 | 10.3494 16.5515 23.8946
444 | 9.41746 16.5241 23.8619
445 | 8.5007 16.4975 23.8292
446 | 7.60105 16.474 23.7942
447 | 7.02098 16.7431 23.4414
448 | 7.04103 17.598 22.4498
449 | 7.05957 18.4763 21.4308
450 | 7.07608 19.3787 20.3837
451 | 7.09613 20.312 19.301
452 | 7.11643 21.2741 18.1848
453 | 7.13645 22.2658 17.0342
454 | 7.1567 23.289 15.847
455 | 7.18446 24.3536 14.6122
456 | 23.5025 18.0543 24.3421
457 | 22.3372 18.0062 24.3058
458 | 21.1958 17.9611 24.2679
459 | 20.0752 17.9174 24.23
460 | 18.9745 17.8748 24.1924
461 | 17.8942 17.834 24.1543
462 | 16.8288 17.7913 24.1197
463 | 15.7845 17.7516 24.0832
464 | 14.7575 17.7124 24.0474
465 | 13.7465 17.673 24.0132
466 | 12.7512 17.6334 23.9804
467 | 11.773 17.5951 23.9476
468 | 10.811 17.5576 23.915
469 | 9.86606 17.5223 23.8813
470 | 8.9367 17.4879 23.8477
471 | 8.02451 17.4565 23.8121
472 | 7.13172 17.4307 23.7717
473 | 7.03069 18.1764 22.9117
474 | 7.04677 19.0608 21.9023
475 | 7.06359 19.9723 20.862
476 | 7.0824 20.9133 19.7882
477 | 7.10186 21.8838 18.6808
478 | 7.12179 22.885 17.5383
479 | 7.14725 23.924 16.353
480 | 24.1828 19.2059 24.3644
481 | 22.9901 19.1412 24.333
482 | 21.8293 19.0852 24.2945
483 | 20.6898 19.0307 24.2563
484 | 19.5716 18.9781 24.2178
485 | 18.4731 18.9266 24.1797
486 | 17.3916 18.8746 24.1438
487 | 16.3297 18.8241 24.1078
488 | 15.2869 18.7755 24.0714
489 | 14.2606 18.7268 24.0365
490 | 13.2511 18.6787 24.0025
491 | 12.2598 18.6326 23.9678
492 | 11.2835 18.5862 23.9347
493 | 10.3243 18.5417 23.901
494 | 9.38098 18.4982 23.8675
495 | 8.45306 18.4558 23.8341
496 | 7.54671 18.4209 23.7942
497 | 7.01417 18.7483 23.3797
498 | 7.03331 19.6443 22.3741
499 | 7.0497 20.5641 21.3417
500 | 7.06943 21.5151 20.2743
501 | 7.08827 22.4942 19.1753
502 | 7.10885 23.5058 18.04
503 | 7.17718 24.6038 16.8116
504 | 23.6606 20.3047 24.3591
505 | 22.4771 20.2352 24.3223
506 | 21.3185 20.1697 24.2834
507 | 20.1818 20.1063 24.2442
508 | 19.065 20.0439 24.2059
509 | 17.9669 19.9821 24.1688
510 | 16.8885 19.9219 24.1318
511 | 15.8287 19.8627 24.0954
512 | 14.7875 19.8051 24.0591
513 | 13.7638 19.7484 24.0234
514 | 12.757 19.6925 23.9885
515 | 11.7662 19.6369 23.9548
516 | 10.7921 19.5826 23.9212
517 | 9.83455 19.5298 23.8877
518 | 8.89249 19.4778 23.8546
519 | 7.96691 19.4281 23.8206
520 | 7.08611 19.4099 23.7561
521 | 7.02154 20.2291 22.8439
522 | 7.03738 21.1571 21.8192
523 | 7.05659 22.1166 20.7599
524 | 7.07399 23.1035 19.6702
525 | 7.09548 24.1256 18.5419
526 | 24.3537 21.5009 24.3813
527 | 23.1453 21.4163 24.3469
528 | 21.9623 21.3356 24.3108
529 | 20.8064 21.2608 24.2711
530 | 19.671 21.1873 24.2323
531 | 18.5569 21.1162 24.193
532 | 17.4611 21.0454 24.1554
533 | 16.386 20.9771 24.1171
534 | 15.3283 20.9092 24.0802
535 | 14.288 20.8419 24.0446
536 | 13.2647 20.7752 24.01
537 | 12.2586 20.7097 23.976
538 | 11.2696 20.6455 23.9423
539 | 10.2972 20.5827 23.9089
540 | 9.34096 20.5212 23.8757
541 | 8.40113 20.4616 23.8422
542 | 7.47583 20.4024 23.8097
543 | 7.00829 20.812 23.315
544 | 7.02583 21.7505 22.2957
545 | 7.04254 22.7164 21.2466
546 | 7.05968 23.7122 20.1649
547 | 23.8286 22.625 24.3728
548 | 22.6223 22.5299 24.3382
549 | 21.4471 22.4436 24.2976
550 | 20.2936 22.3594 24.2573
551 | 19.1605 22.2765 24.2178
552 | 18.0477 22.1953 24.1789
553 | 16.9543 22.1154 24.1407
554 | 15.879 22.0363 24.1038
555 | 14.821 21.9573 24.0687
556 | 13.7822 21.8806 24.0333
557 | 12.7619 21.806 23.9977
558 | 11.7578 21.7318 23.9635
559 | 10.7703 21.6588 23.93
560 | 9.79963 21.5874 23.8966
561 | 8.8462 21.5185 23.8625
562 | 7.90671 21.4492 23.8304
563 | 7.0531 21.4568 23.7258
564 | 7.0113 22.3404 22.7751
565 | 7.02945 23.3168 21.7319
566 | 7.05142 24.3274 20.6525
567 | 23.3026 23.7564 24.363
568 | 22.1045 23.6558 24.3235
569 | 20.9312 23.5593 24.2827
570 | 19.7781 23.4638 24.2433
571 | 18.6464 23.3707 24.204
572 | 17.5349 23.2793 24.1654
573 | 16.4415 23.1882 24.1286
574 | 15.3683 23.0997 24.0915
575 | 14.3132 23.0124 24.0554
576 | 13.2757 22.9261 24.0203
577 | 12.2556 22.841 23.9861
578 | 11.2543 22.7588 23.9511
579 | 10.2687 22.6771 23.9176
580 | 9.30021 22.5975 23.8838
581 | 8.34758 22.5192 23.8507
582 | 7.41936 22.4518 23.809
583 | 6.99935 22.9327 23.2515
584 | 7.01634 23.9168 22.2169
585 | 18.1335 24.4742 24.1871
586 | 17.0173 24.367 24.1536
587 | 15.9274 24.2677 24.1153
588 | 14.8561 24.1698 24.078
589 | 13.8023 24.0724 24.0424
590 | 12.768 23.9782 24.0062
591 | 11.7484 23.8826 23.9731
592 | 10.7482 23.7908 23.9388
593 | 9.76624 23.7021 23.9035
594 | 8.79924 23.6135 23.8701
595 | 7.85001 23.5284 23.8354
596 | 7.03166 23.5743 23.6817
597 | 7.01943 24.535 22.6845
598 | 7.36098 24.5551 23.8087
599 |
--------------------------------------------------------------------------------
/examples/render_binvox.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | if [ -z "$BLENDER" ]; then
3 | export BLENDER="blender"
4 | fi
5 |
6 | "$BLENDER" --background --python render_binvox.py -- --binvox examples/0.binvox --output examples/0.png
7 |
--------------------------------------------------------------------------------
/examples/render_off.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | if [ -z "$BLENDER" ]; then
3 | export BLENDER="blender"
4 | fi
5 |
6 | "$BLENDER" --background --python render_off.py -- --off examples/0.off --output examples/0.png
7 |
--------------------------------------------------------------------------------
/examples/render_off_txt.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | if [ -z "$BLENDER" ]; then
3 | export BLENDER="blender"
4 | fi
5 |
6 | "$BLENDER" --background --python render_off_txt.py -- --off examples/0.off --txt examples/0.txt --output examples/0.png
7 |
--------------------------------------------------------------------------------
/examples/render_txt.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | if [ -z "$BLENDER" ]; then
3 | export BLENDER="blender"
4 | fi
5 |
6 | "$BLENDER" --background --python render_txt.py -- --txt examples/0.txt --output examples/0.png
--------------------------------------------------------------------------------
/import_off.py:
--------------------------------------------------------------------------------
1 | #####
2 | #
3 | # Copyright 2014 Alex Tsui
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 | #
17 | #####
18 |
19 | #
20 | # http://wiki.blender.org/index.php/Dev:2.5/Py/Scripts/Guidelines/Addons
21 | #
22 | import os
23 | import bpy
24 | import mathutils
25 | from bpy.props import (BoolProperty,
26 | FloatProperty,
27 | StringProperty,
28 | EnumProperty,
29 | )
30 | from bpy_extras.io_utils import (ImportHelper,
31 | ExportHelper,
32 | unpack_list,
33 | unpack_face_list,
34 | axis_conversion,
35 | )
36 |
37 | #if "bpy" in locals():
38 | # import imp
39 | # if "import_off" in
40 |
41 | bl_info = {
42 | "name": "OFF format",
43 | "description": "Import-Export OFF, Import/export simple OFF mesh.",
44 | "author": "Alex Tsui, Mateusz Kłoczko",
45 | "version": (0, 3),
46 | "blender": (2, 74, 0),
47 | "location": "File > Import-Export",
48 | "warning": "", # used for warning icon and text in addons panel
49 | "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.5/Py/"
50 | "Scripts/My_Script",
51 | "category": "Import-Export"}
52 |
53 | class ImportOFF(bpy.types.Operator, ImportHelper):
54 | """Load an OFF Mesh file"""
55 | bl_idname = "import_mesh.off"
56 | bl_label = "Import OFF Mesh"
57 | filename_ext = ".off"
58 | filter_glob = StringProperty(
59 | default="*.off",
60 | options={'HIDDEN'},
61 | )
62 |
63 | axis_forward = EnumProperty(
64 | name="Forward",
65 | items=(('X', "X Forward", ""),
66 | ('Y', "Y Forward", ""),
67 | ('Z', "Z Forward", ""),
68 | ('-X', "-X Forward", ""),
69 | ('-Y', "-Y Forward", ""),
70 | ('-Z', "-Z Forward", ""),
71 | ),
72 | default='Y',
73 | )
74 | axis_up = EnumProperty(
75 | name="Up",
76 | items=(('X', "X Up", ""),
77 | ('Y', "Y Up", ""),
78 | ('Z', "Z Up", ""),
79 | ('-X', "-X Up", ""),
80 | ('-Y', "-Y Up", ""),
81 | ('-Z', "-Z Up", ""),
82 | ),
83 | default='Z',
84 | )
85 |
86 | def execute(self, context):
87 | #from . import import_off
88 |
89 | keywords = self.as_keywords(ignore=('axis_forward',
90 | 'axis_up',
91 | 'filter_glob',
92 | ))
93 | global_matrix = axis_conversion(from_forward=self.axis_forward,
94 | from_up=self.axis_up,
95 | ).to_4x4()
96 |
97 | mesh = load(self, context, **keywords)
98 | if not mesh:
99 | return {'CANCELLED'}
100 |
101 | scene = bpy.context.scene
102 | obj = bpy.data.objects.new(mesh.name, mesh)
103 | scene.objects.link(obj)
104 | scene.objects.active = obj
105 | obj.select = True
106 |
107 | obj.matrix_world = global_matrix
108 |
109 | scene.update()
110 |
111 | return {'FINISHED'}
112 |
113 | class ExportOFF(bpy.types.Operator, ExportHelper):
114 | """Save an OFF Mesh file"""
115 | bl_idname = "export_mesh.off"
116 | bl_label = "Export OFF Mesh"
117 | filter_glob = StringProperty(
118 | default="*.off",
119 | options={'HIDDEN'},
120 | )
121 | check_extension = True
122 | filename_ext = ".off"
123 |
124 | axis_forward = EnumProperty(
125 | name="Forward",
126 | items=(('X', "X Forward", ""),
127 | ('Y', "Y Forward", ""),
128 | ('Z', "Z Forward", ""),
129 | ('-X', "-X Forward", ""),
130 | ('-Y', "-Y Forward", ""),
131 | ('-Z', "-Z Forward", ""),
132 | ),
133 | default='Y',
134 | )
135 | axis_up = EnumProperty(
136 | name="Up",
137 | items=(('X', "X Up", ""),
138 | ('Y', "Y Up", ""),
139 | ('Z', "Z Up", ""),
140 | ('-X', "-X Up", ""),
141 | ('-Y', "-Y Up", ""),
142 | ('-Z', "-Z Up", ""),
143 | ),
144 | default='Z',
145 | )
146 | use_colors = BoolProperty(
147 | name="Vertex Colors",
148 | description="Export the active vertex color layer",
149 | default=False,
150 | )
151 |
152 | def execute(self, context):
153 | keywords = self.as_keywords(ignore=('axis_forward',
154 | 'axis_up',
155 | 'filter_glob',
156 | 'check_existing',
157 | ))
158 | global_matrix = axis_conversion(to_forward=self.axis_forward,
159 | to_up=self.axis_up,
160 | ).to_4x4()
161 | keywords['global_matrix'] = global_matrix
162 | return save(self, context, **keywords)
163 |
164 | def menu_func_import(self, context):
165 | self.layout.operator(ImportOFF.bl_idname, text="OFF Mesh (.off)")
166 |
167 | def menu_func_export(self, context):
168 | self.layout.operator(ExportOFF.bl_idname, text="OFF Mesh (.off)")
169 |
170 | def register():
171 | bpy.utils.register_module(__name__)
172 | bpy.types.INFO_MT_file_import.append(menu_func_import)
173 | bpy.types.INFO_MT_file_export.append(menu_func_export)
174 |
175 | def unregister():
176 | bpy.utils.unregister_module(__name__)
177 | bpy.types.INFO_MT_file_import.remove(menu_func_import)
178 | bpy.types.INFO_MT_file_export.remove(menu_func_export)
179 |
180 | def load(operator, context, filepath):
181 | # Parse mesh from OFF file
182 | # TODO: Add support for NOFF and COFF
183 | filepath = os.fsencode(filepath)
184 | file = open(filepath, 'r')
185 | first_line = file.readline().rstrip()
186 | use_colors = (first_line == 'COFF')
187 | colors = []
188 | vcount, fcount, ecount = [int(x) for x in file.readline().split()]
189 | verts = []
190 | facets = []
191 | edges = []
192 | i=0;
193 | while i 3:
220 | facets.append(tuple(ids[1:]))
221 | elif len(ids) == 3:
222 | edges.append(tuple(ids[1:]))
223 | except ValueError:
224 | i=i+1
225 | continue
226 | i=i+1
227 |
228 | # Assemble mesh
229 | off_name = bpy.path.display_name_from_filepath(filepath)
230 | mesh = bpy.data.meshes.new(name=off_name)
231 | mesh.from_pydata(verts,edges,facets)
232 | # mesh.vertices.add(len(verts))
233 | # mesh.vertices.foreach_set("co", unpack_list(verts))
234 |
235 | # mesh.faces.add(len(facets))
236 | # mesh.faces.foreach_set("vertices", unpack_face_list(facets))
237 |
238 | mesh.validate()
239 | mesh.update()
240 |
241 | if use_colors:
242 | color_data = mesh.vertex_colors.new()
243 | for i, facet in enumerate(mesh.polygons):
244 | for j, vidx in enumerate(facet.vertices):
245 | color_data.data[3*i + j].color = colors[vidx]
246 |
247 | return mesh
248 |
249 | def save(operator, context, filepath,
250 | global_matrix = None,
251 | use_colors = False):
252 | # Export the selected mesh
253 | APPLY_MODIFIERS = True # TODO: Make this configurable
254 | if global_matrix is None:
255 | global_matrix = mathutils.Matrix()
256 | scene = context.scene
257 | obj = scene.objects.active
258 | mesh = obj.to_mesh(scene, APPLY_MODIFIERS, 'PREVIEW')
259 |
260 | # Apply the inverse transformation
261 | obj_mat = obj.matrix_world
262 | mesh.transform(global_matrix * obj_mat)
263 |
264 | verts = mesh.vertices[:]
265 | facets = [ f for f in mesh.tessfaces ]
266 | # Collect colors by vertex id
267 | colors = False
268 | vertex_colors = None
269 | if use_colors:
270 | colors = mesh.tessface_vertex_colors.active
271 | if colors:
272 | colors = colors.data
273 | vertex_colors = {}
274 | for i, facet in enumerate(mesh.tessfaces):
275 | color = colors[i]
276 | color = color.color1[:], color.color2[:], color.color3[:], color.color4[:]
277 | for j, vidx in enumerate(facet.vertices):
278 | if vidx not in vertex_colors:
279 | vertex_colors[vidx] = (int(color[j][0] * 255.0),
280 | int(color[j][1] * 255.0),
281 | int(color[j][2] * 255.0))
282 | else:
283 | use_colors = False
284 |
285 | # Write geometry to file
286 | filepath = os.fsencode(filepath)
287 | fp = open(filepath, 'w')
288 |
289 | if use_colors:
290 | fp.write('COFF\n')
291 | else:
292 | fp.write('OFF\n')
293 |
294 | fp.write('%d %d 0\n' % (len(verts), len(facets)))
295 |
296 | for i, vert in enumerate(mesh.vertices):
297 | fp.write('%.16f %.16f %.16f' % vert.co[:])
298 | if use_colors:
299 | fp.write(' %d %d %d 255' % vertex_colors[i])
300 | fp.write('\n')
301 |
302 | #for facet in facets:
303 | for i, facet in enumerate(mesh.tessfaces):
304 | fp.write('%d' % len(facet.vertices))
305 | for vid in facet.vertices:
306 | fp.write(' %d' % vid)
307 | fp.write('\n')
308 |
309 | fp.close()
310 |
311 | return {'FINISHED'}
312 |
--------------------------------------------------------------------------------
/mesh.py:
--------------------------------------------------------------------------------
1 | import os
2 | import numpy as np
3 |
4 |
5 | def write_off(file, vertices, faces):
6 | """
7 | Writes the given vertices and faces to OFF.
8 |
9 | :param vertices: vertices as tuples of (x, y, z) coordinates
10 | :type vertices: [(float)]
11 | :param faces: faces as tuples of (num_vertices, vertex_id_1, vertex_id_2, ...)
12 | :type faces: [(int)]
13 | """
14 |
15 | num_vertices = len(vertices)
16 | num_faces = len(faces)
17 |
18 | assert num_vertices > 0
19 | assert num_faces > 0
20 |
21 | with open(file, 'w') as fp:
22 | fp.write('OFF\n')
23 | fp.write(str(num_vertices) + ' ' + str(num_faces) + ' 0\n')
24 |
25 | for vertex in vertices:
26 | assert len(vertex) == 3, 'invalid vertex with %d dimensions found (%s)' % (len(vertex), file)
27 | fp.write(str(vertex[0]) + ' ' + str(vertex[1]) + ' ' + str(vertex[2]) + '\n')
28 |
29 | for face in faces:
30 | assert face[0] == 3, 'only triangular faces supported (%s)' % file
31 | assert len(face) == 4, 'faces need to have 3 vertices, but found %d (%s)' % (len(face), file)
32 |
33 | for i in range(len(face)):
34 | assert face[i] >= 0 and face[i] < num_vertices, 'invalid vertex index %d (of %d vertices) (%s)' % (face[i], num_vertices, file)
35 |
36 | fp.write(str(face[i]))
37 | if i < len(face) - 1:
38 | fp.write(' ')
39 |
40 | fp.write('\n')
41 |
42 | # add empty line to be sure
43 | fp.write('\n')
44 |
45 | def read_off(file):
46 | """
47 | Reads vertices and faces from an off file.
48 |
49 | :param file: path to file to read
50 | :type file: str
51 | :return: vertices and faces as lists of tuples
52 | :rtype: [(float)], [(int)]
53 | """
54 |
55 | assert os.path.exists(file), 'file %s not found' % file
56 |
57 | with open(file, 'r') as fp:
58 | lines = fp.readlines()
59 | lines = [line.strip() for line in lines]
60 |
61 | # Fix for ModelNet bug were 'OFF' and the number of vertices and faces are
62 | # all in the first line.
63 | if len(lines[0]) > 3:
64 | assert lines[0][:3] == 'OFF' or lines[0][:3] == 'off', 'invalid OFF file %s' % file
65 |
66 | parts = lines[0][3:].split(' ')
67 | assert len(parts) == 3
68 |
69 | num_vertices = int(parts[0])
70 | assert num_vertices > 0
71 |
72 | num_faces = int(parts[1])
73 | assert num_faces > 0
74 |
75 | start_index = 1
76 | # This is the regular case!
77 | else:
78 | assert lines[0] == 'OFF' or lines[0] == 'off', 'invalid OFF file %s' % file
79 |
80 | parts = lines[1].split(' ')
81 | assert len(parts) == 3
82 |
83 | num_vertices = int(parts[0])
84 | assert num_vertices > 0
85 |
86 | num_faces = int(parts[1])
87 | assert num_faces > 0
88 |
89 | start_index = 2
90 |
91 | vertices = []
92 | for i in range(num_vertices):
93 | vertex = lines[start_index + i].split(' ')
94 | vertex = [float(point.strip()) for point in vertex if point != '']
95 | assert len(vertex) == 3
96 |
97 | vertices.append(vertex)
98 |
99 | faces = []
100 | for i in range(num_faces):
101 | face = lines[start_index + num_vertices + i].split(' ')
102 | face = [index.strip() for index in face if index != '']
103 |
104 | # check to be sure
105 | for index in face:
106 | assert index != '', 'found empty vertex index: %s (%s)' % (lines[start_index + num_vertices + i], file)
107 |
108 | face = [int(index) for index in face]
109 |
110 | assert face[0] == len(face) - 1, 'face should have %d vertices but as %d (%s)' % (face[0], len(face) - 1, file)
111 | assert face[0] == 3, 'only triangular meshes supported (%s)' % file
112 | for index in face:
113 | assert index >= 0 and index < num_vertices, 'vertex %d (of %d vertices) does not exist (%s)' % (index, num_vertices, file)
114 |
115 | assert len(face) > 1
116 |
117 | faces.append(face)
118 |
119 | return vertices, faces
120 |
121 | assert False, 'could not open %s' % file
122 |
123 | def write_obj(file, vertices, faces):
124 | """
125 | Writes the given vertices and faces to OBJ.
126 |
127 | :param vertices: vertices as tuples of (x, y, z) coordinates
128 | :type vertices: [(float)]
129 | :param faces: faces as tuples of (num_vertices, vertex_id_1, vertex_id_2, ...)
130 | :type faces: [(int)]
131 | """
132 |
133 | num_vertices = len(vertices)
134 | num_faces = len(faces)
135 |
136 | assert num_vertices > 0
137 | assert num_faces > 0
138 |
139 | with open(file, 'w') as fp:
140 | for vertex in vertices:
141 | assert len(vertex) == 3, 'invalid vertex with %d dimensions found (%s)' % (len(vertex), file)
142 | fp.write('v' + ' ' + str(vertex[0]) + ' ' + str(vertex[1]) + ' ' + str(vertex[2]) + '\n')
143 |
144 | for face in faces:
145 | assert len(face) == 3, 'only triangular faces supported (%s)' % file
146 | fp.write('f ')
147 |
148 | for i in range(len(face)):
149 | assert face[i] >= 0 and face[i] < num_vertices, 'invalid vertex index %d (of %d vertices) (%s)' % (face[i], num_vertices, file)
150 |
151 | # face indices are 1-based
152 | fp.write(str(face[i] + 1))
153 | if i < len(face) - 1:
154 | fp.write(' ')
155 |
156 | fp.write('\n')
157 |
158 | # add empty line to be sure
159 | fp.write('\n')
160 |
161 | def read_obj(file):
162 | """
163 | Reads vertices and faces from an obj file.
164 |
165 | :param file: path to file to read
166 | :type file: str
167 | :return: vertices and faces as lists of tuples
168 | :rtype: [(float)], [(int)]
169 | """
170 |
171 | assert os.path.exists(file), 'file %s not found' % file
172 |
173 | with open(file, 'r') as fp:
174 | lines = fp.readlines()
175 | lines = [line.strip() for line in lines if line.strip()]
176 |
177 | vertices = []
178 | faces = []
179 | for line in lines:
180 | parts = line.split(' ')
181 | parts = [part.strip() for part in parts if part]
182 |
183 | if parts[0] == 'v':
184 | assert len(parts) == 4, \
185 | 'vertex should be of the form v x y z, but found %d parts instead (%s)' % (len(parts), file)
186 | assert parts[1] != '', 'vertex x coordinate is empty (%s)' % file
187 | assert parts[2] != '', 'vertex y coordinate is empty (%s)' % file
188 | assert parts[3] != '', 'vertex z coordinate is empty (%s)' % file
189 |
190 | vertices.append([float(parts[1]), float(parts[2]), float(parts[3])])
191 | elif parts[0] == 'f':
192 | assert len(parts) == 4, \
193 | 'face should be of the form f v1/vt1/vn1 v2/vt2/vn2 v2/vt2/vn2, but found %d parts (%s) instead (%s)' % (len(parts), line, file)
194 |
195 | components = parts[1].split('/')
196 | assert len(components) >= 1 and len(components) <= 3, \
197 | 'face component should have the forms v, v/vt or v/vt/vn, but found %d components instead (%s)' % (len(components), file)
198 | assert components[0].strip() != '', \
199 | 'face component is empty (%s)' % file
200 | v1 = int(components[0])
201 |
202 | components = parts[2].split('/')
203 | assert len(components) >= 1 and len(components) <= 3, \
204 | 'face component should have the forms v, v/vt or v/vt/vn, but found %d components instead (%s)' % (len(components), file)
205 | assert components[0].strip() != '', \
206 | 'face component is empty (%s)' % file
207 | v2 = int(components[0])
208 |
209 | components = parts[3].split('/')
210 | assert len(components) >= 1 and len(components) <= 3, \
211 | 'face component should have the forms v, v/vt or v/vt/vn, but found %d components instead (%s)' % (len(components), file)
212 | assert components[0].strip() != '', \
213 | 'face component is empty (%s)' % file
214 | v3 = int(components[0])
215 |
216 | #assert v1 != v2 and v2 != v3 and v3 != v2, 'degenerate face detected: %d %d %d (%s)' % (v1, v2, v3, file)
217 | if v1 == v2 or v2 == v3 or v1 == v3:
218 | print('[Info] skipping degenerate face in %s' % file)
219 | else:
220 | faces.append([v1 - 1, v2 - 1, v3 - 1]) # indices are 1-based!
221 | else:
222 | assert False, 'expected either vertex or face but got line: %s (%s)' % (line, file)
223 |
224 | return vertices, faces
225 |
226 | assert False, 'could not open %s' % file
227 |
228 | def write_ply(file, vertices, faces):
229 | """
230 | Writes the given vertices and faces to PLY.
231 |
232 | :param vertices: vertices as tuples of (x, y, z) coordinates
233 | :type vertices: [(float)]
234 | :param faces: faces as tuples of (num_vertices, vertex_id_1, vertex_id_2, ...)
235 | :type faces: [(int)]
236 | """
237 |
238 | num_vertices = len(vertices)
239 | num_faces = len(faces)
240 |
241 | assert num_vertices > 0
242 | assert num_faces > 0
243 |
244 | with open(file, 'w') as fp:
245 | fp.write('ply\n')
246 | fp.write('format ascii 1.0\n')
247 | fp.write('element vertex ' + str(len(vertices)))
248 | fp.write('property float x')
249 | fp.write('property float y')
250 | fp.write('property float z')
251 | fp.write('element face ' + str(len(faces)))
252 | fp.write('property list uchar int vertex_indices')
253 | fp.write('end_header')
254 |
255 | for vertex in vertices:
256 | assert len(vertex) == 3, 'invalid vertex with %d dimensions found (%s)' % (len(vertex), file)
257 | fp.write(str(vertex[0]) + ' ' + str(vertex[1]) + ' ' + str(vertex[2]) + '\n')
258 |
259 | for face in faces:
260 | assert len(face) == 3, 'only triangular faces supported (%s)' % file
261 | fp.write('3 ')
262 |
263 | for i in range(len(face)):
264 | assert face[i] >= 0 and face[i] < num_vertices, 'invalid vertex index %d (of %d vertices) (%s)' % (face[i], num_vertices, file)
265 |
266 | # face indices are 0-based
267 | fp.write(str(face[i]))
268 | if i < len(face) - 1:
269 | fp.write(' ')
270 |
271 | fp.write('\n')
272 |
273 | # add empty line to be sure
274 | fp.write('\n')
275 |
276 | class Mesh:
277 | """
278 | Represents a mesh.
279 | """
280 |
281 | def __init__(self, vertices = [[]], faces = [[]]):
282 | """
283 | Construct a mesh from vertices and faces.
284 |
285 | :param vertices: list of vertices, or numpy array
286 | :type vertices: [[float]] or numpy.ndarray
287 | :param faces: list of faces or numpy array, i.e. the indices of the corresponding vertices per triangular face
288 | :type faces: [[int]] fo rnumpy.ndarray
289 | """
290 |
291 | self.vertices = np.array(vertices, dtype = float)
292 | """ (numpy.ndarray) Vertices. """
293 |
294 | self.faces = np.array(faces, dtype = int)
295 | """ (numpy.ndarray) Faces. """
296 |
297 | assert self.vertices.shape[1] == 3
298 | assert self.faces.shape[1] == 3
299 |
300 | def extents(self):
301 | """
302 | Get the extents.
303 |
304 | :return: (min_x, min_y, min_z), (max_x, max_y, max_z)
305 | :rtype: (float, float, float), (float, float, float)
306 | """
307 |
308 | min = [0]*3
309 | max = [0]*3
310 |
311 | for i in range(3):
312 | min[i] = np.min(self.vertices[:, i])
313 | max[i] = np.max(self.vertices[:, i])
314 |
315 | return tuple(min), tuple(max)
316 |
317 | def switch_axes(self, axis_1, axis_2):
318 | """
319 | Switch the two axes, this is usually useful for switching y and z axes.
320 |
321 | :param axis_1: index of first axis
322 | :type axis_1: int
323 | :param axis_2: index of second axis
324 | :type axis_2: int
325 | """
326 |
327 | temp = np.copy(self.vertices[:, axis_1])
328 | self.vertices[:, axis_1] = self.vertices[:, axis_2]
329 | self.vertices[:, axis_2] = temp
330 |
331 | def mirror(self, axis):
332 | """
333 | Mirror given axis.
334 |
335 | :param axis: axis to mirror
336 | :type axis: int
337 | """
338 |
339 | self.vertices[:, axis] *= -1
340 |
341 | def scale(self, scales):
342 | """
343 | Scale the mesh in all dimensions.
344 |
345 | :param scales: tuple of length 3 with scale for (x, y, z)
346 | :type scales: (float, float, float)
347 | """
348 |
349 | assert len(scales) == 3
350 |
351 | for i in range(3):
352 | self.vertices[:, i] *= scales[i]
353 |
354 | def translate(self, translation):
355 | """
356 | Translate the mesh.
357 |
358 | :param translation: translation as (x, y, z)
359 | :type translation: (float, float, float)
360 | """
361 |
362 | assert len(translation) == 3
363 |
364 | for i in range(3):
365 | self.vertices[:, i] += translation[i]
366 |
367 | @staticmethod
368 | def from_off(filepath):
369 | """
370 | Read a mesh from OFF.
371 |
372 | :param filepath: path to OFF file
373 | :type filepath: str
374 | :return: mesh
375 | :rtype: Mesh
376 | """
377 |
378 | vertices, faces = read_off(filepath)
379 |
380 | real_faces = []
381 | for face in faces:
382 | assert len(face) == 4
383 | real_faces.append([face[1], face[2], face[3]])
384 |
385 | return Mesh(vertices, real_faces)
386 |
387 | def to_off(self, filepath):
388 | """
389 | Write mesh to OFF.
390 |
391 | :param filepath: path to write file to
392 | :type filepath: str
393 | """
394 |
395 | faces = np.ones((self.faces.shape[0], 4), dtype = int)*3
396 | faces[:, 1:4] = self.faces[:, :]
397 |
398 | write_off(filepath, self.vertices.tolist(), faces.tolist())
399 |
400 | @staticmethod
401 | def from_obj(filepath):
402 | """
403 | Read a mesh from OBJ.
404 |
405 | :param filepath: path to OFF file
406 | :type filepath: str
407 | :return: mesh
408 | :rtype: Mesh
409 | """
410 |
411 | vertices, faces = read_obj(filepath)
412 | return Mesh(vertices, faces)
413 |
414 | def to_obj(self, filepath):
415 | """
416 | Write mesh to OBJ file.
417 |
418 | :param filepath: path to OBJ file
419 | :type filepath: str
420 | """
421 |
422 | write_obj(filepath, self.vertices.tolist(), self.faces.tolist())
--------------------------------------------------------------------------------
/obj_to_off.py:
--------------------------------------------------------------------------------
1 | import os
2 | import argparse
3 | from mesh import Mesh
4 |
5 |
6 | def main():
7 | """
8 | Convert OBJ to OFF.
9 | """
10 |
11 | parser = argparse.ArgumentParser(description='Convert OBJ to OFF.')
12 | parser.add_argument('input', type=str, help='OBJ file.')
13 | parser.add_argument('output', type=str, help='OFF file.')
14 |
15 | args = parser.parse_args()
16 | if not os.path.exists(args.input):
17 | print('Input file does not exist.')
18 | exit(1)
19 |
20 | mesh = Mesh.from_obj(args.input)
21 | print('Read %s.' % args.input)
22 |
23 | mesh.to_off(args.output)
24 | print('Wrote %s.' % args.output)
25 |
26 |
27 | if __name__ == '__main__':
28 | main()
29 |
--------------------------------------------------------------------------------
/off_to_obj.py:
--------------------------------------------------------------------------------
1 | import os
2 | import argparse
3 | from mesh import Mesh
4 |
5 |
6 | def main():
7 | """
8 | Convert OFF to OBJ.
9 | """
10 |
11 | parser = argparse.ArgumentParser(description='Convert OFF to OBJ.')
12 | parser.add_argument('input', type=str, help='OFF file.')
13 | parser.add_argument('output', type=str, help='OBJ file.')
14 |
15 | args = parser.parse_args()
16 | if not os.path.exists(args.input):
17 | print('Input file does not exist.')
18 | exit(1)
19 |
20 | mesh = Mesh.from_off(args.input)
21 | print('Read %s.' % args.input)
22 |
23 | mesh.to_obj(args.output)
24 | print('Wrote %s.' % args.output)
25 |
26 |
27 | if __name__ == '__main__':
28 | main()
29 |
--------------------------------------------------------------------------------
/point_cloud.py:
--------------------------------------------------------------------------------
1 | import os
2 | import numpy as np
3 |
4 |
5 | class PointCloud:
6 | """
7 | Encapsulates a set of points stored as N x 3 matrix.
8 | """
9 |
10 | def __init__(self, points=None):
11 | """
12 | Constructor.
13 |
14 | :param points: points as list or np.array
15 | """
16 |
17 | if points is None:
18 | self.points = np.zeros((0, 3))
19 | else:
20 | self.points = np.array(points)
21 |
22 | @staticmethod
23 | def from_txt(filepath):
24 | """
25 | Load from TXT file.
26 |
27 | :param filepath: path to TXT file.
28 | :return: point cloud
29 | """
30 |
31 | assert os.path.exists(filepath)
32 |
33 | with open(filepath, 'r') as f:
34 | lines = f.readlines()
35 | lines = [line.strip() for line in lines if line.strip() != '']
36 |
37 | num_points = int(lines[0])
38 | points = np.zeros((num_points, 3))
39 | assert num_points == len(lines) - 1
40 |
41 | for i in range(0, num_points):
42 | line = lines[i + 1]
43 |
44 | parts = line.split(' ')
45 | assert len(parts) == 3, "invalid line: %s" % line
46 |
47 | for j in range(3):
48 | points[i, j] = float(parts[j])
49 |
50 | return PointCloud(points)
51 |
52 | def to_txt(self, filepath):
53 | """
54 | Write TXT.
55 |
56 | :param filepath: path to output file
57 | """
58 | with open(filepath, 'w') as f:
59 | f.write(str(self.points.shape[0]) + '\n')
60 |
61 | for n in range(self.points.shape[0]):
62 | f.write(str(self.points[n, 0]) + ' ' + str(self.points[n, 1]) + ' ' + str(self.points[n, 2]) + '\n')
63 |
64 | def to_ply(self, filepath):
65 | """
66 | To PLY file.
67 |
68 | :param filepath: path to output file
69 | """
70 |
71 | with open(filepath, 'w') as f:
72 | f.write('ply\n')
73 | f.write('format ascii 1.0\n')
74 | #f.write('format binary_little_endian 1.0\n')
75 | #f.write('format binary_big_endian 1.0\n')
76 | f.write('element vertex ' + str(self.points.shape[0]) + '\n')
77 | f.write('property float x\n')
78 | f.write('property float y\n')
79 | f.write('property float z\n')
80 | f.write('property uchar red\n')
81 | f.write('property uchar green\n')
82 | f.write('property uchar blue\n')
83 | f.write('end_header\n')
84 |
85 | for n in range(self.points.shape[0]):
86 | f.write(str(self.points[n, 0]) + ' ' + str(self.points[n, 1]) + ' ' + str(self.points[n, 2]))
87 | f.write(' 0 0 0\n')
--------------------------------------------------------------------------------
/render_binvox.py:
--------------------------------------------------------------------------------
1 | import os
2 | import argparse
3 | from blender_utils import *
4 |
5 |
6 | def main():
7 | """
8 | Main function for rendering a specific binvox file.
9 | """
10 |
11 | parser = argparse.ArgumentParser(description='Renders an occupancy grid (BINVOX file).')
12 | parser.add_argument('--binvox', type=str, help='Path to OFF file.')
13 | parser.add_argument('--output', type=str, default='output.png', help='Path to output PNG image.')
14 |
15 | try:
16 | argv = sys.argv[sys.argv.index("--") + 1:]
17 | except ValueError:
18 | argv = ""
19 | args = parser.parse_args(argv)
20 |
21 | if not os.path.exists(args.binvox):
22 | log('BINVOX file not found.', LogLevel.ERROR)
23 | exit()
24 |
25 | camera_target = initialize()
26 | binvox_material = make_material('BRC_Material_Occupancy', (0.66, 0.45, 0.23), 0.8, True)
27 |
28 | load_binvox(args.binvox, 0.0125, binvox_material, (0, 0, 0), (1, 1, 1), 'zxy')
29 |
30 | rotation = (5, 0, -55)
31 | distance = 0.5
32 | render(camera_target, args.output, rotation, distance)
33 |
34 |
35 | if __name__ == '__main__':
36 | main()
37 |
--------------------------------------------------------------------------------
/render_off.py:
--------------------------------------------------------------------------------
1 | import os
2 | import argparse
3 | from blender_utils import *
4 |
5 |
6 | def main():
7 | """
8 | Render a mesh from an OFF file.
9 | """
10 |
11 | parser = argparse.ArgumentParser(description='Renders a mesh (OFF file).')
12 | parser.add_argument('--off', type=str, help='Path to OFF file.')
13 | parser.add_argument('--output', type=str, default='output.png', help='Path to output PNG image.')
14 |
15 | try:
16 | argv = sys.argv[sys.argv.index("--") + 1:]
17 | except ValueError:
18 | argv = ""
19 | args = parser.parse_args(argv)
20 |
21 | if not os.path.exists(args.off):
22 | log('OFF file not found.', LogLevel.ERROR)
23 | exit()
24 |
25 | camera_target = initialize()
26 | off_material = make_material('BRC_Material_Mesh', (0.66, 0.45, 0.23), 0.8, True)
27 |
28 | load_off(args.off, off_material, (-0.5, -0.5, -0.5), 0.03125, 'xzy')
29 |
30 | rotation = (5, 0, -55)
31 | distance = 0.5
32 | render(camera_target, args.output, rotation, distance)
33 |
34 |
35 | if __name__ == '__main__':
36 | main()
37 |
--------------------------------------------------------------------------------
/render_off_txt.py:
--------------------------------------------------------------------------------
1 | import os
2 | import argparse
3 | from blender_utils import *
4 |
5 |
6 | def main():
7 | """
8 | Render a mesh and a point cloud together.
9 | """
10 |
11 | parser = argparse.ArgumentParser(description='Renders a mesh (OFF file) and a point cloud (TXT file) together.')
12 | parser.add_argument('--off', type=str, help='Path to OFF file.')
13 | parser.add_argument('--txt', type=str, help='Path to TXT file.')
14 | parser.add_argument('--output', type=str, default='output.png', help='Path to output PNG image.')
15 |
16 | try:
17 | argv = sys.argv[sys.argv.index("--") + 1:]
18 | except ValueError:
19 | argv = ""
20 | args = parser.parse_args(argv)
21 |
22 | if not os.path.exists(args.off):
23 | log('OFF file not found.', LogLevel.ERROR)
24 | exit()
25 | if not os.path.exists(args.txt):
26 | log('TXT file not found.', LogLevel.ERROR)
27 | exit()
28 |
29 | camera_target = initialize()
30 | off_material = make_material('BRC_Material_Mesh', (0.66, 0.45, 0.23), 0.8, True)
31 | txt_material = make_material('BRC_Material_Point_Cloud', (0.65, 0.23, 0.25), 1, True)
32 |
33 | load_off(args.off, off_material, (-0.5, -0.5, -0.5), 0.03125, 'xzy')
34 | load_txt(args.txt, 0.0075, txt_material, (-0.5, -0.5, -0.5), 0.03125, 'xzy')
35 |
36 | rotation = (5, 0, -55)
37 | distance = 0.5
38 | render(camera_target, args.output, rotation, distance)
39 |
40 |
41 | if __name__ == '__main__':
42 | main()
43 |
--------------------------------------------------------------------------------
/render_txt.py:
--------------------------------------------------------------------------------
1 | import os
2 | import argparse
3 | from blender_utils import *
4 |
5 |
6 | def main():
7 | """
8 | Render a point cloud from a TXT file.
9 | """
10 |
11 | parser = argparse.ArgumentParser(description='Renders a mesh (OFF file) and a point cloud (TXT file) together.')
12 | parser.add_argument('--txt', type=str, help='Path to TXT file.')
13 | parser.add_argument('--output', type=str, default='output.png', help='Path to output PNG image.')
14 |
15 | try:
16 | argv = sys.argv[sys.argv.index("--") + 1:]
17 | except ValueError:
18 | argv = ""
19 | args = parser.parse_args(argv)
20 |
21 | if not os.path.exists(args.txt):
22 | log('TXT file not found.', LogLevel.ERROR)
23 | exit()
24 |
25 | camera_target = initialize()
26 | txt_material = make_material('BRC_Material_Point_Cloud', (0.65, 0.23, 0.25), 1, True)
27 | load_txt(args.txt, 0.0075, txt_material, (-0.5, -0.5, -0.5), 0.03125, 'xzy')
28 |
29 | rotation = (5, 0, -55)
30 | distance = 0.5
31 | render(camera_target, args.output, rotation, distance)
32 |
33 |
34 | if __name__ == '__main__':
35 | main()
36 |
--------------------------------------------------------------------------------
/screenshot.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davidstutz/bpy-visualization-utils/9aec34f4b58d83992c3c3ce7eb6369d9055760bc/screenshot.jpg
--------------------------------------------------------------------------------
/txt_to_ply.py:
--------------------------------------------------------------------------------
1 | import os
2 | import argparse
3 | from point_cloud import PointCloud
4 |
5 |
6 | def main():
7 | """
8 | Convert TXT to PLY.
9 | """
10 |
11 | parser = argparse.ArgumentParser(description='Convert TXT to PLY.')
12 | parser.add_argument('input', type=str, help='TXT file.')
13 | parser.add_argument('output', type=str, help='PLY file.')
14 |
15 | args = parser.parse_args()
16 | if not os.path.exists(args.input):
17 | print('Input file does not exist.')
18 | exit(1)
19 |
20 | point_cloud = PointCloud.from_txt(args.input)
21 | print('Read %s.' % args.input)
22 |
23 | point_cloud.to_ply(args.output)
24 | print('Wrote %s.' % args.output)
25 |
26 |
27 | if __name__ == '__main__':
28 | main()
29 |
--------------------------------------------------------------------------------
/write_binvox.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import argparse
3 | import binvox_rw
4 |
5 | if __name__ == '__main__':
6 | parser = argparse.ArgumentParser(description='Write an example BINVOX file.')
7 | parser.add_argument('output', type=str, help='BINVOX file.')
8 |
9 | args = parser.parse_args()
10 |
11 | volume = np.zeros((32, 32, 32))
12 | volume[10:22, 10:22, 10:22] = 1
13 |
14 | model = binvox_rw.Voxels(volume > 0.5, volume.shape, (0, 0, 0), 1)
15 | with open(args.output, 'w') as fp:
16 | model.write(fp)
17 | print('Wote %s.' % args.output)
--------------------------------------------------------------------------------
/write_txt.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import argparse
3 | from point_cloud import PointCloud
4 |
5 |
6 | if __name__ == '__main__':
7 |
8 | parser = argparse.ArgumentParser(description='Write an example point cloud as TXT.')
9 | parser.add_argument('output', type=str, help='TXT file for example point cloud.')
10 |
11 | args = parser.parse_args()
12 | point_cloud = PointCloud(np.random.random((100, 3)))
13 | point_cloud.to_txt(args.output)
14 | print('Wrote %s.' % args.output)
15 |
--------------------------------------------------------------------------------