Can't draw lines

Aurora python

Aiding civil engineers to build reinforced concrete buildings

from pandac.PandaModules import WindowProperties
from direct.gui.DirectGui import *
from panda3d.core import NodePath, LineSegs
import win32gui, win32con
from direct.showbase.DirectObject import DirectObject # for event handling
import direct.directbase.DirectStart
import sys

def P3DCreateAxes(lineThickness=1):
ls = LineSegs()
ls.setThickness(lineThickness)

# X axis
ls.setColor(1.0, 0.0, 0.0, 1.0)
ls.moveTo(0.0, 0.0, 0.0)
ls.drawTo(1.0, 0.0, 0.0)

# Y axis
ls.setColor(0.0, 1.0, 0.0, 1.0)
ls.moveTo(0.0, 0.0, 0.0)
ls.drawTo(0.0, 1.0, 0.0)

# Z axis
ls.setColor(0.0, 0.0, 1.0, 1.0)
ls.moveTo(0.0, 0.0, 0.0)
ls.drawTo(0.0, 0.0, 1.0)

node = ls.create()
return NodePath(node)

class World(DirectObject):

def __init__(self):
    # Get window handle (Windows only).
    super().__init__()
    self.hwnd = win32gui.GetForegroundWindow()

    # Maximise window (Windows only).
    win32gui.PostMessage(self.hwnd, win32con.WM_SYSCOMMAND, win32con.SC_MAXIMIZE, 0)

    # On ESC close window
    self.accept("escape", sys.exit)

Configuring the menu file

file_menu = DirectOptionMenu(items=[‘Arquivo’, ‘Novo’, ‘Abrir’, ‘Salvar’, ‘Fechar’],
initialitem=0,
scale=.05,
textMayChange=0,
pos=(-0.96, 0, 0.96))

Configuring the menu architecture

arq_menu = DirectOptionMenu(items=[‘Arquitetura’, ‘Laje’, ‘Viga’, ‘Pilar’, ‘Fundação’],
initialitem=0,
scale=.05,
textMayChange=0,
pos=(-0.74, 0, 0.96))

Configuring window

w = World()
ucs = P3DCreateAxes(1)
ucs.reparentTo(base.render)

props = WindowProperties()
props.setTitle(‘Aurora Python’)
base.win.requestProperties(props)
base.setBackgroundColor(0, 0, 0)
base.run()

I cannot manage to draw the lines called by the method P3DCreateAxes, any ideas why?

Firstly, please note that there is a Python symbol on top of the text you are typing. If you click on it, it will show all of your code with Python marking. You are new so I suspect you don’t know this

Yeah, I just copied and pasted the text without formatting, sorry.

Hi, welcome to the Panda3D community :slight_smile: !

The lines are drawn correctly, but they are not visible because they are at the same position as the camera (at coordinates (0., 0., 0.)). You can keep the right mouse button pressed and drag the mouse upwards–this moves the camera backwards–in order to see them.

See this manual page for more info on controlling the camera.

On a side note, it is advised to not use DirectStart, as this is a deprecated interface. And unless you are using a very old version of Panda, it is recommended to import from panda3d.core instead of from pandac.PandaModules. So the start of your script could look like this:

from panda3d.core import WindowProperties, NodePath, LineSegs
from direct.showbase.ShowBase import ShowBase
from direct.gui.DirectGui import *
import win32gui, win32con
from direct.showbase.DirectObject import DirectObject # for event handling
import sys


base = ShowBase()


def P3DCreateAxes(lineThickness=1):
from pandac.PandaModules import WindowProperties
from direct.gui.DirectGui import *
from direct.showbase.ShowBase import ShowBase
from panda3d.core import NodePath, LineSegs
import win32gui, win32con



class App(ShowBase):
    def __init__(self):
        ShowBase.__init__(self)
        # Apply scale and position transforms on the model.
        self.camera.setPos(0,0,10)
        #Maximizing window
        self.hwnd = win32gui.GetForegroundWindow()
        win32gui.PostMessage(self.hwnd, win32con.WM_SYSCOMMAND, win32con.SC_MAXIMIZE, 0)

app = App()

def P3DCreateAxes(lineThickness=1):
    ls = LineSegs()
    ls.setThickness(lineThickness)

    # X axis
    ls.setColor(1.0, 0.0, 0.0, 1.0)
    ls.moveTo(0.0, 0.0, 0.0)
    ls.drawTo(1.0, 0.0, 0.0)

    # Y axis
    ls.setColor(0.0, 1.0, 0.0, 1.0)
    ls.moveTo(0.0, 0.0, 0.0)
    ls.drawTo(0.0, 1.0, 0.0)

    # Z axis
    ls.setColor(0.0, 0.0, 1.0, 1.0)
    ls.moveTo(0.0, 0.0, 0.0)
    ls.drawTo(0.0, 0.0, 1.0)

    node = ls.create()
    return NodePath(node)

# Configuring the menu file
file_menu = DirectOptionMenu(items=['Arquivo', 'Novo', 'Abrir', 'Salvar', 'Fechar'],
                             initialitem=0,
                             scale=.05,
                             textMayChange=0,
                             pos=(-0.96, 0, 0.96))

# Configuring the menu architecture
arq_menu = DirectOptionMenu(items=['Arquitetura', 'Laje', 'Viga', 'Pilar', 'Fundação'],
                            initialitem=0,
                            scale=.05,
                            textMayChange=0,
                            pos=(-0.74, 0, 0.96))



# Configuring window
ucs = P3DCreateAxes(1)
ucs.reparentTo(app.render)


props = WindowProperties()
props.setTitle('Aurora Python')
app.win.requestProperties(props)
app.setBackgroundColor(0, 0, 0)

app.run()

I managed to see the lines with the mouse controlled camera, but not by setting a distance on app constructor, what am I doing wrong?

You’ll need to call self.disable_mouse() in your App class. Don’t worry, it doesn’t mean your mouse will suddenly walk on crutches :grin:. It’s a bit of a misnomer and simply means that the camera is no longer controlled by the built-in system.

EDIT:
But you should offset the camera along the negative Y-direction in order to see the lines, not along the positive Z-direction:

        self.disable_mouse()
        self.camera.set_pos(0., -10., 0)

I want to control it by mouse, since it will be an architecture software. self.disable_mouse() does nothing on code though.

Yeah, I didn’t notice that you’re moving the camera upwards instead of backwards in your code, I edited my answer above.

Thanks, it worked. :smiley:

Glad I could help :slight_smile: .

Is there a way to enable mouse without moving the camera?

If you want to control the camera using code while keeping the built-in system enabled, then you can change the position of the trackball node that is used by the system internally:

        self.trackball.node().set_pos(0., 10., 0.)

Note that the coordinates need to be inverted for this to work, which is not always intuitive.
Is this what you were looking for, or did you mean something else?
(The mouse can still be used to click on buttons etc. after calling self.disable_mouse(), by the way.)

I meant if can I control the camera using mouse after calling disable_mouse? Controlling the camera is a vital aspect of my software.

Yes, you can create a custom camera controller that responds to mouse events. There are some examples of custom controllers on these forums, but I believe most of them are keyboard-controlled. Here is one that I implemented some time ago; it uses the left mouse button for orbiting and the mouse wheel for zooming:

class CameraController:

    def __init__(self, showbase):

        self.showbase = showbase
        self.task_mgr = showbase.task_mgr
        self.mouse_watcher = showbase.mouseWatcherNode
        self.world = showbase.render
        self.cam = showbase.camera
        self.cam_lens = showbase.camLens
        self.cam_target = showbase.render.attach_new_node("camera_target")
        self.cam.reparent_to(self.cam_target)
        self.cam.set_y(-10.)
        self.mouse_prev = Point2()
        win_props = showbase.win.get_properties()
        w, h = win_props.get_x_size(), win_props.get_y_size()
        self.orbit_speed = (w * .15, h * .15)
        self.pan_start_pos = Point3()
        self.listener = listener = DirectObject()
        listener.accept_once("mouse1", self.start_orbiting)
        listener.accept_once("mouse3", self.start_panning)
        listener.accept("wheel_up", self.zoom_step_in)
        listener.accept("wheel_down", self.zoom_step_out)

    def stop_navigating(self):

        self.task_mgr.remove("transform_cam")
        self.listener.accept_once("mouse1", self.start_orbiting)
        self.listener.accept_once("mouse3", self.start_panning)

    def start_orbiting(self):

        win_props = self.showbase.win.get_properties()
        w, h = win_props.get_x_size(), win_props.get_y_size()
        self.orbit_speed = (w * .15, h * .15)
        self.mouse_prev = Point2(self.mouse_watcher.get_mouse())
        self.task_mgr.add(self.orbit, "transform_cam")
        self.listener.ignore("mouse3")
        self.listener.accept_once("mouse1-up", self.stop_navigating)

    def orbit(self, task):
        """
        Orbit the camera about its target point by offsetting the orientation
        of the target node with the mouse motion.

        """

        if self.mouse_watcher.has_mouse():
            mouse_pos = self.mouse_watcher.get_mouse()
            speed_x, speed_y = self.orbit_speed
            d_h, d_p = (mouse_pos - self.mouse_prev)
            d_h *= speed_x
            d_p *= speed_y
            target = self.cam_target
            target.set_hpr(target.get_h() - d_h, target.get_p() + d_p, 0.)
            self.mouse_prev = Point2(mouse_pos)

        return task.cont

    def zoom_step_in(self):
        """Translate the camera along its positive local Y-axis to zoom in"""

        target_dist = self.cam.get_y()
        self.cam.set_y(self.cam, -target_dist * .1)

    def zoom_step_out(self):
        """Translate the camera along its negative local Y-axis to zoom out"""

        target_dist = self.cam.get_y()
        self.cam.set_y(self.cam, target_dist * .1)

    def __get_pan_pos(self, pos):

        if not self.mouse_watcher.has_mouse():
            return False

        target = self.cam_target
        target_pos = target.get_pos()
        normal = self.world.get_relative_vector(target, Vec3(0., 1., 0.))
        plane = Plane(normal, target_pos)
        m_pos = self.mouse_watcher.get_mouse()
        near_point = Point3()
        far_point = Point3()
        self.cam_lens.extrude(m_pos, near_point, far_point)
        near_point = self.world.get_relative_point(self.cam, near_point)
        far_point = self.world.get_relative_point(self.cam, far_point)
        plane.intersects_line(pos, near_point, far_point)

        return True

    def start_panning(self):

        if not self.mouse_watcher.has_mouse():
            return

        self.listener.ignore("mouse1")
        self.listener.accept_once("mouse3-up", self.stop_navigating)
        self.__get_pan_pos(self.pan_start_pos)
        self.task_mgr.add(self.pan, "transform_cam")

    def pan(self, task):

        pan_pos = Point3()

        if not self.__get_pan_pos(pan_pos):
            return task.cont

        self.cam_target.set_pos(self.cam_target.get_pos() + (self.pan_start_pos - pan_pos))

        return task.cont


class App(ShowBase):
    def __init__(self):
        ShowBase.__init__(self)
        ucs = P3DCreateAxes(1)
        ucs.reparentTo(self.render)
        self.disable_mouse()
        cam_ctrl = CameraController(self)

app = App()
app.run()

Of course this is just an example and not very complete (no panning, for example), but it might be a good start.

EDIT:
added panning (use right mouse button).

Worked like a charm, I’ll try to implement pan to complete it.

Actually, panning is pretty much the hardest part to get right, so I looked for it in some of my other projects and integrated it into the code I posted previously.

Hopefully this can save you some work :slight_smile: .

1 Like

Thanks, you are a life saver! I’ll study your code and integrate into mine.

There is some problem with your camera controller, I guess.

    def zoom_step_in(self):
        """Translate the camera along its positive local Y-axis to zoom in"""

        target_dist = self.cam.get_y()
        new_target_y = -target_dist *0.1
        print(new_target_y)
        self.cam.set_y(self.cam, new_target_y)

    def zoom_step_out(self):
        """Translate the camera along its negative local Y-axis to zoom out"""

        target_dist = self.cam.get_y()
        new_target_y = target_dist * 0.1
        print(new_target_y)
        self.cam.set_y(self.cam, new_target_y)

This makes me zoom less and less or more and more (depending on the wheel of the mouse) each step, what do you say that could fix this?

Looking at the code, I suspect that said behaviour is intentional, producing smaller changes when near to the target-object and larger changes when far from it.

If you want constant zoom-step sizes, you could change the code to something like this:

def zoom_step_in(self):
    current_dist = self.cam.get_y()
    new_dist = current_dist - 0.1 # Or some value that you prefer

    # Now, this would allow the camera-position to run below zero,
    # which may cause problems. Thus we set up a threshold at
    # which it stops.
    if new_dist < 0.1: # Or some other threshold, as appropriate
        new_dist = 0.1 # Again, or whatever threshold you prefer

    print (new_dist)
    self.cam.set_y(new_dist)

And then similarly for the other function.

(Note that the above is untested!)

The current behavior makes zooming a more comfortable experience (for me personally, at least), as otherwise I wouldn’t have enough control when zooming in very closely (due to the high speed, i.e. relatively big zoom steps), while zooming out of a large scene would be very slow. It also prevents zooming past the object I’m viewing.

What I would suggest is to temporarily change the behavior by holding down a modifier key.
Here is a revision of the code that allows zooming with a constant value when you keep the control key pressed, but keeps the current behavior when the key is not pressed (you could switch this around, of course):

class CameraController:

    def __init__(self, showbase):

        self.showbase = showbase
        self.task_mgr = showbase.task_mgr
        self.mouse_watcher = showbase.mouseWatcherNode
        self.world = showbase.render
        self.cam = showbase.camera
        self.cam_lens = showbase.camLens
        self.cam_target = showbase.render.attach_new_node("camera_target")
        self.cam.reparent_to(self.cam_target)
        self.cam.set_y(-10.)
        self.mouse_prev = Point2()
        win_props = showbase.win.get_properties()
        w, h = win_props.get_x_size(), win_props.get_y_size()
        self.orbit_speed = (w * .15, h * .15)
        self.pan_start_pos = Point3()
        self.zoom_step_factor = 1.  # additional zoom step multiplier
        self.dolly_speed = .1
        self.listener = listener = DirectObject()
        listener.accept_once("mouse1", self.start_orbiting)
        listener.accept_once("mouse3", self.start_panning)
        listener.accept("wheel_up", self.zoom_step_in)
        listener.accept("wheel_down", self.zoom_step_out)
        listener.accept("control-wheel_up", self.zoom_step_in_const)
        listener.accept("control-wheel_down", self.zoom_step_out_const)
        listener.accept("-", self.decr_zoom_step_factor)
        listener.accept("--repeat", self.decr_zoom_step_factor)
        listener.accept("+", self.incr_zoom_step_factor)
        listener.accept("+-repeat", self.incr_zoom_step_factor)
        listener.accept_once("arrow_down", self.start_dollying_backwards)
        listener.accept_once("arrow_up", self.start_dollying_forwards)

    def stop_navigating(self):

        self.task_mgr.remove("transform_cam")
        self.listener.accept_once("mouse1", self.start_orbiting)
        self.listener.accept_once("mouse3", self.start_panning)
        self.listener.accept_once("arrow_down", self.start_dollying_backwards)
        self.listener.accept_once("arrow_up", self.start_dollying_forwards)

    def start_orbiting(self):

        win_props = self.showbase.win.get_properties()
        w, h = win_props.get_x_size(), win_props.get_y_size()
        self.orbit_speed = (w * .15, h * .15)
        self.mouse_prev = Point2(self.mouse_watcher.get_mouse())
        self.task_mgr.add(self.orbit, "transform_cam")
        self.listener.ignore("mouse3")
        self.listener.ignore("arrow_down")
        self.listener.ignore("arrow_up")
        self.listener.accept_once("mouse1-up", self.stop_navigating)

    def orbit(self, task):
        """
        Orbit the camera about its target point by offsetting the orientation
        of the target node with the mouse motion.

        """

        if self.mouse_watcher.has_mouse():
            mouse_pos = self.mouse_watcher.get_mouse()
            speed_x, speed_y = self.orbit_speed
            d_h, d_p = (mouse_pos - self.mouse_prev)
            d_h *= speed_x
            d_p *= speed_y
            target = self.cam_target
            target.set_hpr(target.get_h() - d_h, target.get_p() + d_p, 0.)
            self.mouse_prev = Point2(mouse_pos)

        return task.cont

    def zoom_step_in(self):
        """Translate the camera along its positive local Y-axis to zoom in"""

        target_dist = self.cam.get_y()
        self.cam.set_y(self.cam, -target_dist * .1)

    def zoom_step_out(self):
        """Translate the camera along its negative local Y-axis to zoom out"""

        target_dist = self.cam.get_y()
        self.cam.set_y(self.cam, target_dist * .1)

    def zoom_step_in_const(self):
        """Zoom in using a constant step value"""

        current_dist = self.cam.get_y()
        new_dist = current_dist + .1 * self.zoom_step_factor

        # Prevent the camera to move past its target node
        if new_dist > -.01:
            new_dist = -.01

        self.cam.set_y(new_dist)

    def zoom_step_out_const(self):
        """Zoom out using a constant step value"""

        current_dist = self.cam.get_y()
        new_dist = current_dist - .5 * self.zoom_step_factor
        self.cam.set_y(new_dist)

    def decr_zoom_step_factor(self):
        """Decrease the value with which to multiply the zoom steps"""

        self.zoom_step_factor -= .1 # Or some value that you prefer

        # Prevent the multiplier from becoming too small
        if self.zoom_step_factor < .001:
            self.zoom_step_factor = .001

        print("self.zoom_step_factor:", self.zoom_step_factor)

    def incr_zoom_step_factor(self):
        """Increase the value with which to multiply the zoom steps"""

        self.zoom_step_factor += .1 # Or some value that you prefer
        print("self.zoom_step_factor:", self.zoom_step_factor)

    def start_dollying_backwards(self):

        self.task_mgr.add(self.dolly, "transform_cam")
        self.listener.ignore("mouse3")
        self.listener.ignore("mouse1")
        self.listener.ignore("arrow_up")
        self.listener.accept_once("arrow_down-up", self.stop_navigating)

    def start_dollying_forwards(self):

        self.task_mgr.add(self.dolly, "transform_cam")
        self.listener.ignore("mouse3")
        self.listener.ignore("mouse1")
        self.listener.ignore("arrow_down")
        self.listener.accept_once("arrow_up-up", self.stop_navigating)

    def dolly_backwards(self, task):
        """Move the camera target backwards"""

        self.cam_target.set_y(self.cam_target, -self.dolly_speed)

        return task.cont

    def dolly_forwards(self, task):
        """Move the camera target forwards"""

        self.cam_target.set_y(self.cam_target, self.dolly_speed)

        return task.cont

    def __get_pan_pos(self, pos):

        if not self.mouse_watcher.has_mouse():
            return False

        target = self.cam_target
        target_pos = target.get_pos()
        normal = self.world.get_relative_vector(target, Vec3(0., 1., 0.))
        plane = Plane(normal, target_pos)
        m_pos = self.mouse_watcher.get_mouse()
        near_point = Point3()
        far_point = Point3()
        self.cam_lens.extrude(m_pos, near_point, far_point)
        near_point = self.world.get_relative_point(self.cam, near_point)
        far_point = self.world.get_relative_point(self.cam, far_point)
        plane.intersects_line(pos, near_point, far_point)

        return True

    def start_panning(self):

        if not self.mouse_watcher.has_mouse():
            return

        self.listener.ignore("mouse1")
        self.listener.ignore("arrow_down")
        self.listener.ignore("arrow_up")
        self.listener.accept_once("mouse3-up", self.stop_navigating)
        self.__get_pan_pos(self.pan_start_pos)
        self.task_mgr.add(self.pan, "transform_cam")

    def pan(self, task):

        pan_pos = Point3()

        if not self.__get_pan_pos(pan_pos):
            return task.cont

        self.cam_target.set_pos(self.cam_target.get_pos() + (self.pan_start_pos - pan_pos))

        return task.cont

The added code is similar to what Thaumaturge suggested. Additionally, the code is extended further to allow the + and - keys to increase or decrease the constant step size.
The default step size to zoom out is 0.5, while the default step size to zoom in is 0.1; feel free to change those values to whatever you prefer as well.

2 Likes