import pytest
from selenium import webdriver

from mtxweb.page.Mtx_Left import Mtx_Left
from mtxweb.page.Mtx_Login import Mtx_Login
from mtxweb.page.Mtx_Myjifen import Mtx_Myjifen
from mtxweb.tools.log import Logger

class Test_Myjifen():

    def setup_class(self):
        self.dev = webdriver.Chrome()
        self.loggerobj = Logger().get_logger()
        self.loginobj = Mtx_Login(self.dev)
        self.leftobj = Mtx_Left(self.dev)
        self.jifenobj = Mtx_Myjifen(self.dev)
        self.loggerobj.info("初始化")

    def test_login(self):
        self.loginobj.login()
        self.loggerobj.info("登录")

    def test_goto_jifen(self):
        self.leftobj.myshopt()
        self.leftobj.myjifen()
        self.loggerobj.info("积分页面")

    def test_search(self):
        self.jifenobj.jifen_search()
        self.loggerobj.info("搜索")

    def test_search_condition(self):
        self.jifenobj.jifen_clear()
        self.jifenobj.jifen_search_condition()
        self.loggerobj.info("搜索条件")

    def teardown_class(self):
        self.dev.quit()
        self.loggerobj.info("退出")

if __name__ == '__main__':
    pytest.main(['-s'])


import pandas as pd
import seaborn as sns
import os
import numpy as np
import matplotlib.pyplot as plt
from collections import Counter

df=pd.read_csv('/home/frozenwolfy/Desktop/Ed/PS_ML/Telemarketing/cities.csv')
df.dropna

counts = dict()
row,col=df.shape
for i in range(row):
  counts[df.iloc[i,0]] = counts.get(df.iloc[i,0], 0) + 1

# print(counts)

mapping_dict={
"City" : {
"Kota":1,
"Jaipur":2,
"Indore":3,
"Other":0
}
}

df=df.replace(mapping_dict)
# print(type(counts.keys))
print(counts.values())
print(counts.keys())

############################################
###############PIE GRAPH#####################
#############################################

# labels = counts.keys()
# sizes = counts.values()
# explode = (0, 0, 0, 0)  # only "explode" the 2nd slice (i.e. 'Hogs')
#
# fig1, ax1 = plt.subplots()
# ax1.pie(sizes, explode=explode, labels=labels, autopct='%1.1f%%',
#         shadow=True, startangle=90)
# ax1.axis('equal')  # Equal aspect ratio ensures that pie is drawn as a circle.
# plt.title("""Cities from the Telemarketing data
# """)
# plt.show()

# pos = np.arange(len(counts.keys()))
# width = 1.0     # gives histogram aspect to the bar diagram
#
# ax = plt.axes()
# ax.set_xticks(pos + (width / 2))
# ax.set_xticklabels(counts.keys())
#
# plt.bar(counts.keys(),counts.values(), color='g')
# #                            ^^^^^^ what should I put here?
# plt.show()

##############################################
###############HISTOGRAM######################
##############################################
plt.bar(counts.keys(), counts.values(), color='k')
plt.show()


#!/usr/bin/python3

#print("purpose is to download weather information and save every hour as e.g. mon-1pm-grid.json or mon-5am-hourly.json")
import os # used for evironment variables
from dotenv import load_dotenv
load_dotenv()

import urllib.request
#from urllib.error import HTTPError
import json

from datetime import date, datetime, timedelta, timezone
import pytz

def getURLdata(url):
    try:
        operUrl = urllib.request.urlopen(url)
        if(operUrl.getcode()==200):
            data = operUrl.read()
            jsonData = json.loads(data)
        else:
            print("Error receiving data", operUrl.getcode())
            jsonData = {'status': 'failure to retrieve'}
    except:
        jsonData = {'status': 'error: failure to retrieve'}
        print(jsonData['status'])
    return jsonData

def writeFile(filedata, myfilename):
    if (len(filedata)) > 1:
        with open(myfilename, 'w') as outfile:
            json.dump(filedata, outfile)

def main():
    eastern = pytz.timezone("US/Eastern")
    dtnow = eastern.localize(datetime.now())
    fdt = dtnow.strftime("%a-%I%p")
    homeDir = os.environ.get("WUTILFILEPATH")
    #print(homeDir)
    hourData = "https://api.weather.gov/gridpoints/OKX/47,53/forecast/hourly"
    gridData = "https://api.weather.gov/gridpoints/OKX/47,53"

    hourInfo = getURLdata(hourData)
    writeFile(hourInfo,homeDir + fdt+"-hour.json")

    gridInfo = getURLdata(gridData)
    writeFile(gridInfo,homeDir + fdt+"-grid.json")

if __name__ == "__main__":
    main()

#print(os.environ)


import re
from configparser import ConfigParser
from enum import Enum
from typing import Iterable, Iterator, NamedTuple, TextIO, Tuple

MAX_PARALLEL_DOWNLOADS = 16

# NB: The 'main' section in `{yum,dnf}.conf` acts similarly to
# ConfigParser's magic 'DEFAULT', in that it provides default values for
# some of the repo options.  I did not investigate this in enough detail to
# say that setting `default_section='main'` would be appropriate.  Since
# this code currently only cares about `baseurl`, this is good enough.
_NON_REPO_SECTIONS = ["DEFAULT", "main"]

class YumDnf(Enum):
    yum = "yum"
    dnf = "dnf"

class YumDnfConfRepo(NamedTuple):
    name: str
    base_url: str
    gpg_key_urls: Tuple[str]

    @classmethod
    def from_config_section(cls, name, cfg_sec) -> "YumDnfConfRepo":
        assert "/" not in name and "\0" not in name, f"Bad repo name {name}"
        return YumDnfConfRepo(
            name=name,
            base_url=cfg_sec["baseurl"],
            # pyre-fixme[6]: For 3rd param expected `Tuple[str]` but got
            #  `Union[Tuple[], typing.Tuple[typing.Any, ...]]`.
            gpg_key_urls=tuple(re.findall(r"[^\n\s,]+", cfg_sec["gpgkey"]))
            if "gpgkey" in cfg_sec
            else (),
        )

def _isolate_ssl_options(cfg) -> None:
    # We don't actually need the SSL options, because we serve everything
    # over HTTP from the local `repo-server`, so they shouldn't affect
    # anything.  However, `yum` prints this annoying logspam when the
    # snapshot's original `yum.conf` contains `sslcacert`, and when that
    # cert is no longer included in the image with the snapshot:
    #     Repo EACH_REPO forced skip_if_unavailable=True due to BAD_CA_FILE
    # So, let's eliminate references to non-default files that may not exist
    # (this leaves `sslverify` alone since it's not a file):
    for ssl_opt in ["sslcacert", "sslclientcert", "sslclientkey"]:
        cfg.pop(ssl_opt, None)

class YumDnfConfIsolator:
    """
    The functions in this class ATTEMPT to edit `{yum,dnf}.conf` in such a
    way that the package manager will:
      - never interact with state, caches, or configuration from the host
        filesystem,
      - never interact with servers outside of the ones we specify.

    As per the file-docblock note, `dnf` isolation is likely incomplete.

    IMPORTANT: With `yum`, it is actually impossible to configure it such
    that it does not touch the host filesystem.  A couple of specific
    examples:

    (1) Regardless of the configuration, `yum` will look in
        `$host_root/$cachedir` BEFORE `$installroot/$cachedir`, which is
        breaks isolation of RPM content and repodata.

    (2) Regardless of the configuration, `yum` will attempt to read
        `/etc/yum/vars` from the host, breaking isolation of configuration.

    There are other examples. To see the bind-mount protections we use to
    avoid leakage from the host, read `_isolate_yum_dnf_and_wait_until_ready` --
    and of course, the larger purpose of `yum-dnf-from-snapshot` is to run
    its `yum` or `dnf` inside a private network namespace to guarantee no
    off-host repo accesses.
    """

    def __init__(self, yum_dnf: YumDnf, cp: ConfigParser) -> None:
        self._yum_dnf = yum_dnf
        self._cp = ConfigParser()
        self._cp.read_dict(cp)  # Make a copy
        self._isolated_main = False
        self._isolated_repos = False

    def isolate_repos(self, repos: Iterable[YumDnfConfRepo]) -> "YumDnfConfIsolator":
        """
        Asserts that the passed repos are exactly those defined in the
        config file. This ensures that we leave no repo unisolated.

        For each specified repo, sets the config values specified in its
        `YumDnfConfRepo`, and clears `proxy`.  Other config keys are left
        unchanged -- but seeing some "known bad" configs in the config file
        will cause an assertion error.

        IMPORTANT: See the class docblock, this is not **ENOUGH**.
        """
        unchanged_repos = {r for r in self._cp if r not in _NON_REPO_SECTIONS}
        for repo in repos:
            unchanged_repos.remove(repo.name)
            assert repo.name not in _NON_REPO_SECTIONS
            repo_sec = self._cp[repo.name]
            repo_sec["baseurl"] = (
                "\n".join(repo.base_url)
                if isinstance(repo.base_url, list)
                else repo.base_url
            )
            repo_sec["gpgkey"] = "\n".join(repo.gpg_key_urls)
            repo_sec.pop("proxy", None)  # We talk only to a local reposerver.
            # These are not handled for now, but could be supported. The
            # goal of asserting their absence is to avoid accidentally
            # having non-isolated URLs in the config.
            for unsupported_key in [
                "include",
                "metalink",
                "mirrorlist",
                "gpgcakey",
            ]:
                assert unsupported_key not in repo_sec, (unsupported_key, repo)
            _isolate_ssl_options(repo_sec)
        assert not unchanged_repos, f"Failed to isolate {unchanged_repos}"
        self._isolated_repos = True
        return self

    def isolate_main(
        self, *, config_path: str, pluginconf_dir: str, cache_dir: str
    ) -> "YumDnfConfIsolator":
        """
        Set keys that could cause `yum` or `dnf` to interact with the host
        filesystem.  IMPORTANT: See the class docblock, this is not ENOUGH.
        """
        prog_name = self._yum_dnf.value
        main_sec = self._cp["main"]
        assert (
            "include" not in main_sec and "include" not in self._cp["DEFAULT"]
        ), "Includes are not supported"

        # Since we have an immutable snapshot of the repos, we can pre-build
        # the cache as part of the snapshot, and it never expires.
        main_sec["cachedir"] = cache_dir
        main_sec["metadata_expire"] = "never"
        main_sec["check_config_file_age"] = "0"

        # This list was obtained by scrolling through `man yum.conf`.  To be
        # really thorough, we'd also remove glob filesystem dependencies
        # from options like `exclude`, `includepkgs`, `protected_packages`,
        # `exactarchlist`, etc -- but this is a moot point now that all RPM
        # installs go trough a build appliance.
        #
        # `persistdir` is under `--installroot`, so no isolation needed.
        # However, ensuring defaults makes later container customization
        # (e.g.  cleanup) easier.  These can be optionalized later if a good
        # reason arises.
        main_sec["persistdir"] = f"/var/lib/{prog_name}"  # default
        # Specify repos only via this `.conf` -- that eases isolating them.
        main_sec["reposdir"] = "/dev/null"
        # See the note about `persistdir` -- the same logic applies.
        main_sec["logfile"] = f"/var/log/{prog_name}.log"  # default
        main_sec["config_file_path"] = config_path
        # Having `dnf` download with high concurrency from the FB-internal
        # repo snapshot storage results in a speedup of over 2x.  This is
        # because concurrency masks significant per-blob setup overheads.
        main_sec["max_parallel_downloads"] = str(MAX_PARALLEL_DOWNLOADS)
        # CI hosts can experience resource starvation and slowdowns.  We
        # never expect to see timeouts in interactive use, so err on the
        # side of making them higher to improve CI reliability.
        main_sec["timeout"] = "90"
        main_sec.pop("proxy", None)  # We talk only to a local reposerver.

        # This forces that any incoming repo or RPM installation MUST be signed
        # with a key that is trusted (imported) in the destination RPM DB.
        main_sec["gpgcheck"] = "1"
        # FIXME: Temporarily block this out to try to troubleshoot some CI
        # code signing issues.  This isn't introducing a true security
        # problem, in the sense that any local RPM must have made it into
        # the source repo somehow -- but disabling the check does makes it
        # easier to pull in code without double-checking its trust.
        main_sec["localpkg_gpgcheck"] = "0"

        _isolate_ssl_options(main_sec)

        # `yum-dnf-from-snapshot` and friends need certain plugins, and they
        # are off by default in `yum`, so just enable them.  Don't worry,
        # this won't turn on all plugins installed in the BA --
        # `yum-dnf-from-snapshot` hardcodes a list of known-good ones on the
        # command-line.
        main_sec["plugins"] = "1"
        # Provide custom configuration, e.g. to set up version locking for
        # the RPM snapshot that'll use this yum/dnf config.
        main_sec["pluginconfpath"] = pluginconf_dir

        # This option seems to only exist for `dnf`.
        main_sec["varsdir"] = "/dev/null"

        # This final block of options seems only to exist for `yum`.
        #
        # Shouldn't make a difference for as-root runs, but it's good hygiene
        main_sec["usercache"] = "0"
        main_sec["syslog_device"] = ""  # We'll just use `logfile`.
        main_sec["bugtracker_url"] = ""
        main_sec["fssnap_devices"] = "!*"  # Snapshots don't make sense.
        assert not main_sec.get("commands")  # This option seems dodgy.

        # Make yum fail if one package in a list can't be installed.
        if self._yum_dnf == YumDnf.yum:
            main_sec["skip_missing_names_on_install"] = "0"
            main_sec["skip_missing_names_on_update"] = "0"

        # Disable weak/recommended deps. If you want it, explicitly install it.
        if self._yum_dnf == YumDnf.dnf:
            main_sec["install_weak_deps"] = "0"

        self._isolated_main = True
        return self

    def write(self, out: TextIO) -> None:
        "Outputs a `{yum,dnf}.conf` file with the changed configuration."
        assert self._isolated_main and self._isolated_repos
        self._cp.write(out)

class YumDnfConfParser:
    def __init__(self, yum_dnf: YumDnf, conf: TextIO) -> None:
        self._yum_dnf = yum_dnf
        self._cp = ConfigParser()
        self._cp.read_file(conf)

    def gen_repos(self) -> Iterator[YumDnfConfRepo]:
        "Raises if repo names cannot be used as directory names."
        for repo, cfg in self._cp.items():
            if repo not in _NON_REPO_SECTIONS:
                yield YumDnfConfRepo.from_config_section(repo, cfg)

    def isolate(self) -> YumDnfConfIsolator:
        return YumDnfConfIsolator(self._yum_dnf, self._cp)


import json
import base64
import base58
import struct
import requests
from typing import NamedTuple

class Creator(NamedTuple):
    Address: str
    Verified: bool
    Share: int

class Data(NamedTuple):
    Name: str
    Symbol: str
    Uri: str
    SellerFeeBasisPoints: int
    Creators: list

class Metadata(NamedTuple):
    Key: int
    UpdateAuthority: str
    Mint: str
    Data: Data
    PrimarySaleHappened: bool
    IsMutable: bool
    EditionNonce: int

def decode_rust_string(buffer: bytes, offset: int, isascii=True):
    ssize_r = struct.unpack("I", buffer[offset:(offset+4)])
    ssize = ssize_r[0]  # ssize_r=(ssize,)
    data = buffer[(offset+4):(offset+ssize+4)]
    if isascii:
        data_str = data.decode("ascii")
    else:
        data_str = data
    return data_str, ssize + 4  # sizeof uint is 4

def deserialize(s: str) -> str:
    """metadata struct:
    Key                     uchar
    UpdateAuthority         pubkey (4 bytes)
    Mint                    pubkey (4 bytes)
    Data                    Data
    PrimarySaleHappened     bool
    IsMutable               bool
    EditionNonce            *uint8

    Data struct:
    Name                    string
    Symbol                  string
    Uri                     string
    SellerFeeBasisPoints    uint16
    Creators                *[]Creator

    Creator struct:
    Address                 pubkey (4 bytes)
    Verified                bool
    Share                   uint8
    """
    b = base64.b64decode(s)
    key_r = struct.unpack("B", b[:1])
    key = key_r[0]  # key_r = (key,)
    ua = base58.b58encode(b[1:33]).decode("ascii")
    mint = base58.b58encode(b[33:65]).decode("ascii")

    name, name_offset = decode_rust_string(b, 65)
    symbol, symbol_offset = decode_rust_string(b, 65 + name_offset)
    uri, uri_offset = decode_rust_string(b, 65 + symbol_offset + name_offset)

    new_offset = 65 + symbol_offset + name_offset + uri_offset
    sfbp = struct.unpack("H", b[new_offset:(new_offset+2)])[0]
    new_offset += 2

    # creators pointer
    creators_pt = struct.unpack("B", b[new_offset:(new_offset+1)])[0]
    new_offset += 1

    # unpack creators
    size = struct.unpack("I", b[new_offset:(new_offset+4)])[0]
    new_offset += 4
    creators = list()
    creators_out = list()
    for _ in range(size):
        c = dict()
        c["Address"] = base58.b58encode(b[new_offset:(new_offset+32)]).decode("ascii")
        new_offset += 32
        c["Verified"] = struct.unpack("?", b[new_offset:(new_offset+1)])[0]
        new_offset += 1
        c["Share"] = struct.unpack("B", b[new_offset:(new_offset+1)])[0]
        new_offset += 1
        creators.append(c)
        creators_out.append(
            #Creator(c["Address"], c["Verified"], c["Share"])
            {
                'address' : c["Address"],
                'verified' : c["Verified"]
            }
        )

    primary_sale_happened = struct.unpack("?", b[new_offset:(new_offset+1)])[0]
    new_offset += 1
    is_mutable = struct.unpack("?", b[new_offset:(new_offset+1)])[0]
    new_offset += 1

    # creators pointer
    edition_pt = struct.unpack("B", b[new_offset:(new_offset+1)])[0]
    new_offset += 1
    edition_nonce = struct.unpack("B", b[new_offset:(new_offset+1)])[0]
    new_offset += 1

    #
    #  post processing
    #
    def remove_x00(s:str) -> str:
        return s.replace(b"\x00".decode("ascii"), "")
    uri = remove_x00(uri)
    name = remove_x00(name)
    symbol = remove_x00(symbol)

    # create data struct with creators list of structs inside
    #data_out = Data(name, symbol, uri, sfbp, creators_out)
    data_out = {
        'name': name,
        'symbol': symbol,
        'uri' : uri,
        'sfbp' :sfbp,
        'creators_out' : creators_out
    }

    #metadata_out = dict(key, ua, mint, data_out, primary_sale_happened, is_mutable, edition_nonce)
    metadata_out = {
        'key' : key,
        'ua' :ua,
        'mint': mint,
        'data_out': data_out,
        'primary_sale_happened': primary_sale_happened,
        'is_mutable': is_mutable,
        'edition_nonce' : edition_nonce
    }

    if False:
        print(f"""key={key}
mint={mint}
ua={ua}
name={name}
symbol={symbol}
uri={uri}
sfbp={sfbp}
*creators={creators_pt}
creators={creators}
primary_sale_happened={primary_sale_happened}
is_mutable={is_mutable}
*edition_nonce={edition_pt}
edition_nonce={edition_nonce}
"""
    )
    # print(b[new_offset:])
    # possibly some leftover shit

    return metadata_out

def deserialize_handler(d: dict) -> str:
    return deserialize(d["account"]["data"][0])

output = []
temp_output = []
creator_list = dict()

def set_wide_analysis(data):
    #with open("kam1_analysis.txt", "w") as f:
    for i in range(len(data["result"])):
        res = deserialize_handler(data['result'][i])
        for row in res['data_out']['creators_out']:
            if (row['verified'] == True):
                verified_address = row['address']
                if verified_address not in creator_list:
                    creator_list[verified_address] = 1
                else:
                    creator_list[verified_address] += 1
        temp_output.append([verified_address, res])

    # creators = []
    # for row in temp_output:
    #     if row[0] == max(creator_list, key=creator_list.get):
    #         print(row[1]['mint'])
    #         r = requests.get(row[1]['data_out']['uri']).json()
    #         creator_data = r['properties']['creators']
    #         for row in creator_data:
    #             creators.append(row['address'])
    #         break

    # print(creators)

    #Compare it to the most popular verified creator... doesn't work for multiple creators
    #Will need to check through mint authority
    for row in temp_output:
        if row[0] == max(creator_list, key=creator_list.get):
            output.append(row[1])
        else:
            #print(row[1]['mint'])
            continue

    with open('testoutput.json', 'w') as f:
        json.dump(temp_output, f)

    print(len(output))
    print(creator_list)

    #print(creator_list)

    #print(json.dumps(output))

with open("output_KAM1.json", "r") as f:
    data = json.load(f)

# print(deserialize_handler(data['result'][1]))
set_wide_analysis(data)


from datetime import datetime, timedelta
import subprocess
from sqlalchemy import BigInteger,\
                       Boolean,\
                       Column,\
                       DateTime,\
                       Numeric,\
                       Text
from sqlalchemy.event import listens_for

from models.utils.db import db, Model
from models.manager import Manager
from utils.config import FLASK_ROOT_PATH

class Question(Manager, Model):
    isPublic = Column(Boolean)

    isPublished = Column(Boolean, nullable=False, default=False)

    latitude = Column(Numeric(8, 5), nullable=True)

    longitude = Column(Numeric(8, 5), nullable=True)

    publishDuration = Column(BigInteger, nullable=False, default=180)

    radius = Column(BigInteger)

    text = Column(Text)

    voteDate = Column(DateTime,
                      nullable=False,
                      default=datetime.utcnow)

    voteDuration = Column(BigInteger, nullable=False, default=180)

    @property
    def publishDate(self):
        return self.voteDate + timedelta(seconds=self.voteDuration)

    def secondsLeftBeforePublish(self):
        timedeltaBeforePublish = self.publishDate - datetime.utcnow()
        return timedeltaBeforePublish.total_seconds()

    def timeLeftBeforePublish(self):
        secondsLeftBeforePublish = self.secondsLeftBeforePublish()
        m, s = divmod(secondsLeftBeforePublish, 60)
        return "%02d:%02d" % (m, s)

    def secondsLeftAfterPublish(self):
        timedeltaAfterPublish = self.publishDate \
                                + timedelta(seconds=self.publishDuration) \
                                - datetime.utcnow()
        return timedeltaAfterPublish.total_seconds()

    def timeLeftAfterPublish(self):
        secondsLeftAfterPublish = self.secondsLeftAfterPublish()
        m, s = divmod(secondsLeftAfterPublish, 60)
        return "%02d:%02d" % (m, s)

    def hasVoted(self, footprint):
        for answer in self.answers:
            if answer.hasVoted(footprint):
                return True
        return False

    def cleanAfterVote(self):
        for answer in self.answers:
            answer.cleanAfterVote()

    def cleanAfterPublish(self):
        if self.isPublic:
            return
        for answer in self.answers:
            db.session.delete(answer)
        db.session.delete(self)
        db.session.commit()

class MissingCoordsWithRadiusException(Exception):
    pass

def _check_coords_with_radius(question):
    if question.radius and (not question.latitude or not question.longitude):
        raise MissingCoordsWithRadiusException

@listens_for(Question, 'before_insert')
def after_insert(mapper, connect, self):
    _check_coords_with_radius(self)

def _trigger_vote(question):
    subprocess.Popen('PYTHONPATH="." python scripts/manager.py vote'
                     + ' --id ' + str(question.id),
                     shell=True,
                     cwd=FLASK_ROOT_PATH)

@listens_for(Question, 'after_insert')
def after_insert(mapper, connect, self):
    _trigger_vote(self)

def _trigger_publish(question):
    subprocess.Popen('PYTHONPATH="." python scripts/manager.py publish'
                     + ' --id ' + str(question.id),
                     shell=True,
                     cwd=FLASK_ROOT_PATH)

@listens_for(Question, 'after_update')
def after_update(mapper, connect, self):
    if self.isPublished:
        _trigger_publish(self)

class NotDeletablePublicQuestionException(Exception):
    pass

@listens_for(Question, 'before_delete')
def after_insert(mapper, connect, self):
    if self.isPublic:
        raise NotDeletablePublicQuestionException


# SPDX-License-Identifier: BSD-3-Clause
"""Simple node for testing tf."""
from __future__ import annotations

import contextlib
import typing

import numpy as np

import rclpy
from rclpy.node import Node
from tf2_ros import TransformException
from tf2_ros.buffer import Buffer
from tf2_ros.transform_listener import TransformListener

from spatial_transformation import Transformation, Rotation

if typing.TYPE_CHECKING:
    from geometry_msgs.msg import TransformStamped

def msg_to_transform(msg: TransformStamped) -> Transformation:
    """Convert a transform message to a Transformation instance."""
    rot = Rotation.from_quat(
        [
            msg.transform.rotation.x,
            msg.transform.rotation.y,
            msg.transform.rotation.z,
            msg.transform.rotation.w,
        ]
    )
    trans = [
        msg.transform.translation.x,
        msg.transform.translation.y,
        msg.transform.translation.z,
    ]

    return Transformation(rot, trans)

class FrameListener(Node):
    def __init__(self):
        super().__init__("vicon_tf2_frame_listener")

        # Declare and acquire `target_frame` parameter
        self.target_frame = (
            self.declare_parameter("target_frame", "rll_muscle_base")
            .get_parameter_value()
            .string_value
        )

        self.tf_buffer = Buffer()
        self.tf_listener = TransformListener(self.tf_buffer, self)

        # Call on_timer function every second
        self.timer = self.create_timer(1.0, self.on_timer)

    def on_timer(self):
        # Store frame names in variables that will be used to
        # compute transformations
        from_frame_rel = self.target_frame
        to_frame_rel = "world"

        # Look up for the transformation between frames
        try:
            transform_msg = self.tf_buffer.lookup_transform(
                to_frame_rel, from_frame_rel, rclpy.time.Time()
            )
            transform = msg_to_transform(transform_msg)
        except TransformException as ex:
            self.get_logger().exception(
                f"Could not transform {to_frame_rel} to {from_frame_rel}: {ex}"
            )
            return

        point = np.array([0.0, 0.0, 1.0])
        point_transformed = transform.apply(point)

        print()
        print(f"base ({self.target_frame}): {transform.translation}")
        print(f"point: {point_transformed}")

def main():
    rclpy.init()
    node = FrameListener()
    with contextlib.suppress(KeyboardInterrupt):
        rclpy.spin(node)

    rclpy.shutdown()

if __name__ == "__main__":
    main()


import multiprocessing
import time

"""
Semaphore管理一个内置的计数器，
每当调用acquire()时内置计数器-1；
调用release() 时内置计数器+1；
计数器不能小于0；当计数器为0时，acquire()将阻塞线程直到其他线程调用release()。
"""

# 使用信号量控制进程个数

# Semaphore用来控制访问共享资源的进程数量

# Semaphore 相当于 N 把锁，获取其中一把就可以执行了。信号量的总数 N 在构造时传入，s = Semaphore(N)。

# 例如multiprocessing.Semaphore(2)代表最多有两把锁，也就是说最多有两个进程同时访问共享资源
def worker(s, i):
    s.acquire()
    print(multiprocessing.current_process().name + " acquire")
    time.sleep(i)
    print(multiprocessing.current_process().name + " release")
    s.release()

if __name__ == "__main__":
    s = multiprocessing.Semaphore(2)
    for i in range(5):
        p = multiprocessing.Process(target=worker, args=(s, i * 2))
        p.start()


import sys
import pandas as pd
import requests
from fake_useragent import UserAgent
from concurrent.futures import ThreadPoolExecutor
from threading import Lock

proxies = {
    'http': 'http://127.0.0.1:7890',
    'https': 'http://127.0.0.1:7890',
}

ua = UserAgent()

# 获取输入文件名和输出文件名
input_file_path = f'./data/urls-to-be-redirected/google-splited/'
output_file_path = f'./data/urls-with-redirection/google-splited/'
input_file = input_file_path + sys.argv[1] + '.csv'
output_file = output_file_path + sys.argv[1] + '.feather'

# 读取输入文件
df = pd.read_csv(input_file)
df = df.reindex(columns=df.columns.to_list() + ['redirect_urls', 'final_url'])
df[['redirect_urls', 'final_url']] = df[['redirect_urls', 'final_url']].astype('object')

# 创建锁
lock = Lock()

# 设置计时器初始值为 0
timer = 0

def process_url(i):
    global timer
    url = df.at[i, 'original_url']
    # user_agent = ua.random
    # headers = {'User-Agent': user_agent}
    headers = {
        'user-agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36'}
    try:
        response = requests.get(url=url, proxies=proxies, headers=headers, timeout=10)
    except:
        return

    if response.history:
        redirect_urls = []
        for redirect_cnt, history in enumerate(response.history):
            redirect_urls.append(history.url)
        df.at[i, 'redirect_urls'] = redirect_urls
    df.at[i, 'final_url'] = response.url

    print("URL index:", i)

    # 增加计时器值
    timer += 1

    # 每处理 1000 条数据 保存一次 DataFrame
    if timer >= 1000:
        print('Saving DataFrame...')
        with lock:
            df.to_feather(output_file)

        # 重置计时器
        timer = 0

# 创建线程池
executor = ThreadPoolExecutor(max_workers=12)

# 提交任务到线程池
futures = []
for i in range(len(df)):
    future = executor.submit(process_url, i)
    futures.append(future)

# 等待所有任务完成
for future in futures:
    future.result()

# 保存最终的 DataFrame
with lock:
    df.to_feather(output_file)


import cv2 as cv

img = cv.imread('./images/frutas.jpg', cv.IMREAD_UNCHANGED)
cv.imshow('Street Italy Market', img)

print(img.shape)
print(type(img))
print(img.dtype)

cv.waitKey(0)
cv.destroyAllWindows


import socket
import os
import json
import struct

client = socket.socket()

server_ip_port = ('192.168.0.114',8001)

client.connect(server_ip_port)
# 获得文件大小
filesize = os.path.getsize(r'F:\python_study\视频.mp4')

# 统计文件描述信息给服务端，服务端按照此描述信息来保存文件，命名文件等，将信息置于字典中
file_info = {
    "file_name":"视频.mp4",
    "file_size":filesize,
}
# by_dic = bytes(str(file_info), encoding='utf-8')
# 由于字典无法直接转化为bytes类型的数据，所以需要json将字典转化为json字符串，再讲字符串转化为bytes类型

# json.dumps()将字典转化为字符串
file_info_json = json.dumps(file_info)
# 将字符串转化为bytes类型
file_info_bytes = file_info_json.encode('utf-8')
# 为防止粘包现象，将文件长度打包后，与文件的描述信息数据一起发送过去
data_len = len(file_info_bytes)
data_len_struct = struct.pack('i', data_len)

client.send(data_len_struct + file_info_bytes)
sum = 0  # 作为每次读取文件长度的累计值
with open('../视频.mp4', mode="rb") as f:
    while sum < filesize:
        # 每次读取文件内容
        every_read_data = f.read(1024)  # 一次读取1024b的数据
        sum = sum + len(every_read_data)  # 将sum累加，统计长度
        client.send(every_read_data)    # 将每次读取文件的真实数据发送给服务器

day28 ftp上传与下载 socketserver/01 ftp上传简单实例客户端.py

01 ftp上传简单实例客户端.py


import re

import numpy as np
import pytest
from helpers import get_data
from numpy.testing import assert_array_equal

from random_forestry import RandomForest

def test_fit_validator():
    forest = RandomForest()

    X, y = get_data()

    with pytest.raises(ValueError, match="The dimension of input dataset x doesn't match the output y."):
        forest.fit(X, y.drop(0))

    with pytest.raises(ValueError, match="y contains missing data."):
        forest.fit(X, y.replace(0, np.NaN))

    with pytest.raises(ValueError, match="Training data column cannot be all missing values."):
        X_nan = X.copy()
        X_nan["nan_col"] = np.NaN
        forest.fit(X_nan, y)

    with pytest.raises(
        TypeError,
        match=re.escape(
            (
                "fit() takes 3 positional arguments but 4 positional arguments "
                "(and 5 keyword-only arguments) were given"
            )
        ),
    ):
        forest.fit(X, y, 23)

    with pytest.raises(ValueError, match="monotonic_constraints must have the size of x"):
        forest.fit(X, y, monotonic_constraints=[0, 2, 3])

    monotonic_constraints = np.array([0] * X.shape[1])
    monotonic_constraints[0] = 2
    with pytest.raises(ValueError, match="monotonic_constraints must be either 1, 0, or -1"):
        forest.fit(X, y, monotonic_constraints=monotonic_constraints)

    forest.set_parameters(linear=True)
    monotonic_constraints = np.array([0] * X.shape[1])
    monotonic_constraints[0] = 1
    with pytest.raises(ValueError, match="Cannot use linear splitting with monotonic_constraints"):
        forest.fit(X, y, monotonic_constraints=monotonic_constraints)

    observation_weights = np.array([0] * X.shape[0])
    forest.set_parameters(replace=True)
    with pytest.raises(ValueError, match="There must be at least one non-zero weight in observation_weights"):
        forest.fit(X, y, observation_weights=observation_weights)

    observation_weights = np.array([1] * X.shape[0])
    with pytest.raises(ValueError, match=re.escape("observation_weights must have length len(x)")):
        forest.fit(X, y, observation_weights=np.delete(observation_weights, 0))

    observation_weights[0] = -2
    with pytest.raises(ValueError, match="The entries in observation_weights must be non negative"):
        forest.fit(X, y, observation_weights=observation_weights)

    lin_feats = np.array([0] * X.shape[1])
    lin_feats[0] = -1
    with pytest.raises(
        ValueError, match=re.escape("lin_feats must contain positive integers less than len(x.columns).")
    ):
        forest.fit(X, y, lin_feats=lin_feats)

    lin_feats = np.array([0] * X.shape[1])
    lin_feats[0] = X.shape[1] + 1
    with pytest.raises(
        ValueError, match=re.escape("lin_feats must contain positive integers less than len(x.columns).")
    ):
        forest.fit(X, y, lin_feats=lin_feats)

def test_observation_weights():
    X, y = get_data()
    forest = RandomForest()
    observation_weights = np.array([1] * X.shape[0])
    forest.fit(X, y, observation_weights=observation_weights)
    pred_avg = forest.predict(X, aggregation="average")

    assert len(pred_avg) == len(X)

    forest = RandomForest()
    observation_weights = np.array([0] * X.shape[0])
    n_weighted_obs = 10
    observation_weights[0:n_weighted_obs] = 1
    forest.fit(X, y, observation_weights=observation_weights)
    pred = forest.predict(X, aggregation="average")
    pred_weight_matrix = forest.predict(X, aggregation="average", return_weight_matrix=True)

    assert_array_equal(pred, pred_weight_matrix["predictions"])
    assert not np.any(pred_weight_matrix["weightMatrix"][:, n_weighted_obs : (X.shape[0])])


import os
import traceback
from json import JSONDecodeError

from PIL import Image
from django.conf.global_settings import DEBUG
from django.contrib.contenttypes.models import ContentType
from rest_framework import status
from rest_framework.parsers import MultiPartParser
from rest_framework.utils import json

from ds4reboot.api.utils import illegal_action, log_validation_errors, log_exception, success_action, Map
from plugins.models import RestAttachment
from plugins.serializers.attachment import AttachmentsSchema

class AttachmentsUploadMixin():
    parser_classes = (MultiPartParser,)

    def local_create(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        instance = serializer.save()
        return instance

    def __init_subclass__(cls, **kwargs):
        if not getattr(cls, 'content_type', None):
            raise ValueError("content_type needs to be specified in ViewSet/APIView class to define attachments")
        if not 'model' in cls.content_type or not 'app_label' in cls.content_type:
            raise ValueError("content_type needs to be a Dict with model and app_label key/value pairs.")

    def put(self, request):
        if DEBUG:
            print(request.data)
        marsh = AttachmentsSchema().load(data=request.data)
        if not 'errors' in marsh:
            # Flatten data important to the creation of subclass
            try:
                json_data = json.loads(request.data['json_data'])
            except JSONDecodeError as e:
                return log_exception(f"The attachments were found, but the json_data field was not parsable.")

            for key, value in json_data.items():
                request.data[key] = value
            request.data.pop('json_data')
            attachments = request.data.pop('attachment')

            # Propagate create and expect instance
            object = self.local_create(request)
            if not object.id:
                return log_exception(f"The {object} was not created and uploading attachment was skipped.")

            for file in attachments:
                try:
                    img = Image.open(file)
                    img.verify()
                except Exception as e:
                    _, ext = os.path.splitext(file._name)
                    if not ext in ['.csv', '.tiff', '.jpg', '.jpeg', '.png', '.bmp', '.xls', '.xlsx', '.pdf', '.svg']:
                        return illegal_action("Unsupported attachment type" + str(e))
                attachment = RestAttachment(creator_id=request.user.id,
                                            content_type=ContentType.objects.get(**self.content_type),
                                            object_id=object.id)
                try:
                    attachment.attachment_file.save(name=file.name, content=file)
                except Exception as e:
                    return log_exception(e, traceback.format_exc())
        else:
            return log_validation_errors(Map(marsh).errors)

        if getattr(self, 'RESPONSE_ROOT_NAME', None):
            output = {
                self.RESPONSE_ROOT_NAME: self.get_serializer(object).data,
            }
        else:
            output = self.get_serializer(object).data,
        return success_action(data=output, status=status.HTTP_201_CREATED)


from panda3d.core import Point3, Vec3, VBase3
from direct.tkwidgets.AppShell import *
from direct.showbase.TkGlobal import *
from direct.tkwidgets.Tree import *
from direct.tkwidgets import Slider, Floater
from tkinter.simpledialog import askstring
from tkinter.messagebox import showwarning, askyesno
from tkinter import *
from direct.showbase.PythonUtil import Functor, list2dict
from direct.gui.DirectGui import DGG
import tkinter.filedialog
from direct.showbase import DirectObject
import math
import operator
from direct.tkwidgets import Valuator
from direct.tkwidgets import VectorWidgets
from otp.level import LevelConstants
from direct.directtools import DirectSession
import Pmw

class InGameEditor(AppShell):
    appname = 'In-Game Editor'
    frameWidth = 900
    frameHeight = 475
    usecommandarea = 1
    usestatusarea = 1
    contactname = 'Darren Ranalli'
    contactphone = '(818) 623-3904'
    contactemail = 'darren.ranalli@disney.com'
    WantUndo = False

    def __init__(self, level, doneEvent, requestSaveEvent, saveAsEvent, undoEvent, redoEvent, wireframeEvent, oobeEvent, csEvent, runEvent, texEvent, **kw):
        DGG.INITOPT = Pmw.INITOPT
        optiondefs = (('title', 'In-Game ' + level.getName() + ' Editor', None),)
        self.defineoptions(kw, optiondefs)
        self.level = level
        self.doneEvent = doneEvent
        self.requestSaveEvent = requestSaveEvent
        self.saveAsEvent = saveAsEvent
        self.undoEvent = undoEvent
        self.redoEvent = redoEvent
        self.wireframeEvent = wireframeEvent
        self.oobeEvent = oobeEvent
        self.csEvent = csEvent
        self.runEvent = runEvent
        self.texEvent = texEvent
        self.entityCopy = None
        self.curEntId = None
        self.curEntWidgets = {}
        self.visZonesEditor = None
        AppShell.__init__(self)
        self.initialiseoptions(self.__class__)
        self.accept(self.level.getAttribChangeEventName(), self.handleAttribChange)
        self.accept('DIRECT_selectedNodePath', self.selectedNodePathHook)
        self.accept('DIRECT_manipulateObjectCleanup', self.manipCleanupHook)
        self.accept('DIRECT_undo', self.manipCleanupHook)
        self.accept('DIRECT_redo', self.manipCleanupHook)
        return

    def getEventMsgName(self, event):
        return 'InGameEditor%s_%s' % (self.level.getLevelId(), event)

    def getEntityName(self, entId):
        return self.level.levelSpec.getEntitySpec(entId)['name']

    def appInit(self):
        from direct.directtools import DirectSession
        direct.enableLight()
        direct.disable()
        bboard.post(DirectSession.DirectSession.DIRECTdisablePost)

    def createMenuBar(self):
        menuBar = self.menuBar
        menuBar.addmenuitem('File', 'command', 'Save the level spec on the AI', label='Save on AI', command=self.handleRequestSave)
        menuBar.addmenuitem('File', 'command', 'Save the level spec locally', label='Save Locally As...', command=self.handleSaveAs)
        menuBar.addmenuitem('File', 'separator')
        menuBar.addmenuitem('File', 'command', 'Export a single entity', label='Export Entity...', command=self.level.handleExportEntity)
        menuBar.addmenuitem('File', 'command', 'Export a tree of entities', label='Export Entity Tree...', command=self.level.handleExportEntityTree)
        menuBar.addmenuitem('File', 'separator')
        menuBar.addmenuitem('File', 'command', 'Import a tree of entities', label='Import Entities...', command=self.level.handleImportEntities)
        menuBar.addmenuitem('File', 'separator')
        if InGameEditor.WantUndo:
            menuBar.addmenu('Edit', 'Edit Operations')
            menuBar.addmenuitem('Edit', 'command', 'Undo the last edit', label='Undo', command=self.doUndo)
            menuBar.addmenuitem('Edit', 'command', 'Redo the last undo', label='Redo', command=self.doRedo)
        menuBar.addmenu('Entity', 'Entity Operations')
        menuBar.addmenuitem('Entity', 'command', 'Duplicate Selected Entity', label='Duplicate Selected Entity', command=self.level.duplicateSelectedEntity)
        menuBar.addmenuitem('Entity', 'separator')
        permanentTypes = self.level.entTypeReg.getPermanentTypeNames()
        entTypes = list(self.level.entTypes)
        for permanentType in permanentTypes:
            entTypes.remove(permanentType)
        entTypes.sort()
        numEntities = len(entTypes)
        cascadeMenu = ''
        for index in range(numEntities):
            type = entTypes[index]
            if index % 10 == 0:
                lastIndex = min(index + 9, numEntities - 1)
                cascadeMenu = '%s->%s' % (entTypes[index], entTypes[lastIndex])
                menuBar.addcascademenu('Entity', cascadeMenu)

            def doInsert(self = self, type = type):
                self.level.insertEntity(type)
                self.explorer.update(fUseCachedChildren=0)

            menuBar.addmenuitem(cascadeMenu, 'command', 'Insert %s' % type, label='Insert %s' % type, command=doInsert)

        menuBar.addmenuitem('Entity', 'separator')
        menuBar.addmenuitem('Entity', 'command', 'Remove Selected Entity', label='Remove Selected Entity', command=self.removeSelectedEntity)
        menuBar.addmenuitem('Entity', 'command', 'Remove Selected EntityTree', label='Remove Selected Entity Tree', command=self.removeSelectedEntityTree)
        menuBar.addmenuitem('Entity', 'separator')
        menuBar.addmenuitem('Entity', 'command', 'Go To Selected Entity', label='Go To Selected Entity', command=self.level.moveAvToSelected)
        menuBar.addmenuitem('Entity', 'command', 'Refresh Entity Explorer', label='Refresh Entity Explorer', command=self.refreshExplorer)
        self.menuBar.addmenu('DIRECT', 'Direct Session Panel Operations')
        self.menuBar.addmenuitem('DIRECT', 'checkbutton', 'DIRECT Enabled', label='Enable', variable=direct.panel.directEnabled, command=direct.panel.toggleDirect)
        self.menuBar.addmenuitem('DIRECT', 'checkbutton', 'DIRECT Grid Enabled', label='Enable Grid', variable=direct.panel.directGridEnabled, command=direct.panel.toggleDirectGrid)
        self.menuBar.addmenuitem('DIRECT', 'command', 'Toggle Object Handles Visability', label='Toggle Widget Viz', command=direct.toggleWidgetVis)
        self.menuBar.addmenuitem('DIRECT', 'command', 'Toggle Widget Move/COA Mode', label='Toggle Widget Mode', command=direct.manipulationControl.toggleObjectHandlesMode)
        self.menuBar.addmenuitem('DIRECT', 'checkbutton', 'DIRECT Widget On Top', label='Widget On Top', variable=direct.panel.directWidgetOnTop, command=direct.panel.toggleWidgetOnTop)
        self.menuBar.addmenuitem('DIRECT', 'command', 'Deselect All', label='Deselect All', command=direct.deselectAll)
        if InGameEditor.WantUndo:
            undoFrame = Frame(self.menuFrame)
            self.redoButton = Button(undoFrame, text='Redo', command=self.doRedo)
            self.redoButton.pack(side=RIGHT, expand=0)
            self.undoButton = Button(undoFrame, text='Undo', command=self.doUndo)
            self.undoButton.pack(side=RIGHT, expand=0)
            undoFrame.pack(side=RIGHT, expand=0)
        toggleFrame = Frame(self.menuFrame)
        self.wireframeButton = Button(toggleFrame, text='Wire', command=self.doWireframe)
        self.wireframeButton.pack(side=RIGHT, expand=0)
        self.texButton = Button(toggleFrame, text='Tex', command=self.doTex)
        self.texButton.pack(side=RIGHT, expand=0)
        self.csButton = Button(toggleFrame, text='Cs', command=self.doCs)
        self.csButton.pack(side=RIGHT, expand=0)
        self.runButton = Button(toggleFrame, text='Run', command=self.doRun)
        self.runButton.pack(side=RIGHT, expand=0)
        self.oobeButton = Button(toggleFrame, text='Oobe', command=self.doOobe)
        self.oobeButton.pack(side=RIGHT, expand=0)
        toggleFrame.pack(side=RIGHT, expand=0, padx=5)
        AppShell.createMenuBar(self)

    def createInterface(self):
        interior = self.interior()
        mainFrame = Frame(interior)
        self.framePane = Pmw.PanedWidget(mainFrame, orient=DGG.HORIZONTAL)
        self.explorerFrame = self.framePane.add('left', min=250)
        self.widgetFrame = self.framePane.add('right', min=300)
        self.explorer = LevelExplorer(parent=self.explorerFrame, editor=self)
        self.explorer.pack(fill=BOTH, expand=1)
        self.explorerFrame.pack(fill=BOTH, expand=1)
        self.pageOneFrame = Pmw.ScrolledFrame(self.widgetFrame, borderframe_relief=GROOVE, horizflex='elastic')
        self.pageOneFrame.pack(expand=1, fill=BOTH)
        self.widgetFrame.pack(fill=BOTH, expand=1)
        self.framePane.pack(fill=BOTH, expand=1)
        mainFrame.pack(fill=BOTH, expand=1)
        self.createButtons()
        self.initialiseoptions(self.__class__)
        self.attribWidgets = []

    def selectedNodePathHook(self, nodePath):
        np = nodePath.findNetTag('entity')
        if not np.isEmpty():
            if np.id() != nodePath.id():
                np.select()
            else:
                self.findEntityFromNP(np)

    def findEntityFromNP(self, nodePath):
        entId = self.level.nodePathId2EntId.get(nodePath.id())
        if entId:
            self.selectEntity(entId)
        else:
            for entId in self.level.levelSpec.getAllEntIds():
                np = self.level.getEntInstanceNP(entId)
                if np:
                    if np.id() == nodePath.id():
                        self.selectEntity(entId)
                        return

    def manipCleanupHook(self, nodePathList):
        if not nodePathList:
            return
        entId = self.level.nodePathId2EntId.get(nodePathList[0].id())
        if entId:
            t = nodePathList[0].getTransform()
            entSpec = self.level.levelSpec.getEntitySpec(entId)
            entPos = entSpec.get('pos')
            if entPos and t.getPos() != entPos:
                self.level.setAttribEdit(entId, 'pos', Point3(t.getPos()))
            entHpr = entSpec.get('hpr')
            if entHpr and t.getHpr() != entHpr:
                self.level.setAttribEdit(entId, 'hpr', Vec3(t.getHpr()))
            entScale = entSpec.get('scale')
            if entScale and t.getScale() != entScale:
                self.level.setAttribEdit(entId, 'scale', Vec3(t.getScale()))

    def refreshExplorer(self):
        self.explorer.update()

    def selectEntity(self, entId):
        node = self.explorer._node.find(entId)
        if node:
            node.reveal()
            self.explorer._node.deselecttree()
            node.select()

    def removeSelectedEntity(self):
        if self.level.selectedEntity:
            parentEntId = self.level.selectedEntity.parentEntId
        else:
            parentEntId = None
        if self.level.removeSelectedEntity() != -1:
            self.explorer.update(fUseCachedChildren=0)
            if parentEntId:
                self.selectEntity(parentEntId)
        return

    def removeSelectedEntityTree(self):
        if self.level.selectedEntity:
            parentEntId = self.level.selectedEntity.parentEntId
        else:
            parentEntId = None
        if self.level.removeSelectedEntityTree() != -1:
            self.explorer.update(fUseCachedChildren=0)
            if parentEntId:
                self.selectEntity(parentEntId)
        return

    def clearAttribEditPane(self):
        for widg in self.attribWidgets:
            widg.destroy()

        if self.visZonesEditor:
            self.visZonesEditor.destroy()
        self.attribWidgets = []
        self.curEntId = None
        self.curEntWidgets = {}
        self.cbDict = {}
        return

    def updateEntityCopy(self, entId):
        if self.entityCopy == None:
            self.entityCopy = self.level.getEntInstanceNPCopy(entId)
            if self.entityCopy is not None:
                self.entityCopy.setRenderModeWireframe()
                self.entityCopy.setTextureOff(1)
                self.entityCopy.setColor(1, 0, 0)
        return

    def updateAttribEditPane(self, entId, levelSpec, entTypeReg):
        self.clearAttribEditPane()
        self.curEntId = entId
        widgetSetter = None
        entSpec = levelSpec.getEntitySpec(entId)
        typeDesc = entTypeReg.getTypeDesc(entSpec['type'])
        attribNames = typeDesc.getAttribNames()
        attribDescs = typeDesc.getAttribDescDict()
        for attribName in attribNames:
            desc = attribDescs[attribName]
            params = desc.getParams()
            datatype = desc.getDatatype()
            if datatype == 'int':
                self.addIntWidget(levelSpec, entSpec, entId, attribName, params)
            elif datatype == 'float':
                self.addFloatWidget(levelSpec, entSpec, entId, attribName, params)
            elif datatype == 'bool':
                self.addBoolWidget(levelSpec, entSpec, entId, attribName, params)
            elif datatype == 'choice':
                self.addChoiceWidget(levelSpec, entSpec, entId, attribName, params)
            elif datatype == 'multiChoice':
                self.addMultiChoiceWidget(levelSpec, entSpec, entId, attribName, params)
            elif datatype in ['pos', 'hpr', 'scale']:
                self.addVec3Widget(levelSpec, entSpec, entId, attribName, params, datatype)
            elif datatype == 'color':
                self.addColorWidget(levelSpec, entSpec, entId, attribName, params)
            elif datatype == 'bamfilename':
                self.addFileWidget(levelSpec, entSpec, entId, attribName, params)
            elif datatype == 'visZoneList':
                self.addVisZoneWidget(levelSpec, entSpec, entId, attribName, params)
            elif datatype == 'entId':
                self.addEntIdWidget(levelSpec, entSpec, entId, attribName, params, entTypeReg)
            elif datatype == 'string':
                self.addStringWidget(levelSpec, entSpec, entId, attribName, params)
            elif datatype == 'const':
                self.addConstWidget(levelSpec, entSpec, entId, attribName, params)
            else:
                self.addPythonWidget(levelSpec, entSpec, entId, attribName, params)

        return

    def addIntWidget(self, levelSpec, entSpec, entId, attribName, params):
        minVal = params.get('min', None)
        maxVal = params.get('max', None)
        if minVal is not None and maxVal is not None:
            widgClass = Slider.Slider
        else:
            widgClass = Floater.Floater
        widg = widgClass(self.pageOneFrame.interior(), text=attribName, value=entSpec[attribName], numDigits=0, label_width=15, label_anchor=W, label_justify=LEFT, label_font=None, min=minVal, max=maxVal, resolution=1.0)

        def clientIntCommand(val):
            entity = self.level.getEntInstance(entId)
            if entity:
                entity.handleAttribChange(attribName, int(val))

        def finalIntCommand():
            self.level.setAttribEdit(entId, attribName, int(widg.get()))

        widg['command'] = clientIntCommand
        widg['postCallback'] = finalIntCommand
        widg.pack(fill=X, expand=1)
        self.attribWidgets.append(widg)
        self.curEntWidgets[attribName] = lambda x: widg.set(x, 0)
        return

    def addFloatWidget(self, levelSpec, entSpec, entId, attribName, params):
        minVal = params.get('min', None)
        maxVal = params.get('max', None)
        widg = Floater.Floater(self.pageOneFrame.interior(), text=attribName, value=entSpec[attribName], label_width=15, label_anchor=W, label_justify=LEFT, label_font=None, min=minVal, max=maxVal)

        def clientFloatCommand(val):
            entity = self.level.getEntInstance(entId)
            if entity:
                entity.handleAttribChange(attribName, val)

        def finalFloatCommand():
            self.level.setAttribEdit(entId, attribName, widg.get())

        widg['command'] = clientFloatCommand
        widg['postCallback'] = finalFloatCommand
        widg.pack(fill=X, expand=1)
        self.attribWidgets.append(widg)
        self.curEntWidgets[attribName] = lambda x: widg.set(x, 0)
        return

    def addBoolWidget(self, levelSpec, entSpec, entId, attribName, params):
        flag = BooleanVar()
        flag.set(entSpec[attribName])

        def booleanCommand(booleanVar = flag):
            self.level.setAttribEdit(entId, attribName, flag.get())

        frame = Frame(self.pageOneFrame.interior())
        label = Label(frame, text=attribName, width=15, anchor=W, justify=LEFT)
        label.pack(side=LEFT, expand=0)
        trueButton = Radiobutton(frame, text='True', value=1, variable=flag, command=booleanCommand)
        trueButton.pack(side=LEFT, expand=0)
        falseButton = Radiobutton(frame, text='False', value=0, variable=flag, command=booleanCommand)
        falseButton.pack(side=LEFT, expand=0)
        frame.pack(fill=X, expand=1)
        self.attribWidgets.append(frame)
        self.curEntWidgets[attribName] = flag.set

    def addChoiceWidget(self, levelSpec, entSpec, entId, attribName, params):
        if attribName in entSpec:
            attributeValue = entSpec.get(attribName)
        else:
            typeDesc = self.level.entTypeReg.getTypeDesc(entSpec['type'])
            attribDesc = typeDesc.getAttribDescDict()[attribName]
            attributeValue = attribDesc.getDefaultValue()
        valueDict = params.get('valueDict', {})
        for key, value in list(valueDict.items()):
            if value == attributeValue:
                attributeValue = key
                break

        radioVar = StringVar()
        radioVar.set(attributeValue)

        def radioCommand(radioVar = radioVar):
            value = valueDict.get(radioVar.get(), radioVar.get())
            self.level.setAttribEdit(entId, attribName, value)

        frame = Frame(self.pageOneFrame.interior(), relief=GROOVE, borderwidth=2)
        label = Label(frame, text=attribName, width=15, anchor=W, justify=LEFT)
        label.pack(side=LEFT, expand=0)
        for choice in params.get('choiceSet', []):
            if type(choice) is str:
                choiceStr = choice
            else:
                choiceStr = repr(choice)
            if choiceStr not in valueDict:
                valueDict[choiceStr] = choice
            choiceButton = Radiobutton(frame, text=choiceStr, value=choiceStr, variable=radioVar, command=radioCommand)
            choiceButton.pack(side=LEFT, expand=0)

        frame.pack(fill=X, expand=1)
        self.attribWidgets.append(frame)

        def setRadioVar(attributeValue):
            for key, value in list(valueDict.items()):
                if value == attributeValue:
                    attributeValue = key
                    break

            radioVar.set(attributeValue)

        self.curEntWidgets[attribName] = setRadioVar

    def addMultiChoiceWidget(self, levelSpec, entSpec, entId, attribName, params):
        frame = Frame(self.pageOneFrame.interior(), relief=GROOVE, borderwidth=2)
        label = Label(frame, text=attribName, width=15, anchor=W, justify=LEFT)
        label.pack(side=LEFT, expand=0)
        valueDict = params.get('valueDict', {})
        self.cbDict[attribName] = list2dict(entSpec[attribName], value=1)
        checkbuttonDict = {}
        base.cbButtons = []
        base.checkbuttonDict = checkbuttonDict
        for choice in params.get('choiceSet', []):
            trueValue = valueDict.get(choice, choice)
            cbVar = IntVar()
            cbVar.set(trueValue in self.cbDict[attribName])
            checkbuttonDict[trueValue] = cbVar

            def cbCommand(var, trueValue = trueValue):
                vd = self.cbDict[attribName]
                print(vd)
                if var.get():
                    print('got it', trueValue, vd)
                    vd[trueValue] = 1
                else:
                    print('not it', trueValue, vd)
                    if trueValue in vd:
                        del vd[trueValue]
                value = list(vd.keys())
                print('SENDING', value)
                self.level.setAttribEdit(entId, attribName, value)

            if type(choice) is str:
                labelStr = choice
            else:
                labelStr = repr(choice)
            func = Functor(cbCommand, cbVar)
            choiceButton = Checkbutton(frame, text=labelStr, variable=cbVar, command=lambda : func())
            choiceButton.pack(side=LEFT, expand=0)
            base.cbButtons.append(choiceButton)

        frame.pack(fill=X, expand=1)
        self.attribWidgets.append(frame)

        def setCheckbuttonVar(attributeValueList):
            print('COMING BACK', attributeValueList)
            for attributeValue, cb in list(checkbuttonDict.items()):
                if attributeValue in attributeValueList:
                    cb.set(1)
                else:
                    cb.set(0)

        self.curEntWidgets[attribName] = setCheckbuttonVar

    def addVec3Widget(self, levelSpec, entSpec, entId, attribName, params, datatype):

        def veCommand(poslist):
            self.level.setAttribEdit(entId, attribName, Point3(*poslist))

        vec = entSpec[attribName]
        if not isinstance(vec, VBase3):
            vec = Vec3(vec)
        value = [vec[0], vec[1], vec[2]]
        minVal = maxVal = None
        if datatype == 'pos':
            floaterLabels = ['x', 'y', 'z']
            floaterType = 'floater'
        elif datatype == 'hpr':
            floaterLabels = ['h', 'p', 'r']
            floaterType = 'angledial'
        else:
            floaterLabels = ['sx', 'sy', 'sz']
            floaterType = 'slider'
            minVal = 0
            maxVal = 1000
        widg = VectorWidgets.VectorEntry(self.pageOneFrame.interior(), text=attribName, value=value, type=floaterType, bd=0, relief=None, min=minVal, max=maxVal, label_justify=LEFT, label_anchor=W, label_width=14, label_bd=0, labelIpadx=0, floaterGroup_labels=floaterLabels)
        widg['command'] = veCommand
        widg.pack(fill=X, expand=1)
        if attribName in ('pos', 'hpr', 'scale'):

            def placeEntity():
                selectedEntityNP = self.level.getEntInstanceNP(entId)
                if selectedEntityNP is not None:
                    selectedEntityNP.place()
                return

            widg.menu.add_command(label='Place...', command=placeEntity)

        def adjustCopy(vec):
            self.updateEntityCopy(entId)
            if self.entityCopy is not None:
                if datatype == 'pos':
                    self.entityCopy.setPos(vec[0], vec[1], vec[2])
                elif datatype == 'hpr':
                    self.entityCopy.setHpr(vec[0], vec[1], vec[2])
                elif datatype == 'scale':
                    self.entityCopy.setScale(vec[0], vec[1], vec[2])
            widg.set(vec, 0)
            return

        widg._floaters['command'] = adjustCopy
        widg._floaters.component('valuatorGroup')['postCallback'] = lambda x, y, z: veCommand([x, y, z])
        self.attribWidgets.append(widg)
        self.curEntWidgets[attribName] = lambda x: widg.set(x, 0)
        return

    def addColorWidget(self, levelSpec, entSpec, entId, attribName, params):

        def veCommand(colorlist):
            self.level.setAttribEdit(entId, attribName, Vec4(*colorlist) / 255.0)

        vec = entSpec[attribName]
        value = [vec[0] * 255.0,
         vec[1] * 255.0,
         vec[2] * 255.0,
         vec[3] * 255.0]
        floaterLabels = ['r',
         'g',
         'b',
         'a']
        widg = VectorWidgets.ColorEntry(self.pageOneFrame.interior(), text=attribName, type='slider', relief=None, bd=0, label_justify=LEFT, label_anchor=W, label_width=14, label_bd=0, labelIpadx=0, floaterGroup_labels=floaterLabels, value=value, floaterGroup_value=value)
        widg['command'] = veCommand
        widg.pack(fill=X, expand=1)

        def adjustEntity(vec):
            entity = self.level.getEntInstance(entId)
            if entity is not None:
                entity.setColor(vec[0] / 255.0, vec[1] / 255.0, vec[2] / 255.0, vec[3] / 255.0)
            widg.set(vec, 0)
            return

        widg._floaters['command'] = adjustEntity
        widg._floaters.component('valuatorGroup')['postCallback'] = lambda x, y, z, a: veCommand([x,
         y,
         z,
         a])
        self.attribWidgets.append(widg)
        self.curEntWidgets[attribName] = lambda x: widg.set(x * 255.0, 0)
        return

    def addFileWidget(self, levelSpec, entSpec, entId, attribName, params):
        text = StringVar()
        text.set(repr(entSpec[attribName]))

        def handleReturn(event):
            self.handleAttributeChangeSubmit(attribName, text, entId, levelSpec)

        def askFilename(callback = handleReturn):
            if text.get() == 'None':
                initialDir = Filename.expandFrom('$TTMODELS/built/').toOsSpecific()
            else:
                initialDir = Filename.expandFrom('$TTMODELS/built/%s' % text.get()[1:-1]).toOsSpecific()
            print(text, text.get()[1:-1], initialDir)
            rawFilename = askopenfilename(defaultextension='*', initialdir=initialDir, filetypes=(('Bam Files', '*.bam'),
             ('Egg Files', '*.egg'),
             ('Maya Binaries', '*.mb'),
             ('All files', '*')), title='Load Model File', parent=self.interior())
            if rawFilename != '':
                filename = Filename.fromOsSpecific(rawFilename)
                filename.findOnSearchpath(getModelPath().getValue())
                text.set("'%s'" % repr(filename))
                handleReturn(None)
            return

        frame = Frame(self.pageOneFrame.interior())
        label = Button(frame, text=attribName, width=14, borderwidth=0, anchor=W, justify=LEFT)
        label['command'] = askFilename
        label.pack(side=LEFT, expand=0)

        def enterWidget(event, widg = label):
            widg['background'] = '#909090'

        def leaveWidget(event, widg = label):
            widg['background'] = 'SystemButtonFace'

        label.bind('<Enter>', enterWidget)
        label.bind('<Leave>', leaveWidget)
        widg = Entry(frame, textvariable=text)
        widg.bind('<Return>', handleReturn)
        widg.pack(side=LEFT, fill=X, expand=1)
        frame.pack(fill=X, expand=1)
        self.attribWidgets.append(frame)
        self.curEntWidgets[attribName] = lambda x: text.set(repr(x))

    def addVisZoneWidget(self, levelSpec, entSpec, entId, attribName, params):
        text = StringVar()
        text.set(repr(entSpec[attribName]))

        def handleReturn(event):
            self.handleAttributeChangeSubmit(attribName, text, entId, levelSpec)

        def handleUpdate(visZoneList):
            self.level.setAttribEdit(entId, attribName, visZoneList)

        def getZoneList(callback = handleReturn):
            zoneEntIds = list(self.level.entType2ids['zone'])
            zoneEntIds.remove(LevelConstants.UberZoneEntId)
            zoneEntIds.sort()
            self.visZonesEditor = LevelVisZonesEditor(self, entId, entSpec[attribName], modelZones=zoneEntIds, updateCommand=handleUpdate)

        frame = Frame(self.pageOneFrame.interior())
        label = Button(frame, text=attribName, width=14, borderwidth=0, anchor=W, justify=LEFT)
        label['command'] = getZoneList
        label.pack(side=LEFT, expand=0)

        def enterWidget(event, widg = label):
            widg['background'] = '#909090'

        def leaveWidget(event, widg = label):
            widg['background'] = 'SystemButtonFace'

        label.bind('<Enter>', enterWidget)
        label.bind('<Leave>', leaveWidget)
        widg = Entry(frame, textvariable=text)
        widg.bind('<Return>', handleReturn)
        widg.pack(side=LEFT, fill=X, expand=1)
        frame.pack(fill=X, expand=1)
        self.attribWidgets.append(frame)

        def refreshWidget(visZoneList):
            text.set(repr(visZoneList))
            if self.visZonesEditor:
                self.visZonesEditor.setVisible(visZoneList)

        self.curEntWidgets[attribName] = refreshWidget

    def addEntIdWidget(self, levelSpec, entSpec, entId, attribName, params, entTypeReg):
        text = StringVar()
        text.set(repr(entSpec[attribName]))

        def handleReturn(event):
            self.handleAttributeChangeSubmit(attribName, text, entId, levelSpec)

        def handleMenu(id):
            text.set(id)
            self.level.setAttribEdit(entId, attribName, id)

        frame = Frame(self.pageOneFrame.interior())
        label = Label(frame, text=attribName, width=15, anchor=W, justify=LEFT)
        label.pack(side=LEFT, expand=0)
        widg = Entry(frame, textvariable=text)
        widg.bind('<Return>', handleReturn)
        widg.pack(side=LEFT, fill=X, expand=1)
        if attribName is 'parentEntId':
            buttonText = 'Reparent To'
        else:
            buttonText = 'Select Entity'
        entButton = Menubutton(frame, width=8, text=buttonText, relief=RAISED, borderwidth=2)
        entMenu = Menu(entButton, tearoff=0)
        entButton['menu'] = entMenu
        entButton.pack(side=LEFT, fill='x', expand=1)
        entType = params.get('type')
        entOutput = params.get('output')
        idDict = {}
        if entType == 'grid':
            for eType in entTypeReg.getDerivedTypeNames('grid'):
                idDict[eType] = self.level.entType2ids.get(eType, [])

        elif entType == 'nodepath':
            for eType in entTypeReg.getDerivedTypeNames('nodepath'):
                idDict[eType] = self.level.entType2ids.get(eType, [])

        elif entOutput == 'bool':
            for eType in entTypeReg.getTypeNamesFromOutputType('bool'):
                idDict[eType] = self.level.entType2ids.get(eType, [])

        else:
            for eType in list(self.level.entType2ids.keys()):
                idDict[eType] = self.level.entType2ids.get(eType, [])

        typeKeys = list(idDict.keys())
        typeKeys.sort()

        def getChildEntIds(entity):
            entIds = []
            for child in entity.getChildren():
                entIds.append(child.entId)
                entIds.extend(getChildEntIds(child))

            return entIds

        thisEntity = self.level.getEntity(entId)
        forbiddenEntIds = [entId, thisEntity.parentEntId]
        forbiddenEntIds.extend(getChildEntIds(thisEntity))
        for eType in typeKeys:
            idList = list(idDict[eType])
            for forbiddenId in forbiddenEntIds:
                if forbiddenId in idList:
                    idList.remove(forbiddenId)

            if len(idList) == 0:
                continue
            subMenu = Menu(entMenu, tearoff=0)
            entMenu.add_cascade(label=eType, menu=subMenu)
            idList.sort()
            numIds = len(idList)
            idIndex = 0
            for id in idList:
                if idIndex % 10 == 0:
                    if numIds > 10:
                        m = Menu(subMenu, tearoff=0)
                        firstId = idList[idIndex]
                        lastIndex = min(idIndex + 9, numIds - 1)
                        lastId = idList[lastIndex]
                        subMenu.add_cascade(label='Ids %d-%d' % (firstId, lastId), menu=m)
                    else:
                        m = subMenu
                m.add_command(label='%d: %s' % (id, self.getEntityName(id)), command=lambda id = id, h = handleMenu: h(id))
                idIndex += 1

        frame.pack(fill=X, expand=1)
        self.attribWidgets.append(frame)
        self.curEntWidgets[attribName] = text.set

    def addStringWidget(self, levelSpec, entSpec, entId, attribName, params):
        text = StringVar()
        text.set(str(entSpec[attribName]))

        def handleReturn(event):
            self.level.setAttribEdit(entId, attribName, text.get())

        frame = Frame(self.pageOneFrame.interior())
        label = Label(frame, text=attribName, width=15, anchor=W, justify=LEFT)
        label.pack(side=LEFT, expand=0)
        widg = Entry(frame, textvariable=text)
        widg.bind('<Return>', handleReturn)
        widg.pack(side=LEFT, fill=X, expand=1)
        frame.pack(fill=X, expand=1)
        self.attribWidgets.append(frame)
        self.curEntWidgets[attribName] = text.set

    def addConstWidget(self, levelSpec, entSpec, entId, attribName, params):
        frame = Frame(self.pageOneFrame.interior())
        label = Label(frame, text=attribName, width=15, anchor=W, justify=LEFT)
        label.pack(side=LEFT, expand=0)
        text = str(entSpec[attribName])
        valueLabel = Label(frame, text=text, anchor=W, justify=LEFT, relief=GROOVE)
        valueLabel.pack(side=LEFT, fill=X, expand=1)
        frame.pack(fill=X, expand=1)
        self.attribWidgets.append(frame)

    def addPythonWidget(self, levelSpec, entSpec, entId, attribName, params):
        text = StringVar()
        text.set(repr(entSpec[attribName]))

        def handleReturn(event):
            self.handleAttributeChangeSubmit(attribName, text, entId, levelSpec)

        frame = Frame(self.pageOneFrame.interior())
        label = Label(frame, text=attribName, width=15, anchor=W, justify=LEFT)
        label.pack(side=LEFT, expand=0)
        widg = Entry(frame, textvariable=text)
        widg.bind('<Return>', handleReturn)
        widg.pack(side=LEFT, fill=X, expand=1)
        frame.pack(fill=X, expand=1)
        self.attribWidgets.append(frame)
        self.curEntWidgets[attribName] = lambda x: text.set(repr(x))

    def handleAttributeChangeSubmit(self, attribName, textObj, entId, levelSpec):
        newText = textObj.get()
        try:
            value = eval(newText)
        except:
            showwarning('ERROR', 'that is not a valid Python object', parent=self.parent)
            return

        self.level.setAttribEdit(entId, attribName, value)

    def handleAttribChange(self, entId, attribName, value, username):
        if username == self.level.editUsername:
            if self.entityCopy:
                self.entityCopy.removeNode()
                self.entityCopy = None
        if entId == self.curEntId:
            widgetSetter = self.curEntWidgets[attribName]
            if widgetSetter is not None:
                widgetSetter(value)
        return

    def createButtons(self):
        pass

    def toggleBalloon(self):
        if self.toggleBalloonVar.get():
            self.balloon().configure(state='both')
        else:
            self.balloon().configure(state='none')

    def onDestroy(self, event):
        from direct.directtools import DirectSession
        direct.disable()
        bboard.remove(DirectSession.DirectSession.DIRECTdisablePost)
        self.ignore(self.level.getAttribChangeEventName())
        self.ignore('DIRECT_selectedNodePath')
        self.ignore('DIRECT_manipulateObjectCleanup')
        self.ignore('DIRECT_undo')
        self.ignore('DIRECT_redo')
        print('InGameEditor.onDestroy()')
        if self.visZonesEditor:
            self.visZonesEditor.destroy()
        self.explorer._node.destroy()
        del self.level
        messenger.send(self.doneEvent)

    def handleRequestSave(self):
        messenger.send(self.requestSaveEvent)

    def handleSaveAs(self):
        filename = tkinter.filedialog.asksaveasfilename(parent=self.parent, defaultextension='.py', filetypes=[('Python Source Files', '.py'), ('All Files', '*')])
        if len(filename) > 0:
            messenger.send(self.saveAsEvent, [filename])

    def doUndo(self):
        messenger.send(self.undoEvent)

    def doRedo(self):
        messenger.send(self.redoEvent)

    def doWireframe(self):
        messenger.send(self.wireframeEvent)

    def doOobe(self):
        messenger.send(self.oobeEvent)

    def doCs(self):
        messenger.send(self.csEvent)

    def doRun(self):
        messenger.send(self.runEvent)

    def doTex(self):
        messenger.send(self.texEvent)

    def resetLevel(self):
        self.showTodo(what='resetLevel')

    def showTodo(self, what = ''):
        self.showWarning('%s\nThis is not yet implemented.' % what, 'TODO')

    def showWarning(self, msg, title = 'Warning'):
        showwarning(title, msg, parent=self.parent)

    def askYesNo(self, msg, title = 'Query'):
        return askyesno(title, msg, parent=self.parent)

    def popupLevelDialog(self):
        data = askstring('Input Level Data', 'Level Data:', parent=self)
        if data:
            self.messageBar().helpmessage(data)

class LevelVisZonesEditor(Pmw.MegaToplevel):

    def __init__(self, editor, entId, visible, modelZones = [], updateCommand = None, parent = None, **kw):
        DGG.INITOPT = Pmw_INITOPT
        optiondefs = (('title', 'Level Vis-Zone Editor', None),)
        self.defineoptions(kw, optiondefs)
        Pmw.MegaToplevel.__init__(self, parent, title=self['title'])
        self.editor = editor
        self.entId = entId
        self.modelZones = modelZones
        self.modelZones.sort()
        self.updateCommand = updateCommand
        hull = self.component('hull')
        balloon = self.balloon = Pmw.Balloon(hull)
        self.balloon.configure(state='none')
        menuFrame = Frame(hull, relief=GROOVE, bd=2)
        menuFrame.pack(fill=X, expand=1)
        menuBar = Pmw.MenuBar(menuFrame, hotkeys=1, balloon=balloon)
        menuBar.pack(side=LEFT, expand=1, fill=X)
        menuBar.addmenu('Vis Zones Editor', 'Visability Zones Editor Operations')
        menuBar.addmenuitem('Vis Zones Editor', 'command', 'Exit Visability Zones Editor', label='Exit', command=self.destroy)
        menuBar.addmenu('Help', 'Visibility Zones Editor Help Operations')
        self.toggleBalloonVar = IntVar()
        self.toggleBalloonVar.set(0)
        menuBar.addmenuitem('Help', 'checkbutton', 'Toggle balloon help', label='Balloon Help', variable=self.toggleBalloonVar, command=self.toggleBalloon)
        sf = Pmw.ScrolledFrame(hull, horizflex='elastic', usehullsize=1, hull_width=200, hull_height=400)
        frame = sf.interior()
        sf.pack(padx=5, pady=3, fill=BOTH, expand=1)
        self.radioSelect = Pmw.RadioSelect(frame, selectmode=MULTIPLE, orient=DGG.VERTICAL, pady=0, command=self.toggleVisZone)
        self.buttons = []
        for modelZoneNum in self.modelZones:
            buttonStr = '%d - %s' % (modelZoneNum, self.editor.getEntityName(modelZoneNum))
            button = self.radioSelect.add(buttonStr, width=12)
            button.configure(anchor=W, justify=LEFT)
            self.buttons.append(button)

        self.radioSelect.pack(expand=1, fill=X)
        sf.reposition()
        buttonFrame = Frame(hull)
        self.showMode = IntVar()
        self.showMode.set(self.editor.level.level.getColorZones())
        self.showActiveButton = Radiobutton(buttonFrame, text='Stash Invisible', value=0, indicatoron=1, variable=self.showMode, command=self.setVisRenderMode)
        self.showActiveButton.pack(side=LEFT, fill=X, expand=1)
        self.showAllButton = Radiobutton(buttonFrame, text='Color Invisible', value=1, indicatoron=1, variable=self.showMode, command=self.setVisRenderMode)
        self.showAllButton.pack(side=LEFT, fill=X, expand=1)
        buttonFrame.pack(fill=X, expand=1)
        self.initialiseoptions(LevelVisZonesEditor)
        self.setVisible(visible)
        return None

    def toggleVisZone(self, zoneStr, state):
        zoneNum = int(zoneStr.split('-')[0])
        if state == 0:
            if zoneNum in self.visible:
                self.visible.remove(zoneNum)
        elif zoneNum not in self.visible:
            self.visible.append(zoneNum)
            self.visible.sort()
        if self.updateCommand:
            self.updateCommand(self.visible)

    def setVisible(self, visible):
        self.visible = visible
        self.refreshVisibility()

    def setVisRenderMode(self):
        self.editor.level.level.setColorZones(self.showMode.get())

    def refreshVisibility(self):
        self.radioSelect.selection = []
        for button in self.buttons:
            componentName = button['text']
            modelZone = int(componentName.split('-')[0])
            if modelZone in self.visible:
                button.configure(relief='sunken')
                self.radioSelect.selection.append(componentName)
            else:
                button.configure(relief='raised')

    def destroy(self):
        self.editor.visZonesEditor = None
        Pmw.MegaToplevel.destroy(self)
        return

    def toggleBalloon(self):
        if self.toggleBalloonVar.get():
            self.balloon.configure(state='balloon')
        else:
            self.balloon.configure(state='none')

DEFAULT_MENU_ITEMS = ['Update Explorer',
 'Separator',
 'Select',
 'Deselect',
 'Separator',
 'Delete',
 'Separator',
 'Fit',
 'Flash',
 'Isolate',
 'Toggle Vis',
 'Show All',
 'Separator',
 'Set Reparent Target',
 'Reparent',
 'WRT Reparent',
 'Separator',
 'Place',
 'Set Name',
 'Set Color',
 'Explore',
 'Separator']

class LevelExplorer(Pmw.MegaWidget, DirectObject.DirectObject):

    def __init__(self, parent = None, editor = None, **kw):
        optiondefs = (('menuItems', [], Pmw.INITOPT),)
        self.defineoptions(kw, optiondefs)
        Pmw.MegaWidget.__init__(self, parent)
        self.editor = editor
        interior = self.interior()
        interior.configure(relief=GROOVE, borderwidth=2)
        self._scrolledCanvas = self.createcomponent('scrolledCanvas', (), None, Pmw.ScrolledCanvas, (interior,), hull_width=300, hull_height=80, usehullsize=1)
        self._canvas = self._scrolledCanvas.component('canvas')
        self._canvas['scrollregion'] = ('0i', '0i', '2i', '4i')
        self._scrolledCanvas.resizescrollregion()
        self._scrolledCanvas.pack(padx=3, pady=3, expand=1, fill=BOTH)
        self._canvas.bind('<ButtonPress-2>', self.mouse2Down)
        self._canvas.bind('<B2-Motion>', self.mouse2Motion)
        self._canvas.bind('<Configure>', lambda e, sc = self._scrolledCanvas: sc.resizescrollregion())
        self.interior().bind('<Destroy>', self.onDestroy)
        self._treeItem = LevelExplorerItem(self.editor.level, self.editor)
        self._node = TreeNode(self._canvas, None, self._treeItem, DEFAULT_MENU_ITEMS + self['menuItems'])
        self._node.expand()
        self._parentFrame = Frame(interior)
        self.accept('Level_Update Explorer', lambda f, s = self: s.update())
        self.initialiseoptions(LevelExplorer)
        return

    def update(self, fUseCachedChildren = 1):
        self._node.update(fUseCachedChildren)
        self._node.expand()

    def mouse2Down(self, event):
        self._width = 1.0 * self._canvas.winfo_width()
        self._height = 1.0 * self._canvas.winfo_height()
        xview = self._canvas.xview()
        yview = self._canvas.yview()
        self._left = xview[0]
        self._top = yview[0]
        self._dxview = xview[1] - xview[0]
        self._dyview = yview[1] - yview[0]
        self._2lx = event.x
        self._2ly = event.y

    def mouse2Motion(self, event):
        newx = self._left - (event.x - self._2lx) / self._width * self._dxview
        self._canvas.xview_moveto(newx)
        newy = self._top - (event.y - self._2ly) / self._height * self._dyview
        self._canvas.yview_moveto(newy)
        self._2lx = event.x
        self._2ly = event.y
        self._left = self._canvas.xview()[0]
        self._top = self._canvas.yview()[0]

    def onDestroy(self, event):
        self.ignore('Level_Update Explorer')

class LevelExplorerItem(TreeItem):

    def __init__(self, levelElement, editor):
        self.levelElement = levelElement
        self.editor = editor

    def GetText(self):
        return self.levelElement.getName()

    def GetKey(self):
        return self.levelElement.id()

    def IsEditable(self):
        return 1

    def SetText(self, text):
        self.levelElement.setNewName(text)

    def GetIconName(self):
        return 'sphere2'

    def IsExpandable(self):
        return self.levelElement.getNumChildren() != 0

    def GetSubList(self):
        sublist = []
        for element in self.levelElement.getChildren():
            item = LevelExplorerItem(element, self.editor)
            sublist.append(item)

        return sublist

    def OnSelect(self):
        messenger.send(self.editor.getEventMsgName('Select'), [self.levelElement])

    def MenuCommand(self, command):
        messenger.send(self.editor.getEventMsgName(command), [self.levelElement])


"""
给你一个整数数组 nums 和一个整数 k ，判断数组中是否存在两个 不同的索引 i 和 j ，满足 nums[i] == nums[j] 且 abs(i - j) <= k 。如果存在，返回 true ；否则，返回 false 。
 *
 * 示例 1：
 *
 * 输入：nums = [1,2,3,1], k = 3
 * 输出：true
 * 示例 2：
 *
 * 输入：nums = [1,0,1,1], k = 1
 * 输出：true
 * 示例 3：
 *
 * 输入：nums = [1,2,3,1,2,3], k = 2
 * 输出：false
"""
from typing import List

class Solution:
    def containsNearbyDuplicate(self, nums: List[int], k: int) -> bool:
        d:dict= {}
        for i in range(len(nums)):
            if nums[i] not in d:
                d[nums[i]] = i
            else:
                value = d.get(nums[i])
                if abs(value - i) <= k:
                    return True
                else:
                    d[nums[i]] = i
        return False

if __name__ == "__main__":
    s = Solution()
    nums = [1,2,3,1,2,3]
    print(s.containsNearbyDuplicate(nums, 2))


import requests
from bs4 import BeautifulSoup
import csv
import matplotlib.pyplot as plt

# Fetch website content
url = "https://www.imdb.com/chart/top"
response = requests.get(url)
soup = BeautifulSoup(response.content, "html.parser")

# Find list of movie links
movies_list = soup.find('tbody', class_='lister-list')
movies = []
for row in movies_list.find_all('tr'):
    title_col = row.find('td', class_='titleColumn')
    rating_col = row.find('td', class_='ratingColumn')
    title = title_col.a.text
    year = title_col.span.text.strip('()')
    rating = rating_col.strong.text
    movies.append({
        'title': title,
        'year': year,
        'rating': rating
    })

    if len(movies) == 20:
        break

# Sort movies by rating
movies_sorted = sorted(movies, key=lambda x: float(x['rating']), reverse=True)

# Create visualization
top_titles = [movie['title'] for movie in movies_sorted]
top_ratings = [float(movie['rating']) for movie in movies_sorted]

fig, ax = plt.subplots(figsize=(8, 10))
ax.barh(top_titles, top_ratings, height=0.5)
ax.invert_yaxis()
ax.set_xlabel('Rating')
ax.set_xlim(0, 10)
ax.set_xticks(range(0, 11))
ax.set_title('Top Rated Movies on IMDB')
plt.tight_layout()

# Save chart as PNG
fig.savefig('top_rated_movies.png')

# Save as CSV
with open('movies_scraped.csv', mode='w', newline='', encoding='utf-8') as csv_file:
    fieldnames = ['title', 'year', 'rating']
    writer = csv.DictWriter(csv_file, fieldnames=fieldnames)
    writer.writeheader()
    for movie in movies_sorted:
        writer.writerow(movie)

print("Done")