Given two lines, can we create a centre line between them? This MicroStation Python AddIn lets you choose two lines, arcs, curves or B-splines, then construct a centre line between them …
This example demonstrates several aspects of MicroStation Python …
This AddIn uses the
CurveVector
and related classes CurvePrimitive
and CurveLocatonDetail
to compute points along a line.
The UI is created using PyQt standard Widgets.
QPushButton
. It starts an Element Picker tool
QPushButton
. It starts an Element Picker tool
QPushButton
. It starts the calculation to create a centre line
QLabel
QLineEdit
where user can adjust the interval between points
QLabel
QPushButton
. Its behaviour is modified so it behaves like a toggle switch
QMenuBar
. It displays File and Help menus, which are QAction
s.
Beneath the Pick Line 1 and Pick Line 2 buttons are QLabel
s.
They display the element ID of the element picked after clicking those buttons.
The point interval value is shown in a QLineEdit widget. I've added a QIntValidator to constrain the value that a user may enter.
DgnElementSetTool
cmdPickLineElement
inherits from (i.e. is a sub-class of) DgnElementSetTool
.
The _OnPostLocate
gets an ElementHandle
from the supplied HitPath
and converts it to a CurveVector
…
eh = ElementHandle(path.GetHeadElem(), path.GetRoot()) curve = ICurvePathQuery.ElementToCurveVector(eh)
It tests the CurveVector
to verify that is is an open path (curve.IsOpenPath()
).
It accepts the CurveVector
if it contains at least one suitable type …
_ACCEPTABLE_TYPES = (ICurvePrimitive.eCURVE_PRIMITIVE_TYPE_Line,
ICurvePrimitive.eCURVE_PRIMITIVE_TYPE_LineString,
ICurvePrimitive.eCURVE_PRIMITIVE_TYPE_Arc,
ICurvePrimitive.eCURVE_PRIMITIVE_TYPE_CurveVector,
ICurvePrimitive.eCURVE_PRIMITIVE_TYPE_BsplineCurve,
)
primitiveType = primitive.GetCurvePrimitiveType()
if primitiveType in acceptable_types:
# We can use this element
CurveLocationDetail
To calculate the centre line we first compute a set of points on each line spaced by interval. Then we construct a chord between each pair of corresponding points Finally, we draw a line-string connecting the mid-point of each chord.
In this example, we label each point to illustrate the result of the calculation. User can choose not to label the points
Both PyQt and TkInter use windows messaging. MicroStation uses Windows messaging. Unfortunately, the two messaging mechanisms clash, or more accurately become entangled in a deadlock.
A consequence of such a deadlock is that the host application (MicroStation) can freeze. Worse still, the application may crash (terminate with no explanation).
During development I found that placing any MicroStation function call in a QWidget
's action implementation causes a crash.
For example, this function crashes MicroStation …
def queue_action_pick_line(self):
# The following statement crashes MicroStation
cmdPickLineElement.InstallNewInstance() # Calls the DgnElementSetTool
constructor
My work-around removes code that calls into MicroStation from those PyQt action functions. It re-implements the calls outside the PyQt messaging loop by setting an action flag …
def queue_action_pick_line(self): self._cmd_pick_line_requested = True
The action flags are stored in a list _requests
.
The flags are tested in the UI's messaging loop …
def ms_mainLoop(self): """ Custom main loop that pumps both Qt and MicroStation events, and executes any deferred tool-start calls in a safe context. This loop is the key to letting the UI and tool coexist. """ while win32gui.IsWindow(self._storedWinId): # Standard pump for a hybrid Qt/MicroStation application self._loop.processEvents() PyCadInputQueue.PythonMainLoop() self._set_requested_flags() # Check if a tool start was requested by a QWidget action ((mod, cmd), flag) = self._requests.have_request() if flag: # If yes, run the stored function (e.g., InstallNewInstance) print(f"requested '{mod}:{cmd}'") invoke_function(mod, cmd, self) # Clear the flag so it only runs once per click self._requests.clear_requests() time.sleep(0.001)
That _requests logic is encapsulated in class CommandRequests
(module command_requests.py
).
That logic is clunky and not scaleable. If this application had, say, ten commands and not two then maintenance becomes harder. A permanent solution would be if Bentley Systems could create a better way to raise a barrier between MicroStation messaging and UI messaging.
Unpack the ZIP file and copy the Python files into a folder that MicroStation knows about.
Use MicroStation's Python Manager to find and execute the script …
Post questions about MicroStation Python programming to the MicroStation Programming Forum.