wiki:PythonOcc/elbow

Version 37 (modified by Leon Kos, 10 years ago) (diff)

Pokaži travo

Komolec

Koordinatni sistem Na primeru komolca bomo pokazali osnovne operacije in gradnjo modela s PythonOcc za postavitev v globalnem koordinatnem sistemu podanem s tremi točkami:

  • p0 - središče komolca
  • p1 - točka ki podaja os axis1 iz središča p0
  • p2 - točka za drugo os axis2 iz središča p0

Cilj takega podajanja položaja je, da se poenostavi orientacijo in s tem tudi gradnjo sestavov iz tega in podobnih členkov. Model na skici naj bi omogočal tudi enostavno gradnjo sestavov. Skica kolena

Poleg globalnega določanja položaja želimo nadzorovati tudi stopnjo izdelave podrobnosti, saj se nekateri detajli ne potrebujejo za vse namene. Poglaviten namen je predvsem prikaz v sestavu. Takrat lahko zmanjšamo podrobnosti zaokrožitev in notranjosti.

Pričnemo z manjšim programom na katerega bomo postopoma dograjevali model. Kot osnovo vzamemo program s cilindrom. Osnutek

from OCC.Display.SimpleGui import *
from OCC.BRepPrimAPI import *
R = 31
L = 120
myPrim1 = BRepPrimAPI_MakeCylinder(R, L)
(display, start_display, add_menu, add_function_to_menu) = init_display()
display.DisplayShape(myPrim1.Shape())
start_display()

Za izdelavo cilindra smo uporabili API ukaze iz nabora OCC.BRepPrimAPI, ki poenostavljajo gradnjo osnovnih primitivov brez gradnje topologije. Prav tako je potrebno poudariti, da zaradi objektnega programiranja uporabimo različne izpeljanke iste funkcije. Tako je BRepPrimAPI_MakeCylinder možno klicati v naslednjih oblikah:

BRepPrimAPI_MakeCylinder(Standard_Real R, Standard_Real H) 
BRepPrimAPI_MakeCylinder(Standard_Real R, Standard_Real H, Standard_Real Angle)
BRepPrimAPI_MakeCylinder(gp_Ax2 Axes, Standard_Real R, Standard_Real H)
BRepPrimAPI_MakeCylinder(gp_Ax2 Axes, Standard_Real R, Standard_Real H, Standard_Real Angle)

kar lahko preverimo, če v Python Shell napišemo

>>> from OCC.BRepPrimAPI import *
>>> help(BRepPrimAPI_MakeCylinder)

ali si ogledamo spletna navodila za BRepPrimAPI_MakeCylinder. Za naš namen postavljanja cilindra v globalni prostor je primernejša funkcija s parametri, ki poda še os poleg premera in višine. Za podajanje osi gp_Ax2 se uporablja dva 3D vektorja. Prvi je izhodiščni položaj drugi pa usmeritev. Vsi ti vektorja so podani v knjižnici gp (general purpose). Knjižnica gp podpira različne tipe vektorjev od 2D do 3D čeprav so načeloma istega ranka predvsem zaradi tega, da se da enostavno pretvarjati iz enega tipa v drugi. Tako imamo v gp_Ax2 točko in smerni vektor, ki sestavlja podatka za os. Običajno je tako, da najprej pogledamo možne izpeljane funkcije in izberemo inačico z nam najprimernejšimi podatki. Nato glede na zahtevane vhodne argumente pripravimo v obliki, ki je najkrajša in ustreza namenu. Prav tako želimo pripraviti model tako, da se glavne dimenzije postavi kot parametre s katerimi lahko enostavno spremenimo model. V našem primeru sta to R in L. Razširitev programa s knjižnico gp je naslednja

from OCC.gp import *
from OCC.Display.SimpleGui import *
from OCC.BRepPrimAPI import *

R = 31
L = 120
p = gp_Pnt(0., 0., 0.)
d = gp_Dir(1., 0., 0.) 
myAxes1 = gp_Ax2(p,d) 
myPrim1 = BRepPrimAPI_MakeCylinder(myAxes1, R, L)
(display, start_display, add_menu, add_function_to_menu) = init_display()
display.DisplayShape(myPrim1.Shape())
start_display()

Srebren model Podobno kot pri gradnji primitiva lahko prikaz cilindra dopolnimo s spremembo barve ali predpisani lastnosti materiala površine s tem da uporabimo

display.DisplayColoredShape(myPrim1.Shape(), 'CYAN')

ali

from OCC.Graphic3d import *
#...
material = Graphic3d_MaterialAspect(Graphic3d_NOM_SILVER)
display.DisplayShape(myPrim1.Shape(), material)

Za izdelavo posnetja je potrebno podati ploskev in rob. Chamfer

mkFillet = BRepFilletAPI_MakeChamfer(myPrim1.Shape())
ex = TopExp_Explorer(myPrim1.Shape(), TopAbs_EDGE)
ex2 = TopExp_Explorer(myPrim1.Shape(), TopAbs_FACE)
edge = TopoDS_edge(ex.Current())
face = TopoDS_face(ex2.Current())
mkFillet.Add(15.0, 3.0, edge, face)
#...
display.DisplayColoredShape(mkFillet.Shape(), 'BLUE')

in naslednje vključke na vrhu

from OCC.BRepFilletAPI import *
from OCC.Utils.Topology import Topo
from OCC.TopExp import *
from OCC.TopAbs import *
from OCC.TopoDS import *

Izrežemo še luknjo v cilinder s posnetjem Cut

from OCC.BRepAlgoAPI import *
#...
myPrim2 = BRepPrimAPI_MakeCylinder(myAxes1, R-5, L)
myShp1 = BRepAlgoAPI_Cut(mkFillet.Shape(),myPrim2.Shape())
#...
display.DisplayColoredShape(myShp1.Shape(), 'CYAN')

V izhodišče dodamo še kroglo, ki nam bo zapolnilo komolec.

mySphere =  BRepPrimAPI_MakeSphere(p, R)
#...
display.DisplayColoredShape(mySphere.Shape(), 'BLUE')

Za drugi del komolca izberemo kar prvotno pripravljen model myShp1(), ki ga rotiramo do osi y okoli osi z. Pri tem moramo pripraviti transformacijsko matriko in kos zavrteti za 90 stopinj

from math import radians
#...
rotateAxis = gp_Ax1(p, gp_Dir(0.0,0.0,1.0)) #Os rotacije
transfRot1 = gp_Trsf()
transfRot1.SetRotation(rotateAxis, radians(90.0))
#...
display.DisplayColoredShape(myShp2.Shape(),'GREEN')

Celoten model zlijemo skupaj s tem, da zanemarimo nepravilno izvrtino v področju krogle, ki ni vidno po tem, vstavimo še cevi.

myShp3 = BRepAlgoAPI_Fuse(myShp1.Shape(),myShp2.Shape())
myShp4 = BRepAlgoAPI_Fuse(myShp3.Shape(), mySphere.Shape())
#...
material = Graphic3d_MaterialAspect(Graphic3d_NOM_SILVER)
display.DisplayShape(myShp4.Shape(), material)

Končni kos Na koncu model še izvozimo v formatu STL, ki ga potrebujemo za prikaz na spletni strani.

from OCC.StlAPI import *
#...
stl_writer = StlAPI()
stl_writer.Write(myShp4.Shape(), "elbow.stl", False)

Celoten program za izdelavo modela je naslednji:

from OCC.gp import *
from OCC.BRepPrimAPI import *
from OCC.TopExp import *
from OCC.TopAbs import *
from OCC.BRep import *
from OCC.Geom import *
from OCC.GCE2d import *
from OCC.GC import *
from OCC.Geom2d import *
from OCC.BRepLib import *
from OCC.BRepFeat import *
from OCC.Utils.Topology import Topo
from OCC.BRepBuilderAPI import *
from OCC.BRepAlgoAPI import *
from OCC.BRepFilletAPI import *
from OCC.TopoDS import *
from OCC.StlAPI import *
from math import radians

from OCC.Display.SimpleGui import *
from OCC.Graphic3d import *

R = 31
L = 120
p = gp_Pnt(0.,0.,0)
d = gp_Dir(1. ,0., 0.) 
myAxes1 = gp_Ax2(p,d) 
myPrim1 = BRepPrimAPI_MakeCylinder(myAxes1, R, 100)

mkFillet = BRepFilletAPI_MakeChamfer(myPrim1.Shape())
ex = TopExp_Explorer(myPrim1.Shape(), TopAbs_EDGE)
ex2 = TopExp_Explorer(myPrim1.Shape(), TopAbs_FACE)
edge = TopoDS_edge(ex.Current())
face = TopoDS_face(ex2.Current())
mkFillet.Add(15.0, 3.0, edge, face)

myAxes2 = gp_Ax2(p, gp_Dir(0,-1,0))
myPrim2 = BRepPrimAPI_MakeCylinder(myAxes1, R-5, L)
myShp1 = BRepAlgoAPI_Cut(mkFillet.Shape(),myPrim2.Shape())

mySphere =  BRepPrimAPI_MakeSphere(p, R)

rotateAxis = gp_Ax1(p, gp_Dir(0.0,0.0,1.0)) #Os rotacije
transfRot1 = gp_Trsf()
transfRot1.SetRotation(rotateAxis, radians(90.0))
myShp2 = BRepBuilderAPI_Transform(myShp1.Shape(), transfRot1)
myShp3 = BRepAlgoAPI_Fuse(myShp1.Shape(),myShp2.Shape())
myShp4 = BRepAlgoAPI_Fuse(myShp3.Shape(), mySphere.Shape())
stl_writer = StlAPI()
stl_writer.Write(myShp4.Shape(), "elbow.stl", False)

display, start_display, add_menu, add_function_to_menu = init_display()
material = Graphic3d_MaterialAspect(Graphic3d_NOM_SILVER)
display.DisplayShape(myShp4.Shape(), material)
start_display()

Rebro s spremenljivo zaokrožitvijo

Rebro Ker si nismo točno zastavili velikost rebra, je le ta podan kar v enaki velikosti kot zunanji radi. Za izdelavo rebra je potrebno podati črto aWire in ravnino, ki seka model in iz katere je potem v smeri dveh vektorjev izvečeno.

# Izdelamo navadno rebro
rib_thickness = 6.0 # polovica debeline rebra
rib1 = gp_Pnt(2*R,R,0)
rib2 = gp_Pnt(R,2*R,0)
aPlane = Geom_Plane(0,0,1.0,0.0)
edge1 = BRepBuilderAPI_MakeEdge(rib1, rib2).Edge()
aWire = BRepBuilderAPI_MakeWire(edge1)
rib = BRepFeat_MakeLinearForm( myShp4.Shape(), aWire.Wire(), aPlane.GetHandle(), \
                gp_Vec(0.,0.,rib_thickness), gp_Vec(0.,0.,-rib_thickness), 1, True )
rib.Perform()

V vogal želimo dodati tudi zaokrožitev z naraščajočim radijem. Poiščemo vse štiri robove na obeh debelinah rebra, ki imajo začetno in končno Z isto in so vzporedni z osjo X ali Y ter jim dodamo spremenljivo debelino zaokrožitve. Z nekaj sreče, ker je začetek in konec robov pravilno orientiran, lahko dodamo vsem najdenim robovom še naraščajočo zaokrožitev. Zaokrožitev

TOL=1E-6
with_fillet = BRepFilletAPI_MakeFillet(rib.Shape())
topology_traverser = Topo(rib.Shape())
for aEdge in topology_traverser.edges():  
    first, last = TopExp().FirstVertex(aEdge), TopExp().LastVertex(aEdge)
    first_vert, last_vert = BRep_Tool().Pnt(first), BRep_Tool().Pnt(last)
    if abs(abs(first_vert.Z())-rib_thickness) < TOL and \
       abs(first_vert.Z() - last_vert.Z()) < TOL and \
      (abs(first_vert.X()-last_vert.X())<TOL or abs(first_vert.Y()-last_vert.Y())<TOL):
        with_fillet.Add(25, 0.0, aEdge)

Končni program z dodanim rebrom je podan v priponki kot elbow.py

Prikaz modela na spletni strani s knjižnico jsc3d

Za prikaz modela na spletni strani Trac moramo pripeti model elbow.stl tej strani in pripeti še skrčeno knjižnico JSC3D in natipkati naslednjo kodo, ki je mešanica HTML5 in JavaScripta.

{{{
#!html
<script src="/vaje/raw-attachment/wiki/PythonOcc/elbow/jsc3d.min.js"></script>
<script type="text/javascript">
function onLoad(){
  var canvas = document.getElementById('cv');
  var viewer = new JSC3D.Viewer(canvas);
  viewer.setParameter('SceneUrl', '/vaje/raw-attachment/wiki/PythonOcc/elbow/elbow.stl');
  viewer.init();
  viewer.update();
}      
window.onload = function() {onLoad();}        
</script>  
<canvas id="cv" width="600" height="400"
  style="background:lightgrey; border: 1px solid; float:right;" ></canvas>
}}}

Osnovno kodo lahko še dodatno opremimo z lastnostmi, ki so podrobneje opisane v osnovnem primeru jsc3d.

Sestavljanje, materiali, transformacije

V formatu STL ni možno nastavljati barve. Če želimo posameznim delom nastaviti barvo lahko to storimo tako, da naložimo vsak kos posebej in mu nastavimo material. Če sestavavljamo modele z uporabo več enakih kosov je ugodno, da naložimo le osnovne dele, ki jih potem kopiramo in prestavljamo po prostoru in tako sestavimo celoten sestav. Nalaganje posameznih modelov je asinhrono, kar pomeni da se hkrati nalagajo in ni zagotovljen vrstni red. S takim načinom prenašanja modelov do brskalnika se čas do prvega prikaza sestava skrajša. Je pa zaradi tega potrebno nekoliko več programiranja transformacij in kopiranje položajev točk. Zmanjša se tudi poraba pomnilnika v brskalniku, kar omogoča hitrejše obračanje sestava.

Dokumentacija objektov SJC3D nam je lahko v pomoč pri programiranju v JavaScriptu?. Poleg tega smo želeli nastaviti avtomatsko prilagajanje prikaznega okna (canvas) razpoložljivi širini v brskalniku. Zaradi tega je bilo potrebno prirediti dogodku onresize funkcijo, ki ob spremembi velikosti okna prav tako spremeni širino konteksta ter prepriča naš pregledovalnik (viewer), da naj spremeni velikost prikaza modela za kar pa je bilo potrebno uvesti globalno spremenljivko var viewer, ki se jo potem ob dogodku ustrezno spremeni.

Dodali smo tudi nekaj začetnih nastavitev prikaza, kot je začetna rotacija in lomni kot CreaseAngle, ki določa kot med normalami posameznih ploskvic, ki se v gladkem načinu prikaza (RenderMode) upošteva za določitev ostrega prehoda.

Za izbor barve lahko uporabimo tudi kakšen spletni program kot je barvno kolo.

{{{
#!html
<script src="/vaje/raw-attachment/wiki/PythonOcc/elbow/jsc3d.min.js"></script>
<script type="text/javascript">
var viewer = null;
function onLoad2(){
  var canvas = document.getElementById('cv2');
  canvas.width = 0.9*window.innerWidth;
  canvas.height = canvas.width/1.6;
  viewer = new JSC3D.Viewer(canvas);
  //viewer.setParameter('SceneUrl', '/vaje/raw-attachment/wiki/PythonOcc/elbow/pipe.stl');
  viewer.setParameter('BackgroundColor1', '#FFFFFF');
  viewer.setParameter('BackgroundColor2', '#383840');
  viewer.setParameter('InitRotationX', 30);
  viewer.setParameter('InitRotationY', 30);
  viewer.setParameter('InitRotationZ', 30);
  viewer.setParameter('RenderMode', 'smooth');
  viewer.setParameter('CreaseAngle', 15);
  viewer.init();
  
  var theScene = new JSC3D.Scene;
  var totalParts = 2;
  var numOfLoaded = 0;  

  var onPipeLoaded = function(scene) {
  var meshes = scene.getChildren();
    for (var i=0; i<meshes.length; i++) {
     material =  new JSC3D.Material();
     material.diffuseColor = 0x3f3f7f;
     meshes[i].setMaterial(material);
     theScene.addChild(meshes[i]);
    }
    if (++numOfLoaded == totalParts)
      viewer.replaceScene(theScene);
  };

  var onElbowLoaded = function(scene) {
    var meshes = scene.getChildren();
    for (var i=0; i<meshes.length; i++) {
        material =  new JSC3D.Material();
        material.diffuseColor = 0x0ab610;
        material.transparency = 0.4;
        meshes[i].setMaterial(material);
        theScene.addChild(meshes[i]);

        var xformMat = new JSC3D.Matrix3x4;
        var rotMat = new JSC3D.Matrix3x4;
        xformMat.identity();
        rotMat.identity();
        rotMat.rotateAboutZAxis(90);
        xformMat.multiply(rotMat);
        xformMat.translate(500, 0, 0);

        myMesh = new JSC3D.Mesh();
        myMesh.vertexBuffer = new Array( meshes[i].vertexBuffer.length);
        myMesh.faceNormalBuffer = new Array( meshes[i].faceNormalBuffer.length);
        myMesh.indexBuffer = meshes[i].indexBuffer;
        // transform and rewrite vertex coordinates
        JSC3D.Math3D.transformVectors(xformMat, meshes[i].vertexBuffer, myMesh.vertexBuffer);
        // transform and rewrite face normals
        JSC3D.Math3D.transformVectors(rotMat, meshes[i].faceNormalBuffer, myMesh.faceNormalBuffer);
        myMesh.setMaterial(material);
        theScene.addChild(myMesh); 
    }
    if (++numOfLoaded == totalParts)
      viewer.replaceScene(theScene);
  };
                                   

  var pipe_loader = new JSC3D.StlLoader;
  pipe_loader.onload = onPipeLoaded;
  pipe_loader.loadFromUrl('/vaje/raw-attachment/wiki/PythonOcc/elbow/pipe.stl');
  var elbow_loader = new JSC3D.StlLoader;
  elbow_loader.onload = onElbowLoaded;
  elbow_loader.loadFromUrl('/vaje/raw-attachment/wiki/PythonOcc/elbow/elbow.stl');

  viewer.update();
}

window.onload = function() {onLoad(); onLoad2();}  
window.onresize = function() {
  var canvas = document.getElementById('cv2');
  canvas.width = 0.9*window.innerWidth;
  canvas.height = canvas.width/1.6;
  viewer.ctx = canvas.getContext('2d');
  viewer.canvasData = viewer.ctx.getImageData(0, 0, canvas.width, canvas.height);
  viewer.frameWidth = canvas.width;
  viewer.frameHeight = canvas.height;
  viewer.drawBackground();
  viewer.update();
}           
</script>  
<canvas id="cv2" width="600" height="400"
  style="background:lightgrey; border: 1px solid; float:center;" ></canvas>
}}}

V primeru dodatnih kosov je potrebno ponastaviti transformacijo in narediti novo mrežo, za kar pa je bolje da naredimo podprogram copy_mesh(), ki kot vhod potrebuje le mrežo, sceno v katero to postavljamo in transformacijo.

function copy_mesh(mesh, scene, rotx, roty, rotz, transx, transy, transz)
{
        var xformMat = new JSC3D.Matrix3x4;
        var rotMat = new JSC3D.Matrix3x4;
        xformMat.identity();
        rotMat.identity();
        rotMat.rotateAboutXAxis(rotx);
        rotMat.rotateAboutYAxis(roty);
        rotMat.rotateAboutZAxis(rotz);
        xformMat.multiply(rotMat);
        xformMat.translate(transx, transy, transz);

        myMesh = new JSC3D.Mesh();
        myMesh.vertexBuffer = new Array(mesh.vertexBuffer.length);
        myMesh.faceNormalBuffer = new Array(mesh.faceNormalBuffer.length);
        myMesh.indexBuffer = mesh.indexBuffer;
        // transform and rewrite vertex coordinates
        JSC3D.Math3D.transformVectors(xformMat, mesh.vertexBuffer, myMesh.vertexBuffer);
        // transform and rewrite face normals
        JSC3D.Math3D.transformVectors(rotMat, mesh.faceNormalBuffer, myMesh.faceNormalBuffer);
        myMesh.setMaterial(mesh.material);
        scene.addChild(myMesh); 
}

Le to potem uporabimo v funkciji ob naložitvi kosa kot

        copy_mesh(meshes[i], theScene, 0, 0, 90, 500, 0, 0);
        copy_mesh(meshes[i], theScene, 0, 0, 180, 500, 500, 0);
        copy_mesh(meshes[i], theScene, 0, 0, -90, 0, 500, 0);

Teksture

Naredimo podlago za naš model tako, da mrežo naredimo kar v JSC3D in nanjo prilepimo teksturo. Namesto trikotnikov bomo podali kar štirikotnik. Teksturo trave pripnemo na stran.

viewer.setParameter('RenderMode', 'texture');
///...
  pod = new JSC3D.Mesh();
  pod.isDoubleSided = false; w = 1000; h = -50;
  pod.vertexBuffer = [-w, -w, h, w, -w, h, w, w, h, -w, w, h];
  pod.indexBuffer = [0, 1, 2, 3, -1];
  pod.texCoordBuffer = [0, 0, 1, 0, 1, 1,  0, 1];
  pod.texCoordIndexBuffer = [0,1,2,3,-1];
  pod.init();
  var tex = new JSC3D.Texture;
  tex.onready = function() {
      pod.setTexture(this);
      viewer.update();
  };
 tex.createFromUrl('/vaje/raw-attachment/wiki/PythonOcc/elbow/Grass_by_jaqx_textures.jpg');                                  
 theScene.addChild(pod);
 viewer.update();

Attachments (16)

Download all attachments as: .zip