diff --git a/Scratch.ipynb b/Scratch.ipynb index b844896..9a9493d 100644 --- a/Scratch.ipynb +++ b/Scratch.ipynb @@ -13,11 +13,17 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "c = meshutil.cube(open_xz=False)\n", + "mesh_fname = \"cube_test.stl\"\n", + "c2 = meshutil.cube_distort(0.3)\n", + "c2.v = c2.v/2 + [0,1,0]\n", + "c = c.concat(c2)\n", + "m = c.to_stl_mesh()\n", + "\"\"\"\n", "c1 = meshutil.cube_distort(0.3)\n", "c1[\"vectors\"] = c1[\"vectors\"]/2 + [0,1,0]\n", "c2 = meshutil.cube_distort(0.3)\n", @@ -25,32 +31,23 @@ "c3 = meshutil.cube_distort(0.3)\n", "c3[\"vectors\"] = c3[\"vectors\"]/2 + [0,1,0.5]\n", "c4 = meshutil.cube_distort(0.3)\n", - "c4[\"vectors\"] = c4[\"vectors\"]/2 + [0.5,1,0.5]" + "c4[\"vectors\"] = c4[\"vectors\"]/2 + [0.5,1,0.5]\n", + "\"\"\"\n", + "m.save(mesh_fname)" ] }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 3, "metadata": {}, "outputs": [], "source": [ - "d = numpy.concatenate([c,c1,c2,c3,c4])" + "#d = numpy.concatenate([c,c1,c2,c3,c4])" ] }, { "cell_type": "code", - "execution_count": 15, - "metadata": {}, - "outputs": [], - "source": [ - "mesh = stl.mesh.Mesh(d)\n", - "mesh_fname = \"cube_test.stl\"\n", - "mesh.save(mesh_fname)" - ] - }, - { - "cell_type": "code", - "execution_count": 16, + "execution_count": 4, "metadata": {}, "outputs": [], "source": [ @@ -59,14 +56,16 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 5, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ - "face_normals didn't match triangles, ignoring!\n" + "face_normals didn't match triangles, ignoring!\n", + "/home/hodapp/.local/lib/python3.6/site-packages/IPython/core/display.py:694: UserWarning: Consider using IPython.display.IFrame instead\n", + " warnings.warn(\"Consider using IPython.display.IFrame instead\")\n" ] }, { @@ -264,7 +263,7 @@ "Math.pow(distanceToCamera,2)+\n", "Math.pow(distanceToCamera,2));camera.position.set(len,len,len);controls.update();camera.lookAt(boundingSphere.center);controls.target.set(boundingSphere.center.x,boundingSphere.center.y,boundingSphere.center.z);camera.updateProjectionMatrix();}\n", "function centerControls(obj,camera,controls){const boundingBox=new THREE.Box3().setFromObject(obj);const boundingSphere=new THREE.Sphere();boundingBox.getBoundingSphere((target=boundingSphere));controls.update();controls.target.set(boundingSphere.center.x,boundingSphere.center.y,boundingSphere.center.z);}\n", - "function init(){scene=new THREE.Scene();scene.background=new THREE.Color(0xffffff);tracklight=new THREE.DirectionalLight(0xffffff,1.75);scene.add(tracklight);base64_data="Z2xURgIAAAA4BwAAsAMAAEpTT057InNjZW5lIjogMCwgInNjZW5lcyI6IFt7Im5vZGVzIjogWzBdfV0sICJhc3NldCI6IHsidmVyc2lvbiI6ICIyLjAiLCAiZ2VuZXJhdG9yIjogImh0dHBzOi8vZ2l0aHViLmNvbS9taWtlZGgvdHJpbWVzaCJ9LCAiYWNjZXNzb3JzIjogW3siYnVmZmVyVmlldyI6IDAsICJjb21wb25lbnRUeXBlIjogNTEyNSwgImNvdW50IjogMTMyLCAibWF4IjogWzI4XSwgIm1pbiI6IFswXSwgInR5cGUiOiAiU0NBTEFSIn0sIHsiYnVmZmVyVmlldyI6IDEsICJjb21wb25lbnRUeXBlIjogNTEyNiwgImNvdW50IjogMjksICJ0eXBlIjogIlZFQzMiLCAiYnl0ZU9mZnNldCI6IDAsICJtYXgiOiBbMS4wLCAxLjc2MjAyMjQ5NTI2OTc3NTQsIDEuMF0sICJtaW4iOiBbLTAuMTcwMDkxODUyNTQ1NzM4MjIsIDAuMCwgLTAuMTcwMDkxODUyNTQ1NzM4MjJdfV0sICJtZXNoZXMiOiBbeyJuYW1lIjogImN1YmVfdGVzdC5zdGwiLCAicHJpbWl0aXZlcyI6IFt7ImF0dHJpYnV0ZXMiOiB7IlBPU0lUSU9OIjogMX0sICJpbmRpY2VzIjogMCwgIm1vZGUiOiA0fV19XSwgImNhbWVyYXMiOiBbeyJuYW1lIjogImNhbWVyYV9VTk5VSVEiLCAidHlwZSI6ICJwZXJzcGVjdGl2ZSIsICJwZXJzcGVjdGl2ZSI6IHsiYXNwZWN0UmF0aW8iOiAxLjMzMzMzMzMzMzMzMzMzMzMsICJ5Zm92IjogMC43ODUzOTgxNjMzOTc0NDgzLCAiem5lYXIiOiAwLjAxfX1dLCAibm9kZXMiOiBbeyJuYW1lIjogIndvcmxkIiwgImNoaWxkcmVuIjogWzFdfSwgeyJuYW1lIjogImN1YmVfdGVzdC5zdGxfVURNTVhVT0RFUjNSIiwgIm1lc2giOiAwfV0sICJidWZmZXJzIjogW3siYnl0ZUxlbmd0aCI6IDg3Nn1dLCAiYnVmZmVyVmlld3MiOiBbeyJidWZmZXIiOiAwLCAiYnl0ZU9mZnNldCI6IDAsICJieXRlTGVuZ3RoIjogNTI4fSwgeyJidWZmZXIiOiAwLCAiYnl0ZU9mZnNldCI6IDUyOCwgImJ5dGVMZW5ndGgiOiAzNDh9XX0gIGwDAABCSU4AAAAAAAcAAAAFAAAAAAAAAAIAAAAHAAAABQAAAAgAAAAGAAAABQAAAAcAAAAIAAAABgAAAAMAAAABAAAABgAAAAgAAAADAAAAAQAAAAIAAAAAAAAAAQAAAAMAAAACAAAAAgAAAAgAAAAHAAAAAgAAAAMAAAAIAAAAAAAAAAUAAAAGAAAAAAAAAAYAAAABAAAAAgAAAAsAAAASAAAAAgAAAA4AAAALAAAAEgAAABgAAAAUAAAAEgAAAAsAAAAYAAAAFAAAABsAAAAEAAAAFAAAABgAAAAbAAAABAAAAA4AAAACAAAABAAAABsAAAAOAAAAEgAAABYAAAAHAAAAEgAAABkAAAAWAAAABwAAAA0AAAAJAAAABwAAABYAAAANAAAACQAAABAAAAAUAAAACQAAAA0AAAAQAAAAFAAAABkAAAASAAAAFAAAABAAAAAZAAAABAAAAAoAAAAUAAAABAAAAA8AAAAKAAAAFAAAABcAAAATAAAAFAAAAAoAAAAXAAAAEwAAABwAAAADAAAAEwAAABcAAAAcAAAAAwAAAA8AAAAEAAAAAwAAABwAAAAPAAAAFAAAABUAAAAJAAAAFAAAABoAAAAVAAAACQAAAAwAAAAIAAAACQAAABUAAAAMAAAACAAAABEAAAATAAAACAAAAAwAAAARAAAAEwAAABoAAAAUAAAAEwAAABEAAAAaAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA/AAAAAAAAgD8AAAAAAAAAAAAAgD8AAIA/AAAAAAAAgD8AAAA/AACAPwAAAAAAAAAAAACAPwAAAAAAAIA/AACAPwAAgD8AAAAAAACAPwAAgD8AAIA/AACAPwAAgD8AAAA/QaGuPiegzj+46ag+QaGuPiegzj+PLC6+GJlRP/SJ4T8YmVE/GJlRP/SJ4T8wMqM+bU4Xvlq2uz9tThe+bU4Xvlq2uz/KWLQ+uOmoPiegzj9Boa4+uOmoPiegzj+gUFc/AAAAPwAAgD8AAAAAAAAAPwAAgD8AAIA/AAAAPwAAgD8AAAA/oFBXPyegzj+46ag+oFBXPyegzj+PLC6+MDKjPvSJ4T8YmVE/MDKjPvSJ4T8wMqM+yli0Plq2uz9tThe+yli0Plq2uz/KWLQ+jywuviegzj9Boa4+jywuviegzj+gUFc/";;renderer=new THREE.WebGLRenderer({antialias:true});renderer.setPixelRatio(window.devicePixelRatio);renderer.setSize(window.innerWidth,window.innerHeight);document.body.appendChild(renderer.domElement);loader=new THREE.GLTFLoader();loader.load("data:text/plain;base64,"+base64_data,function(gltf){scene.add(gltf.scene);camera=gltf.cameras[0];controls=new THREE.TrackballControls(camera);controls.rotateSpeed=1.0;controls.zoomSpeed=1.2;controls.panSpeed=0.8;controls.noZoom=false;controls.noPan=false;controls.staticMoving=true;controls.dynamicDampingFactor=0.3;controls.keys=[65,83,68];controls.addEventListener("change",render);centerControls(scene,camera,controls);render();window.addEventListener("resize",onWindowResize,false);animate();});}\n", + "function init(){scene=new THREE.Scene();scene.background=new THREE.Color(0xffffff);tracklight=new THREE.DirectionalLight(0xffffff,1.75);scene.add(tracklight);base64_data="Z2xURgIAAABwBQAAsAMAAEpTT057InNjZW5lIjogMCwgInNjZW5lcyI6IFt7Im5vZGVzIjogWzBdfV0sICJhc3NldCI6IHsidmVyc2lvbiI6ICIyLjAiLCAiZ2VuZXJhdG9yIjogImh0dHBzOi8vZ2l0aHViLmNvbS9taWtlZGgvdHJpbWVzaCJ9LCAiYWNjZXNzb3JzIjogW3siYnVmZmVyVmlldyI6IDAsICJjb21wb25lbnRUeXBlIjogNTEyNSwgImNvdW50IjogNjAsICJtYXgiOiBbMTRdLCAibWluIjogWzBdLCAidHlwZSI6ICJTQ0FMQVIifSwgeyJidWZmZXJWaWV3IjogMSwgImNvbXBvbmVudFR5cGUiOiA1MTI2LCAiY291bnQiOiAxNSwgInR5cGUiOiAiVkVDMyIsICJieXRlT2Zmc2V0IjogMCwgIm1heCI6IFsxLjAsIDEuNzYyMDIyNDk1MjY5Nzc1NCwgMS4wXSwgIm1pbiI6IFstMC4xNzAwOTE4NTI1NDU3MzgyMiwgMC4wLCAtMC4xNzAwOTE4NTI1NDU3MzgyMl19XSwgIm1lc2hlcyI6IFt7Im5hbWUiOiAiY3ViZV90ZXN0LnN0bCIsICJwcmltaXRpdmVzIjogW3siYXR0cmlidXRlcyI6IHsiUE9TSVRJT04iOiAxfSwgImluZGljZXMiOiAwLCAibW9kZSI6IDR9XX1dLCAiY2FtZXJhcyI6IFt7Im5hbWUiOiAiY2FtZXJhXzBHUVdIQyIsICJ0eXBlIjogInBlcnNwZWN0aXZlIiwgInBlcnNwZWN0aXZlIjogeyJhc3BlY3RSYXRpbyI6IDEuMzMzMzMzMzMzMzMzMzMzMywgInlmb3YiOiAwLjc4NTM5ODE2MzM5NzQ0ODMsICJ6bmVhciI6IDAuMDF9fV0sICJub2RlcyI6IFt7Im5hbWUiOiAid29ybGQiLCAiY2hpbGRyZW4iOiBbMV19LCB7Im5hbWUiOiAiY3ViZV90ZXN0LnN0bF8wVkZQWUxVMENMWFMiLCAibWVzaCI6IDB9XSwgImJ1ZmZlcnMiOiBbeyJieXRlTGVuZ3RoIjogNDIwfV0sICJidWZmZXJWaWV3cyI6IFt7ImJ1ZmZlciI6IDAsICJieXRlT2Zmc2V0IjogMCwgImJ5dGVMZW5ndGgiOiAyNDB9LCB7ImJ1ZmZlciI6IDAsICJieXRlT2Zmc2V0IjogMjQwLCAiYnl0ZUxlbmd0aCI6IDE4MH1dfSAgIKQBAABCSU4AAAAAAAcAAAAFAAAAAAAAAAIAAAAHAAAABQAAAAgAAAAGAAAABQAAAAcAAAAIAAAABgAAAAMAAAABAAAABgAAAAgAAAADAAAAAQAAAAIAAAAAAAAAAQAAAAMAAAACAAAAAgAAAAgAAAAHAAAAAgAAAAMAAAAIAAAAAAAAAAUAAAAGAAAAAAAAAAYAAAABAAAAAgAAAAkAAAALAAAAAgAAAAoAAAAJAAAACwAAAA0AAAAMAAAACwAAAAkAAAANAAAADAAAAA4AAAAEAAAADAAAAA0AAAAOAAAABAAAAAoAAAACAAAABAAAAA4AAAAKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA/AAAAAAAAgD8AAAAAAAAAAAAAgD8AAIA/AAAAAAAAgD8AAAA/AACAPwAAAAAAAAAAAACAPwAAAAAAAIA/AACAPwAAgD8AAAAAAACAPwAAgD8AAIA/QaGuPiegzj+PLC6+bU4Xvlq2uz9tThe+AAAAPwAAgD8AAAAAAAAAPwAAgD8AAAA/MDKjPvSJ4T8wMqM+jywuviegzj9Boa4+";;renderer=new THREE.WebGLRenderer({antialias:true});renderer.setPixelRatio(window.devicePixelRatio);renderer.setSize(window.innerWidth,window.innerHeight);document.body.appendChild(renderer.domElement);loader=new THREE.GLTFLoader();loader.load("data:text/plain;base64,"+base64_data,function(gltf){scene.add(gltf.scene);camera=gltf.cameras[0];controls=new THREE.TrackballControls(camera);controls.rotateSpeed=1.0;controls.zoomSpeed=1.2;controls.panSpeed=0.8;controls.noZoom=false;controls.noPan=false;controls.staticMoving=true;controls.dynamicDampingFactor=0.3;controls.keys=[65,83,68];controls.addEventListener("change",render);centerControls(scene,camera,controls);render();window.addEventListener("resize",onWindowResize,false);animate();});}\n", "function onWindowResize(){camera.aspect=window.innerWidth/window.innerHeight;camera.updateProjectionMatrix();renderer.setSize(window.innerWidth,window.innerHeight);controls.handleResize();render();}\n", "function animate(){requestAnimationFrame(animate);controls.update();}\n", "function render(){tracklight.position.copy(camera.position);renderer.render(scene,camera);}\n", @@ -275,7 +274,7 @@ "" ] }, - "execution_count": 17, + "execution_count": 5, "metadata": {}, "output_type": "execute_result" } diff --git a/meshutil.py b/meshutil.py index 59346a9..1809825 100644 --- a/meshutil.py +++ b/meshutil.py @@ -16,42 +16,76 @@ rbb = numpy.array([1,0,1]) ltb = numpy.array([0,1,1]) rtb = numpy.array([1,1,1]) +class FaceVertexMesh(object): + def __init__(self, v, f): + # v & f should both be of shape (N,3)ll + self.v = v + self.f = f + def concat(self, other_mesh): + v2 = numpy.concatenate([self.v, other_mesh.v]) + # Note index shift! + f2 = numpy.concatenate([self.f, other_mesh.f + self.v.shape[0]]) + m2 = FaceVertexMesh(v2, f2) + return m2 + def to_stl_mesh(self): + data = numpy.zeros(self.f.shape[0], dtype=stl.mesh.Mesh.dtype) + v = data["vectors"] + for i, (iv0, iv1, iv2) in enumerate(self.f): + v[i] = [self.v[iv0], self.v[iv1], self.v[iv2]] + return stl.mesh.Mesh(data) + def cube(open_xz=False): + verts = numpy.array([ + lbf, rbf, ltf, rtf, + lbb, rbb, ltb, rtb, + ], dtype=numpy.float32) if open_xz: - data = numpy.zeros(8, dtype=stl.mesh.Mesh.dtype) + faces = numpy.zeros((8,3), dtype=int) else: - data = numpy.zeros(12, dtype=stl.mesh.Mesh.dtype) - v = data["vectors"] - v[0] = [lbf, rtf, rbf] - v[1] = [lbf, ltf, rtf] - v[2] = [rbf, rtb, rbb] - v[3] = [rbf, rtf, rtb] - v[4] = [rbb, ltb, lbb] - v[5] = [rbb, rtb, ltb] - v[6] = [lbb, ltf, lbf] - v[7] = [lbb, ltb, ltf] + faces = numpy.zeros((12,3), dtype=int) + faces[0,:] = [0, 3, 1] + faces[1,:] = [0, 2, 3] + faces[2,:] = [1, 7, 5] + faces[3,:] = [1, 3, 7] + faces[4,:] = [5, 6, 4] + faces[5,:] = [5, 7, 6] + faces[6,:] = [4, 2, 0] + faces[7,:] = [4, 6, 2] if not open_xz: - v[8] = [ltf, rtb, rtf] - v[9] = [ltf, ltb, rtb] - v[10] = [lbf, rbf, rbb] - v[11] = [lbf, rbb, lbb] - return data + faces[8,:] = [2, 7, 3] + faces[9,:] = [2, 6, 7] + faces[10,:] = [0, 1, 5] + faces[11,:] = [0, 5, 4] + # winding order? + return FaceVertexMesh(verts, faces) def cube_distort(angle): - data = numpy.zeros(8, dtype=stl.mesh.Mesh.dtype) - v = data["vectors"] q = quat.rotation_quaternion(numpy.array([-1,0,1]), angle) ltf2 = quat.conjugate_by(ltf, q)[0,:] rtf2 = quat.conjugate_by(rtf, q)[0,:] ltb2 = quat.conjugate_by(ltb, q)[0,:] rtb2 = quat.conjugate_by(rtb, q)[0,:] # TODO: Just make these functions work right with single vectors - v[0] = [lbf, rtf2, rbf] - v[1] = [lbf, ltf2, rtf2] - v[2] = [rbf, rtb2, rbb] - v[3] = [rbf, rtf2, rtb2] - v[4] = [rbb, ltb2, lbb] - v[5] = [rbb, rtb2, ltb2] - v[6] = [lbb, ltf2, lbf] - v[7] = [lbb, ltb2, ltf2] - return data + verts = numpy.array([ + lbf, rbf, ltf2, rtf2, + lbb, rbb, ltb2, rtb2, + ], dtype=numpy.float32) + if True: #open_xz: + faces = numpy.zeros((8,3), dtype=int) + else: + faces = numpy.zeros((12,3), dtype=int) + faces[0,:] = [0, 3, 1] + faces[1,:] = [0, 2, 3] + faces[2,:] = [1, 7, 5] + faces[3,:] = [1, 3, 7] + faces[4,:] = [5, 6, 4] + faces[5,:] = [5, 7, 6] + faces[6,:] = [4, 2, 0] + faces[7,:] = [4, 6, 2] + if False: # not open_xz: + faces[8,:] = [2, 7, 3] + faces[9,:] = [2, 6, 7] + faces[10,:] = [0, 1, 5] + faces[11,:] = [0, 5, 4] + # winding order? + return FaceVertexMesh(verts, faces)