>when you mention
>import sys
>sys.path.append("./ns-3-dev/build/bindings/python")
>sys.path.append("./ns-3-dev/build/lib")
>from ns import ns
>where is this occurring?
>In
 Ubuntu, I can add to the path by modifying .bashrc or do you mean 
adding this information to visual studio code somewhere?
>or do 
you mean that the four commands should occur after I enter the python 
venv and before I run visual studio code in ns-3-dev?
>I have a feeling this is critical information that would be helpful to others.
This
 goes into your python script, or jupyter notebook. This is an 
alternative to explicitly setting LIBRARY_PATH and PYTHONPATH 
environmental variables.
You then run this from your python 
interpreter after activating the venv. This way it always work as long 
as the interpreter has cppyy available and ns-3 was built from source.
$ git clone 
https://gitlab.com/nsnam/ns-3-dev$ pip install cppyy
$ cd ns-3-dev
$ python -m venv ns3env
$ source ./ns3env/bin/activate
$ ./ns3 configure --enable-python-bindings
$ ./ns3 build
Then when you need to run, if you call via the ns3 script, it will set these environment variables for you, so you don't need to do anything
$ cp examples/tutorial/first.py ./
$ ./ns3 run ./first.py
[0/2] Re-checking globbed directories...
ninja: no work to do.
[runStaticInitializersOnce]: Failed to materialize symbols: { (main, { __cxx_global_var_initcling_module_148_, _GLOBAL__sub_I_cling_module_148, _ZN3ns3L16g_timeInitHelperE, $.cling-module-148.__inits.0, __orc_init_func.cling-module-148 }) }
[runStaticInitializersOnce]: Failed to materialize symbols: { (main, { __orc_init_func.cling-module-148 }) }
At time +2s client sent 1024 bytes to 10.1.1.2 port 9
At time +2.00369s server received 1024 bytes from 10.1.1.1 port 49153
At time +2.00369s server sent 1024 bytes to 10.1.1.1 port 49153
At time +2.00737s client received 1024 bytes from 10.1.1.2 port 9
Ignore the failed to materialize symbols stuff. It is an bug in Cppyy >=3 that appeared when they updated from Clang 9 to 13.
Working like this is terrible, in my opinion. You really want debuggers available when working Cppyy, because autocomplete doesn't really exist.
And their smart pointer stuff doesn't really play nicely with our custom implementation, so it is quite cumbersome dereferencing pointers.
If you have a quick look at the demos you will see what I mean.
If you don't set the  environment variables not set this code block I referred to, you will get this (for ns-3 examples, which have a custom error handler. For normal python scripts, you will get a ModuleNotFoundException or something like this.).
$ python3 ./first.py
Error: ns3 Python module not found; Python bindings may not be enabled or your PYTHONPATH might not be properly configured
If you prepend this code to the ./first.py, it will work normally again. 
+import sys
+import os
+sys.path.append(f"{os.path.dirname(__file__)}/build/bindings/python")
+sys.path.append(f"{os.path.dirname(__file__)}/build/lib") # this assumes first.py is inside the ns-3-dev directory
try:
    from ns import ns
except ModuleNotFoundError: 
    raise SystemExit(
        "Error: ns3 Python module not found;"
                                    " Python bindings may not be enabled"
                                    " or your PYTHONPATH might not be properly configured"
    )
And now you can debug it with your favorite IDE, like any python script.
$ python3 first.py
[runStaticInitializersOnce]: Failed to materialize symbols: { (main, { _GLOBAL__sub_I_cling_module_148, _ZN3ns3L16g_timeInitHelperE, __orc_init_func.cling-module-148, __cxx_global_var_initcling_module_148_, $.cling-module-148.__inits.0 }) }
[runStaticInitializersOnce]: Failed to materialize symbols: { (main, { __orc_init_func.cling-module-148 }) }
At time +2s client sent 1024 bytes to 10.1.1.2 port 9
At time +2.00369s server received 1024 bytes from 10.1.1.1 port 49153
At time +2.00369s server sent 1024 bytes to 10.1.1.1 port 49153
At time +2.00737s client received 1024 bytes from 10.1.1.2 port 9